diff --git a/Dockerfile b/Dockerfile index 1d5b90d..69472a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,88 +1,40 @@ FROM alpine:latest -LABEL maintainer="Óscar de Arriba " + +ARG BUILD_DATE + +LABEL org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="TimeMachine - Samba" \ + org.label-schema.description="TimeMachine server using Samba protocol." \ + org.label-schema.vcs-url="https://github.com/odarriba/docker-timemachine" \ + org.label-schema.schema-version="1.0" ################## ## BUILDING ## ################## -# Versions to use -ENV netatalk_version 3.1.12 - WORKDIR / -# Prerequisites RUN apk update && \ - apk upgrade && \ - apk add --no-cache \ - bash \ - curl \ - libldap \ - libgcrypt \ - python \ - dbus \ - dbus-glib \ - py-dbus \ - linux-pam \ - cracklib \ - db \ - libevent \ - file \ - tzdata \ - acl \ - openssl \ - supervisor && \ - apk add --no-cache --virtual .build-deps \ - build-base \ - autoconf \ - automake \ - libtool \ - libgcrypt-dev \ - linux-pam-dev \ - cracklib-dev \ - acl-dev \ - db-dev \ - dbus-dev \ - libevent-dev && \ - ln -s -f /bin/true /usr/bin/chfn && \ - cd /tmp && \ - curl -o netatalk-${netatalk_version}.tar.gz -L https://downloads.sourceforge.net/project/netatalk/netatalk/${netatalk_version}/netatalk-${netatalk_version}.tar.gz && \ - tar xvf netatalk-${netatalk_version}.tar.gz && \ - cd netatalk-${netatalk_version} && \ - CFLAGS="-Wno-unused-result -O2" ./configure \ - --prefix=/usr \ - --localstatedir=/var/state \ - --sysconfdir=/etc \ - --with-dbus-sysconf-dir=/etc/dbus-1/system.d/ \ - --with-init-style=debian-sysv \ - --sbindir=/usr/bin \ - --enable-quota \ - --with-tdb \ - --enable-silent-rules \ - --with-cracklib \ - --with-cnid-cdb-backend \ - --enable-pgp-uam \ - --with-acls && \ - make && \ - make install && \ - cd /tmp && \ - rm -rf netatalk-${netatalk_version} netatalk-${netatalk_version}.tar.gz && \ - apk del .build-deps + apk upgrade && \ + apk add --no-cache \ + bash \ + samba-server \ + samba-common-tools \ + samba-winbind \ + supervisor RUN mkdir -p /timemachine && \ - mkdir -p /var/log/supervisor && \ - mkdir -p /conf.d/netatalk - -# Create the log file -RUN touch /var/log/afpd.log + mkdir -p /etc/samba && \ + mkdir -p /var/log/supervisor -ADD entrypoint.sh /entrypoint.sh +ADD bin/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -ADD start_netatalk.sh /start_netatalk.sh ADD bin/add-account /usr/bin/add-account -ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf -ADD afp.conf /etc/afp.conf +ADD config/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +ADD config/smb.conf /etc/samba/smb.conf +ADD config/com.apple.TimeMachine.quota.plist /com.apple.TimeMachine.quota.plist -EXPOSE 548 636 +EXPOSE 137/UDP 138/UDP 139/TCP 445/TCP VOLUME ["/timemachine"] diff --git a/README.md b/README.md index 22b71c7..20158ff 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,27 @@ # docker-timemachine -A docker container to compile the lastest version of Netatalk in order to run a Time Machine server. +A docker container to run a Time Machine server using SMB. + +**Note:** This image is now using Samba as the network protocol. There is a AFP container available to use with old OSX versions. Check README in `afp` branch for more information. + +**Coming from AFP?** We have an [upgrade guide](UPGRADE.md) to have you covered. ## Running on ARM / RPi -If you want to use this on an ARM-Device (like the Raspberry Pi), you have two options: - -- Get the precompiled image (latest compilation on 29-03-2018): - ``` - $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /external_volume:/timemachine -it -p 548:548 -p 636:636 odarriba/timemachine-rpi - ``` -- Build the image directly on your device: - ``` - $ docker build -t timemachine-rpi:latest -f Dockerfile . - $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /external_volume:/timemachine -it -p 548:548 -p 636:636 timemachine-rpi - ``` +If you want to use this on an ARM-Device (like the Raspberry Pi), you have to compile it first: + +``` +$ docker build -t timemachine-rpi:latest -f Dockerfile . + +$ docker run -h timemachine \ + --name timemachine \ + --restart=unless-stopped \ + -dit \ + -v /external_volume:/timemachine \ + -p 137:137/udp \ + -p 138:138/udp \ + -p 139:139/tcp \ + -p 445:445/tcp \ + timemachine-rpi +``` ## Installation @@ -21,7 +30,17 @@ If you want to use this on an ARM-Device (like the Raspberry Pi), you have two o To download the docker container and execute it, simply run: ``` -$ 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 +$ docker run -h timemachine \ + --name timemachine \ + --restart=unless-stopped \ + -dit \ + -v /external_volume:/timemachine \ + -p 137:137/udp \ + -p 138:138/udp \ + -p 139:139/tcp \ + -p 445:445/tcp \ + --ulimit nofile=65536:65536 \ + odarriba/timemachine ``` Replace `external_volume` with a local path where you want to store your data. @@ -35,21 +54,19 @@ As the image has been started using the `--restart=always` flag, it will start w To add a user, run: ``` -$ docker exec timemachine add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] +$ docker exec timemachine add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [TM_SIZE_MB] ``` Or, if you want to add a user with a specific UID/GID, use the following format ``` -$ docker exec timemachine add-account -i 1000 -g 1000 USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] +$ docker exec timemachine add-account -i 1000 -g 1000 USERNAME PASSWORD VOL_NAME VOL_ROOT [TM_SIZE_MB] ``` But take care that: * `VOL_NAME` will be the name of the volume shown on your OSX as the network drive * `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. -* `VOL_SIZE_MB` is an optional parameter. It indicates the max volume size for that user. - -Now you have a docker instance running `netatalk`. +* `TM_SIZE_MB` is an optional parameter. It indicates the max Time Machine size for that user. ### Step 3 - Enable Auto Discovery @@ -62,7 +79,8 @@ Avahi isn't built into this Docker image because, due to Docker's networking lim * Install `avahi-daemon`: run `sudo apt-get install avahi-daemon avahi-utils` * Copy the file from `avahi/nsswitch.conf` to `/etc/nsswitch.conf` -* Copy the service description file from `avahi/afpd.service` to `/etc/avahi/services/afpd.service` +* Copy the service description file from `avahi/smbd.service` to `/etc/avahi/services/smbd.service` +* Replace VOL_NAME with same name as by adding user `sudo nano /etc/avahi/services/smbd.service`. * Restart Avahi's daemon: `sudo /etc/init.d/avahi-daemon restart` @@ -70,7 +88,7 @@ Avahi isn't built into this Docker image because, due to Docker's networking lim Make sure -* your server can receive traffic on port `548` and `636` (e.g., `ufw allow 548`, (`636` respectively)). +* your server can receive traffic on the ports used by the container * your Mac allows outgoing connections (Little Snitch?) @@ -82,7 +100,7 @@ To start using it, follow these steps: * If you use Avahi, open **Finder**, go to **Shared** and connect to your server with your new username and password. -* 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`). +* Alternatively (or if you don't use Avahi) from **Finder** press **CMD-K** and type `smb://your-server` where `your-server` can be your server's name or IP address (e.g., `smb://my-server` or `smb://192.168.0.5`). * Go to **System Preferences**, and open **Time Machine** settings. @@ -103,10 +121,10 @@ You can configure the container using environment variables (for example, if you There are these environment variables: -* **AFP_LOGIN**: User name -* **AFP_PASSWORD**: User password -* **AFP_NAME**: Name of the volume -* **AFP_SIZE_LIMIT**: Size in MB of the volume (optional) +* **SMB_LOGIN**: User name +* **SMB_PASSWORD**: User password +* **SMB_NAME**: Name of the volume +* **TM_SIZE_LIMIT**: Time Machine size limit in MB (optional) * **PUID**: For UID * **PGID**: For GID @@ -123,7 +141,7 @@ To find your `PUID` and `GUID` use `id user` as below: #### I got Docker running, my firewall is configured, but I still don't find the service in Time Machine. -Make sure you actually mount the server volume (see Step 5) before trying to find it in Time Machine settingss. +Make sure you actually mount the server volume (see Step 5) before trying to find it in Time Machine settings. ### My container restarted and I can't login @@ -136,23 +154,32 @@ Alternativey, you can script the account creation and upload a custom entrypoint set -e # Repeat for all your accounts -add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] -add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [VOL_SIZE_MB] +add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [TM_SIZE_MB] +add-account USERNAME PASSWORD VOL_NAME VOL_ROOT [TM_SIZE_MB] /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf ``` Save the above file as `entrypoint.sh` and make sure it is marked as executable (`chmod +x entrypoint.sh`). Then invoke `docker run` as: ``` -$ 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 +$ docker run -h timemachine \ + --name timemachine \ + --restart=unless-stopped \ + -dit \ + -v /external_volume:/timemachine \ + -p 137:137/udp \ + -p 138:138/udp \ + -p 139:139/tcp \ + -p 445:445/tcp \ + -v entrypoint.sh:/entrypoint.sh \ + odarriba/timemachine ``` #### I am still having trouble ... * 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. -* 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. - +* A Time Machine network disk is just a disk image in an SMB volume that supports the correct level of encryption. So to be recognised by the Time Machine daemon, you should mount the unit manually for the first time, configure Time Machine on your computer, and then the OS will do that for you automatically. #### Why do I need to install Avahi on your host and not in the container? @@ -160,7 +187,6 @@ $ docker run -h timemachine --name timemachine --restart=unless-stopped -d -v /e Because if you don't do it this way, the discovery message won't be able to reach your computers. - ## Contributors * Óscar de Arriba (odarriba@gmail.com) diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..c329845 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,57 @@ +# Upgrade guide: from AFP to SMB + +## Why you should switch to the SMB version? + +Because of **performance** and **stability**. Apple has deprecated AFP and they +only support Samba now as the network protocol for Time Machine. + +AFP still works, but it is not maintained and it will probably stop working in +future versions of OSX. + +Also, we could see amazing performance improvements in some scenarios (like +using Wi-Fi conections for backup). + +## Will I lose my old backups? + +**No.** + +The disk format is still the same, so only the transfer protocol will change. +Time Machine will recognise the old backup and will continue using it. + +## Migration isntructions + +1. First of all, check that your backups are inside your host machine using a + volume. + + That means, check that you can find your backup folder in your disk and that + there are contents inside. If you followed the instructions of the README, the + data should be there. + +2. Stop all Time Machine process in your Macs. To do that, enter on + `Preferences > Time Machine` and check that there is no backup in progress. + +3. Stop current timemachine container and remove it: + ``` + $ docker stop timemachine + $ docker rm timemachine + ``` + +4. Remove local image: + ``` + $ docker rmi odarriba/timemachine + ``` + +5. Follow current version instructions to start the new container. Check that: + - you use the same data directory as before + - you create the same users as before (with same UID and GID if you specified them) + + **Note** The ENV vars have changed, and now all `AFP_` are `SMB_`. if you use + them in a docker compose file, you should update them. + +6. in all your macs, connect again to the server: + - Connect to your server as explained in the README, using `smb://` instead of + `afp://`. + - Go to `Preferences > Time Machine` and click on `Select disk`. + - Select your new server + - Time Machine will ask to use the previous backup (if you keeped it) and will + ask for it's password if it was encrypted. diff --git a/afp.conf b/afp.conf deleted file mode 100644 index 01f3742..0000000 --- a/afp.conf +++ /dev/null @@ -1,5 +0,0 @@ -[Global] -mimic model = Xserve -log file = /var/log/afpd.log -log level = default:warn -zeroconf = no \ No newline at end of file diff --git a/avahi/afpd.service b/avahi/afpd.service deleted file mode 100644 index 834e708..0000000 --- a/avahi/afpd.service +++ /dev/null @@ -1,14 +0,0 @@ - - - - %h - - _afpovertcp._tcp - 548 - - - _device-info._tcp - 0 - model=Xserve - - diff --git a/avahi/smbd.service b/avahi/smbd.service new file mode 100644 index 0000000..da9f325 --- /dev/null +++ b/avahi/smbd.service @@ -0,0 +1,20 @@ + + + + %h + + _smb._tcp + 445 + + + _device-info._tcp + 9 + model=TimeCapsule8,119 + + + _adisk._tcp + 9 + dk0=adVN=,adVF=0x82 + sys=adVF=0x100 + + diff --git a/bin/add-account b/bin/add-account index 5748fb2..0cefd27 100755 --- a/bin/add-account +++ b/bin/add-account @@ -19,27 +19,35 @@ mkdir -p ${4} if [[ $_uid ]] && [[ $_gid ]]; then addgroup -g $_gid $1 - adduser -u $_uid -S -H -G $1 $1 + adduser -u $_uid -S -H -D -G $1 $1 chown -R $1:$1 ${4} else - adduser -S -H -G root $1 + adduser -S -H -D -G root $1 chown -R $1:root ${4} fi -echo $1:$2 | chpasswd +smbpasswd -L -a -n $1 +smbpasswd -L -e -n $1 +echo -e "$2\n$2" | smbpasswd -L -s $1 # Add config to timemachine echo " [${3}] - path = ${4} - time machine = yes - valid users = ${1} - spotlight = no" >> /etc/afp.conf + fruit:aapl = yes + fruit:time machine = yes + path = ${4} + valid users = ${1} + browseable = yes + writable = yes + kernel oplocks = no + kernel share modes = no + posix locking = no + vfs objects = catia fruit streams_xattr" >> /etc/samba/smb.conf if [[ $# -eq 5 ]]; then - echo " - vol size limit = ${5}" >> /etc/afp.conf + TM_SIZE=$((${5} * 1000000)) + sed "s#REPLACE_TM_SIZE#${TM_SIZE}#" /com.apple.TimeMachine.quota.plist > ${4}/.com.apple.TimeMachine.quota.plist fi -pkill -HUP afpd +pkill -HUP smbd exit 0 diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh new file mode 100755 index 0000000..18ae120 --- /dev/null +++ b/bin/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +if [ ! -e /.initialized_user ] && [ ! -z "$SMB_LOGIN" ] && [ ! -z "$SMB_PASSWORD" ] && [ ! -z "$SMB_NAME" ] && [ ! -z $PUID ] && [ ! -z $PGID ]; then + add-account -i $PUID -g $PGID "$SMB_LOGIN" "$SMB_PASSWORD" "$SMB_NAME" /timemachine $TM_SIZE_LIMIT + touch /.initialized_user +fi + +exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/config/com.apple.TimeMachine.quota.plist b/config/com.apple.TimeMachine.quota.plist new file mode 100644 index 0000000..f558b7a --- /dev/null +++ b/config/com.apple.TimeMachine.quota.plist @@ -0,0 +1,8 @@ + + + + + GlobalQuota + REPLACE_TM_SIZE + + \ No newline at end of file diff --git a/config/smb.conf b/config/smb.conf new file mode 100644 index 0000000..6d0d12b --- /dev/null +++ b/config/smb.conf @@ -0,0 +1,9 @@ +[global] + server role = standalone server + unix password sync = Yes + idmap config * : backend = tdb + syslog=0 + + security = user + load printers = no + fruit:model = TimeCapsule8,119 diff --git a/config/supervisord.conf b/config/supervisord.conf new file mode 100644 index 0000000..1ce022d --- /dev/null +++ b/config/supervisord.conf @@ -0,0 +1,11 @@ +[supervisord] +nodaemon=true + +[program:smbd] +command=/usr/sbin/smbd --foreground --no-process-group "$SMBDOPTIONS" + +[program:winbindd] +command=/usr/sbin/winbindd --foreground --no-process-group "$WINBINDOPTIONS" + +[program:nmbd] +command=/usr/sbin/nmbd --foreground --no-process-group "$WINBINDOPTIONS" diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index d64396f..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -e - -if [ ! -e /.initialized_user ] && [ ! -z "$AFP_LOGIN" ] && [ ! -z "$AFP_PASSWORD" ] && [ ! -z "$AFP_NAME" ] && [ ! -z $PUID ] && [ ! -z $PGID ]; then - add-account -i $PUID -g $PGID "$AFP_LOGIN" "$AFP_PASSWORD" "$AFP_NAME" /timemachine $AFP_SIZE_LIMIT - touch /.initialized_user -fi - -exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/hooks/build b/hooks/build new file mode 100644 index 0000000..aeea8d6 --- /dev/null +++ b/hooks/build @@ -0,0 +1,7 @@ +#!/bin/bash +# $IMAGE_NAME var is injected into the build so the tag is correct. +set -xe + +docker build \ + --build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + -t "$IMAGE_NAME" . diff --git a/start_netatalk.sh b/start_netatalk.sh deleted file mode 100755 index 80d000f..0000000 --- a/start_netatalk.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Clean out old locks -/bin/rm -f /var/lock/netatalk - -if [ ! -e /var/run/dbus/system_bus_socket ]; then - dbus-daemon --system -fi - -netatalk -d \ No newline at end of file diff --git a/supervisord.conf b/supervisord.conf deleted file mode 100644 index 5bd860d..0000000 --- a/supervisord.conf +++ /dev/null @@ -1,5 +0,0 @@ -[supervisord] -nodaemon=true - -[program:timemachine] -command=/start_netatalk.sh \ No newline at end of file