Files
57_Wolve c00ca055f2 feat(copyparty): add file-server deployment with SFTP/FTPS + security-notices updater
New deployments/copyparty/: copyparty (copyparty/ac) behind Caddy/LE for the
web UI/WebDAV, plus its own SFTP (password auth) and FTPS listeners published
directly. Ships update.sh, which drives container updates off copyparty's
security-advisories API (api.copyparty.eu/advisories) -- policies latest|security|off.

- Real client IP end-to-end: Caddy XFF/X-Real-IP + copyparty xff-src: lan.
- SFTP host key + self-signed FTPS cert generated/persisted in /cfg; admin
  password generated on first deploy; conf auto-included via the image's % /cfg.
- Firewall opens 80/443 + SFTP/FTPS + passive range (colon form for ports.d).
- Wired into automations.sh, README, .gitignore; cloud-init for fresh VMs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:56:24 -05:00

79 lines
3.1 KiB
YAML

# copyparty stack -- caddy (TLS) + copyparty (portable file server).
#
# Topology:
# Browser/WebDAV --> caddy:443 --> copyparty:3923 (HTTP, internal)
# SFTP / FTPS --> copyparty:{3922,3990,passive} (direct, NOT via caddy)
#
# Caddy terminates TLS and reverse-proxies the web/WebDAV UI. copyparty's SFTP
# and FTPS listeners are their own TCP services, so they are published straight
# from the copyparty container -- Caddy is HTTP-only and not in that path.
#
# The image's baked-in config does `chdir /w` + `% /cfg` (include every *.conf in
# /cfg), so our ./cfg/copyparty.conf is picked up automatically -- no `command:`
# override needed. XDG_CONFIG_HOME=/cfg in the image, so generated SSH host keys
# live under /cfg and persist across restarts (the bind-mount is read-write).
name: copyparty
volumes:
caddy-data:
caddy-config:
services:
# ---------------------------------------------------------------------------
# Caddy -- TLS termination + reverse proxy for the web/WebDAV UI. The only
# service on 80/443. Auto-issues a Let's Encrypt cert for ${COPYPARTY_DOMAIN}.
# ---------------------------------------------------------------------------
caddy:
image: caddy:${CADDY_TAG:-2-alpine}
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
environment:
COPYPARTY_DOMAIN: "${COPYPARTY_DOMAIN}"
ACME_EMAIL: "${ACME_EMAIL}"
depends_on:
- copyparty
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# ---------------------------------------------------------------------------
# copyparty -- the file server. HTTP 3923 stays internal (Caddy fronts it);
# only the direct SFTP/FTPS listeners are published. Runs as PUID:PGID so
# files on the host bind-mount have sane ownership.
#
# A published Docker port BYPASSES the host INPUT firewall, so set BIND_ADDR
# to pin these listeners to a trusted interface if the box is multi-homed.
# ---------------------------------------------------------------------------
copyparty:
image: ${COPYPARTY_IMAGE:-copyparty/ac}:${COPYPARTY_TAG:-latest}
container_name: copyparty
restart: unless-stopped
user: "${PUID:-1000}:${PGID:-1000}"
ports:
- "${BIND_ADDR:-0.0.0.0}:${SFTP_PORT:-3922}:3922" # SFTP
- "${BIND_ADDR:-0.0.0.0}:${FTPS_PORT:-3990}:3990" # FTPS (explicit TLS)
- "${BIND_ADDR:-0.0.0.0}:${FTP_PASV_RANGE:-12000-12099}:${FTP_PASV_RANGE:-12000-12099}" # FTPS passive
volumes:
- ${DATA_DIR:-/srv/copyparty/data}:/w
- ./cfg:/cfg
healthcheck:
# copyparty's own python -- no extra deps in the image. A successful TCP
# connect to the HTTP listener means the server is up.
test: ["CMD", "python3", "-c", "import socket; socket.create_connection(('127.0.0.1',3923),3).close()"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s