├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── afp.conf ├── avahi ├── afpd.service └── nsswitch.conf ├── bin └── add-account ├── docs └── overview.jpg ├── entrypoint.sh ├── start_netatalk.sh └── supervisord.conf /.gitignore: -------------------------------------------------------------------------------- 1 | ####################### 2 | ## Windows gitignore ## 3 | ####################### 4 | # Windows image file caches 5 | Thumbs.db 6 | ehthumbs.db 7 | 8 | # Folder config file 9 | Desktop.ini 10 | 11 | # Recycle Bin used on file shares 12 | $RECYCLE.BIN/ 13 | 14 | # Windows Installer files 15 | *.cab 16 | *.msi 17 | *.msm 18 | *.msp 19 | 20 | # Windows shortcuts 21 | *.lnk 22 | 23 | ################### 24 | ## OSX gitignore ## 25 | ################### 26 | .DS_Store 27 | .AppleDouble 28 | .LSOverride 29 | 30 | # Icon must end with two \r 31 | Icon 32 | 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear on external disk 37 | .Spotlight-V100 38 | .Trashes 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | 47 | ##################### 48 | ## Linux gitginore ## 49 | ##################### 50 | 51 | *~ 52 | 53 | # KDE directory preferences 54 | .directory 55 | 56 | ####################### 57 | ## Sublime gitignore ## 58 | ####################### 59 | 60 | # cache files for sublime text 61 | *.tmlanguage.cache 62 | *.tmPreferences.cache 63 | *.stTheme.cache 64 | 65 | # workspace files are user-specific 66 | *.sublime-workspace 67 | 68 | # project files should be checked into the repository, unless a significant 69 | # proportion of contributors will probably not be using SublimeText 70 | *.sublime-project 71 | 72 | # sftp configuration file 73 | sftp-config.json 74 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | LABEL maintainer="Óscar de Arriba " 3 | 4 | ################## 5 | ## BUILDING ## 6 | ################## 7 | 8 | # Versions to use 9 | ENV netatalk_version 3.1.12 10 | 11 | WORKDIR / 12 | 13 | # Prerequisites 14 | RUN apk update && \ 15 | apk upgrade && \ 16 | apk add --no-cache \ 17 | bash \ 18 | curl \ 19 | libldap \ 20 | libgcrypt \ 21 | python3 \ 22 | dbus \ 23 | dbus-glib \ 24 | py-dbus \ 25 | linux-pam \ 26 | cracklib \ 27 | db \ 28 | libevent \ 29 | file \ 30 | tzdata \ 31 | acl \ 32 | openssl \ 33 | supervisor && \ 34 | apk add --no-cache --virtual .build-deps \ 35 | build-base \ 36 | autoconf \ 37 | automake \ 38 | libtool \ 39 | libgcrypt-dev \ 40 | linux-pam-dev \ 41 | cracklib-dev \ 42 | acl-dev \ 43 | db-dev \ 44 | dbus-dev \ 45 | libevent-dev && \ 46 | ln -s -f /bin/true /usr/bin/chfn && \ 47 | cd /tmp && \ 48 | curl -o netatalk-${netatalk_version}.tar.gz -L https://downloads.sourceforge.net/project/netatalk/netatalk/${netatalk_version}/netatalk-${netatalk_version}.tar.gz && \ 49 | tar xvf netatalk-${netatalk_version}.tar.gz && \ 50 | cd netatalk-${netatalk_version} && \ 51 | CFLAGS="-Wno-unused-result -O2" ./configure \ 52 | --prefix=/usr \ 53 | --localstatedir=/var/state \ 54 | --sysconfdir=/etc \ 55 | --with-dbus-sysconf-dir=/etc/dbus-1/system.d/ \ 56 | --with-init-style=debian-sysv \ 57 | --sbindir=/usr/bin \ 58 | --enable-quota \ 59 | --with-tdb \ 60 | --enable-silent-rules \ 61 | --with-cracklib \ 62 | --with-cnid-cdb-backend \ 63 | --enable-pgp-uam \ 64 | --with-acls && \ 65 | make && \ 66 | make install && \ 67 | cd /tmp && \ 68 | rm -rf netatalk-${netatalk_version} netatalk-${netatalk_version}.tar.gz && \ 69 | apk del .build-deps 70 | 71 | RUN mkdir -p /timemachine && \ 72 | mkdir -p /var/log/supervisor && \ 73 | mkdir -p /conf.d/netatalk 74 | 75 | # Create the log file 76 | RUN touch /var/log/afpd.log 77 | 78 | ADD entrypoint.sh /entrypoint.sh 79 | RUN chmod +x /entrypoint.sh 80 | ADD start_netatalk.sh /start_netatalk.sh 81 | ADD bin/add-account /usr/bin/add-account 82 | ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf 83 | ADD afp.conf /etc/afp.conf 84 | 85 | EXPOSE 548 636 86 | 87 | VOLUME ["/timemachine"] 88 | 89 | CMD ["/entrypoint.sh"] 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Óscar de Arriba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-timemachine 2 | A docker container to compile the lastest version of Netatalk in order to run a Time Machine server. 3 | 4 | ## Running on ARM / RPi 5 | If you want to use this on an ARM-Device (like the Raspberry Pi), you have two options: 6 | 7 | - Get the precompiled image (latest compilation on 29-03-2018): 8 | ``` 9 | $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /external_volume:/timemachine -it -p 548:548 -p 636:636 odarriba/timemachine-rpi 10 | ``` 11 | - Build the image directly on your device: 12 | ``` 13 | $ docker build -t timemachine-rpi:latest -f Dockerfile . 14 | $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /external_volume:/timemachine -it -p 548:548 -p 636:636 timemachine-rpi 15 | ``` 16 | 17 | ## Installation 18 | 19 | ### Step 1 - Start the Server 20 | 21 | To download the docker container and execute it, simply run: 22 | 23 | ``` 24 | $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /external_volume:/timemachine -it -p 548:548 -p 636:636 --ulimit nofile=65536:65536 odarriba/timemachine 25 | ``` 26 | 27 | Replace `external_volume` with a local path where you want to store your data. 28 | 29 | As the image has been started using the `--restart=always` flag, it will start when the computers boots up. 30 | 31 | 32 | 33 | ### Step 2 - Add a User 34 | 35 | To add a user, run: 36 | 37 | ``` 38 | $ docker exec timemachine add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] 39 | ``` 40 | 41 | Or, if you want to add a user with a specific UID/GID, use the following format 42 | 43 | ``` 44 | $ docker exec timemachine add-account -i 1000 -g 1000 USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] 45 | ``` 46 | 47 | But take care that: 48 | * `VOL_NAME` will be the name of the volume shown on your OSX as the network drive 49 | * `VOL_ROOT` should be an absolute path, preferably a sub-path of `/timemachine` (e.g., `/timemachine/backup`), so it will be stored in the according sub-path of your external volume. 50 | * `VOL_SIZE_MB` is an optional parameter. It indicates the max volume size for that user. 51 | 52 | Now you have a docker instance running `netatalk`. 53 | 54 | 55 | ### Step 3 - Enable Auto Discovery 56 | 57 | Avahi daemon is commonly used to help your computers to find the services provided by a server. 58 | 59 | Avahi isn't built into this Docker image because, due to Docker's networking limitations, Avahi can't spread it's messages to announce the services. 60 | 61 | **If you want to enable this feature, you can install Avahi daemon on your host** following these steps (Ubuntu version): 62 | 63 | * Install `avahi-daemon`: run `sudo apt-get install avahi-daemon avahi-utils` 64 | * Copy the file from `avahi/nsswitch.conf` to `/etc/nsswitch.conf` 65 | * Copy the service description file from `avahi/afpd.service` to `/etc/avahi/services/afpd.service` 66 | * Restart Avahi's daemon: `sudo /etc/init.d/avahi-daemon restart` 67 | 68 | 69 | ### Step 4 - Configure Your Firewall 70 | 71 | Make sure 72 | 73 | * your server can receive traffic on port `548` and `636` (e.g., `ufw allow 548`, (`636` respectively)). 74 | 75 | * your Mac allows outgoing connections (Little Snitch?) 76 | 77 | 78 | 79 | ### Step 5 - Start Using It 80 | 81 | To start using it, follow these steps: 82 | 83 | * If you use Avahi, open **Finder**, go to **Shared** and connect to your server with your new username and password. 84 | 85 | * Alternatively (or if you don't use Avahi) from **Finder** press **CMD-K** and type `afp://your-server` where `your-server` can be your server's name or IP address (e.g., `afp://my-server` or `afp://192.168.0.5`). 86 | 87 | * Go to **System Preferences**, and open **Time Machine** settings. 88 | 89 | * Open **Add or Remove Backup Disk...** 90 | 91 | * Select your new volume. 92 | 93 | 94 | In the example below, the Docker instance is running on server `central`. For `USERNAME` the account `Backup` along with a `PASSWORD` was created. Once connected, the account `Backup` is available in Time Machine settings: 95 | ![alt text](docs/overview.jpg "Getting Started") 96 | 97 | 98 | ## Advanced Usage 99 | 100 | ### Configure using environment variables 101 | 102 | You can configure the container using environment variables (for example, if you use a `docker-compose` environment). 103 | 104 | There are these environment variables: 105 | 106 | * **AFP_LOGIN**: User name 107 | * **AFP_PASSWORD**: User password 108 | * **AFP_NAME**: Name of the volume 109 | * **AFP_SIZE_LIMIT**: Size in MB of the volume (optional) 110 | * **PUID**: For UID 111 | * **PGID**: For GID 112 | 113 | Using these variables, the container will create a user at boot time (only one per container) and **the data will be stored directly in the volume `/timemachine`, without subfolders**. 114 | 115 | To find your `PUID` and `GUID` use `id user` as below: 116 | ``` 117 | $ id 118 | uid=1000(dockeruser) gid=1000(dockeruser) groups=1000(dockergroup) 119 | ``` 120 | 121 | ## FAQ 122 | 123 | 124 | #### I got Docker running, my firewall is configured, but I still don't find the service in Time Machine. 125 | 126 | Make sure you actually mount the server volume (see Step 5) before trying to find it in Time Machine settingss. 127 | 128 | 129 | ### My container restarted and I can't login 130 | 131 | The user accounts are ephemeral and you'll have to run `Step 2` again to re-create the accounts. 132 | Alternativey, you can script the account creation and upload a custom entrypoint with the details: 133 | 134 | ```bash 135 | #!/bin/bash 136 | set -e 137 | 138 | # Repeat for all your accounts 139 | add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] 140 | add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] 141 | /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 142 | ``` 143 | 144 | Save the above file as `entrypoint.sh` and make sure it is marked as executable (`chmod +x entrypoint.sh`). Then invoke `docker run` as: 145 | 146 | ``` 147 | $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /external_volume:/timemachine -it -p 548:548 -p 636:636 -v entrypoint.sh:/entrypoint.sh odarriba/timemachine-rpi 148 | ``` 149 | 150 | #### I am still having trouble ... 151 | 152 | * The idea of using avahi-daemon installed in the bare metal server is to avoid having to execute the container with --net=host, which a potentially insecure flag. But, as the last option to check things out, it should be fine. You just should know what you are enabling. 153 | 154 | * A Time Machine network disk is just a disk image in an AFP volume that supports the correct level of encryption. So to be recognised by the TimeMachine daemon, you should mount the unit manually for the first time, configure TimeMachine on your computer, and then the OS will do that for you automatically. 155 | 156 | 157 | 158 | #### Why do I need to install Avahi on your host and not in the container? 159 | 160 | Because if you don't do it this way, the discovery message won't be able to reach your computers. 161 | 162 | 163 | 164 | ## Contributors 165 | 166 | * Óscar de Arriba (odarriba@gmail.com) 167 | * Daniel Iñigo (demil133@gmail.com) 168 | * Josef Friedrich ([@Josef-Friedrich](https://github.com/Josef-Friedrich)) 169 | -------------------------------------------------------------------------------- /afp.conf: -------------------------------------------------------------------------------- 1 | [Global] 2 | mimic model = Xserve 3 | log file = /var/log/afpd.log 4 | log level = default:warn 5 | zeroconf = no -------------------------------------------------------------------------------- /avahi/afpd.service: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %h 5 | 6 | _afpovertcp._tcp 7 | 548 8 | 9 | 10 | _device-info._tcp 11 | 0 12 | model=Xserve 13 | 14 | 15 | -------------------------------------------------------------------------------- /avahi/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # /etc/nsswitch.conf 2 | # 3 | # Example configuration of GNU Name Service Switch functionality. 4 | # If you have the `glibc-doc-reference' and `info' packages installed, try: 5 | # `info libc "Name Service Switch"' for information about this file. 6 | 7 | passwd: compat 8 | group: compat 9 | shadow: compat 10 | 11 | hosts: files mdns4_minimal [NOTFOUND=return] dns mdns 12 | networks: files 13 | 14 | protocols: db files 15 | services: db files 16 | ethers: db files 17 | rpc: db files 18 | 19 | netgroup: nis -------------------------------------------------------------------------------- /bin/add-account: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":i:g:e:" Option 4 | do 5 | case $Option in 6 | i ) _uid=$OPTARG ;; 7 | g ) _gid=$OPTARG ;; 8 | esac 9 | done 10 | shift $(($OPTIND - 1)) 11 | 12 | if [[ $# -lt 4 ]]; then 13 | echo "Usage: add-account [ -i uid -g gid ] USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB]" 14 | exit 1 15 | fi 16 | 17 | # Create mountpoint 18 | mkdir -p ${4} 19 | 20 | if [[ $_uid ]] && [[ $_gid ]]; then 21 | addgroup -g $_gid $1 22 | adduser -u $_uid -S -H -G $1 $1 23 | chown -R $1:$1 ${4} 24 | else 25 | adduser -S -H -G root $1 26 | chown -R $1:root ${4} 27 | fi 28 | 29 | echo $1:$2 | chpasswd 30 | 31 | # Add config to timemachine 32 | echo " 33 | [${3}] 34 | path = ${4} 35 | time machine = yes 36 | valid users = ${1} 37 | spotlight = no" >> /etc/afp.conf 38 | 39 | if [[ $# -eq 5 ]]; then 40 | echo " 41 | vol size limit = ${5}" >> /etc/afp.conf 42 | fi 43 | 44 | pkill -HUP afpd 45 | exit 0 46 | -------------------------------------------------------------------------------- /docs/overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odarriba/docker-timemachine/aacc138dcf480a1a817f737bddd554f938ab769a/docs/overview.jpg -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ ! -e /.initialized_user ] && [ ! -z "$AFP_LOGIN" ] && [ ! -z "$AFP_PASSWORD" ] && [ ! -z "$AFP_NAME" ] && [ ! -z $PUID ] && [ ! -z $PGID ]; then 6 | add-account -i $PUID -g $PGID "$AFP_LOGIN" "$AFP_PASSWORD" "$AFP_NAME" /timemachine $AFP_SIZE_LIMIT 7 | touch /.initialized_user 8 | fi 9 | 10 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 11 | -------------------------------------------------------------------------------- /start_netatalk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Clean out old locks 4 | /bin/rm -f /var/lock/netatalk 5 | 6 | if [ ! -e /var/run/dbus/system_bus_socket ]; then 7 | dbus-daemon --system 8 | fi 9 | 10 | netatalk -d -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:timemachine] 5 | command=/start_netatalk.sh --------------------------------------------------------------------------------