diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ed8adcc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.venv +.git +data +**/__pycache__ +.pytest_cache +.mypy_cache +.ruff_cache +tests diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..64ae375 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# NetMover im Container (Alternative zum systemd-Dienst, siehe README). +# Braucht zur Laufzeit CAP_SYS_ADMIN und ein Mount-Verzeichnis mit +# Propagation „rshared“ – sonst sieht der Host die Mounts nicht. + +FROM python:3.12-slim-trixie AS build +WORKDIR /src +COPY pyproject.toml README.md ./ +COPY src ./src +RUN pip wheel --no-cache-dir --wheel-dir /wheels . + +FROM python:3.12-slim-trixie +RUN apt-get update \ + && apt-get install -y --no-install-recommends cifs-utils nfs-common \ + && rm -rf /var/lib/apt/lists/* +COPY --from=build /wheels /wheels +RUN pip install --no-cache-dir /wheels/*.whl && rm -rf /wheels + +ENV NETMOVER_HOST=0.0.0.0 \ + NETMOVER_PORT=8080 \ + NETMOVER_DATA_DIR=/var/lib/netmover \ + NETMOVER_RUN_DIR=/run/netmover \ + NETMOVER_MOUNT_BASE=/mnt/netmover \ + PYTHONUNBUFFERED=1 + +VOLUME ["/var/lib/netmover"] +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \ + CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=4).status == 200 else 1)" +CMD ["netmover", "serve"] diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..167a313 --- /dev/null +++ b/compose.yml @@ -0,0 +1,37 @@ +# NetMover per Docker Compose. Vorher auf dem Host: +# mkdir -p /mnt/netmover +# Die Weboberfläche ist nur auf dem Host selbst erreichbar (127.0.0.1:8080). +services: + netmover: + build: . + image: netmover:0.1.0 + container_name: netmover + restart: unless-stopped + cap_add: + - SYS_ADMIN # mount/umount + - DAC_READ_SEARCH # mount.cifs liest die Credentials-Datei + security_opt: + - apparmor:unconfined # Dockers AppArmor-Profil verbietet mount + ports: + - "127.0.0.1:8080:8080" + environment: + NETMOVER_LOG_LEVEL: INFO + # Hinter einem Reverse Proxy mit HTTPS: + # NETMOVER_COOKIE_SECURE: "true" + # NETMOVER_FORWARDED_ALLOW_IPS: "172.16.0.0/12" + volumes: + - netmover-data:/var/lib/netmover + # WICHTIG: rshared – sonst bleiben die Mounts im Container gefangen und + # weder der Host noch andere Container sehen sie. + - type: bind + source: /mnt/netmover + target: /mnt/netmover + bind: + propagation: rshared + # Lokale Ziele (z. B. Paperless-Consume-Ordner) zusätzlich einbinden: + # - /opt/paperless/consume:/opt/paperless/consume + tmpfs: + - /run/netmover:mode=0700 + +volumes: + netmover-data: diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100755 index 0000000..cb7b36c --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# NetMover auf Debian 13 installieren bzw. aktualisieren (als root ausführen). +# +# ./deploy/install.sh # aus diesem Verzeichnis installieren +# ./deploy/install.sh 'git+https://git.wilde-server.de/florian.wilde/netmover.git@v0.1.0' +set -eu + +SOURCE="${1:-$(cd "$(dirname "$0")/.." && pwd)}" +PREFIX=/opt/netmover + +if [ "$(id -u)" -ne 0 ]; then + echo "Bitte als root ausführen." >&2 + exit 1 +fi + +echo "==> Pakete" +apt-get update -qq +apt-get install -y -qq python3 python3-venv cifs-utils nfs-common >/dev/null + +echo "==> Virtuelle Umgebung in $PREFIX/venv" +install -d -m 0755 "$PREFIX" +[ -x "$PREFIX/venv/bin/python" ] || python3 -m venv "$PREFIX/venv" +"$PREFIX/venv/bin/pip" install -q --upgrade pip +"$PREFIX/venv/bin/pip" install -q --upgrade "$SOURCE" + +echo "==> Konfiguration" +install -d -m 0755 /etc/netmover +if [ ! -f /etc/netmover/netmover.env ]; then + install -m 0640 "$(dirname "$0")/netmover.env" /etc/netmover/netmover.env +fi +install -d -m 0755 /mnt/netmover + +echo "==> systemd" +install -m 0644 "$(dirname "$0")/netmover.service" /etc/systemd/system/netmover.service +systemctl daemon-reload +systemctl enable netmover >/dev/null 2>&1 +systemctl restart netmover + +sleep 2 +systemctl --no-pager --lines=0 status netmover || true +echo +if journalctl -u netmover -n 200 --no-pager | grep -q "setup.required"; then + echo "Ersteinrichtung: Einrichtungscode aus dem Log:" + journalctl -u netmover -n 200 --no-pager | grep "setup.required" | tail -1 +fi +echo "Fertig. Weboberfläche: http://127.0.0.1:8080 (README: SSH-Tunnel / Reverse Proxy)" diff --git a/deploy/netmover.env b/deploy/netmover.env new file mode 100644 index 0000000..e15ab74 --- /dev/null +++ b/deploy/netmover.env @@ -0,0 +1,17 @@ +# Umgebungsvariablen für NetMover (/etc/netmover/netmover.env). +# Nach Änderungen: systemctl restart netmover + +# Nur lokal erreichbar – für Zugriff aus dem LAN einen Reverse Proxy mit TLS +# davorsetzen (README, „Reverse Proxy“) oder per SSH-Tunnel arbeiten. +NETMOVER_HOST=127.0.0.1 +NETMOVER_PORT=8080 + +# Hinter einem Reverse Proxy mit HTTPS: +# NETMOVER_COOKIE_SECURE=true +# NETMOVER_FORWARDED_ALLOW_IPS=127.0.0.1 + +# NETMOVER_MOUNT_BASE=/mnt/netmover +# NETMOVER_SESSION_HOURS=12 +NETMOVER_LOG_LEVEL=INFO +# logfmt (gut lesbar im Journal) oder json +NETMOVER_LOG_FORMAT=logfmt diff --git a/deploy/netmover.service b/deploy/netmover.service new file mode 100644 index 0000000..9c1bd2a --- /dev/null +++ b/deploy/netmover.service @@ -0,0 +1,45 @@ +# NetMover als systemd-Dienst direkt auf dem Host (empfohlen). +# Installation: siehe README, Abschnitt „Installation“. +[Unit] +Description=NetMover – NFS/SMB-Mounts und zeitgesteuertes Verschieben +Documentation=https://git.wilde-server.de/florian.wilde/netmover +Wants=network-online.target +After=network-online.target remote-fs-pre.target + +[Service] +Type=exec +# root ist nötig: mount/umount brauchen CAP_SYS_ADMIN, und Dateien auf beliebigen +# Freigaben sollen mit den richtigen Eigentümern geschrieben werden (siehe README). +User=root +EnvironmentFile=-/etc/netmover/netmover.env +ExecStart=/opt/netmover/venv/bin/netmover serve +Restart=on-failure +RestartSec=5 +# Beim Stoppen werden Mounts bewusst NICHT ausgehängt. +KillMode=mixed +TimeoutStopSec=20 + +# /var/lib/netmover (Datenbank, Schlüssel) und /run/netmover (kurzlebige +# Credentials-Dateien für mount.cifs) – beide nur für root lesbar. +StateDirectory=netmover +StateDirectoryMode=0700 +RuntimeDirectory=netmover +RuntimeDirectoryMode=0700 + +# Härtung, soweit mit Mounten vereinbar. +# Die Optionen unten erzeugen einen eigenen Mount-Namespace für den Dienst. +# MountFlags=shared sorgt dafür, dass Mounts unter /mnt/netmover trotzdem im +# System sichtbar sind (prüfen mit: findmnt -R /mnt/netmover). +MountFlags=shared +# /usr, /boot und /etc schreibgeschützt; /mnt, /opt, /srv, /var bleiben schreibbar +# (lokale Ziele wie /opt/paperless/consume funktionieren also). +ProtectSystem=full +PrivateTmp=yes +NoNewPrivileges=yes +ProtectControlGroups=yes +LockPersonality=yes +RestrictRealtime=yes +UMask=0022 + +[Install] +WantedBy=multi-user.target