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
..

copyparty

copyparty — a portable file server with resumable uploads, dedup, WebDAV, SFTP, FTPS, media indexing and thumbnails — behind Caddy with automatic Let's Encrypt TLS. Ships with a security-notices-aware updater (update.sh).

The web UI / WebDAV is fronted by Caddy on 443; SFTP and FTPS are served directly by copyparty (Caddy is HTTP-only and not in that path).

Required .env values

Variable Notes
COPYPARTY_DOMAIN Public hostname for the web UI (e.g. files.example.com).
ACME_EMAIL Let's Encrypt registration email.
DATA_DIR Host folder shared as the data root (mounted at /w). Point at a data disk.
COPYPARTY_IMAGE / COPYPARTY_TAG Image edition (copyparty/ac) + pinned version.
SFTP_PORT / FTPS_PORT / FTP_PASV_RANGE Direct-listener ports (3922 / 3990 / 12000-12099).
FTP_NAT This host's public IP, for passive FTPS through NAT (blank if direct).
UPDATE_POLICY latest (default) / security / off — see Updates.

See .env.example for the rest. Accounts and shares live in copyparty.conf.example (deploy.sh generates the real cfg/copyparty.conf with a random admin password on first run).

Deploy

./automations.sh        # Deploy on this host → deploy: copyparty

Or build + run the self-contained artifact:

./build.sh
scp deploy.sh root@host:
ssh root@host 'bash deploy.sh'
# non-interactive:
#   COPYPARTY_DOMAIN=files.example.com ACME_EMAIL=me@example.com SKIP_PROMPTS=1 bash deploy.sh

Unattended provisioning: cloud-init.yml. On first run deploy.sh pins COPYPARTY_TAG to the newest release, generates cfg/copyparty.conf (random admin password, printed once) and a self-signed cfg/ftps.pem, opens the ports, brings the stack up, and schedules update.sh.

Accounts & shares

The default config is private: one admin account with full access to the whole data root, nothing public. Edit cfg/copyparty.conf (commented examples included) to add users or open read-only / anonymous-upload shares, then hot-reload:

docker compose exec copyparty kill -s USR1 1

Real client IP (behind Caddy)

For the web UI, copyparty must see the genuine client IP (for its logs and abuse bans), which means both sides are configured:

  • Caddy is the internet edge, so it appends the real address to X-Forwarded-For and also sets X-Real-IP (see Caddyfile).
  • copyparty trusts that upstream via xff-src: lan in copyparty.conf.example — without it copyparty ignores the header and logs/bans the Docker-network address.

lan (trust private ranges) is safe here because copyparty's HTTP port is never published — only Caddy can reach it. If copyparty prints an xff-src warning at startup, narrow it to the exact subnet it names. SFTP/FTPS are direct TCP, so they already see the real client IP (no XFF involved).

SFTP & FTPS

Both authenticate against the same [accounts] as the web UI.

sftp -P 3922 admin@files.example.com           # SFTP (password auth enabled)
lftp -u admin -e 'set ftp:ssl-force true; set ssl:verify-certificate no' \
     ftp://files.example.com:3990              # explicit-TLS FTPS
  • SFTP host key is generated on first start and persisted under /cfg, so clients don't get "host key changed" warnings across restarts.

  • FTPS cert defaults to the self-signed cfg/ftps.pem (clients must accept it). To serve a publicly-trusted cert, reuse Caddy's Let's Encrypt cert: mount the caddy-data volume read-only into the copyparty service and point copyparty at it, e.g.

    # docker-compose.yml, copyparty service:
    volumes:
      - caddy-data:/caddy:ro
    
    # cfg/copyparty.conf [global]:
    cert:    /caddy/caddy/certificates/acme-v02.api.letsencrypt.org-directory/files.example.com/files.example.com.crt
    certkey: /caddy/caddy/certificates/acme-v02.api.letsencrypt.org-directory/files.example.com/files.example.com.key
    

    (copyparty re-reads the cert on kill -s USR1; refresh after a renewal.)

  • Passive FTPS needs the FTP_PASV_RANGE ports published (they are) and FTP_NAT set to the server's public IP when clients connect through NAT.

  • A published Docker port bypasses the host INPUT firewall, so set BIND_ADDR to pin the listeners to a trusted interface on multi-homed hosts.

Updates & security notices

copyparty doesn't self-update, but it publishes a machine-readable security-advisories feed (the same one its built-in --vc-url check uses). This deployment uses it on two levels:

  1. In-appvc-url + vc-age in cfg/copyparty.conf make copyparty itself check whether the running version has a known advisory and warn in the log / control panel. (vc-exit, opt-in, makes it shut down if vulnerable.)
  2. update.sh — reads the same feed to act, pinning the new version in .env and recreating the container (with health-checked rollback).
./update.sh check      # report current vs latest + any advisory; change nothing
./update.sh update     # update now (TARGET_VERSION=1.20.11 to pin a version)
./update.sh install    # (re)schedule the daily run    /  uninstall to stop

UPDATE_POLICY (in .env or /etc/copyparty-update.conf) controls the scheduled run:

Policy Behaviour
latest (default) Update to the newest release whenever one exists.
security Update only when the running version has a known advisory, to the patched release named in the feed.
off Never change the running version (check/notify only).

VC_FEED selects the feed (advisories-panic / advisories / advisories-all). Update results reuse the ntfy config at /etc/ssh-notify.conf (same as the SSH login notifier / host auto-update), when present.

Files

File Purpose
docker-compose.yml caddy + copyparty; publishes SFTP/FTPS/passive ports.
Caddyfile TLS + reverse proxy for the web UI/WebDAV (real-IP headers).
copyparty.conf.example Config template (globals, security check, SFTP/FTPS, accounts, volumes).
update.sh Security-notices-aware container updater.
.env.example Stack tunables.
deploy.sh / build.sh Self-contained installer + archive embedder.
cloud-init.yml Fresh-VM bootstrap (harden SSH, then deploy).

Notes

  • No Anubis PoW gate — it would break WebDAV / API / upload clients.
  • The web UI/WebDAV go through Caddy; SFTP/FTPS bypass it by design.
  • DNS for COPYPARTY_DOMAIN must resolve to the host and 80/443 be reachable before deploy for the LE cert to issue.