# copyparty Admin Guide

This guide is for the administrators, operators, and developers who deploy, secure, tune, and extend copyparty. It covers the architecture, the complete configuration model, security hardening, operational procedures, and the internals needed to understand — or reproduce — the project. End-user features (the browser UI, uploading, media playback) live in the **User Guide**; this guide links there where appropriate.

copyparty is **version 1.20.16** ("sftp is fine too"), a portable file server written in Python with accelerated resumable uploads, deduplication, WebDAV/SFTP/FTP/TFTP/SMB protocols, zeroconf discovery, a media indexer, video thumbnails, audio transcoding, and write-only folders. Its single mandatory dependency is `jinja2`; everything else is optional and feature-gated.

---

## 1. Executive Summary

| Aspect | Detail |
|--------|--------|
| **What it is** | A single-process, multi-protocol file server runnable from one `.py` file |
| **Language** | Python (2.7 and 3.3+), no compiled components required |
| **Mandatory deps** | `jinja2` only (bundled in sfx/exe) |
| **Default HTTP port** | 3923 (HTTP and HTTPS on the same port) |
| **Config model** | Global options + per-volume "volflags"; CLI args and/or YAML-ish config files |
| **Auth model** | Accounts + per-folder permissions; optional IdP / header / IP-range auth |
| **Storage** | Plain filesystem volumes; optional SQLite index per volume (`.hist/up2k.db`) |
| **Deployment** | Standalone, systemd, OpenRC, Docker/Podman, NixOS, behind a reverse proxy |
| **License** | MIT (with optional GPL tooling kept strictly out-of-process) |

**Design philosophy** — "inverse unix philosophy": do *all* the things and do an *okay* job. The priorities are: run anywhere, support every browser and Python version, minimize mandatory dependencies, and require no build step (the JS and Python are editable in place).

> [!NOTE]
> Becoming rich is explicitly *not* a project goal. This shapes the architecture: simplicity, portability, and plaintext auditability are valued over polish or enterprise integration.

---

## 2. Architecture

### 2.1 High-level component map

```
                          ┌──────────────────────────────────────────┐
                          │              SvcHub (svchub.py)            │
                          │  orchestrator: config, logging, lifecycle, │
                          │  signal handling, service startup/reload   │
                          └───────────────┬────────────────────────────┘
                                          │ owns / starts
        ┌──────────────┬──────────────┬───┴────────┬──────────────┬───────────────┐
        ▼              ▼              ▼            ▼              ▼               ▼
   ┌─────────┐   ┌──────────┐   ┌─────────┐  ┌──────────┐  ┌──────────┐   ┌────────────┐
   │ AuthSrv │   │ HttpSrv  │   │  Up2k   │  │ ThumbSrv │  │  Ftpd /  │   │ MDNS/SSDPd │
   │ authsrv │   │ httpsrv  │   │ up2k.py │  │ th_srv   │  │ Sftpd /  │   │ zeroconf   │
   │  .py    │   │  .py     │   │         │  │          │  │ Tftpd /  │   │            │
   │ VFS +   │   │ per-conn │   │ upload  │  │ image/   │  │ SMB      │   │ LAN announce│
   │ accounts│   │ HttpConn │   │ engine, │  │ video/   │  │ servers  │   │            │
   │ + vols  │   │ +HttpCli │   │ dedup,  │  │ audio    │  │          │   │            │
   └─────────┘   └────┬─────┘   │ index   │  │ thumbs   │  └──────────┘   └────────────┘
                      │         └────┬────┘  └──────────┘
                      ▼              ▼
                 ┌─────────┐   ┌──────────┐
                 │  U2idx  │   │   MTag   │
                 │ u2idx.py│   │ mtag.py  │
                 │ search  │   │ metadata │
                 └─────────┘   └──────────┘

   TcpSrv (tcpsrv.py) — owns sockets / interface detection / port binding
   BrokerMp / BrokerThr — message bus between processes (multiprocessing) or threads
```

### 2.2 Core components

| Component | Module | Responsibility |
|-----------|--------|----------------|
| **SvcHub** | `svchub.py` | Master orchestrator. Sets up DB, logging, signal handlers; starts/stops/reloads FTP/SFTP/TFTP/HTTP servers; owns the broker. |
| **AuthSrv** | `authsrv.py` | Parses accounts, groups, and volumes; builds the **VFS** (virtual filesystem) tree; resolves per-request permissions; loads IdP/session databases. |
| **VFS** | `authsrv.py` | The virtual filesystem — maps URL paths to real folders, applies volflags, computes access (`can_access`, `get_perms`), supports shadowing and bubbling flags. |
| **HttpSrv** | `httpsrv.py` | Accepts HTTP(S) connections, manages worker threads, dispatches each to an `HttpConn`. |
| **HttpConn** | `httpconn.py` | One TCP connection; handles TLS handshake, reverse-proxy detection, and hands requests to `HttpCli`. |
| **HttpCli** | `httpcli.py` | The request handler — an ~80-method class implementing every HTTP verb, the [HTTP API](#7-http-api), uploads, downloads, listings, search, and the web UI. |
| **Up2k** | `up2k.py` | The resumable-upload engine: chunked hashing, dedup (symlink/hardlink/reflink), the SQLite index, file-search backend, lifetime/self-destruct. |
| **U2idx** | `u2idx.py` | Query engine over the up2k index — powers search and file-search. |
| **MTag** | `mtag.py` | Media-tag extraction via Mutagen or FFprobe, plus external parser plugins (`mtp`). |
| **ThumbSrv / ThumbCli** | `th_srv.py` / `th_cli.py` | On-demand thumbnail and audio-transcode generation (Pillow / pyvips / FFmpeg), with a disk cache. |
| **TcpSrv** | `tcpsrv.py` | Socket creation, NIC/interface detection, port binding, network monitoring. |
| **BrokerMp / BrokerThr** | `broker_mp.py` / `broker_thr.py` | Inter-node message bus. `BrokerMp` spans real OS processes (multiprocessing); `BrokerThr` is the single-process threaded fallback. |

### 2.3 Protocol servers

Each non-HTTP protocol is an optional, self-contained server bound to its own port:

| Protocol | Module | Backend library | Default port |
|----------|--------|-----------------|--------------|
| FTP / FTPS | `ftpd.py` | `pyftpdlib` (+ `pyopenssl` for TLS) | 3921 / 3990 |
| SFTP | `sftpd.py` | `paramiko` | 3922 |
| SMB / CIFS | `smbd.py` | `impacket` | 3945 (→445) |
| TFTP | `tftpd.py` | `partftpy` | 3969 (→69) |
| WebDAV | within `httpcli.py` | — (shares the HTTP port) | 3923 |
| mDNS / SSDP | `mdns.py` / `ssdp.py` (`multicast.py`) | bundled `stolen/dnslib` | 5353 / 1900 UDP |

> [!IMPORTANT]
> The HTTP/HTTPS API and WebDAV share port 3923. FTP, SFTP, TFTP, and SMB each require a **dedicated** port that cannot be shared with HTTP.

### 2.4 Multiprocessing model

By default copyparty runs single-process with a thread pool. Passing `-j N` (or `-j0` for all cores) spawns multiple worker processes coordinated through `BrokerMp`. Multiprocessing reduces latency under heavy concurrency and accelerates directory listings when the index is enabled, but it **increases** filesystem/HDD load and adds inter-process overhead, so the single-process default is best for most deployments.

> [!CAUTION]
> Several Prometheus metrics (`cpp_http_conns`, `cpp_http_reqs`, `cpp_sus_reqs`, `cpp_active_bans`, `cpp_total_bans`) are counted **incorrectly** when multiprocessing is enabled, because each process counts independently.

### 2.5 Bundled ("stolen") dependencies

To keep the mandatory dependency list at one (`jinja2`), copyparty vendors a few small libraries under `copyparty/stolen/`: `dnslib` (for mDNS), `ifaddr` (NIC discovery), and `qrcodegen.py` (QR codes). Each can be forced to use the system version via `PRTY_SYS_DNSLIB`, `PRTY_SYS_IFADDR`, `PRTY_SYS_QRCG`, or all at once with `PRTY_SYS_ALL`.

---

## 3. System Requirements

| Resource | Requirement |
|----------|-------------|
| **Python** | 2.7, or 3.3 through 3.14. CPython, PyPy, and Jython are all supported. |
| **CPU/RAM** | Minimal — known to run on an SGI O2 with 64 MiB RAM. RAM scales with thumbnail engine (pyvips uses ~270 % more than Pillow but is faster). |
| **Disk** | Volume storage + per-volume `.hist/` (index DB, thumbnail cache, transcode cache, markdown history). Put `.hist/` on an SSD for best performance. |
| **OS** | Windows, Linux, macOS, FreeBSD, Android, iOS, and many exotic platforms (big-endian, IRIX, etc.). |

### Optional dependencies and the features they unlock

| Dependency | Feature |
|-----------|---------|
| `argon2-cffi` | Hashed passwords in config (`--ah-alg argon2`) |
| `pyftpdlib` | FTP server (bundled in sfx) |
| `pyftpdlib` + `pyopenssl` | FTPS (TLS) |
| `paramiko` | SFTP server |
| `partftpy>=0.4.0` | TFTP server |
| `impacket==0.13.0` | SMB/CIFS server (not recommended) |
| `mutagen` *or* `ffprobe` | Audio/media tag indexing |
| `Pillow` and/or `pyvips` and/or `ffmpeg` | Image thumbnails |
| `ffmpeg` + `ffprobe` | Video/audio thumbnails and transcoding |
| `pyvips` / `ffmpeg` / `pillow-heif` | HEIF/HEIC thumbnails |
| `libraw dcraw_emu` *or* `rawpy` | RAW-photo thumbnails |
| `pyzmq` | ZeroMQ messages from event hooks |
| `python-magic` | Filetype detection for nameless uploads |
| `psutil` | Reaping stuck hooks/plugins on Windows |

The `pip` extras map directly to these: `pip install copyparty[all]`, or granular extras `thumbnails`, `thumbnails2`, `audiotags`, `sftp`, `ftpd`, `ftps`, `tftpd`, `pwhash`, `zeromq`.

> [!WARNING]
> `mutagen` makes copyparty's effective license GPL-adjacent and is fast but skips some tags. `ffprobe` is ~20× slower, catches more tags (key, resolution, framerate), avoids GPL code — but runs FFmpeg on incoming files, which is dangerous if your FFmpeg has an unpatched CVE and you allow anonymous upload.

---

## 4. Configuration Mastery

copyparty has two configuration scopes:

- **Global options** — server-wide; set as CLI flags (`--foo`) or in the config-file `[global]` section.
- **Volflags** — per-volume; set inline on a `-v` flag (`:c,foo=bar`) or in a volume's `flags:` block.

Most options work in both scopes. The authoritative, always-current list of every option is `copyparty --help`, every volflag is `--help-flags`, and accounts syntax is `--help-accounts`.

### 4.1 Volume and account syntax

```bash
-a USERNAME:PASSWORD
-v SRC:DST:PERM[,USER...]:PERM[,USER...]:c,VOLFLAG[=VALUE]
```

- `SRC` = real filesystem path, `DST` = URL path, then permission groups (see User Guide §6.1), then `c,`-prefixed volflags.
- Example combining everything:
  ```bash
  -v /mnt/ss:i:rw,u1:g:c,fk=4
  # /mnt/ss served at /i; read-write for u1; get-only for everyone else; filekey length 4
  ```

### 4.2 Config file structure

```yaml
[global]
  e2dsa            # global options, one per line; value after the name
  z                # boolean flags need no value
  lo: /var/log/cpp/%Y-%m-%d.txt

[accounts]
  ed: hunter2      # comments need TWO spaces before #

[groups]
  admins: ed, k

[/url/path]        # a volume
  /srv/realpath    # the folder it maps to
  accs:
    r: *
    rw: ed
  flags:
    e2ds
    fk: 4
```

> [!TIP]
> A config file may pass *additional* CLI arguments through `[global]`. You can chain multiple `-c file.conf`. Set `PRTY_CONFIG=foo.conf` as an environment variable as an alternative to `-c`.

### 4.3 Option groups (from `--help`)

`copyparty --help` is organized into these sections — use them as a map of the configuration surface:

| Group | Covers |
|-------|--------|
| general options | core behavior, ports, `-c` config files |
| qr options | `--qr`, `--qrs`, `--qrl`, `--qr-file`, … |
| filesystem options | volumes, `--hist`, `--xdev`, `--xvol`, dedup |
| share-url options | `--shr`, `--shr-adm`, `--shr-who`, `--shr-rt` |
| upload options | `--u2sz`, `--u2ow`, `--unpost`, lifetime, rules |
| network options | `-i`, `-p`, `--xff-hdr`, `--xff-src`, `--rproxy`, `--rp-loc` |
| SSL/TLS options | `--cert`, `--certkey`, `--no-crt` |
| TLS certificate generator | cfssl-based CA/cert generation, `--crt-*` |
| IdP / identity provider | `--idp-*`, `--auth-ord`, header auth |
| user-changeable passwords | `--chpw`, `--chpw-no`, `--chpw-db` |
| Zeroconf / mDNS / SSDP | `-z`, `--z-on`, `--zsv`, `--name` |
| SFTP / FTP / WebDAV / TFTP / SMB | each protocol's flags |
| OPDS options | `--opds`, `--opds-exts` |
| handlers / event hooks | `--xau`, `--xbu`, etc.; `--help-hooks` |
| grafana/prometheus metrics | `--stats`, `--nos-*` |
| yolo / opt-outs / safety | `--allow-csrf`, `-s`/`-ss`/`-sss`, `--no-*` |
| salting options | `--fk-salt`, `--dk-salt`, `--ah-salt` |
| logging options | `-q`, `-lo`, `--flo`, `--no-ansi`, `--log-date` |
| admin panel / thumbnail / transcoding | `--no-thumb`, `--th-*`, `--ac-maxage` |
| tailing / RSS / textfile / og / ui | viewer + embed + UI tweaks |
| general & metadata db options | `-e2*`, `--db-*`, `-mte`/`-mtp`/`-mtm` |
| debug options | diagnostics |
| help sections | `--help-accounts`, `--help-flags`, `--help-hooks`, … |

### 4.4 File indexing

Indexing powers search, upload-undo, and reliable dedup. Two tables live in `.hist/up2k.db`: the up2k filetree (`-e2d`) and metadata tags (`-e2t`).

| Flag | Effect |
|------|--------|
| `-e2d` | Index files on upload |
| `-e2ds` | + scan writable folders on startup |
| `-e2dsa` | + scan **all** volumes (including read-only) on startup |
| `-e2t` | Index media tags on upload |
| `-e2ts` | + scan untagged files on startup |
| `-e2tsr` | + delete all tags and do a full reindex |
| `-e2v` / `-e2vu` / `-e2vp` | Verify integrity at startup / patch DB / panic on mismatch |

The same flags work as volflags; `d2d`, `d2ds`, `d2t`, `d2ts`, `d2v` *disable* indexing per-volume.

> [!NOTE]
> The recommended baseline for most deployments is `-e2dsa -e2ts` (index everything, scan media tags). Folder thumbnails are chosen during indexing, so a [periodic rescan](#410-periodic-rescan) keeps them current.

### 4.5 Database location

By default each volume gets a `.hist/` subfolder. Relocate it with:

- `--hist /path` — global default location.
- `hist=` volflag — per-volume override (`-` restores the in-volume default).
- `--dbpath` / `dbpath` volflag — move just the SQLite DB, separate from thumbnails.
- `landmark` volflag — require a known file to exist before touching the DB (guards against NFS/HDD outages causing spurious rescans).

> [!TIP]
> Put `.hist/` on an SSD. On Windows, `--hist` takes a regular path but the volflag path is cyg-style (`/c/temp` = `C:\temp`).

### 4.6 Deduplication

Enable with `--dedup` (global) or `dedup` (volflag). When a duplicate is uploaded, copyparty links it instead of storing a second copy:

| Mode | Flag | Trade-off |
|------|------|-----------|
| Symlink (default) | `--dedup` | Per-copy timestamps; obviously not a regular file |
| Hardlink | `--hardlink-only` / `hardlinkonly` | Looks like a regular file; movable by other tools |
| Reflink (CoW) | `--reflink` / `reflink` | Safest; Linux 5.3+, Python 3.14+, btrfs/xfs only |

> [!WARNING]
> Editing the contents of a deduplicated file edits **all** copies — most surprising with hardlinks, which look like ordinary files. When enabling dedup, also enable indexing (`-e2dsa`) so renames/deletes stay safe. `--safe-dedup 1` speeds uploads by skipping content re-verification (safe only if no other software touches the files). `--no-clone` disables dedup detection entirely (useful for expensive cold storage like S3-Glacier).

### 4.7 Upload rules (volflags)

| Volflag | Effect |
|---------|--------|
| `sz=1k-3m` | Allowed filesize range |
| `df=4g` | Block uploads below this free disk space |
| `vmaxb=1g` / `vmaxn=4k` | Cap total volume bytes / file count (needs `e2ds`) |
| `nosub` | Disallow uploads into subdirectories |
| `rotn=1000,2` | Auto-bucket uploads into subfolders (1000/folder, 2 deep) |
| `rotf=%Y/%m/%d/%H` | Force date-structured upload paths |
| `lifetime=300` | Self-destruct uploads after N seconds |
| `maxn=250,3600` | Per-IP: 250 files / hour |
| `maxb=1g,300` | Per-IP: 1 GiB / 5 min |

> [!NOTE]
> Per-IP transaction limits (`maxn`/`maxb`) assume `-j 1`. With multiprocessing the effective limit multiplies by the worker count, since each node tracks independently.

### 4.8 Compression on upload

- `gz` / `xz` volflags allow that algorithm; `pk` **forces** compression on all files (`pk=xz,0` = forced xz level 0).
- URL params `?pk`, `?gz`, `?xz` request compression per-upload.
- Compressed files are indexed *after* compression, so dedup/file-search won't match the originals.

### 4.9 Filesystem guards

- `--xdev` / `xdev` — never traverse into another filesystem (skip cross-device symlinks/bind-mounts).
- `--xvol` / `xvol` — ignore symlinks leaving the volume root (bind-mounts still allowed).

Since v1.7.0 these also block runtime access, not just indexing. Expect up to ~14 % slower listings and ~35 % slower tar downloads.

### 4.10 Periodic rescan

If other software modifies the filesystem, keep the index fresh:

- `--re-maxage 60` (global) or `scan=60` (volflag) — rescan every N seconds.
- Uploads pause during a rescan; rescans are deferred by `--db-act` (default 10 s) while writes are in progress.

### 4.11 Metadata configuration

- `-mte` — which tags to index *and display* (and their order); per-volume via `mte=`.
- `-mth` — index + make searchable but hide from default display.
- `-mtm qux=foo,bar` — define/redefine a tag mapping (`qux` from `foo` or `bar`).
- `-mtp .bpm=~/bin/audio-bpm.py` — external parser plugin to provide a tag. Plugins support audio-only (`ay`), non-audio (`an`), all (`ad`), extension filters (`eexe`), timeouts (`t5`), ordering (`p1`), and kill modes (`kt`/`km`/`kn`).
- `--db-xattr user.foo,user.bar` — index Linux extended attributes (must also appear in `-mte`).

### 4.12 Other notable options

- `--mime gif=image/jif` — override a file extension's MIME type (`--mimes` lists defaults).
- `cachectl` / `?cache=321` — control browser `Cache-Control`.
- `chmod_f` / `chmod_d` / `uid` / `gid` volflags — per-volume filesystem permissions and ownership (`uid` requires running as root).
- `dothidden` — cosmetically hide files listed in a folder's `.hidden` file (not a security boundary).
- `--unlist` — hide a whole volume from HTTP listings (HTTP-only; `dothidden` also affects ftp/sftp).

---

## 5. Network, TLS, and Reverse Proxy

### 5.1 Listening

- `-i` sets the bind interface(s); default `::` (all). `-p 80,443` listens on multiple ports.
- Listen on a **unix socket** for ~10 % more performance and tighter access control: `-i unix:770:www:/dev/shm/party.sock`.

### 5.2 Binding to ports 80/443

| Platform | Approach |
|----------|----------|
| Windows / macOS | Just `-p 80,443` |
| Linux (recommended) | A reverse proxy (also gives real HTTPS) |
| Linux (NAT) | `iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3923` |
| Linux (capability) | `setcap CAP_NET_BIND_SERVICE=+eip $(realpath $(which python))` |

### 5.3 HTTPS / TLS

Both HTTP and HTTPS are accepted on the same port. copyparty has built-in TLS, but the **recommended** setup is to terminate TLS at a reverse proxy:

- Reverse proxies follow TLS best practices better and support HTTP/2 and QUIC (copyparty speaks neither — though HTTP/1.1 is often faster anyway).
- If `cfssl` is installed, copyparty auto-generates a self-signed CA + server cert on startup (written to `--crt-dir`); you must install `ca.pem` into clients.
- To supply your own cert: `--no-crt --cert server.pem` (key+cert+chain concatenated), or `--no-crt --cert server.crt --certkey server.key`. All files must be PEM.

### 5.4 Reverse-proxy and real-IP

When behind any proxy (including Cloudflare), **real-IP configuration is critical** — bans, rate limits, and IP-auth all rely on the true client IP.

```yaml
[global]
  xff-hdr: cf-connecting-ip   # header carrying the real IP (x-forwarded-for for most)
  # also configure --xff-src (trusted proxy IP) and --rproxy
```

- `--rproxy 1` if the header contains exactly one (correct) IP; a *negative* value picks from a multi-IP header (`-1` = rightmost/nearest hop, `-2` = second-nearest, …).
- `--rp-loc=/stuff` for location-based proxying (slight perf cost; needs correct real-IP first).
- `--rproxy 0` **only** if copyparty directly faces the internet (otherwise CORS misbehaves).

Reference configs ship in `contrib/` for nginx (recommended), apache, haproxy, lighttpd, caddy, and traefik.

> [!CAUTION]
> Use traefik **v3.6.7 or newer** — older versions are affected by CVE-2025-66490.

**Reverse-proxy performance** (uds, fastest first): `haproxy > caddy > traefik > nginx > apache > lighttpd`. Connecting via a unix socket beats `127.0.0.1` across the board. nginx-QUIC (HTTP/3) is experimental and can make uploads *much* slower — prefer HTTP/1.1.

### 5.5 Zeroconf discovery

- `-z` enables both mDNS and SSDP; `--z-on` / `--z-off` restrict to subnets.
- **mDNS** gives a `.local` domain (e.g. `partybox.local`, from hostname or `--name`) visible to KDE/GNOME/macOS file managers.
- **SSDP** makes copyparty appear in Windows Explorer; use `--zsv` to debug if it doesn't show.

---

## 6. Identity, Authentication, and Access Control

### 6.1 Built-in accounts

Accounts (`-a user:pwd`) with per-volume permissions are the baseline (see User Guide §6). Bruteforce protection: `--ban-pw` (default 24 h ban after 9 fails/hour).

### 6.2 Identity providers (IdP)

`--idp-*` options replace copyparty's passwords with an external SSO (Authelia, authentik, OAuth, LDAP, passkeys, etc.):

- Config-defined users serve as a fallback for requests without a trusted IdP header; `--auth-ord` sets precedence.
- `--idp-login` / `--idp-logout` repoint the UI buttons (`{dst}` expands to the current page).
- `--idp-cookie` lets requests bearing the `cppws` cookie bypass a slow IdP.
- Header mapping: `--idp-hm-usr '^Tailscale-User-Login^[email removed]^alice'` auto-auths by an upstream header.

A docker-compose example (Authelia + traefik) ships in `docs/examples/docker/idp-authelia-traefik`; see also `docs/idp.md`.

### 6.3 IP-based auth

- `--ipu 192.168.123.0/24=spartacus` — autologin a CIDR range as a user.
- `--ipr 192.168.123.0/24,172.16.0.0/16=spartacus` — restrict a user to certain CIDRs.

> [!CAUTION]
> With IP-auth behind a reverse proxy, real-IP **must** be correct, and you should null-map the proxy's own IP (`--ipu=172.24.27.9/32=`) to avoid auto-logging-in the proxy.

### 6.4 User-changeable passwords

`--chpw` lets users change their own passwords in the control panel (default off because account-sharing is common). Stored in `--chpw-db` (default `chpw.json`); `--chpw-no name1,name2` denies specific accounts. Not compatible with IdP.

---

## 7. HTTP API

Authentication order (first non-blank wins): url-param `?pw=`, header `PW:`, basic-auth, then cookie (`cppwd` on HTTP, `cppws` on HTTPS).

### 7.1 Read operations (GET)

| Params | Result |
|--------|--------|
| `?dl` | Download (force, don't render in browser) |
| `?ls` / `?ls&dots` / `?ls=t` / `?ls=v` | List as JSON / +dotfiles / plaintext / terminal |
| `?opds` | List as an OPDS feed |
| `?tree=.` / `?tree` | List one level of subdirs / each level to URL |
| `?tar` / `?tar=gz:9` / `?tar=xz:9` / `?tar=pax` | Download subtree as tar (variants) |
| `?zip` / `?zip=dos` / `?zip=crc` | Download subtree as zip (variants) |
| `?tar&w` / `?tar&j` / `?tar&p` | Pre-generate webp / jpg thumbnails / audio waveforms |
| `?shares` / `?dls` / `?ups` / `?ru` | List shares / active downloads / your uploads / all recent uploads |
| `?v` | Render markdown, or open media in the player |
| `?txt` / `?txt=iso-8859-1` | Get file as plaintext (with charset) |
| `?tail` / `?tail=1024` / `?tail=-128` | Stream a growing file (from byte / from end) |
| `?th` / `?th=opus` / `?th=caf` | Thumbnail / transcode audio to opus / caf |
| `?zls` / `?zget=path` | List / fetch a file inside a zip at URL |
| `?mime=foo` | Force a response MIME type |

### 7.2 Write operations

| Method | Params / Body | Result |
|--------|---------------|--------|
| PUT | (binary) | Upload into file at URL |
| PUT | `?j` / `?ck` / `?ck=md5` | + JSON reply / no checksum / md5 |
| PUT | `?gz` / `?xz` / `?apnd` | Compress with gzip/xz / append |
| mPOST | `f=FILE` | Upload FILE into folder at URL |
| mPOST | `?replace` / `?apnd` / `?media` | Overwrite / append / return medialink |
| mPOST | `act=mkdir`, `name=foo` | Create directory |
| mPOST | `act=tput`, `body=TEXT` | Overwrite a markdown doc |
| POST | `?copy=/foo` / `?move=/foo` | Copy / move-rename |
| POST | `?delete` | Delete URL recursively |
| jPOST | `?delete` `["/foo","/bar"]` | Delete multiple |
| jPOST | `?share` (payload) | Create a temporary share URL |
| POST | `?eshare=rm` / `?eshare=3` | Stop sharing / set 3-min expiry |
| uPOST | `msg=foo` | Send a message into the server log |

**Upload modifiers** (header or url-param): `want=url`/`want=json`, `rand=4`, `life=30`, `replace`, and checksum selectors `ck`, `ck=md5`, `ck=sha1`, `ck=sha256`, `ck=b2`, `ck=b2s`. copyparty returns a truncated, url-safe base64 sha512 of the upload by default.

### 7.3 Admin / general

| Params | Result |
|--------|--------|
| `?reload=cfg` | Reload config files and rescan volumes |
| `?scan` / `?scan=/a,/b` | Rescan the providing volume / specific volumes |
| `?stack` | Dump all thread stacktraces (diagnostics) |
| `?grid` / `?imgs` / `?thumb` | UI view toggles |

---

## 8. Security

### 8.1 Hardening checklist

- [ ] Set explicit accounts and volume permissions — never rely on the zero-arg default.
- [ ] Behind a proxy, configure real-IP (`--xff-hdr`, `--xff-src`, `--rproxy`); set `--rproxy 0` only if directly internet-facing.
- [ ] Listen on a unix socket behind the proxy for tighter access control.
- [ ] For untrusted/anonymous-upload volumes, set volflag `nohtml` to neutralize XSS (serves HTML/SVG as plaintext, disables markdown rendering and JS).
- [ ] Enable a [version-checker](#92-version-checker) feed.
- [ ] Consider [password hashing](#84-password-hashing).
- [ ] Sandbox with [prisonparty](https://github.com/9001/copyparty/blob/hovudstraum/bin/prisonparty.sh) (chroot) or [bubbleparty](https://github.com/9001/copyparty/blob/hovudstraum/bin/bubbleparty.sh) (bubblewrap), especially for SMB.

### 8.2 Safety profiles

| Flag | Bundles |
|------|---------|
| `-s` | `--no-thumb`, `--no-mtag-ff` (use mutagen), `--dotpart` (hide incoming uploads), `--no-robots`, `--force-js` |
| `-ss` | `-s` + `--unpost 0`, `--no-del`, `--no-mv`, `--hardlink`, `--vague-403` (404 instead of 401), `-nih` (hide hostname) |
| `-sss` | `-ss` + `--no-dav`, `--no-logues`, `--no-readme`, disk logging, startup symlink scan |

> [!IMPORTANT]
> Disabling thumbnails/transcoding via `--no-thumb` is strongly recommended when anonymous upload is enabled, because it stops copyparty from running FFmpeg/Pillow/VIPS on attacker-supplied files — a frequent CVE surface.

### 8.3 Gotchas and XSS vectors

- Users without read access can still see `.prologue.html` / `.epilogue.html` / `README.md` / `PREADME.md` contents (intentional, for upload instructions).
- Users may inject auto-running (sandboxed) `<script>` by uploading a `README.md` (block with `--no-readme`) or renaming a file to `.epilogue.html` (block with `--no-logues` / `--no-dot-ren`).
- Markdown documents can carry HTML/`<script>`; the directory-listing embed is sandboxed but the markdown editor isn't 100 % safe. The nuclear option is the `nohtml` volflag.

### 8.4 Password hashing

`--ah-alg argon2` hashes config/argument passwords (prints hashed versions on startup to copy back). `--ah-cli` enters an interactive mode that never writes plaintext to disk. Defaults take ~0.4 s and 256 MiB RAM per password. Keep `--ah-salt` consistent across environments (inspect with `--show-ah-salt`). With `--usernames`, hash as `user:password`.

### 8.5 CORS

By default, all requests except GET/HEAD must either omit `Origin`, match the server domain, or carry the `PW` header. Configure with `--acao` / `--acam`, or disable entirely with `--allow-csrf`.

### 8.6 Filekeys and dirkeys

- **Filekeys** (`fk`): per-file access keys from `--fk-salt` + path + size + inode. `fka` makes weaker keys that survive edits (salt + path only).
- **Dirkeys** (`dk`): per-directory keys from `--dk-salt` + path. `dky` skips the key check (g-users see folder contents but not subfolders); `dks` allows entering subfolders and zip/tar.

### 8.7 Malicious-repo / abuse posture

copyparty bans IPs that send 403/422/malicious requests (visible as `cpp_sus_reqs` / `cpp_active_bans` in metrics). The version-checker can panic-exit on a known vulnerability (`--vc-exit`). For GDPR, disable or rotate logging (`-lo cpp-%Y-%m%d.txt.xz`) and forget uploader IPs after a window (`--forget-ip 43200`).

---

## 9. Operations

### 9.1 Running as a service

| Init system | Resource |
|-------------|----------|
| systemd | `contrib/systemd/copyparty.service` (+ `prisonparty.service` for chroot, `copyparty@.service`, `copyparty-user.service`) |
| OpenRC (Alpine/Gentoo) | `contrib/openrc/copyparty` |
| FreeBSD rc | `contrib/rc/copyparty` |
| Podman + systemd | `contrib/podman-systemd/` |
| NixOS | `services.copyparty` module |

copyparty understands `NOTIFY_SOCKET` (systemd `Type=notify`) and `S6_NOTIFY_FD` (s6/dinit ready-notification).

Open the relevant firewall ports (complete example, every feature enabled):

```bash
firewall-cmd --permanent --add-port={80,443,3921,3922,3923,3945,3990}/tcp
firewall-cmd --permanent --add-port=12000-12099/tcp   # passive FTP
firewall-cmd --permanent --add-port={69,1900,3969,5353}/udp
firewall-cmd --reload
```

(69 tftp, 1900 ssdp, 3921 ftp, 3922 sftp, 3923 http/https, 3945 smb, 3969 tftp, 3990 ftps, 5353 mdns, 12000+ passive-ftp)

### 9.2 Version-checker

Subscribe to a security-advisory feed to be warned of known vulnerabilities:

```yaml
[global]
  vc-url: https://api.copyparty.eu/advisories   # or -panic / -all
  vc-age: 3        # hours between checks
  vc-exit          # panic-exit if current version is vulnerable
```

Without `--vc-exit`, a warning shows in the control panel for admin (`a`/`A`) users.

### 9.3 Logging

stdout by default (terminal / journalctl / docker logs):

```yaml
[global]
  q                            # disable stdout logging (small perf win)
  lo: /var/log/cpp/%Y-%m-%d.txt  # daily logfiles; %Y-%m-%d rotates by date
  flo: 2                       # no colors in the logfile
  log-date: %Y-%m-%d           # show dates on stdout too
```

`--no-ansi` removes colors everywhere; `--rlo` controls collision behavior when a logfile name is taken.

### 9.4 Configuration reload

`systemctl reload copyparty` (or `kill -s USR1`) reloads accounts and volumes from config files without dropping connections; the `[reload cfg]` control-panel button does the same for admin users. **`[global]` changes still require a restart.** The HTTP API equivalent is `GET ?reload=cfg`.

> [!NOTE]
> The SMB hot-reload does **not** pick up `[global]` (commandline) changes.

### 9.5 Prometheus metrics

Enable with `--stats` (and usually `-e2dsa`); scrape `/.cpr/metrics` (OpenMetrics 1.0.0). Only `admin` accounts can read it.

```yaml
scrape_configs:
  - job_name: copyparty
    metrics_path: /.cpr/metrics
    basic_auth:
      password: wark
    static_configs:
      - targets: ['192.168.123.1:3923']
```

Key metrics: `cpp_uptime_seconds`, `cpp_active_dl`, `cpp_http_conns`, `cpp_http_reqs`, `cpp_sus_reqs`, `cpp_active_bans`/`cpp_total_bans`, plus per-volume `cpp_vol_bytes`, `cpp_vol_files`, `cpp_dupe_bytes`/`cpp_dupe_files`, `cpp_disk_size_bytes`/`cpp_disk_free_bytes`, and queue gauges (`cpp_hashing_files`, `cpp_tagq_files`, `cpp_mtpq_files`). Disable subsets with `--nos-hdd` / `--nos-vol` / `--nos-vst` / `--nos-dup` / `--nos-unf`.

### 9.6 Thumbnail pregeneration

Thumbnails are on-demand by default, cached for `--th-maxage` (1 week). To pre-generate on startup, set `th-pregen` / `th_pregen` to a comma-list of formats (`j,jf,jf3,j3,w,wf,...,opus,mp3,flac,wav`). Budget: regular thumbnails ~16 KiB, triple-size ~96 KiB each. Audio transcodes are treated as thumbnails (expiry via `--ac-maxage`).

### 9.7 Event hooks and external integration

- `--xau` / `--xbu` (and move/delete equivalents) run a program before/after an event; see `--help-hooks` and `bin/hooks/`.
- Hooks can instead emit **ZeroMQ** messages: `--xau zmq:pub:tcp://*:5556` (PUB), `--xau t3,zmq:push:tcp://*:5557` (PUSH), `--xau t3,j,zmq:req:tcp://localhost:5555` (REQ with JSON).
- The older `mtp`-based upload-event mechanism (`bin/mtag/`) is non-blocking, multithreaded, gives access to FFmpeg tags, and fires only on new unique files.
- **Handlers** (`bin/handlers/`) replace 403/404 behavior.

### 9.8 Performance tuning

- Disable HTTP/2 and HTTP/3 → up to 5× faster uploads.
- `-q` (disable stdout logging) helps even with `-lo`.
- `--hist` on SSD speeds listings, searches, and thumbnails.
- `--dedup` avoids writing dupes; `--safe-dedup 1` speeds upload verification.
- `--no-dirsz` → ~30 % faster listings (shows inode size, not content total).
- `--no-hash .` when indexing a network disk if you only need names/tags searchable.
- Larger `--iobuf` / `--s-rd-sz` / `--s-wr-sz` (e.g. 1–4 MiB) help on NFS/SMB/S3.
- `-j0` enables real multiprocessing — helps with many connections and saturated 20 Gbps links, but usually *reduces* performance otherwise.
- On musl distros, `mimalloc` (`LD_PRELOAD`) trades 2× RAM for higher throughput.
- On Windows/macOS, `--casechk=n` is much faster (with the usual case-insensitive-FS caveats).

### 9.9 Cloud storage as a volume

There is no native S3 support. Mount cloud storage with rclone / geesefs / JuiceFS as a local folder, then point a copyparty volume at it. Tune with `--iobuf` / `--s-rd-sz` / `--s-wr-sz`; try the default, `sparse`, and `nosparse` volflags to find the best throughput for your stack.

---

## 10. Packaging and Distribution Internals

| Artifact | Notes |
|----------|-------|
| **sfx** (`copyparty-sfx.py`) | Self-extractor; unpacks an embedded `tar.gz` into `$TEMP`. `copyparty-en.py` is English-only/smaller. Repackable to shrink. |
| **exe** | `copyparty.exe` (win8+, bundles Pillow), `copyparty32.exe` (win7, Python 3.7.9 — LAN only, never internet). |
| **zipapp** (`copyparty.pyz`) | Normal zipfile, runs without unpacking to disk; fewer features, can't benefit from newer jinja2. |
| **PyPI** | `copyparty` package; console scripts `copyparty`, `u2c`, `partyfuse`. |
| **PyInstaller** | Build scripts in `scripts/pyinstaller/`. |
| **Docker** | `scripts/docker/`; image variants `min`/`ac`/`im`/`iv`/`dj` bundle increasing dependency sets. |

The version is the single source of truth in `copyparty/__version__.py` (`VERSION = (1, 20, 16)`, `BUILD_DT = (2026, 5, 26)`), consumed by both setuptools and hatch.

> [!CAUTION]
> Docker images and the bootable build cannot decode HEIF/HEIC or H.265/HEVC for legal reasons (`docs/bad-codecs.md`) — iPhone media thumbnails will fail there. Use a native install with `pyvips` for those.

---

## 11. Code Quality and Conventions

| Aspect | Detail |
|--------|--------|
| **Scale** | ~92 classes, ~825 functions across the `copyparty/` package; the largest module is `httpcli.py` (`HttpCli` with ~80 methods). |
| **Formatting** | `black` (pinned `21.12b0`, targeting py27), `isort` (black profile). |
| **Linting** | `ruff` (line-length 120), `pylint` (py3.11), `bandit` (skips B104/B110/B112). |
| **Types** | `mypy --strict` over `copyparty/`. |
| **Python floor** | py2.7 / py3.3 still targeted — hence the deliberately conservative idioms (no f-strings in core, broad compatibility shims). |
| **No build step** | JS and Python are editable in place; this is a stated design constraint. |

The codebase makes heavy use of small dedicated classes per concern (e.g. `Garda` for ban tracking, `Cooldown`, `FHC` file-handle cache, `MTHash` multithreaded hashing, `Pebkac`/`WrongPostKey` exception types) in `util.py`. Per-protocol servers subclass their library's handler (`FtpHandler(FTPHandler)`, `SFTP_Srv(paramiko.SFTPServerInterface)`, etc.), keeping copyparty's VFS logic centralized in `AuthSrv`/`VFS`.

### Feature kill-switches (chickenbits / beefybits)

A distinctive pattern: per-feature environment variables disable a feature when it misbehaves (chickenbits) or force-enable it on broken platforms (beefybits). Examples:

| Env var | Effect |
|---------|--------|
| `PRTY_NO_SQLITE` | Disable all DB features (indexing, dedup logic) |
| `PRTY_NO_MP` | Disable multiprocessing |
| `PRTY_NO_TLS` | Disable native HTTPS (terminate TLS at a proxy) |
| `PRTY_NO_FFMPEG` / `PRTY_NO_FFPROBE` | Disable transcoding / tag-scan via FFmpeg |
| `PRTY_NO_PIL` / `PRTY_NO_VIPS` | Disable a thumbnail engine (saves RAM/startup) |
| `PRTY_FORCE_MP` | Force multiprocessing on macOS and other broken platforms |
| `PRTY_NO_*` (dependency chickenbits) | One per optional dep — `argon2`, `paramiko`, `partftpy`, `dcraw`, `magic`, `mutagen`, `psutil`, `pyftpd`, `rawpy`, and Pillow sub-features (HEIF/AVIF/JXL/WEBP) |

Run with `PRTY_MODSPEC=1` to trace which module copyparty loads each dependency from.

---

## 12. Troubleshooting (Operator)

### Error: `OperationalError('database is locked')`

**Cause:** The `up2k.db` is on a network disk (SMB/NFS) or a filesystem with broken locking (VirtualBox vboxsf, mergerfs), or the share dropped briefly.
**Solution:** Move the DB to a local disk with `--hist` / `hist` volflag, or just the DB with `--dbpath`. Visit `/?stack` to capture live thread stacks for a bug report.
**Prevention:** Keep `.hist/` on a reliable local SSD; set a `landmark` volflag to gate DB access on network volumes.

### Error: Everyone keeps getting `thank you for playing`

**Cause:** copyparty bans the IP it *thinks* is the client; behind a proxy that's often the proxy's IP, so one bad actor bans everyone.
**Solution:** Configure real-IP (`--xff-hdr`, `--xff-src`, `--rproxy`); see §5.4 and `docs/xff.md`.

### Error: copyparty sees HTTP though clients use HTTPS

**Cause:** The proxy isn't forwarding `X-Forwarded-Proto: https` (or an intermediate like Cloudflare terminates HTTPS early).
**Solution:** Ensure the proxy chain forwards the header end to end.

### Error: SMB clients see only ~400 files in large folders

**Cause:** An impacket limitation (`impacket#1433`), worked around by default.
**Solution:** With impacket ≥0.12, disable the workaround with `--smb-nwa-1` (but expect a performance hit).

### Error: Thumbnails fail after sandboxing with prisonparty/bubbleparty + mimalloc

**Cause:** mimalloc needs `/proc` and `/sys`, which the sandbox hides.
**Solution:** Grant the sandbox access to `/proc` and `/sys`.

### Issue: Custom FFmpeg binary names (e.g. macports `ffmpeg8`)

**Cause:** copyparty looks for `ffmpeg`/`ffprobe` in `$PATH`.
**Solution:** Set `PRTY_FFMPEG_BIN` and `PRTY_FFPROBE_BIN` to the correct name or full path.

### Recovering from a Firefox WSOD during upload

Firefox 87 can crash the whole browser mid-upload. In the up2k tab's devtools (`F12`) you can recover the upload state:

```javascript
var tabs = { ok:[], ng:[], bz:[], q:[] }; for (var a of up2k.ui.tab) tabs[a.in].push(a); tabs
```

---

## 13. Potential Improvements

These are observations for operators and contributors, drawn from the project's own notes and code shape:

- **Multiprocessing metric accuracy** — connection/request/ban counters are per-process under `-j`, so aggregate dashboards under-report. A shared counter via the broker would fix this.
- **SMB backend maturity** — the maintainer explicitly states low confidence that read-only is truly read-only and warns of possible path-traversal; it isn't fully integrated with the VFS. Treat SMB as LAN-only and sandbox it. A VFS-integrated SMB layer would close this gap.
- **HTTP/2 and QUIC** — copyparty speaks neither; it relies on a reverse proxy for them, and notes HTTP/1.1 is often faster anyway. Native HTTP/2 could simplify single-binary deployments.
- **Bidirectional sync** — intentionally unsupported; the documented path is to pair with Syncthing. Operators needing two-way sync must run a second tool.
- **`httpcli.py` size** — `HttpCli` is a single ~80-method class handling the entire request surface. It is load-bearing and well-tested but a natural target for decomposition if contributing.

See `docs/TODO.md` in the repo for the maintainer's planned features and fixes, and `docs/devnotes.md` for build instructions (sfx repack, build-from-scratch).

---

## 14. Quick Reference

### Default ports

| Port | Protocol |
|------|----------|
| 3923/tcp | HTTP + HTTPS + WebDAV |
| 3921/tcp | FTP |
| 3990/tcp | FTPS |
| 3922/tcp | SFTP |
| 3945/tcp | SMB (NAT from 445) |
| 3969/udp | TFTP (NAT from 69) |
| 5353/udp | mDNS |
| 1900/udp | SSDP |
| 12000-13000/tcp | FTP passive range (`--ftp-pr`) |

### Essential commands

```bash
# minimal share of current folder
python copyparty-sfx.py

# indexed music server, read-only, with one read-write user
python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts \
  -a kevin:okgo -v /mnt/nas/inc:/inc:rw,kevin --no-robots --theme 2

# run from a config file
python copyparty-sfx.py -c /etc/copyparty/copyparty.conf

# reload config without restart
systemctl reload copyparty      # or: kill -s USR1 <pid>

# see all options / volflags / accounts syntax
copyparty --help
copyparty --help-flags
copyparty --help-accounts
```

### Key environment variables

| Variable | Purpose |
|----------|---------|
| `PRTY_CONFIG` | Path to config file (alternative to `-c`) |
| `XDG_CONFIG_HOME` / `APPDATA` | Where copyparty looks for default config |
| `NOTIFY_SOCKET` / `S6_NOTIFY_FD` | Service readiness notification |
| `PRTY_NO_*` | Feature/dependency chickenbits |
| `PRTY_FORCE_MP` / `PRTY_FORCE_MAGIC` | Beefybits (force-enable) |
| `PRTY_SYS_*` | Use system instead of vendored libraries |
| `PRTY_FFMPEG_BIN` / `PRTY_FFPROBE_BIN` | Custom FFmpeg/FFprobe paths |
| `PRTY_MODSPEC` | Debug: trace module loading |

### Reporting bugs

Primary: [github.com/9001/copyparty](https://github.com/9001/copyparty/) (mirrors on GitLab and Codeberg). Include command-line arguments, config, and — for freezes — the thread stacks from `/?stack` or `journalctl`.

---

_Generated June 2026 from 9001/copyparty at 6e75faa. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
