Files
share/SETUP.md
T
eberkheev 7af9aec0b3 feat(deploy): add install script and untrack share.config
Package the service for setup on a new machine:

- install.sh generates a token, writes share.config (mode 600, token
  stored as a SHA-256 hash) and installs the systemd user unit from
  share.service.template
- share.config is now gitignored, with share.config.example as the
  tracked template — the config holds the secret and must not be in
  the repo
- warn at startup when token is empty, since that means open access
- SETUP.md documents install, verification and security posture
2026-07-27 02:07:21 +04:00

5.8 KiB

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

git clone <repo-url> ~/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

./install.sh --token 'my-own-secret'

Or after the fact, without re-running the installer:

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

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://<lan-ip>: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

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

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