# copyparty [copyparty](https://github.com/9001/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](#updates--security-notices). | See [`.env.example`](.env.example) for the rest. Accounts and shares live in [`copyparty.conf.example`](copyparty.conf.example) (deploy.sh generates the real `cfg/copyparty.conf` with a random admin password on first run). ## Deploy ```bash ./automations.sh # Deploy on this host → deploy: copyparty ``` Or build + run the self-contained artifact: ```bash ./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`](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: ```bash 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`](Caddyfile)). - **copyparty** trusts that upstream via `xff-src: lan` in [`copyparty.conf.example`](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. ```bash 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. ```yaml # docker-compose.yml, copyparty service: volumes: - caddy-data:/caddy:ro ``` ```ini # 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-app** — `vc-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`](update.sh)** — reads the same feed to *act*, pinning the new version in `.env` and recreating the container (with health-checked rollback). ```bash ./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.