What jspod ships, where things live, and how to customize.
An opinionated thin wrapper around JavaScriptSolidServer (JSS) aimed at first-time users. Single-user personal pods, default me/me credentials, localhost-only, batteries included.
The wrapper handles: default flags, port hopping, browser auto-open, seeding a custom landing page and account dashboard, bundling Pilot as a self-hosted Solid app, and shipping a ~200-byte default data browser. JSS is the substrate — everything that's actually a pod, an IDP, an OIDC provider, comes from JSS.
pod-data/)jspod creates pod-data/ in the current working directory on first start (or wherever you pass via --root). Everything the pod is, including identities and signing keys, lives under that single tree.
pod-data/ your pod root ├── index.html welcome page — jspod-seeded, skip-if-exists ├── index.html.acl public-read for the landing page (JSS-managed) ├── signin.html jspod's sign-in app — seeded, skip-if-exists ├── signin.html.acl public-read ├── account.html post-sign-in dashboard — seeded, skip-if-exists ├── account.html.acl public-read ├── docs.html this page — seeded, skip-if-exists ├── docs.html.acl public-read ├── profile/ │ └── card.jsonld your WebID profile (JSS-seeded) ├── public/ public-read container │ ├── links.jsonld starter list of Solid apps (jspod-seeded, skip-if-exists) │ └── apps/pilot/ self-hosted Pilot (jspod-bundled, skip-if-exists) ├── private/ owner-only container ├── inbox/ LDN inbox ├── settings/ prefs + type indexes (JSS-seeded) ├── .acl root container ACL — owner read/write, no public default ├── .idp/ JSS internal identity storage (see below) └── .token-secret JWT signing secret (auto-generated, mode 0600; not created with --no-auth)
index.html, signin.html, account.html, and docs.html are seeded by jspod on first start and never overwritten — edits survive restarts, same as public/links.jsonld and public/apps/pilot/. The trade-off: upgrading jspod won't refresh a page you already have; delete the file and restart to re-seed the current stock copy. In --no-auth mode jspod writes nothing at all — no seeded pages and no .token-secret (auth is bypassed, so no tokens are ever minted).
Everything identity-related is under pod-data/.idp/:
.idp/ ├── accounts/ │ ├── <uuid>.json account record — username, WebID, bcrypt(password) │ ├── _email_index.json │ ├── _username_index.json │ └── _webid_index.json └── keys/ └── jwks.json OIDC signing keys for issued tokens
The account record contains a bcrypt hash of the password — not the plaintext. The signing keys are sensitive (anyone with these can mint valid tokens against your pod). JSS manages file modes on these; jspod doesn't touch them.
The default landing page (pod-data/index.html) is seeded on first start only — just edit it, and your version survives restarts. To return to the stock page, delete pod-data/index.html and restart. To change the template used for new pods, fork jspod and edit welcome.html.
jspod ships you onto rung 1 (default me/me) and makes climbing visible. The dashboard at /account.html exposes a Change password form (rung 2). Passkey support (rung 3) is on the roadmap. See jspod#6 for the full ladder framing.
To set a custom initial password without going through the UI:
JSS_SINGLE_USER_PASSWORD='your-password' npx jspod
Your entire pod — content, identity, signing keys — is one directory.
tar -czf my-pod.tar.gz pod-data/
Restore by extracting the tarball back into a pod-data directory and pointing npx jspod --root /path/to/pod-data at it.
JSS disables the /idp/account/delete endpoint in single-user mode (the account is the pod). To start completely fresh:
rm -rf pod-data npx jspod
A new me/me account is seeded on next start.
jspod bundles Pilot at /public/apps/pilot/. It's served from your own pod, so same-origin sign-in works even on strict browsers (Brave with shields up).
The bundle is copied skip-if-exists, so you can pin a specific Pilot version by editing the files locally and they'll survive jspod upgrades. Delete pod-data/public/apps/pilot/ and restart to re-seed from the current jspod's bundled copy.
jspod enables JSS's git HTTP backend by default. Any pod path you have ACL Read on is git-cloneable; any path you have ACL Write on is git-pushable. Fresh paths auto-initialize on first push (JSS 0.0.195+).
git clone http://localhost:5444/public/apps/pilot ./pilot
git clone https://github.com/solid-apps/penny.git cd penny git remote add pod http://localhost:5444/public/apps/penny git push pod main
Note: git push against a Solid pod needs the same DPoP-bound auth as any other Solid write. Easiest workaround today is to set an Authorization header via git -c http.extraHeader=... with a token obtained from /signin.html. Cleaner ergonomics are a known gap.
To opt out: npx jspod --no-git.