# XFER — copyparty file server behind Cloudflare Tunnel This folder (`/Volumes/4TB/XFER`) is served online by **copyparty**, exposed through a **cloudflared** named tunnel at **https://v01.rasik.life**. Everything copyparty needs is contained in `.server/` (hidden from visitors; also shadowed in the config so it is never served). ## Live architecture ``` browser → https://v01.rasik.life (Cloudflare edge, TLS terminated there) → cloudflared named tunnel "xfer" (id 3150c84b-44fc-4ad3-8c81-32b3c54dbf09) → copyparty on http://127.0.0.1:3923 (loopback only, no open ports) ``` ## Components | What | Where | Notes | |---|---|---| | copyparty sfx | `.server/copyparty-sfx.py` | self-contained, run with system `python3` (3.14, homebrew) | | copyparty config | `.server/copyparty.conf` | **all settings live here** — accounts, permissions, UI flags | | IMAX CRT theme (enabled) | `.server/ui/` (`imax-crt.css`, `imax-crt.js`, `VT323-Regular.ttf`; old `amber-crt.*` files kept but unused) | amber-phosphor CRT control-panel theme inspired by IMAX projection booth. **Enabled by default** — toggled on/off via a button fixed top-right (state in `localStorage` key `imax_crt`). IMAX 1.43:1 letterbox frame is a separate toggle (key `imax_frame`). CSS is linked via `html-head `, JS loaded via `js-browser`. Old `amber-crt.css`/`amber-crt.js` are superseded — do not re-enable them | | index db / thumbnails | `.server/hist/` | kept out of the shared tree on purpose | | logs | `.server/copyparty.log`, `.server/cloudflared.log` | appended on each start | | start/stop scripts | `.server/start.sh`, `.server/stop.sh` | start.sh launches copyparty + named tunnel if not running | | quick-tunnel config | `.server/quicktunnel.yml` | intentionally empty; keeps quick tunnels from inheriting `~/.cloudflared/config.yml` credentials (causes 404s) | | cloudflared launchd agent | `~/Library/LaunchAgents/life.rasik.v01.cloudflared.plist` | starts tunnel at login, KeepAlive; logs to `~/.cloudflared/cloudflared.log` | | login-item launcher app | `~/Applications/XFERFileServer.app` | runs `.server/start.sh` at login (copyparty can't be a launchd agent — see TCC note) | | cloudflared config | `~/.cloudflared/config.yml` | ingress: `v01.rasik.life → http://127.0.0.1:3923` | | cloudflared cert + tunnel creds | `~/.cloudflared/cert.pem`, `~/.cloudflared/3150c84b-*.json` | secrets; cert is scoped to the rasik.life zone | ## Access model (current policy) - **Anonymous visitors** are auto-redirected from `/` to `/ANON/` by an inline `html-head` script (checks for `"acct":"admin"` in the DOM; admin stays at root). Inside `/ANON/` they get `rw` (browse, download, upload, mkdir). Everything outside `/ANON/` is invisible to them (root listing shows only the ANON folder; direct file URLs elsewhere return 403). - **admin: full access** (`A`) everywhere — root + ANON. Username/password in `.server/copyparty.conf` → `[accounts]`. Login via the key button in the UI, or `?pw=user:pass` URL param. - Volumes: `[/]` is `A: admin` only; `[/ANON]` is `rw: *` + `A: admin`; `[/.server]` is `//NULL` (never served). - To restrict anon to read-only (no upload/mkdir), change `rw: *` to `r: *` in `[/ANON]`. To allow anon upload without browsing, change to `w: *`. ## Operating ```bash # start everything (idempotent) /Volumes/4TB/XFER/.server/start.sh # stop everything /Volumes/4TB/XFER/.server/stop.sh # health checks curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3923/ # local curl -s -o /dev/null -w "%{http_code}\n" https://v01.rasik.life/ # public pgrep -fl "copyparty-sfx|cloudflared tunnel run" # processes ``` After editing `.server/copyparty.conf`: `stop.sh && start.sh` (config reload only covers non-`[global]` sections; a full restart is safer). ## Persistence across reboots - **cloudflared**: launchd agent `life.rasik.v01.cloudflared` (RunAtLoad + KeepAlive). All its files live in `~`, so launchd can run it. Manage with `launchctl bootstrap/bootout/kickstart gui/$(id -u)/life.rasik.v01.cloudflared`. - **copyparty**: login item `~/Applications/XFERFileServer.app` runs `start.sh` at login. It is NOT a launchd agent on purpose: macOS TCC blocks launchd-spawned processes from removable volumes (`/Volumes/4TB` → "Operation not permitted", exit 78 with empty logs). The app gets a one-time TCC prompt ("allow access to removable volumes") — it was already opened once; if macOS ever re-asks, click Allow. - Both mechanisms fire at **login**, not boot. For true headless operation enable automatic login (System Settings → Users & Groups), or convert to LaunchDaemons + Full Disk Access grants (not configured). - **start.sh waits for the drive**: the script blocks up to 120s for `/Volumes/4TB/XFER/.server/copyparty-sfx.py` to appear before proceeding, so login-item launch races (drive not yet mounted) no longer cause a silent failure. If the drive doesn't mount within 120s, the script logs an error and exits non-zero instead of failing silently. ## Temporary quick tunnel (optional, disposable) For a random public URL without touching DNS: ```bash cloudflared tunnel --config /Volumes/4TB/XFER/.server/quicktunnel.yml --url http://127.0.0.1:3923 ``` URL is printed at startup, changes every run, dies with the process. Always pass the empty `--config` file (see table above). ## DNS / registrar facts (hard-won, don't rediscover) - Domain `rasik.life` is registered at **Hostinger**; nameservers are `ns1.dns-parking.com` / `ns2.dns-parking.com` (Hostinger's own DNS). **Do NOT switch nameservers to Cloudflare** — rasik.life hosts other apps (PHOTOBooth, FHM, etc.) on Hostinger that would break. Only the `v01` subdomain routes through Cloudflare via a CNAME record on Hostinger's DNS zone. - The `v01` CNAME record on Hostinger's DNS zone points to `3150c84b-44fc-4ad3-8c81-32b3c54dbf09.cfargotunnel.com` (the Cloudflare tunnel). This is the ONLY record that touches Cloudflare. All other records (rasik.life A, www, ftp, neon, dsl, lib) stay on Hostinger. - There is an *older* Cloudflare zone for the same domain on `harlee.ns.cloudflare.com` / `vick.ns.cloudflare.com` (different account, unknown). If DNS ever flips to those, the tunnel record will vanish from the internet. - DNSSEC is **off** at the registry — keep it off unless DS records are updated, or the zone will break. - The Cloudflare tunnel DNS route exists in Cloudflare's zone too (for when the domain was briefly on Cloudflare NS). Recreate if ever lost: `cloudflared tunnel route dns xfer v01.rasik.life` - Hostinger API: base `https://developers.hostinger.com` (use this — `api.hostinger.com` has intermittent 530s/Cloudflare 1016). Bearer token auth, endpoints under `/api/domains/v1/` (portfolio, nameservers) and `/api/dns/v1/` (zone records). **A Hostinger API token was pasted in chat on 2026-07-25; treat it as exposed and rotate it in the Hostinger panel.** - Hostinger Python SDK (`pip install hostinger_api`) is installed in a venv at `.server/hostinger/.venv` but has a pydantic enum case-sensitivity bug (API returns "Active", SDK expects "active"). Use curl against the REST API instead. - To fix DNS if v01 breaks: (1) verify Hostinger NS are set to dns-parking via `GET /api/domains/v1/portfolio/rasik.life`, (2) verify the v01 CNAME via `GET /api/dns/v1/zones/rasik.life`, (3) if missing, add CNAME v01 → tunnel cfargotunnel.com via `PUT /api/dns/v1/zones/rasik.life`. See `.server/hostinger/fix_dns.sh` (needs updating to use the CNAME approach, not the nameserver approach). ## copyparty flags currently enabled (`.server/copyparty.conf`) - `e2dsa` + `e2ts`: file index, search, unpost, media tags (ffprobe) - `usernames`: username+password login dialog - `grid`, `gsel`: gallery view + multiselect by default (good on mobile) - `theme: 2`, `name: v01.rasik.life`, `no-robots` - `html-head`: single line containing `` to `/ui/imax-crt.css` (theme CSS) + inline `