mbentley / docker-timemachine

Docker image to run Samba (compatible Time Machine for macOS)
Apache License 2.0
573 stars 66 forks source link

[Feature]: Verify that /opt/timemachine is a mounted filesystem #150

Closed ganzevoort closed 9 months ago

ganzevoort commented 10 months ago

What problem are you looking to solve?

On my server, the timemachine docker container starts too soon, before the filesystem that I want to bind to /opt/timemachine is available.

That filesystem is ext4, on an external disk over iscsi through a wireguard vpn. Starting the vpn and iscsi systems takes some time but when that's done the performance is ok.

Because docker starts too soon, /opt/timemachine is actually bound to the directory under the mountpoint and the backups of our laptops fill the (small) root filesystem of my raspberry pi.

Describe the solution that you have in mind

At startup, verify that /opt/timemachine/lost+found is a directory. If not, wait a minute then fail.

If the container starts before the mount completes, it will never see the mounted filesystem and instead continue to use the directory under the mountpoint. That's why at startup it cannot loop until mounted, but must "exit 1" and get auto-restarted.

My solution assumes that /opt/timemachine should be a mounted ext4 filesystem. Of course this isn't true for everybody. I think an additional environment variable should be used to enable the behaviour I'd like to see.

Additional Context

The relevant part from my docker-compose.yml:

  timemachine:
    image: mbentley/timemachine:smb
    environment:
      - TM_USERNAME=timemachine
      - PASSWORD=...
    volumes:
      - /volumes/timecapsule/phase5:/opt/timemachine
      - /volumes/timecapsule/var/lib/samba:/var/lib/samba
      - /volumes/timecapsule/var/cache/samba:/var/cache/samba
      - /volumes/timecapsule/run/samba:/run/samba
    restart: unless-stopped

To add wait-until-mounted, I added an entrypoint0.sh with:

#!/bin/sh

DIR="/opt/${TM_USERNAME:-timemachine}"

echo "INFO: checking if ${DIR} is a mounted filesystem"
if [ ! -d "${DIR}/lost+found/" ]
then
        echo "ERROR: ${DIR} is not a mounted filesystem" 1>&2
        echo "INFO: container probably started too soon, retry in 1 minute"
        sleep 60
        exit 1
fi
echo "INFO: entrypoint0 complete; executing /entrypoint.sh '${*}'"
exec /entrypoint.sh "${@}"
ganzevoort commented 10 months ago

I could make a pullrequest like this, but maybe you have ideas about a more generic solution.

diff --git a/entrypoint.sh b/entrypoint.sh
index ea350a9..daa0c97 100755
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -28,6 +28,15 @@ TM_UID="${TM_UID:-${PUID}}"
 TM_GID="${TM_GID:-${PGID:-${TM_UID}}}"

+if [ -n "${TM_LOSTANDFOUND:-}" -a ! -d "/opt/${TM_USERNAME}/lost+found" ]
+then
+  echo "ERROR: /opt/${TM_USERNAME} is not a mounted filesystem"
+  echo "INFO: container probably started too soon, retry in 1 minute"
+  sleep 60
+  exit 1
+fi
+
+
 # common functions
 password_var_or_file() {
   # check PASSWORD and PASSWORD_FILE are both not set
mbentley commented 10 months ago

Your best bet is to use a feature that not as many people are aware of in Docker: use --mount syntax instead of -v for volumes which when using Docker Compose, is called the Long Syntax. The difference between volume and mount is that volume will create the parent directory/directories if they do not exist. Mount or Long Syntax will have the container fail to start with an error if the path does not exist.

Taking their example and minimizing it:

version: "3.8"
services:
  web:
    image: nginx:alpine
    volumes:
      - type: bind
        source: ./static
        target: /opt/app/static

volumes:
  mydata:

When you try to start the container, it fails:

$ docker compose up
[+] Building 0.0s (0/0)                                                                          docker:desktop-linux
[+] Running 1/0
 ✔ Network long_default  Created                                                                                 0.0s
 ⠋ Container long-web-1  Creating                                                                                0.0s
Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /host_mnt/Users/mbentley/temp/long/static

That will happen on both at create time and then just when the container is started, like after a reboot. Here I created the container successfully with the directory existing, stopped the container, removed the directory, and then tried to start the container manually:

$ docker start long-web-1
Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /host_mnt/Users/mbentley/temp/long/static
Error: failed to start containers: long-web-1
ganzevoort commented 10 months ago

Thanks for your suggestion.

I think I'll have to move the content from the root of the backup filesystem to a subdirectory and bind that (using long syntax) instead because the directory I'm binding now exists, it's intended to be the mountpoint for the real backup filesystem.

It feels like a more robust "fail if backupspace is not (yet) available" solution 👍 I'll try and see what it does.

ganzevoort commented 10 months ago

With the missing directory, the container just fails to start without retrying (I have restart: unless-stopped) so this means I'll have to manually start the timemachine container after the filesystem is mounted.

For now I'll stick with the entrypoint0 workaround.

mbentley commented 9 months ago

Closing for now unless someone has a better workaround.