Skip to content

Default --single-user to root pod (#348) - #349

Merged
melvincarvalho merged 7 commits into
gh-pagesfrom
issue-348-single-user-root-pod
May 2, 2026
Merged

Default --single-user to root pod (#348)#349
melvincarvalho merged 7 commits into
gh-pagesfrom
issue-348-single-user-root-pod

Conversation

@melvincarvalho

@melvincarvalho melvincarvalho commented May 2, 2026

Copy link
Copy Markdown
Contributor

Closes #348.

Summary

  • jss start --single-user now mounts the pod at / by default. WebID becomes /profile/card.jsonld#me, the IDP login username is "me".
  • Operators who explicitly pass --single-user-name X are unaffected.
  • Default of singleUserName flips from 'me'null. null / '/' / '' are normalized to null (= root pod) at the top of createServer.
  • Root pod gets the same IDP seeding as a named pod, with username: "me". Without this, --single-user --idp would produce a pod nobody can log in to.

Migration (pre-#348 single-user installs)

We deliberately don't auto-detect — at v0.0.x the operator base is small and a one-time intervention is fine. On the next restart, pick one:

  • Add --single-user-name me → legacy /me/ layout, no further work.
  • Move <root>/me/* to <root>/, delete the legacy IDP account for "me", then restart without the name flag → new root pod with a freshly seeded "me" account.

Documented under "Upgrading from a pre-#348 install" in docs/configuration.md.

Test plan

  • npm test — 593 / 593 pass
  • New: Single-user default — root pod (#348) — verifies seeding lands at /profile/card.jsonld, the WebID resolves at the origin, no /me/ files created on disk, and POST /idp/credentials with {username: "me", password} returns an access_token.
  • Existing tests using singleUserName: 'me' (legacy) and '/' (explicit-root) still pass.

Change the default `singleUserName` from `'me'` to `null`. Without
the flag, `jss start --single-user` now serves the pod at the
server origin (`/profile/card#me`) instead of `/me/profile/card#me`.

Why: in single-user mode there is by definition exactly one pod, so
the `/me/` prefix has no namespace-disambiguation purpose. It only
adds friction — most Solid clients and tutorials assume the pod
coincides with the origin.

Operators who actually want a named pod still pass
`--single-user-name X` explicitly; that path is unchanged.

Behaviour summary:
- `jss start --single-user` → root pod (new default)
- `jss start --single-user --single-user-name me` → `/me/` (legacy)
- `jss start --single-user --single-user-name alice` → `/alice/`

Migration note: anyone upgrading a fresh-default install in place
needs to either move `data/me/*` → `data/*` or pass
`--single-user-name me` to keep the old pod. With the project still
at 0.0.x and the userbase small this is an acceptable break;
deployed servers in our orbit (solid.social, melvin.me,
melvincarvalho.com) all pass `--single-user-name` explicitly and
are unaffected.

Implementation:
- `src/config.js`: defaultConfig.singleUserName: 'me' → null;
  printConfig now formats the root-pod case cleanly.
- `src/server.js`: drop the `?? 'me'` fallback; null = root pod.
- `bin/jss.js`: --single-user-name help text updated.
- `test/idp.test.js`: new "Single-user default — root pod (#348)"
  describe asserting the seeded profile lands at /profile/card.jsonld
  with WebID at the server origin when no name flag is passed.

All 592 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes single-user mode so an unnamed single-user pod is mounted at the server origin (/) instead of under /me/, aligning the default pod layout with the existing root-pod code path and the linked issue’s expected Solid pod shape.

Changes:

  • Change the default singleUserName from 'me' to null, making plain --single-user use the root pod path.
  • Update config/CLI messaging to describe root-pod behavior for unnamed single-user mode.
  • Add an IdP integration test that verifies the default single-user pod seeds /profile/card.jsonld at the origin.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/server.js Switches the single-user default name to null, which routes startup through the root-pod branch.
src/config.js Updates config defaults and printed config text to describe root-pod single-user mode.
bin/jss.js Revises CLI help text for --single-user-name to document the new default mount point.
test/idp.test.js Adds regression coverage for default single-user root-pod seeding and WebID location.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/idp.test.js
Comment on lines +476 to +486
server = createServer({
logger: false,
root: DEFAULT_DATA_DIR,
idp: true,
idpIssuer: baseUrl,
singleUser: true,
// singleUserName intentionally omitted — exercises the new default.
forceCloseConnections: true,
});

await server.listen({ port, host: TEST_HOST });
Comment thread src/config.js Outdated
Comment on lines +410 to +413
let details = isRootPod ? '/ (root pod)' : config.singleUserName;
if (config.idp) {
if (config.singleUserName === '/' || !config.singleUserName) {
details += ' (root pod; password not seeded)';
if (isRootPod) {
details += ' (password not seeded)';
Comment thread src/config.js Outdated
Comment on lines +77 to +80
// null = root pod (mounted at server origin, WebID at /profile/card#me).
// A string mounts the pod at /<name>/ — useful when more than one Solid
// identity coexists on the same origin (#348).
singleUserName: null,
Comment thread src/server.js Outdated
Comment on lines +96 to +98
// Default null = root pod (#348). Pass an explicit singleUserName
// to mount the pod at /<name>/ instead.
const singleUserName = options.singleUserName ?? null;
Round-1 review caught a real gap: my first commit moved single-user
mode's default to root pod but left `seedSingleUserIdpAccount()`
gated on `!isRootPod`, so a fresh `jss start --single-user --idp`
produced a pod no operator could log in to (registration is
disabled in single-user mode, no fallback).

Fix: also seed the IDP account for root-pod mode, defaulting the
login username to "me". The pod URL now lives at the origin while
the login flow stays the same as a named pod — operator types
"me" + password into the login form, gets a token for the WebID at
`${origin}/profile/card.jsonld#me`.

- src/server.js: drop `&& !isRootPod` from the IDP-seed guard;
  derive `username = isRootPod ? 'me' : singleUserName` and
  `podName = isRootPod ? null : singleUserName`.
- src/config.js: print-config now reads
  `Single-user: / (root pod, login as "me") (password: ...)` —
  removes the misleading "password not seeded" wording.
- docs/configuration.md: rewrite the Single-User section to
  document the new default (root pod, WebID at origin, login as
  "me") and show `--single-user-name me` as the explicit-legacy
  knob.
- test/idp.test.js: extend the #348 describe to assert that
  `POST /idp/credentials` with `{username: "me", password}` returns
  a 200 + access_token — black-box check that the seed actually
  produces a loggable account.

All 593 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server.js Outdated
Comment on lines +604 to +609
if (idpEnabled) {
await seedSingleUserIdpAccount({
fastify,
username: singleUserName,
username: isRootPod ? 'me' : singleUserName,
webId,
podName: singleUserName,
podName: isRootPod ? null : singleUserName,
Comment thread src/server.js Outdated
Comment on lines +607 to +609
username: isRootPod ? 'me' : singleUserName,
webId,
podName: singleUserName,
podName: isRootPod ? null : singleUserName,
Comment thread src/config.js Outdated
Comment on lines +406 to +408
// Root pod (#348) seeds the IDP account under the username 'me' —
// the same login flow as a named pod, just at a different mount.
let details = isRootPod ? '/ (root pod, login as "me")' : config.singleUserName;
Comment thread src/config.js Outdated
// Single-user mode (personal pod server)
singleUser: false,
singleUserName: 'me',
// null = root pod (mounted at server origin, WebID at /profile/card#me).
Comment thread docs/configuration.md Outdated
Comment on lines +261 to +263
# Default: pod served at server root (#348). WebID is /profile/card#me;
# the IDP login username is "me". On first run JSS will prompt for an
# initial password (TTY only).
Comment thread docs/configuration.md Outdated

# Custom username
# Mount the pod at a named path instead of the origin. WebID becomes
# /alice/profile/card#me; login as "alice".
Addresses 5 genuinely new points (the other 4 inline comments
re-flag round-1 items already in HEAD).

1. Normalize singleUserName at the top of createServer().
   '/' / '' / null all collapse to null, so downstream code
   (remoteStoragePlugin at line 292: `singleUserName || 'me'`)
   doesn't get a literal '/' string and end up registering
   /storage/%2F/ instead of /storage/me/.

2. Migration warning. When seeding a fresh root pod, check whether
   `/me/profile/card.jsonld` (or legacy extensionless card) already
   exists on disk. If it does, log a clear warning that the default
   changed in #348 and suggest restarting with --single-user-name me.
   This catches the silent-upgrade case where an operator on the
   pre-#348 default ends up with a fresh empty root pod alongside
   their stranded /me/ data.

3. printConfig wording. The "(login as me)" / "(password: ...)"
   bits only fire under --idp now, so a `--single-user --no-idp`
   deployment doesn't claim a built-in login that doesn't exist.

4. WebID examples corrected. Fresh JSS pods seed
   `/profile/card.jsonld#me`, not `/profile/card#me` — the docs and
   config.js comment now reflect the canonical URI. Legacy
   extensionless pods (created before the .jsonld convention)
   continue to work via the existing fallback in onReady.

5. Now that singleUserName is normalized at the top, the inline
   `singleUserName === '/'` check in onReady is redundant; replace
   with a plain truthiness test.

All 593 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server.js Outdated
'Found existing /me/ pod data while seeding the new default root pod. ' +
'The default single-user pod path changed to / (was /me/) — see #348. ' +
'To keep using your existing pod, restart with --single-user-name me. ' +
'Otherwise the new root pod will be empty and the /me/ data unreferenced.'
Comment thread src/server.js Outdated
Comment on lines +633 to +637
await seedSingleUserIdpAccount({
fastify,
username: singleUserName,
username: isRootPod ? 'me' : singleUserName,
webId,
podName: singleUserName,
podName: isRootPod ? null : singleUserName,
Comment thread test/idp.test.js
Comment on lines +464 to +531
describe('Single-user default — root pod (#348)', () => {
let server;
let baseUrl;
const DEFAULT_DATA_DIR = './test-data-348-default-root';
const ROOT_POD_PASSWORD = 'root-pod-test-pw';

before(async () => {
await fs.remove(DEFAULT_DATA_DIR);
await fs.ensureDir(DEFAULT_DATA_DIR);

const port = await getAvailablePort();
baseUrl = `http://${TEST_HOST}:${port}`;

server = createServer({
logger: false,
root: DEFAULT_DATA_DIR,
idp: true,
idpIssuer: baseUrl,
singleUser: true,
// singleUserName intentionally omitted — exercises the new default.
// Provide a password so the seeding path runs non-interactively.
singleUserPassword: ROOT_POD_PASSWORD,
forceCloseConnections: true,
});

await server.listen({ port, host: TEST_HOST });
});

after(async () => {
await server.close();
await fs.remove(DEFAULT_DATA_DIR);
});

it('seeds the profile at /profile/card.jsonld (not /me/profile/...)', async () => {
const root = await fetch(`${baseUrl}/profile/card.jsonld`);
assert.strictEqual(root.status, 200,
'--single-user with no name should default to a root pod');
const me = await fetch(`${baseUrl}/me/profile/card.jsonld`);
assert.notStrictEqual(me.status, 200,
'no /me/ pod should be served when singleUserName is unset (got 200)');
});

it('WebID resolves at the server origin', async () => {
const res = await fetch(`${baseUrl}/profile/card.jsonld`);
const body = await res.json();
const webId = `${baseUrl}/profile/card.jsonld#me`;
const matches = Array.isArray(body)
? body.some(n => n['@id'] === webId)
: body['@id'] === webId || (body['@graph'] || []).some(n => n['@id'] === webId);
assert.ok(matches, `profile should declare WebID ${webId}, got: ${JSON.stringify(body).slice(0, 200)}`);
});

it('seeds an IDP account for "me" so the root pod is loggable', async () => {
// Round-2 review of #348: a regression here would mean a fresh
// `jss start --single-user --idp` produces a pod nobody can log
// in to (registration is disabled in single-user mode, so there
// would be no recovery path other than out-of-band account
// creation). Use the credentials endpoint as a black-box login
// probe — if it issues a token, the seed worked.
const res = await fetch(`${baseUrl}/idp/credentials`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'me', password: ROOT_POD_PASSWORD }),
});
assert.strictEqual(res.status, 200,
`login as "me" should succeed for the default root pod (got ${res.status})`);
const body = await res.json();
assert.ok(body.access_token, 'response should carry an access token');
Round-3 surfaced a concrete bad outcome from the prior approach:
when an operator upgrades a pre-#348 install without moving data,
the wildcard LDP routes still serve /me/* directly from disk, the
old IDP account "me" still authenticates against /me/profile/card,
and the new code seeds an *empty* root pod alongside it. That
split-brain leaves clients reading/writing the legacy pod while
the operator believes they're on the new default.

Switch from "warn loudly and seed anyway" to "auto-fall-back to
--single-user-name me when /me/ data exists". This is strictly
better:
- Fresh install (no /me/ data) → root pod, the new default.
- Pre-#348 install (default 'me' was used) → keeps working
  exactly as before, no surprise pod, no IDP account collision.
- Operator who actively wants to migrate to root → moves
  data/me/* → data/* themselves, then restart picks up root.

Implementation:
- src/server.js: detect pre-existing /me/ pod via existsSync
  before plugin registration (remoteStoragePlugin captures the
  username at registration time, so the check has to be sync).
  When detected, set effective singleUserName = 'me' and stash a
  flag for the onReady warning.
- onReady logs a one-liner explaining the fallback so operators
  know why their pod is at /me/.
- The previous round-2 "split-brain" warning becomes redundant
  (the auto-fallback prevents the split-brain from happening at
  all) and is removed.

Test:
- New describe `Single-user upgrade fallback — pre-existing /me/
  pod (#348)`. Phase 1 seeds the legacy layout (explicit
  --single-user-name me + password). Phase 2 restarts on the same
  data dir with no name flag. Asserts:
  - GET /me/profile/card.jsonld is still 200 (no fresh root pod
    overwrites or hides it)
  - POST /idp/credentials with {username: me, password} still
    returns an access_token (existing IDP account intact)
- Both phases reuse the same port so ACLs (which carry absolute
  URIs) keep matching.

595/595 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/configuration.md
Comment on lines +261 to +263
# Default: pod served at server root (#348). WebID is
# /profile/card.jsonld#me; the IDP login username is "me". On first
# run JSS will prompt for an initial password (TTY only).
Comment thread test/idp.test.js Outdated
Comment on lines +502 to +503
assert.notStrictEqual(me.status, 200,
'no /me/ pod should be served when singleUserName is unset (got 200)');
Comment thread test/idp.test.js Outdated
Comment on lines +599 to +603
// The auto-fallback should NOT have seeded a separate root pod.
// We can't reliably test "the file at / does not exist" via HTTP
// (WAC may rewrite to 401), but we can verify the auto-fallback
// path was taken by checking that /me/'s WebID is still the one
// bound to the IDP account — the login probe below verifies that.
Comment thread src/server.js Outdated
Comment on lines +102 to +126
const rawSingleUserName = options.singleUserName ?? null;
const normalizedName =
(rawSingleUserName === '/' || rawSingleUserName === '')
? null
: rawSingleUserName;

// #348 backwards-compat for in-place upgrades: if the operator
// didn't pass --single-user-name and a /me/ pod from a pre-#348
// install already exists on disk, fall back to the legacy 'me'
// name so the existing pod and IDP account stay live. Without
// this, the new default would seed an empty root pod alongside
// the still-served /me/ data — a split-brain state where the
// operator has no way to log in to the new pod (the 'me' IDP
// account still points at /me/profile/card) and clients keep
// reading/writing the legacy one. The disk check is sync because
// remoteStoragePlugin captures the username at registration time
// (before onReady fires).
const dataRoot = options.root || process.env.DATA_ROOT || './data';
let singleUserName = normalizedName;
let migratedFromMeDefault = false;
if (singleUser && singleUserName === null) {
if (existsSync(join(dataRoot, 'me/profile/card.jsonld')) ||
existsSync(join(dataRoot, 'me/profile/card'))) {
singleUserName = 'me';
migratedFromMeDefault = true;
Comment thread src/server.js Outdated
Comment on lines +620 to +625
fastify.log.warn(
'Detected pre-existing /me/ pod data. Falling back to ' +
'--single-user-name me for backwards compatibility (#348 ' +
'changed the default pod path from /me/ to /). To opt into ' +
'the new default root pod, move data/me/* to data/* and ' +
'remove the IDP account for "me" before restarting.'
Comment thread docs/configuration.md
Comment on lines +261 to +275
# Default: pod served at server root (#348). WebID is
# /profile/card.jsonld#me; the IDP login username is "me". On first
# run JSS will prompt for an initial password (TTY only).
jss start --single-user --idp

# Provide the initial IDP password non-interactively (systemd, containers, CI):
jss start --single-user --idp --single-user-password 'choose-a-good-one'
JSS_SINGLE_USER_PASSWORD='choose-a-good-one' jss start --single-user --idp

# Custom username
# Mount the pod at a named path instead of the origin. WebID becomes
# /alice/profile/card.jsonld#me; login as "alice".
jss start --single-user --single-user-name alice --idp

# Root-level pod (pod at /, WebID at /profile/card#me)
jss start --single-user --single-user-name '' --idp
# Legacy /me/ pod — same as the old default before #348.
jss start --single-user --single-user-name me --idp
The user feedback on round-3 was clear: the original ask was a
simple default change, and "people will figure it out" was explicit
license to skip migration cleverness. Round 3's auto-fallback (and
round 4's tweaks to it) were scope creep.

Revert the auto-fallback. Pre-#348 installs that upgrade in place
without flag changes will get a fresh empty root pod alongside
their existing /me/ data — operators who hit that pick one of two
explicit paths on restart, both documented:

1. Add `--single-user-name me` → legacy layout, no further work.
2. Move `<root>/me/*` to `<root>/`, delete the legacy IDP account
   for "me", restart → new root pod, new account seeded.

At v0.0.x with a small operator base, that one-time intervention
is acceptable and keeps the codebase clean.

Changes from round 3 / unpushed round 4:
- src/server.js: drop the `existsSync(/me/...)` auto-detect
  block, the `migratedFromMeDefault` flag, and the corresponding
  warning in onReady. Drop the `existsSync` import.
- test/idp.test.js: drop the `Single-user upgrade fallback` suite
  (no fallback to test).
- test/idp.test.js: tighten the `seeds the profile at root`
  assertion — also fs.pathExists() check that no /me/ files were
  written, so a regression that left /me/ behind under a 401
  wouldn't slip through (round-4 review #7).
- docs/configuration.md: add an explicit "Upgrading from a
  pre-#348 install" callout under Single-User Mode listing both
  migration paths (round-4 review #11).

593/593 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server.js Outdated
username: isRootPod ? 'me' : singleUserName,
webId,
podName: singleUserName,
podName: isRootPod ? null : singleUserName,
Comment thread src/config.js
Comment on lines +413 to +415
const pwSource = config.singleUserPassword
? 'provided'
: (process.stdin.isTTY ? 'will prompt at startup' : 'missing — login disabled');
Comment thread src/server.js
Comment on lines +107 to +110
const rawSingleUserName = options.singleUserName ?? null;
const singleUserName =
(rawSingleUserName === '/' || rawSingleUserName === '')
? null
Comment thread src/server.js
Comment on lines +102 to +106
// Pre-#348 installs (default 'me') that upgrade in place will see
// a fresh empty root pod alongside their /me/ data. The fix is to
// pass `--single-user-name me` on restart (or move data/me/* out
// to the data root). At v0.0.x we accept that one-time
// intervention rather than carrying detection magic in the code.
Of the 11 inline comments, 5 re-flag round-1/2/3 work already on
the branch, 3 re-raise the pre-#348 upgrade incompatibility we
deliberately decline (documented in the migration callout), 1 is a
pre-existing printConfig accuracy issue out of scope here. The
remaining two are new and worth fixing:

1. Root-pod podName was null, but src/idp/accounts.js:438-440
   surfaces account.podName as the `name` claim under the OIDC
   `profile` scope. A null there propagates as missing/null
   profile.name on every login. Use 'me' for the root-pod case so
   the claim matches the username.

2. Add a getPodName regression test for `singleUserName: null`.
   The existing url.test.js coverage tests `''` and `'/'` but not
   the normalized null shape that most root-pod requests now reach
   it with after the createServer-level normalization.

594/594 tests pass.
@melvincarvalho

Copy link
Copy Markdown
Contributor Author

Round-5 response.

Two genuine new issues — fixed in 3269793:

  • podName: null for the root pod was leaking through as a null name claim under the OIDC profile scope (src/idp/accounts.js:438-440). Now uses 'me' so the claim matches the login username.
  • Added a getPodName unit test for singleUserName: null — the normalized shape most root-pod requests now reach it with.

Already addressed in the branch (re-flags from earlier rounds):

  • Login/credentials assertion in the new Single-user default describe — see test/idp.test.js:535 ('seeds an IDP account for "me" so the root pod is loggable').
  • '/' / '' normalization at the top of createServersingleUserName || 'me' downstream now correctly produces 'me' for --single-user-name /.
  • PR description and docs/configuration.md both already use /profile/card.jsonld#me.
  • docs/configuration.md has an explicit "Upgrading from a pre-Single-user mode: pod should be served at server root, not /me/ #348 install" callout under Single-User Mode.

Intentionally declined — pre-#348 upgrade behavior:
The "in-place upgrade lands a fresh empty root pod alongside the legacy /me/ and the existing me account still authenticates against /me/" outcome is the trade-off accepted in this PR's design (project is at v0.0.x; small operator base; clean default change is preferable to detection magic). Migration is documented with two explicit paths: add --single-user-name me to keep the legacy layout, or move data + delete the me IDP account to opt into root. An auto-detect implementation was prototyped in earlier rounds and reverted as scope creep.

Out of scope:

  • The printConfig "will prompt at startup / login disabled" line is computed from current flags and isn't accurate after a successful first run — but that's a pre-existing behavior, not something this PR introduced.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/configuration.md Outdated

**Upgrading from a pre-#348 install:** if your existing pod was created with the old default (data lives under `<root>/me/`), JSS no longer auto-detects it — restarting plain `jss start --single-user` will start seeding a fresh empty root pod alongside your legacy `/me/` data, and your existing IDP account will keep authenticating against `/me/`. Pick one path on the next restart:
- Add `--single-user-name me` to keep the legacy `/me/` layout exactly as before.
- Move `<root>/me/*` to `<root>/`, delete the IDP account for `me` (so the new root pod's `me` account can be seeded), then restart without the name flag.
Comment thread src/server.js
Comment on lines 626 to +630
await seedSingleUserIdpAccount({
fastify,
username: singleUserName,
username: isRootPod ? 'me' : singleUserName,
webId,
podName: singleUserName,
podName: isRootPod ? 'me' : singleUserName,
Comment thread src/config.js
Comment on lines +77 to +81
// null = root pod (mounted at server origin, WebID at
// /profile/card.jsonld#me). A string mounts the pod at /<name>/ —
// useful when more than one Solid identity coexists on the same
// origin, or when the operator wants the pre-#348 /me/ shape.
singleUserName: null,
Of the 10 inline comments, 8 re-flag earlier-round work or
re-raise the deliberately-declined upgrade trade-off. Two new
points are worth fixing:

1. test/config.test.js: pin the singleUserName: null default at
   the config layer. createServer() has its own root-pod tests, but
   a future refactor of loadConfig() could silently restore the old
   'me' default and only behavioural tests would catch it. Add
   three focused assertions: default is null, explicit CLI arg is
   preserved, and JSS_SINGLE_USER_NAME env is honoured (operator's
   escape hatch back to /me/).

2. docs/configuration.md: the previous migration command
   `mv <root>/me/* <root>/` silently skips dotfiles (`.acl`,
   `.meta`, `.quota.json`), which would leave the migrated root pod
   without ACL or quota state. Replace with two explicit options
   that handle dotfiles correctly: rsync (default) or
   bash+dotglob.

597/597 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/config.test.js
Comment on lines +169 to +190
it('loadConfig() returns singleUserName=null when no flag/env is set', async () => {
delete process.env.JSS_SINGLE_USER_NAME;
const cfg = await loadConfig({}, null);
assert.strictEqual(cfg.singleUserName, null,
'default must be null (= root pod), not the legacy "me"');
});

it('loadConfig() preserves an explicit singleUserName CLI arg', async () => {
delete process.env.JSS_SINGLE_USER_NAME;
const cfg = await loadConfig({ singleUserName: 'alice' }, null);
assert.strictEqual(cfg.singleUserName, 'alice');
});

it('loadConfig() respects JSS_SINGLE_USER_NAME from env', async () => {
process.env.JSS_SINGLE_USER_NAME = 'me';
try {
const cfg = await loadConfig({}, null);
assert.strictEqual(cfg.singleUserName, 'me',
'env var should restore the legacy "me" pod path on demand');
} finally {
delete process.env.JSS_SINGLE_USER_NAME;
}
Comment thread docs/configuration.md
Comment on lines +288 to +290
**Upgrading from a pre-#348 install:** if your existing pod was created with the old default (data lives under `<root>/me/`), JSS no longer auto-detects it — restarting plain `jss start --single-user` will start seeding a fresh empty root pod alongside your legacy `/me/` data, and your existing IDP account will keep authenticating against `/me/`. Pick one path on the next restart:
- **Keep the legacy layout:** add `--single-user-name me` to your launch command. No data movement needed.
- **Migrate to root pod:** move the *entire* contents of `<root>/me/` (including dotfiles like `.acl`, `.meta`, `.quota.json` — a plain `mv <root>/me/* <root>/` skips them) to `<root>/`, delete the IDP account for `me` (so the new root pod's `me` account can be seeded), then restart without the name flag. Use one of:
Comment thread src/server.js
Comment on lines 626 to +630
await seedSingleUserIdpAccount({
fastify,
username: singleUserName,
username: isRootPod ? 'me' : singleUserName,
webId,
podName: singleUserName,
podName: isRootPod ? 'me' : singleUserName,
Comment thread src/config.js
if (isRootPod) details += ', login as "me"';
const pwSource = config.singleUserPassword
? 'provided'
: (process.stdin.isTTY ? 'will prompt at startup' : 'missing — login disabled');
@melvincarvalho
melvincarvalho merged commit 6d4f3e5 into gh-pages May 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Single-user mode: pod should be served at server root, not /me/

2 participants