Agent host: SilverBullet boots into accounts and the vault, not a bare space
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m10s
CI / e2e (pull_request) Successful in 4m20s

SB_NAME alone put SilverBullet 2.11 in single-space mode: no accounts, no
setup wizard, and its /data folder served instead of the vault. The compose
file sets only PUID and PGID now, a test holds it to that, and the host
check fails on a single-space boot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-19 16:28:09 +00:00
1 parent e287eea2d3
commit 1d33d50907
3 files changed
+24 -2

No files matched your search

+5
View File
@@ -10,6 +10,11 @@ bad() { echo "FAIL: $1"; fail=1; }
cd "$stack" || { echo "FAIL: no $stack"; exit 1; } cd "$stack" || { echo "FAIL: no $stack"; exit 1; }
[ "$(sudo docker compose ps --status running -q | wc -l)" -ge 1 ] \ [ "$(sudo docker compose ps --status running -q | wc -l)" -ge 1 ] \
&& ok "SilverBullet is running" || bad "SilverBullet isn't running" && ok "SilverBullet is running" || bad "SilverBullet isn't running"
if sudo docker compose logs --no-color silverbullet 2>/dev/null | grep -q 'boot mode: single-space'; then
bad "it booted a single space with no accounts (an SB_* setting?)"
else
ok "it keeps accounts (no single-space boot)"
fi
code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/) code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/)
case $code in 2??|3??) ok "it answers locally ($code)" ;; *) bad "it answers $code" ;; esac case $code in 2??|3??) ok "it answers locally ($code)" ;; *) bad "it answers $code" ;; esac
probe=.nisse-probe-$$ probe=.nisse-probe-$$
+2 -1
View File
@@ -8,9 +8,10 @@ services:
image: ghcr.io/silverbulletmd/silverbullet:2.11.1-slim image: ghcr.io/silverbulletmd/silverbullet:2.11.1-slim
restart: unless-stopped restart: unless-stopped
environment: environment:
# Only these. Any SB_* setting (even SB_NAME) boots a single space with
# no accounts, serving /data instead of the vault.
PUID: "1000" PUID: "1000"
PGID: "1000" PGID: "1000"
SB_NAME: Nisse vault
ports: ports:
# IPv4 only; setup.sh's firewall lets only the operator's Caddy in. # IPv4 only; setup.sh's firewall lets only the operator's Caddy in.
- "0.0.0.0:3000:3000" - "0.0.0.0:3000:3000"
+17 -1
View File
@@ -67,6 +67,12 @@ def test_no_secrets_in_the_compose_file() -> None:
assert not [k for k in environment if re.search(r"USER|TOKEN|PASS|SECRET|KEY", k)] assert not [k for k in environment if re.search(r"USER|TOKEN|PASS|SECRET|KEY", k)]
def test_it_boots_into_accounts_and_the_vault_not_a_bare_space() -> None:
# SB_NAME alone switched 2.11 to single-space mode: no accounts, no setup
# wizard, and /data served in place of the vault.
assert set(silverbullet()["environment"]) == {"PUID", "PGID"}
def test_its_port_caddys_and_the_firewalls_agree() -> None: def test_its_port_caddys_and_the_firewalls_agree() -> None:
port = setting("SB_PORT") port = setting("SB_PORT")
assert silverbullet()["ports"] == [f"0.0.0.0:{port}:3000"] # IPv4 only assert silverbullet()["ports"] == [f"0.0.0.0:{port}:3000"] # IPv4 only
@@ -120,7 +126,9 @@ def test_the_firewall_lets_only_one_address_in_and_replaces_its_own_rule() -> No
SCRIPT = HOST.parents[1] / "scripts" / "agent-host.sh" SCRIPT = HOST.parents[1] / "scripts" / "agent-host.sh"
def _fake_host(tmp_path: Path, *, memory_writable: bool) -> dict[str, str]: def _fake_host(
tmp_path: Path, *, memory_writable: bool, boot: str = "multi-space"
) -> dict[str, str]:
"""Commands standing in for the host's: ssh runs the command here, sudo """Commands standing in for the host's: ssh runs the command here, sudo
just runs it, and docker reads all of stdin, as docker compose exec does.""" just runs it, and docker reads all of stdin, as docker compose exec does."""
bin_ = tmp_path / "bin" bin_ = tmp_path / "bin"
@@ -134,6 +142,7 @@ def _fake_host(tmp_path: Path, *, memory_writable: bool) -> dict[str, str]:
"cat > /dev/null\n" "cat > /dev/null\n"
'case "$*" in\n' 'case "$*" in\n'
' *"ps --status running"*) echo abc123 ;;\n' ' *"ps --status running"*) echo abc123 ;;\n'
f' *" logs "*) echo "boot mode: {boot}" ;;\n'
f' *"/vault/memory/"*) exit {0 if memory_writable else 1} ;;\n' f' *"/vault/memory/"*) exit {0 if memory_writable else 1} ;;\n'
"esac" "esac"
), ),
@@ -167,6 +176,7 @@ def test_the_check_runs_every_step_even_though_docker_reads_stdin(
assert result.returncode == 0, result.stdout + result.stderr assert result.returncode == 0, result.stdout + result.stderr
assert result.stdout.splitlines() == [ assert result.stdout.splitlines() == [
"ok: SilverBullet is running", "ok: SilverBullet is running",
"ok: it keeps accounts (no single-space boot)",
"ok: it answers locally (200)", "ok: it answers locally (200)",
"ok: the operator's folders are writable", "ok: the operator's folders are writable",
"ok: memory/ is read-only to SilverBullet", "ok: memory/ is read-only to SilverBullet",
@@ -184,3 +194,9 @@ def test_a_writable_memory_folder_fails_the_check(tmp_path: Path) -> None:
) )
assert "ok: the firewall rule is in place" in result.stdout # still checked assert "ok: the firewall rule is in place" in result.stdout # still checked
assert "all checks passed" not in result.stdout assert "all checks passed" not in result.stdout
def test_a_single_space_boot_fails_the_check(tmp_path: Path) -> None:
result = _check(_fake_host(tmp_path, memory_writable=False, boot="single-space"))
assert result.returncode == 1
assert "FAIL: it booted a single space with no accounts" in result.stdout