# Janus Janus publishes an app running on your own machine as a public HTTPS site. It does this by encoding a Holesail P2P public key into a subdomain, so routing needs no database and no account. The app is never uploaded; the gateway holds a peer-to-peer tunnel to your machine for as long as your process is running. Base domain: janus.ssani.dev URL shape: https://0000<52-char-key>.janus.ssani.dev/ ## Quickstart 1. Start the app locally and note its port. Binding to 127.0.0.1 is fine; binding to 0.0.0.0 is NOT required. 2. Publish that port: npx holesail --live --public 3. Copy the printed key. It looks like: hs://0000<52 chars> 4. Open https://0000<52 chars>.janus.ssani.dev/ -- everything after "hs://" is the subdomain, the "0000" prefix included. ## The --public flag is mandatory "holesail --live " without --public prints an s000-prefixed key. That is the SECURE-mode key: it is the serve credential and lets whoever holds it impersonate the backend. This gateway rejects s000 keys by design and answers 400. If you got a 400, this is almost certainly why. ## Security model The key is the only credential. Anyone with the URL reaches the app with whatever privileges it already grants, and a local development server typically has no authentication at all. - The key is visible in the TLS SNI field, which is sent unencrypted. - It is retained in browser history and sent in Referer headers to third-party origins the page links to or loads from. - Treat the URL as a bearer token; do not publish it. - Killing the local process revokes access. Restarting mints a new key unless a fixed seed is supplied. ## Status codes 400 Key malformed, or an s000 secure key. Re-run with --public. 502 Key resolved on the DHT, but the backend did not answer. The local process is probably stopped, or a different port was published. 503 Gateway at tunnel capacity, or backing off after repeated failures to reach this backend. Retry. 429 Rate limited. 404 Unknown host -- the subdomain is not a well-formed key for this domain. If nothing resolves at all, holepunching is failing. It needs outbound UDP, and symmetric NAT or CGNAT (mobile carriers, some ISPs, restrictive office networks) can prevent it. That is a network condition, not an application bug. Retry from a different network. ## Notes for frameworks - The Host header is 0000.janus.ssani.dev. Dev servers that allowlist hostnames must be told about it: server.allowedHosts (Vite), allowedHosts (webpack-dev-server), ALLOWED_HOSTS (Django). - X-Forwarded-Proto arrives as "https,http" because two proxies each append a value. Parse the leftmost entry. An exact match against "https" fails and can cause an HTTPS-redirect loop. - X-Forwarded-For is a list; the client is the leftmost entry. - WebSockets are proxied. Use wss:// on the same hostname.