No description
  • Rust 81%
  • Python 10.8%
  • Shell 3.4%
  • JavaScript 2%
  • C 1%
  • Other 1.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Miles bdaefda199
Some checks failed
CI / Deploy artifacts (push) Successful in 4s
Docker / Build & Push Image (push) Successful in 22s
CI / Lint & Test (push) Failing after 6m24s
Retry transient Debian package downloads
2026-08-23 16:40:58 -05:00
.forgejo/workflows Fix Forgejo CI push trigger 2026-08-23 13:42:03 -05:00
bench Rename project to WebSploot 2026-08-23 13:29:11 -05:00
browser Rename project to WebSploot 2026-08-23 13:29:11 -05:00
config Rename project to WebSploot 2026-08-23 13:29:11 -05:00
deploy Rename project to WebSploot 2026-08-23 13:29:11 -05:00
docs Rename project to WebSploot 2026-08-23 13:29:11 -05:00
LICENSES upd 2026-08-22 14:04:52 -05:00
rarng Rename project to WebSploot 2026-08-23 13:29:11 -05:00
release/evidence Rename project to WebSploot 2026-08-23 13:29:11 -05:00
src Rename project to WebSploot 2026-08-23 13:29:11 -05:00
tests Rename project to WebSploot 2026-08-23 13:29:11 -05:00
tools Rename project to WebSploot 2026-08-23 13:29:11 -05:00
vendor/hickory-server Rename project to WebSploot 2026-08-23 13:29:11 -05:00
.dockerignore Rename project to WebSploot 2026-08-23 13:29:11 -05:00
.env.example Rename project to WebSploot 2026-08-23 13:29:11 -05:00
.gitignore Rename project to WebSploot 2026-08-23 13:29:11 -05:00
build.rs Rename project to WebSploot 2026-08-23 13:29:11 -05:00
Cargo.lock Rename project to WebSploot 2026-08-23 13:29:11 -05:00
Cargo.toml Rename project to WebSploot 2026-08-23 13:29:11 -05:00
deny.toml update 2026-08-13 15:29:10 -05:00
docker-compose.bridge.yml Rename project to WebSploot 2026-08-23 13:29:11 -05:00
docker-compose.ps5.yml Rename project to WebSploot 2026-08-23 13:29:11 -05:00
docker-compose.yml Rename project to WebSploot 2026-08-23 13:29:11 -05:00
Dockerfile Retry transient Debian package downloads 2026-08-23 16:40:58 -05:00
LICENSE update 2026-08-12 16:09:39 -05:00
README.md Rename project to WebSploot 2026-08-23 13:29:11 -05:00
REUSE.toml Rename project to WebSploot 2026-08-23 13:29:11 -05:00
THIRD_PARTY_NOTICES.md Rename project to WebSploot 2026-08-23 13:29:11 -05:00

WebSploot

WebSploot is one Rust/Tokio binary that provides filtering DNS and hosts PlayStation user-guide content. It answers UDP and TCP DNS, serves a console-focused landing page, mounts guide trees directly at /ps4/ and /ps5/, and supports the firmware URL shape /document/{lang}/{console}/.

The server loads verified content, gzip, and Brotli representations into memory. Serving static files does not touch the filesystem or recompress content. Upstream DNS uses Hickory's reusable UDP/TCP connection pool with failover and TCP fallback, surrounded by websploot's bounded cache, singleflight, failure suppression, and stale-on-error behavior.

WebSploot terminates plain HTTP only. Put Caddy or another reverse proxy in front when public TLS is needed. manuals.playstation.net must normally remain plain HTTP: a public CA cannot issue a certificate for Sony's hostname.

Native quick start

Build, generate configuration, install the pinned PS4 and PS5 guides, and validate the complete deployment:

cargo build --release

# Interactive on a terminal; public is the prompted default. `setup` is
# idempotent, so rerun it after an interrupted download.
sudo ./target/release/websploot \
  --config /etc/websploot/websploot.toml setup \
  --state-dir /var/lib/websploot

For automation, the public profile requires an address. Public recursion is the default and always runs behind the fixed safety ceilings described below:

sudo ./target/release/websploot \
  --config /etc/websploot/websploot.toml setup \
  --profile public \
  --dns-address 192.0.2.10 \
  --override-address 192.0.2.10 \
  --state-dir /var/lib/websploot

Other profiles are lan (private recursion) and web-only (DNS disabled). init is the configuration-only primitive and supports init --stdout for container bootstrap. It refuses to replace an existing configuration unless --force is supplied. setup adds guide state, installs both immutable guide archives, and runs the same complete validation as check. Displayed DNS addresses and the exact manuals.playstation.net override are requested separately because they are different configuration responsibilities.

Generated configurations trust X-Forwarded-For only from loopback Caddy. Replace server.trusted_proxies with the proxy's exact peer CIDR when Caddy is on another host or container network; never put an end-user or public range in that list. Administrative HTTP access is default-deny: repeat --admin-client at setup time, or explicitly populate [admin].allow_clients, with only the resolved client CIDRs that need the detailed APIs and metrics.

Inspect the installed revision or validate again without binding a port, then run:

websploot --config /etc/websploot/websploot.toml check
websploot --config /etc/websploot/websploot.toml guides status
sudo websploot --config /etc/websploot/websploot.toml run

The PS4 catalog pins WebKitty commit 10846c6cf201d62f71d8374edc08d489331a6368 from its renamed upstream ArabPixel/WebKitty repository and archive SHA-256 4d20bd7e5c38b0c239220ed7ae9228c20cd4c3a6bcda0e9f4a605654e0a006e0. The archive is downloaded with a hard size limit, hash-checked, extracted into a traversal-safe staging directory, manifested, preloaded, and only then activated. The previous revision is retained.

The PS5 catalog and offline product share one SlopKit lock at commit 85b5759dd9a48f3cc17650065484314419c43577 with archive SHA-256 99ab56622ff06563b2ff6891a9e1cab1301c678ae530cfcf3d047ab3399f874c. It contains no server or telemetry client, setting, route, or telemetry storage. During installation, WebSploot relocates the release's root AppCache inventory beneath /ps5/, rewrites its source link to the public repository, generates a strict served-tree manifest, validates offline closure, and atomically activates it.

An administrator-provided --ps5-root can replace managed SlopKit. The public release intentionally omits the optional browser-install handoff declaration; to enable that action, pass an override tree with the capability declaration and the installed tile target. setup validates the contract before writing the generated configuration:

websploot --config /etc/websploot/websploot.toml setup \
  --profile public --dns-address 192.0.2.10 \
  --ps5-root /srv/websploot/ps5 \
  --public-url https://guides.example.com/ \
  --browser-category media \
  --state-dir /var/lib/websploot

PS5 browser ELF products

Each release attaches two independently installable Media tiles; ELFs are generated under target/browser-products/ and are not committed:

  • WSPL00001-websploot-online.elf installs WebSploot and opens https://jb.rar.ng/. DNS, live status, and server-backed content remain available because this is the network variant.
  • WSPL00002-websploot-offline.elf installs WebSploot Offline at the dedicated https://web.sploot/ origin. The ELF pre-seeds the shared home, pinned SlopKit tree, fallback navigation, license, notices, and provenance directly into the foreground PS5 profile's WebKit AppCache database. Its bundled pages need neither DNS nor an HTTP server.

Both pages share the address bar, favorites, and recents model. The offline variant permanently favorites SlopKit and omits DNS, service-status, and installer controls. Its address bar may still open external HTTP(S) URLs when connectivity exists.

Verify generated artifacts and exact hashes through the locked external builder interface with:

python3 tools/browser_artifacts.py products-check
python3 tools/browser_artifacts.py inspect \
  target/browser-products/WSPL00002-websploot-offline.elf --json

The deterministic offline exporter and full two-product rebuild are documented in browser/QUICKSTART.md. Physical firmware evidence is exact-artifact scoped; see docs/console-acceptance.md.

Guide management

websploot --config websploot.toml guides status
websploot --config websploot.toml guides install [ps4|ps5]
websploot --config websploot.toml guides install ps5 --archive ./pinned.tar.gz
websploot --config websploot.toml guides update --check
websploot --config websploot.toml guides update [ps4|ps5]
websploot --config websploot.toml guides rollback ps5

Managed revisions live below the guide state directory. current changes by an atomic symlink rename. A guide lock generation is polled by the running server; successful changes are loaded off the serving threads and published through an ArcSwap. A malformed or mismatched generation leaves the previous in-memory snapshot live.

There is no runtime git fetch, reset --hard, or Git dependency. Existing custom [[tree]] root configurations remain supported.

Configuration reference

The annotated, load-tested reference is config/websploot.example.toml. Important production surfaces are:

  • http.listen and dns.listen: one socket string or an array of up to 16; dual-stack deployments normally use explicit IPv4 and IPv6 sockets.
  • http.max_connections: shared HTTP admission cap, default 32768.
  • [dns.transport]: UDP task, global TCP, per-prefix TCP, idle-time, and per-connection query bounds. Defaults are 8192, 4096, 64, 5 seconds, and 256.
  • dns.allow_clients: recursion ACL. The public profile deliberately uses both all-address CIDRs; the LAN profile emits private CIDRs only.
  • dns.trusted_clients: bypasses only per-prefix/per-client controls, never global resource ceilings.
  • [dns.lists]: local paths or HTTP(S) sources. The built-in PSN snapshot is immediately usable; default project lists refresh conditionally every 12h.
  • [admin].allow_clients: the exact resolved client CIDRs allowed to use detailed health, DNS status, and metrics. It is empty by default.
  • [[tree]]: administrator-provided roots or the managed PS4/PS5 current symlinks. Direct mounts and deep manual assets remain stable.
  • [telemetry]: mode = "off" by default; anonymous enables only this instance's fixed-schema SQLite store.

landing.auto_redirect, landing.redirect_delay, and tree.console_index are removed. During 0.x they produce actionable migration errors rather than being treated as unknown keys. The default log filter is warn,websploot=info; set RUST_LOG only when diagnosing a bounded interval.

DNS behavior

Policy precedence is:

  1. exact [[dns.override]] local data;
  2. filter allowlist bypass;
  3. blocklist;
  4. upstream recursion.

An allowlist entry no longer creates a local address. The legacy dns.allow_ips key maps only to an exact manuals.playstation.net override for one migration cycle.

The built-in psn preset contains the shipped PSN block/allow snapshot. Local and cached remote sources layer over it, and startup never depends on a network fetch. config/psn-snapshot.json records the source-base revision, snapshot date, line counts, and exact hashes; tools/psn_snapshot.py checks or deliberately refreshes it from a local psn-blocklist checkout. preset = "none" enables a completely custom ruleset.

An exact override may contain independent IPv4 and IPv6 addresses:

[[dns.override]]
name = "manuals.playstation.net"
addresses = ["192.0.2.10", "2001:db8::10"]
ttl = 300

A and AAAA receive only their configured family. Unsupported types receive cacheable NODATA with SOA. Blocked A/AAAA answers use zero IPs by default; block_mode = "nxdomain" is available.

Forwarding provides:

  • reusable pooled UDP/TCP upstream transports and TCP retry after truncation;
  • at most two upstream attempts within a two-second end-to-end deadline;
  • one shared flight for identical concurrent cache misses;
  • a 4,096-request global forwarding ceiling;
  • a bounded cache of at most 100,000 entries and 256 MiB, with TTL aging and DO/CD-bit keys;
  • passive per-upstream health with 1, 2, 4, 8, 16, then 30-second backoff, healthy-server preference, and one half-open recovery probe;
  • five-second failure caching;
  • stale serving for up to one hour after an upstream failure, with a 30-second response TTL;
  • complete answer, authority, additional, response-code, EDNS, and DNSSEC state;
  • per-upstream state, retry time, latency, success, failure, and timeout data in admin JSON and Prometheus, plus stale, coalescing, and in-flight metrics.

DO, CD, and AD state is preserved, but WebSploot does not validate DNSSEC itself. Validation is delegated to the configured recursive upstream.

Malformed and multi-question requests are rejected. UDP answers never exceed 1232 bytes; oversized answers set TC=1 so a real client retries over TCP. ANY receives a small RFC 8482-style HINFO response.

Public recursion warning

The requested default allows 0.0.0.0/0 and ::/0. Loopback, RFC1918, and ULA clients are trusted by default; trusted clients bypass per-client controls but not global resource ceilings. Other clients receive 50 QPS/100 burst budgets and response-rate limiting at 15 responses/sec/30 burst, aggregated over IPv4 /24 and IPv6 /56.

A global 50,000 QPS and 100 Mbps response ceiling applies in addition. Limiter tables have fixed memory and attacker-chosen keys cannot grow them.

These controls mitigate abuse; they cannot eliminate reflector or volumetric risk. Restricting recursion is still the IETF recommendation in RFC 5358. Firewall/provider UDP protection remains necessary for an internet-facing listener. WebSploot keeps a plain statement of this residual risk in setup/check output and startup logs. The warning is intentionally non-blocking; there is no acknowledgement setting, and operational warnings are not exposed on the public health page.

Also check port 53 before deployment. systemd-resolved, another DNS daemon, or a provider firewall commonly owns or blocks it.

For an internet-facing resolver, apply coarse upstream filtering before the process: provider DDoS controls and an nftables rate/policer sized above the measured legitimate peak. Application admission preserves service under bounded overload but cannot recover bandwidth already saturated on the wire.

For example, after measuring the real peak and choosing a deliberately higher edge ceiling, an optional host rule can discard excess UDP packets before they allocate application work (replace 100000/second, the table, and the chain to match the host's existing policy):

table inet websploot_edge {
  chain input {
    type filter hook input priority filter; policy accept;
    udp dport 53 limit rate over 100000/second counter drop
  }
}

The provider-side equivalent is an ACL exposing only required TCP/UDP ports, a UDP packets/second alert above the measured envelope, and automatic scrubbing or upstream null-routing for traffic the host link cannot absorb. Test either policy from a separate source before production; an edge rule that is set below legitimate bursts merely moves the outage in front of WebSploot.

HTTP routes

Route Access Purpose
/ public landing page
/ps4/, /ps5/ public direct guide mounts
/document/{lang}/{id}/ public console firmware URL; index redirects to /?console={id}&lang={lang}
/websploot.manifest public versioned offline inventory for the home page and compatible guides
/health public sanitized human status and configured DNS addresses
/livez public process liveness
/readyz public listener/content readiness
/healthz public SlopKit-compatible build/status identity
/api/v1/telemetry/batch public when enabled anonymous fixed-schema telemetry
/api/v1/actions/browser-installer public when configured no-store schema-1 action descriptor
/api/v1/actions/browser-installer/{sha256}.elf public when configured immutable specialized ELF; GET/HEAD only
/api/v1/health admin CIDRs detailed health
/api/v1/dns/status admin CIDRs DNS/cache/limiter detail
/metrics admin CIDRs Prometheus metrics

/api/health and /api/dns/status are protected 0.x compatibility aliases and will be removed after one 0.x release.

The action routes return 404 when [browser] is absent and deliberately do not enable CORS. Static files support strong representation-specific ETags, conditional 304s, byte ranges, HEAD, AppCache manifests, and precompressed negotiation. Revalidated static responses also allow a saved response to be used for up to one year when the origin fails; online requests remain immediately revalidated. Direct mounts never redirect back to /, so the landing flow cannot loop.

Offline caching

The built-in landing page declares /websploot.manifest, a bounded HTML5 Application Cache inventory generated from the exact in-memory content generation. Its version changes when any owned landing variant, path, or guide byte changes, so a successful reload publishes a matching cache update. It includes the home variants, local artwork, and served files from compatible guides that do not own an AppCache manifest. Dynamic health, telemetry, and action APIs remain in the NETWORK: * section and keep their no-store policy.

Guide ownership is explicit. If any HTML page in a guide declares its own manifest, the complete tree is delegated to that guide. This preserves the external PS4 guide's five firmware-specific manifests under includes/caches/manifest/. Managed SlopKit likewise owns /ps5/slopkit.appcache and its compatibility alias; root URLs are deterministically relocated beneath /ps5/ during installation. WebSploot also enforces SlopKit's narrow relative notify.html notify.html fallback so unique renderer warm-up query URLs reuse the cached document when the origin is unreachable; arbitrary network and API requests remain outside that fallback. The managed revision name versions this deterministic transformation independently of the verified upstream archive, so changing it publishes a new tree instead of overwriting an installed revision. A guide with no manifest is preloaded by the WebSploot home cache; a custom guide that declares manifest="/websploot.manifest" joins that cache when opened directly. The generated home manifest is capped at 8 MiB, counted in http.max_loaded_content_bytes, and reports any omitted unsafe/oversized tree through admin health.

Every guide-owned manifest is closure-checked before publication. Same-origin CACHE entries and FALLBACK targets must resolve to served files under a direct or manual mount. Lexical parent segments are allowed only while they remain inside the guide root; root escapes, encoded separators, denied files, malformed sections, and missing targets fail closed while the last-good generation stays active. Makefile is intentionally not in the default deny set because the pinned PS4 guide requires it offline.

On PlayStation browsers, wait for Home available offline before deliberately disconnecting the server. The first successful load is required: no cache can make content available before the console has downloaded it. AppCache is a legacy API retained by the console WebKit builds; newer browsers that removed it still benefit from validators and stale-if-error, but their offline behavior depends on their HTTP cache implementation.

The landing footer deliberately says Home available offline because that event covers /websploot.manifest, not a guide-owned inventory. Open managed SlopKit once and let its separate /ps5/slopkit.appcache download finish before disconnecting the origin or relying on the launcher after a reboot. When upgrading an already-cached deployment, close and reopen SlopKit once more while the origin is reachable to activate the downloaded cache generation.

Landing page

The built-in page is a flat, dependency-free graphite design for 10-foot and D-pad use: a 5% overscan-safe area, content width up to 1280px, 18px-or-larger copy, 64px controls, and a 4px gold focus ring. All CSS and inline SVG controls ship in the binary; there are no remote fonts or third-party visual assets. Flexbox declarations precede grid declarations for older console WebKit. Focus never scales a control and the page does not intercept D-pad/arrow keys; it leaves spatial movement to the console browser. Targets exceed WCAG 2.2's minimum target-size guidance, with a persistent treatment aligned to its focus-appearance guidance.

Its order is a compact aligned title/settings row, address/search form, favorites, administrator quick links, recents, and a compact DNS/status footer. Favorites begin with server-rendered, non-removable console-guide entries: PS4 / WebKitty and PS5 / SlopKit. Recognized PS4/PS5 clients see only their matching entry; unknown clients see both. An absent guide remains visible as Not installed without a broken link, so a failed or intentionally skipped guide install does not break the home page. Install Browser joins the same pinned shelf only when the PS5 capability contract is ready. The address form permits only HTTP(S) and same-origin paths; websploot never proxies an entered URL. Console- and locale-specific landing documents are private, immediately revalidated, and vary on User-Agent and Accept-Language. Their strong ETags avoid resending an unchanged page, while AppCache supplies the console-offline copy.

User favorites (websploot.favourites) remain capped at 24, independently of the pinned guide entries, and recents (websploot.recents) at eight. Favorites may contain safe same-origin paths, but pinned destinations are removed from stored favorites to avoid duplicate entries. Recents retain only cross-origin HTTP(S) destinations; old same-origin entries are pruned when rendered. Removing one favorite or clearing all saved favorites and recents requires confirmation; pinned entries are never written to or cleared from local storage. The obsolete websploot.autoopen key is ignored. Storage denial/quota failures disable only these conveniences.

Settings and the destructive-action confirmation are ARIA modals with background focus suppression, focus trapping, Escape/cancel handling, and focus restoration. Settings controls preferred language, clearing local history, and opening health. Its two actions share the available width equally and stack on narrow screens. WebSploot never automatically opens a guide.

All interface copy, ARIA labels, and direction come from an embedded catalog. Catalogs must have equal key sets and are RTL-ready; English is the only current catalog. Selection precedence is URL lang → stored preference → Accept-Language → English. The JavaScript is ES5 and uses no dependencies, fetch, modern URL API, or generated markup from stored URLs.

Content manifests

The canonical generated build-manifest.json shape is compatible with slopkit:

{
  "schema": 1,
  "algorithm": "sha256",
  "buildId": "…64 lowercase hex…",
  "sourceRevision": "optional",
  "files": {
    "index.html": {"sha256": "…", "size": 1234}
  }
}

buildId hashes sorted lines of <sha256> <size> <path>\n. Generate one with:

websploot manifest /path/to/tree --revision REV

The reader also accepts slopkit's validated releaseBuildId and qualifiedRuntimeId identity fields; websploot does not invent either field when generating a guide manifest.

verify = "strict" serves only verified inventory. manifest-if-present scans only when the manifest is absent; a present malformed, incomplete, or mismatched manifest fails closed. The earlier supported manifest schema remains readable without preserving any retired project-name aliases.

Every tree is preflighted before body allocation. max_files, max_file_bytes, and max_total_bytes bound its raw inventory; http.max_loaded_content_bytes bounds identity plus enabled compressed representations, installer bytes, pre-rendered landing snapshots, and the generated offline inventory. Reloads must fit the current and complete staged generations simultaneously. A failed size, integrity, traversal, symlink, capability, or memory check leaves the old generation serving.

Browser installer and PS5 handoff

[browser] is optional. If absent, startup is normal, Health reports “not configured,” and no install action is shown. If present, a console-enabled ps5 tree must include websploot-capabilities.json with exactly the schema-1 postActionHandoff contract. A typical section is:

[browser]
target_url = "https://guides.example.com/"
category = "media"
title_id = "WSPL00001"
title = "WebSploot"
language = "en-US"
version = "01.00"

category accepts only game and media, which emit PS5 applicationCategoryType values 0 and 65536 respectively. Omitting it preserves the legacy implicit Games metadata byte-for-byte; newly generated configuration writes an explicit category. Adding or changing an explicit category changes installed metadata, so remove an existing shortcut with the same title ID through the PS5 UI before installing the newly categorized version.

The PS5 card links only to /ps5/?post-action=browser-installer. A compatible SlopKit guide accepts that fixed ID, fetches only the fixed same-origin descriptor path, validates its exact schema/path/size/hash/firmware coverage, and queues it for the next committed jailbreak attempt. After elfldr is ready, SlopKit verifies and sends the requested ELF before an optional terminal Payload Manager action. No descriptor or artifact URL is accepted from a query parameter.

The embedded ELF is a locked carrier generated by the separately maintained ps5-browser-builder. WebSploot checks its hash, ELF/PT_LOAD layout, and unique 4096-byte configuration record, then specializes only that record at startup. Production does not need an SDK. The builders released OCI digest, CLI contract, carrier, artwork, qualification, and provenance are bound in browser/README.md.

SlopKit integration

WebSploot is the production static host for the public SlopKit tree. Run only this process in production; no SlopKit development server accompanies it. The managed release contains no telemetry implementation and generated configurations keep [telemetry].mode = "off".

WebSploot retains /healthz and its fixed-schema telemetry endpoint for wire compatibility with administrator-provided development/private clients. Using that endpoint requires mode = "anonymous" plus a separate client-side user opt-in; neither exists in managed public SlopKit. There is no relay, federation, device identifier, or upstream collector.

Anonymous telemetry

Telemetry is off by default:

[telemetry]
mode = "off" # or "anonymous"
database = "/var/lib/websploot/telemetry.sqlite3"
pending_hours = 3
retention_days = 30
max_state_mib = 512

Browser consent remains a separate guide-side opt-in and is also expected to default off. The endpoint accepts at most 32 KiB and 64 events with fixed event codes, metric names, metadata, outcomes, and exact idempotency semantics. Duplicate JSON keys and free-form errors are rejected. Source rate limiting is 60 requests/minute with burst 20 and a bounded in-memory source table; an aggregate 120 requests/minute, burst-40 ceiling applies across sources.

The dedicated SQLite thread persists no IP, user agent, payload, or free-form error. Pending attempts become interrupted after three hours; terminal records are retained 30 days; logical used pages are capped at 512 MiB. A database failure degrades telemetry and /healthz but does not stop guide hosting.

websploot --config websploot.toml telemetry report
websploot --config websploot.toml telemetry report --json

Native Linux deployment

The supported production platform is Linux. Install the binary, configuration, and unit:

sudo install -m755 target/release/websploot /usr/local/bin/websploot
sudo install -m644 deploy/websploot.service /etc/systemd/system/websploot.service
sudo systemctl daemon-reload
sudo systemctl enable --now websploot

The unit runs a dynamic user, read-only system paths, LimitNOFILE=131072, and only CAP_NET_BIND_SERVICE. Persistent guide/list/telemetry data is under /var/lib/websploot.

The example Caddyfile has a plain-HTTP Sony vhost, a normal HTTPS public guide hostname including action routes, a LAN-only HTTP host/IP, and a CIDR-restricted admin hostname. Caddy sets and sanitizes forwarded headers itself; configure websploot to trust only Caddy's actual peer. Both public vhosts also return 404 for detailed admin diagnostics as a second guard behind websploot's own client-CIDR checks.

Docker

The runtime image contains no Git client, runs as UID 10001, and supports a read-only root filesystem. The binary carries only the cap_net_bind_service file capability; Compose drops the runtime capability set and permits only NET_BIND_SERVICE in its bounding set. Linux Compose defaults to host networking so DNS sees real source addresses and avoids NAT overhead:

./deploy/docker-setup.sh 192.168.1.10
docker compose up -d

The local Compose profile rebuilds the image when the checkout changes, so a plain up cannot silently keep running an older websploot:local image. The setup script writes configuration once, initializes the persistent state volume, downloads and verifies both pinned guide archives, and can be rerun safely. No host guide checkout is required. To replace managed SlopKit with administrator-provided PS5 content, set PS5_ROOT, point its [[tree]] at /srv/websploot/ps5, and use the explicit override:

docker compose -f docker-compose.yml -f docker-compose.ps5.yml up -d

To make that selection persistent, set this alongside PS5_ROOT in .env:

COMPOSE_FILE=docker-compose.yml:docker-compose.ps5.yml

Otherwise leave that variable absent; the base deployment uses managed SlopKit from the persistent state volume and has no PS5 host bind mount. Guide files must be readable by the container's unprivileged UID 10001.

Before bringing up an existing 0.x deployment, apply the migration notes below and validate the exact mounted configuration:

docker compose run --rm websploot \
  --config /etc/websploot/websploot.toml check

Docker host networking is primarily a Linux facility. For development or non-Linux environments:

docker compose -f docker-compose.yml -f docker-compose.bridge.yml up -d

Bridge/NAT mode can hide DNS client identity and adds forwarding overhead. The container keeps only /var/lib/websploot persistent and receives CAP_NET_BIND_SERVICE; its nofile limit is 131072.

Release automation currently builds a Linux amd64 binary and container image. ARM64 publication is temporarily deferred because emulation on the Forgejo runner repeatedly exceeds its Docker/act deadline.

Operations and troubleshooting

The admin health document and /metrics report active/rejected DNS TCP and HTTP connections, dropped UDP work, open/maximum file descriptors, list age and refresh timestamps, limiter occupancy, guide reload failures, telemetry drops, cache bounds, and per-upstream health. Alert on sustained admission rejections, any guide reload failure, file descriptors above 80%, a project list older than 24 hours, telemetry drops when anonymous mode is expected, and repeated upstream backoff. A low nofile limit is reported by check, startup logs, and admin health; the production examples set 131072.

Typical Prometheus alert expressions are:

increase(websploot_dns_tcp_connections_rejected_total[5m]) > 0
increase(websploot_http_connections_rejected_total[5m]) > 0
increase(websploot_dns_udp_work_dropped_total[5m]) > 0
websploot_process_open_fds / websploot_process_max_fds > 0.80
time() - websploot_dns_list_last_refresh_timestamp_seconds > 86400
increase(websploot_guide_reload_failures_total[5m]) > 0

Route these with the provider's bandwidth/packet-rate alarms; process metrics cannot observe packets dropped upstream or a saturated access link.

Common failures:

  • Port 53 already in use: bind the actual LAN address or resolve the conflict with systemd-resolved; do not silently move the console-facing port.
  • A setup download was interrupted: rerun setup. Staging is disposable and the current/rollback links move only after hash, archive, manifest, AppCache closure, and preload validation.
  • A manual index loops or bypasses home: remove obsolete console_index and verify Caddy routes http://manuals.playstation.net to WebSploot unchanged.
  • A guide works online but not offline: inspect its declared AppCache CACHE and FALLBACK closure. Missing, denied, traversing, or cross-mount local targets fail setup, check, and reload before activation.
  • Docker loses client identity: use the host-networking production compose; bridge mode is only the development/non-Linux fallback.

Migration from 0.x

  • Move local DNS addresses from dns.allow_ips to an exact [[dns.override]]. The old field temporarily maps only to manuals.playstation.net.
  • Allowlist rules now mean “bypass blocking and ask upstream,” not “return a local address.”
  • Remove [tree.source]; use guides install/update/rollback or manage a plain tree.root yourself.
  • Remove features = ["attempt-state"], latch_file, and telemetry_log. Configure global [telemetry] instead. These legacy fields fail with a targeted migration error rather than being silently ignored.
  • Replace old public diagnostics with /health; detailed APIs are admin-CIDR only.
  • Add every required administrator CIDR explicitly. Missing [admin] now means allow_clients = []; private and loopback ranges are no longer implicit.
  • Delete dns.open_resolver_acknowledged. It is obsolete and intentionally produces a targeted migration error instead of being silently ignored.
  • Delete landing.auto_redirect, landing.redirect_delay, and tree.console_index. During 0.x they deserialize only to produce a targeted removal error; manual indexes now always enter through the landing page.
  • To adopt managed public SlopKit in an existing configuration, add the ps5 tree from config/websploot.example.toml, rooted at <state-dir>/guides/ps5/current, then rerun setup. It adds the pinned PS5 descriptor to an older PS4-only lockfile and installs the verified archive.

Verification

Automated checks:

cargo fmt --package websploot --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo test --all-targets --all-features --locked
cargo build --release --locked
python3 tools/browser_artifacts.py lock-check
python3 tools/browser_artifacts.py carrier
python3 tools/browser_artifacts.py check-binary \
  "${CARGO_TARGET_DIR:-target}/release/websploot"
python3 -m unittest discover -s bench -p 'test_*.py' -v
python3 -m unittest discover -s tools -p 'test_*.py' -v
python3 tools/psn_snapshot.py --check

The tag workflow additionally runs REUSE, RustSec, cargo-deny license/source policy, OSV Scanner, deployment-file validation, a clean container smoke test, CycloneDX generation, and amd64 verification. It is the only workflow allowed to publish version tags. Release candidates stop at the automated gate; a stable tag is rejected unless release/evidence contains hashed capacity and physical-console records for an unchanged tested candidate and the exact generated artifacts.

Before a release, smoke-test a physical PS4 and PS5: configure DNS, open User Guide, confirm the identity-preserving landing redirect, D-pad every control, open/close settings without a focus trap, invoke the system keyboard, reject unsafe URLs, use favorites/recents, verify absent-guide entries, and enter each configured guide.

The browser-specific console checklist, including absent-ID install, launch target, rerun, partial staging, conflict refusal, reboot persistence, and both AppInst paths, is in docs/console-acceptance.md.

The release performance targets on a dedicated 4-vCPU/2-GiB host are 40k local/cache-hit DNS QPS (p99 <10ms), 20k static HTTP requests/sec (p99 <100ms), 20k simultaneous HTTP keep-alives, one healthy upstream transaction for 10k identical cold misses, and a 30-minute 70% soak below 1 GiB RSS with under 5% post-warmup growth. These are acceptance targets, not benchmark results or a capacity guarantee; validate them on the intended kernel, network, guide corpus, and reverse proxy before publishing a high-traffic deployment. The pinned, repeatable, fail-closed harness and result schema are in bench/README.md.

License and source

WebSploot code and its original UI artwork are AGPL-3.0-or-later. Corresponding source is published at https://git.richey.dev/miles/websploot. Guide content is separately licensed; release binaries additionally embed a GPL-3.0-or-later PS5 SDK-derived browser carrier. See THIRD_PARTY_NOTICES.md.