feat: systemd-Unit, Installationsskript, Dockerfile und compose.yml
Unit mit StateDirectory/RuntimeDirectory, ProtectSystem=full und MountFlags=shared; compose mit SYS_ADMIN, DAC_READ_SEARCH, apparmor:unconfined und rshared-Bind für /mnt/netmover (Propagation zum Host im Container getestet). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
00a0537085
commit
dd9177859e
@@ -0,0 +1,8 @@
|
|||||||
|
.venv
|
||||||
|
.git
|
||||||
|
data
|
||||||
|
**/__pycache__
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
.ruff_cache
|
||||||
|
tests
|
||||||
+29
@@ -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"]
|
||||||
+37
@@ -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:
|
||||||
Executable
+46
@@ -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)"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user