# Deploying Share on a new machine Instructions for a fresh Claude Code session (or a human) bringing this service up from a clean clone. Read this top to bottom before running anything. ## What this is A LAN file-sharing web app: drag & drop upload, clipboard paste, download, delete, auto-expiry of old files. Python 3 stdlib only — no pip install, no virtualenv. It serves **HTTPS with a self-signed certificate**. That is not optional decoration: the browser Clipboard API (`navigator.clipboard.write`) only works on a secure origin, and the paste/copy features depend on it. ## Requirements - Python **3.10+** (the code uses `X | None` annotations evaluated at runtime) - `openssl` on PATH (used once, to generate the self-signed cert) - Linux with systemd user units for the service install; without systemd it still runs fine in the foreground ## Install ```bash git clone ~/projects/share cd ~/projects/share ./install.sh ``` That will: 1. check python/openssl versions, 2. generate a random token, hash it, and write `share.config` (mode 600), 3. install `~/.config/systemd/user/share.service` pointing at wherever you cloned, 4. `enable --now` the service and confirm it actually came up. **The generated token is printed once, at the end. Save it.** Only its SHA-256 hash is stored, so it cannot be recovered afterwards — losing it means re-running with `--force` to set a new one. ### Setting your own secret ```bash ./install.sh --token 'my-own-secret' ``` Or after the fact, without re-running the installer: ```bash python3 share.py hash 'my-own-secret' # prints the SHA-256 hash # paste that hash as the `token = ` value in share.config systemctl --user restart share ``` Both forms are equivalent — `share.config` never holds the plaintext. A 64-char hex value is treated as an already-hashed token; anything else is hashed at startup (convenient, but then the plaintext sits on disk — prefer the hash). ### Other install options | Flag | Effect | |------|--------| | `--token S` | use secret `S` instead of generating one | | `--port N` | listen port (default `3001`) | | `--dir P` | upload directory (default `~/Downloads/shared`) | | `--ttl N` | file lifetime in days, `0` = keep forever | | `--san IP` | extra IP in the cert SAN; repeatable | | `--lang ru\|en` | UI language | | `--no-service` | write config only, skip systemd | | `--force` | overwrite an existing `share.config` | Re-running `install.sh` without `--force` keeps the existing config, so it is safe to run again just to reinstall the unit file. ## Verify it works ```bash systemctl --user status share curl -sk -o /dev/null -w '%{http_code}\n' https://localhost:3001/files # → 401 (no token: correct, auth is on) curl -sk -H "Authorization: Bearer YOUR_TOKEN" https://localhost:3001/files # → [] (empty file list: correct) ``` A `401` from the first call is the success signal — it means the token gate is active. A `200` there means `token` is empty in `share.config` and **anyone on the network can read and write your files**. Then open `https://:3001` from a phone or laptop on the same network. The browser will show a certificate warning; that is expected for a self-signed cert — accept it once. Enter the token on the login page; it is stored in an `HttpOnly` cookie for a year. ## Networking notes - The server binds `0.0.0.0`, so it is reachable from the whole LAN. - The cert's SAN list gets `127.0.0.1` plus the auto-detected LAN IP. If you reach the box by another address (a router doing NAT, a second interface, a static IP that differs from the detected one), pass it with `--san` or the cert will not validate for that address. - Changing SANs later: delete `.certs/` and restart — the cert regenerates. - Open the port in the firewall if one is active (`ufw allow 3001/tcp`, or the `firewalld`/`iptables` equivalent). - The service starts on **login**, not boot. For a headless box that should serve without anyone logging in: `sudo loginctl enable-linger $USER`. ## Managing the service ```bash systemctl --user restart share # after editing share.config systemctl --user stop share systemctl --user disable --now share journalctl --user -u share -f # live logs ``` `share.config` is read only at startup — every config change needs a restart. ## Running without systemd ```bash python3 share.py # foreground, reads share.config python3 share.py -p 4000 --ttl 0 # CLI flags override share.config ``` ## Security posture — know what this is This is a LAN convenience tool, not a hardened public service. Deploy accordingly: - The token is a single shared bearer secret. Everyone who has it has full read/write/delete access to the upload directory. There are no user accounts. - An empty `token` disables auth completely. Never leave it empty. - The cert is self-signed, so clients cannot distinguish it from a man-in-the-middle cert on first use. Fine on a home LAN, not fine on a hostile network. - Do not port-forward this to the internet. It is designed for a trusted LAN. - `share.config` holds the secret hash and is gitignored — keep it that way. Copy it between machines out of band, never through the repo. ## Files | Path | Role | |------|------| | `share.py` | entry point, startup wiring, `hash` subcommand | | `server.py` | HTTP handler, routing, auth, upload/download | | `config.py` | config file + CLI arg loading, constants | | `cert.py` | self-signed cert generation, LAN IP detection | | `cleanup.py` | background thread expiring files past `ttl` | | `static/index.html` | the entire frontend, single file | | `share.config.example` | tracked template — copy to `share.config` | | `share.config` | **gitignored**, holds the token hash | | `share.service.template` | tracked systemd unit template | | `install.sh` | the setup script described above |