move host setup into scripts/setup/
scripts/ was holding two unrelated kinds of thing: install-this-machine, and run-this-occasionally. The eight installers now live in scripts/setup/; what stays at the top level is the build steps (gen-index, prebuild, build/) and the two maintenance scripts (reindex-music, rebuild-soulseek-tree). The move is not just a rename. Three of these derive the repo root from their own location: setup.sh:51 PROJECT_DIR="$(dirname "$SCRIPT_DIR")" setup_mac_light.sh:51 same cleanup-desktop.sh:134 ENV_FILE="$(dirname "$0")/../.env" Left alone, all three would now resolve to scripts/ — and nothing downstream complains. PROJECT_DIR is where .env is written, where `bun install`, `gen:index` and `db:push` run, and what pm2 is pointed at, so a fresh install would have quietly provisioned scripts/ and reported success. cleanup-desktop.sh fails the other way: it would find no .env, print "No .env — skipping", and leave the real VNC_PASSWORD in the real file. All three are now `../..` with a comment saying why the level matters. provision-user-dirs.ts imports data-path.ts relatively; that one tsgo caught. Also disambiguated `setup.sh` where it had become two files. app-store/templates/<name>/setup.sh is a per-sidecar installer with its own contract, and preflight.ts + docs/sidecar-app-store.md discussed both in the same paragraph. The host one is now spelled with its full path at those sites. Verified: bash -n on all six shell scripts, tsgo clean, os-user tests pass, both derivations resolve to the repo root, starship.toml still resolves from os-user-shell.ts, and provision-user-dirs.ts runs under DRY_RUN. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
||||
`'Unverified'` and `signin.ts` refuses anything else with a bare UNAUTHORIZED, which is the trap
|
||||
the hand-INSERT route fell into. The owner sets the password and reads it out; `passwordChangedAt`
|
||||
stays null. Directories come from the shared `provisionUserDirs`/`USER_DIRS` in `data-path.ts`,
|
||||
which `scripts/provision-user-dirs.ts` now imports rather than restating.
|
||||
which `scripts/setup/provision-user-dirs.ts` now imports rather than restating.
|
||||
|
||||
- [ ] **Still no invite flow, and no password reset for a member.** The owner types the password and
|
||||
tells the person, which means the owner knows it and the member cannot change it back if they
|
||||
|
||||
@@ -146,7 +146,7 @@ test (`os-user.test.ts` → "does not pass the platform environment through").
|
||||
sudoers *policy* — `env_reset`, `env_keep`, `always_set_home` — and "which variables cross into a member's
|
||||
shell" must not depend on a config file someone may have edited.
|
||||
|
||||
Root is available: `scripts/setup.sh` §4 installs `/etc/sudoers.d/officer-service` granting the service
|
||||
Root is available: `scripts/setup/setup.sh` §4 installs `/etc/sudoers.d/officer-service` granting the service
|
||||
user `NOPASSWD: ALL` on the full profile. The light profile deliberately skips it, so a light install that
|
||||
wants OS users needs a **narrow** entry — `useradd`, `chown`, `setpriv` — which is better than the blanket
|
||||
rule anyway.
|
||||
|
||||
@@ -107,9 +107,13 @@ consequences, both wanted:
|
||||
|
||||
### Docker is assumed, and nothing guarantees it
|
||||
|
||||
Verified: **nothing in `scripts/` installs Docker, and nothing checks for it.** `setup.sh` calls
|
||||
`setup-dockers.sh`, which invokes `docker compose` with no preflight, so a fresh host without Docker
|
||||
fails partway through setup with a bare "command not found".
|
||||
Verified: **nothing in `scripts/` installs Docker, and nothing checks for it.** The host installer
|
||||
`scripts/setup/setup.sh` calls `scripts/setup/setup-dockers.sh`, which invokes `docker compose` with no
|
||||
preflight, so a fresh host without Docker fails partway through setup with a bare "command not found".
|
||||
|
||||
(Not to be confused with the per-template `setup.sh` below — `app-store/templates/<name>/setup.sh` — which
|
||||
is a different file with a different contract. The host one provisions the machine; a template one
|
||||
provisions a single sidecar.)
|
||||
|
||||
That is the seam where this project's origin shows — it began as one person's own machine, provisioned
|
||||
by his own scripts, where Docker was simply always there.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Linux light profile — the platform without the self-hosted estate around it.
|
||||
//
|
||||
// For a machine that should run the file browser, the terminal and Claude/opencode chat, and nothing
|
||||
// else. Paired with `OFFICER_PROFILE=light bash scripts/setup.sh`, which installs only what these
|
||||
// else. Paired with `OFFICER_PROFILE=light bash scripts/setup/setup.sh`, which installs only what these
|
||||
// processes need: node, bun, ffmpeg, Postgres, pm2 and the two agent CLIs.
|
||||
//
|
||||
// This is a subset of ecosystem.config.cjs, not a copy of it — see ecosystem.profile.cjs for why, and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// macOS light profile — the same process set as the Linux light profile, on a laptop.
|
||||
//
|
||||
// Paired with scripts/setup_mac_light.sh. Runs the file browser, the terminal and Claude/opencode
|
||||
// Paired with scripts/setup/setup_mac_light.sh. Runs the file browser, the terminal and Claude/opencode
|
||||
// chat; nothing else.
|
||||
//
|
||||
// This is a subset of ecosystem.config.cjs, not a copy of it. That distinction is here because of this
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
"format": "{ git diff --name-only HEAD -- 'src/**/*.ts' 'src/**/*.tsx'; git ls-files --others --exclude-standard -- 'src/**/*.ts' 'src/**/*.tsx'; } | xargs -r prettier --write",
|
||||
"format:all": "prettier --write \"src/**/*.{ts,tsx}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
||||
"setup": "bash scripts/setup.sh"
|
||||
"setup": "bash scripts/setup/setup.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.41",
|
||||
|
||||
@@ -26,7 +26,7 @@ set -euo pipefail
|
||||
# their profile in it. Opt in rather than surprise someone.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/cleanup-desktop.sh [--purge-brave]
|
||||
# bash scripts/setup/cleanup-desktop.sh [--purge-brave]
|
||||
#
|
||||
# Reboot afterwards: the GDM and GRUB changes are both boot-time.
|
||||
|
||||
@@ -131,7 +131,9 @@ fi
|
||||
|
||||
# --- Step 7: .env ---
|
||||
echo "[7/7] Cleaning .env..."
|
||||
ENV_FILE="$(cd "$(dirname "$0")/.." && pwd)/.env"
|
||||
# ../.. — this lives in scripts/setup/. A wrong level here is quiet in the other direction: the sed
|
||||
# below simply finds no file, reports "No .env" and leaves the real VNC_PASSWORD in place.
|
||||
ENV_FILE="$(cd "$(dirname "$0")/../.." && pwd)/.env"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
sed -i '/^VNC_PASSWORD=/d; /^VNC_PORT=/d' "$ENV_FILE"
|
||||
echo " Removed VNC entries."
|
||||
@@ -1,7 +1,7 @@
|
||||
// Create the per-user root under DATA_PATH for the given accounts.
|
||||
//
|
||||
// bun scripts/provision-user-dirs.ts a@b.com c@d.com
|
||||
// DRY_RUN=1 bun scripts/provision-user-dirs.ts a@b.com
|
||||
// bun scripts/setup/provision-user-dirs.ts a@b.com c@d.com
|
||||
// DRY_RUN=1 bun scripts/setup/provision-user-dirs.ts a@b.com
|
||||
//
|
||||
// Takes emails as arguments rather than reading the user table: the directory layout does not depend
|
||||
// on the database, and keeping the DB out means this runs with nothing else up. At invite time the
|
||||
@@ -19,13 +19,13 @@ import { join } from 'node:path';
|
||||
// The list and DATA_PATH itself come from the platform rather than being restated here. The owner's
|
||||
// create-account handler provisions the same skeleton, and a script that drifted from it would produce
|
||||
// accounts that differ by how they were made. Importing data-path.ts pulls in no database and no server.
|
||||
import { DATA_PATH, USER_DIRS } from '../src/servers/data-path';
|
||||
import { DATA_PATH, USER_DIRS } from '../../src/servers/data-path';
|
||||
|
||||
const DRY_RUN = process.env.DRY_RUN === '1';
|
||||
const emails = process.argv.slice(2).filter(Boolean);
|
||||
|
||||
if (!emails.length) {
|
||||
console.error('Usage: bun scripts/provision-user-dirs.ts <email> [email...]');
|
||||
console.error('Usage: bun scripts/setup/provision-user-dirs.ts <email> [email...]');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ set -euo pipefail
|
||||
# capture an Xorg server, NOT a Wayland compositor — so we install the full GNOME desktop but force GDM
|
||||
# onto the Xorg session (WaylandEnable=false). Auto-login is enabled so a user session owns :0 for the
|
||||
# mirror to attach to. Switching the display manager takes effect on the next reboot.
|
||||
# Usage: ./scripts/setup-desktop.sh
|
||||
# Usage: ./scripts/setup/setup-desktop.sh
|
||||
|
||||
echo "=== Officer Remote Desktop Setup (Ubuntu GNOME on Xorg) ==="
|
||||
echo ""
|
||||
@@ -4,8 +4,8 @@
|
||||
# Outputs parseable key=value lines to stdout; all prompts go to stderr.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/setup-dockers.sh
|
||||
# eval "$(bash scripts/setup-dockers.sh)"
|
||||
# bash scripts/setup/setup-dockers.sh
|
||||
# eval "$(bash scripts/setup/setup-dockers.sh)"
|
||||
#
|
||||
# Environment overrides:
|
||||
# SETUP_DOCKER_SERVICES="1 2 3" — pre-select services (or "all"/"none")
|
||||
@@ -3,8 +3,8 @@
|
||||
# Run once on a fresh Ubuntu/Debian host before launching the server.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/setup.sh # full server install
|
||||
# OFFICER_PROFILE=light bash scripts/setup.sh # light install
|
||||
# bash scripts/setup/setup.sh # full server install
|
||||
# OFFICER_PROFILE=light bash scripts/setup/setup.sh # light install
|
||||
#
|
||||
# PROFILES
|
||||
# full Everything: the self-hosted estate, the remote desktop, the music/audio stack, the shell
|
||||
@@ -48,7 +48,10 @@ is_light() { [ "$OFFICER_PROFILE" = "light" ]; }
|
||||
if is_light; then ECOSYSTEM_FILE="ecosystem.light.config.cjs"; else ECOSYSTEM_FILE="ecosystem.config.cjs"; fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
# ../.. — this lives in scripts/setup/, so the repo root is two levels up, not one. Nothing here fails
|
||||
# loudly if that is wrong: PROJECT_DIR is where .env is written, where `bun install` and `db:push` run and
|
||||
# where pm2 is pointed, so an off-by-one level silently sets up scripts/ instead of the repo.
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Resolve the real user's home even when running under sudo
|
||||
if [[ -n "${SUDO_USER:-}" ]]; then
|
||||
@@ -540,7 +543,7 @@ if [ ! -f "$STARSHIP_DEST" ]; then
|
||||
elif cmp -s "$SCRIPT_DIR/starship.toml" "$STARSHIP_DEST"; then
|
||||
skip "starship config"
|
||||
else
|
||||
warn "starship config kept — yours differs (cp scripts/starship.toml ~/.config/ to take this one)"
|
||||
warn "starship config kept — yours differs (cp scripts/setup/starship.toml ~/.config/ to take this one)"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Officer — macOS laptop setup.
|
||||
#
|
||||
# The barebones counterpart to scripts/setup.sh (which targets an Ubuntu/Debian server and is left
|
||||
# The barebones counterpart to scripts/setup/setup.sh (which targets an Ubuntu/Debian server and is left
|
||||
# alone). This installs only what a laptop workflow needs: the file browser, Claude/opencode chat,
|
||||
# and a terminal. No Go/Rust/cliamp/PulseAudio, no neovim, no shell dotfile stack, no VNC desktop,
|
||||
# no sudoers grant, no power-management changes.
|
||||
#
|
||||
# EVERY STEP IS OPTIONAL. Each one prompts before doing anything, and can be preset non-interactively:
|
||||
#
|
||||
# SETUP_POSTGRES=0 SETUP_OPENCODE=0 bash scripts/setup_mac_light.sh
|
||||
# SETUP_POSTGRES=0 SETUP_OPENCODE=0 bash scripts/setup/setup_mac_light.sh
|
||||
#
|
||||
# SETUP_PACKAGES brew node@22 / bun / ffmpeg SETUP_CLAUDE claude code CLI
|
||||
# SETUP_POSTGRES brew postgresql@18 + createdb SETUP_OPENCODE opencode CLI
|
||||
@@ -22,7 +22,7 @@
|
||||
# This script never calls sudo itself — everything lands under the Homebrew prefix or $HOME. Note
|
||||
# that Homebrew's own installer does ask for an administrator password on a fresh Mac.
|
||||
#
|
||||
# Usage: bash scripts/setup_mac_light.sh
|
||||
# Usage: bash scripts/setup/setup_mac_light.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -48,7 +48,10 @@ FAILURES=()
|
||||
note_failure() { FAILURES+=("$1"); fail "$1"; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
# ../.. — this lives in scripts/setup/. See the note in setup.sh: PROJECT_DIR is where .env is written
|
||||
# and where bun install, gen:index, db:push and pm2 are pointed, and none of them fails loudly on the
|
||||
# wrong directory.
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
PG_FORMULA="postgresql@18"
|
||||
PG_DATABASE="officer_dev"
|
||||
@@ -103,7 +106,7 @@ echo "════════════════════════
|
||||
step "Preflight"
|
||||
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
fail "This script is macOS-only. On Linux use scripts/setup.sh."
|
||||
fail "This script is macOS-only. On Linux use scripts/setup/setup.sh."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -532,5 +535,5 @@ echo " • Not run on macOS: VNC desktop, email sync, music indexer, cliamp aud
|
||||
echo " that front a container or an external service — vault, slskd, headscale, transmission,"
|
||||
echo " invoiceshelf, memos, photos, caldav, notify, wallet. See ecosystem.mac.light.config.cjs."
|
||||
echo " • Pin a specific Claude CLI with CLAUDE_BIN=/path/to/claude in .env if you need to"
|
||||
echo " • Re-run any single step with e.g. SETUP_OPENCODE=1 bash scripts/setup_mac_light.sh"
|
||||
echo " • Re-run any single step with e.g. SETUP_OPENCODE=1 bash scripts/setup/setup_mac_light.sh"
|
||||
echo ""
|
||||
@@ -9,9 +9,10 @@ import type { CatalogueEntry } from './catalogue';
|
||||
//
|
||||
// ── What this deliberately does NOT do ──
|
||||
//
|
||||
// It does not install anything. Today nothing in `scripts/` installs Docker either — `setup.sh` runs
|
||||
// `setup-dockers.sh`, which invokes `docker compose` without ever checking it exists, so a fresh host
|
||||
// without Docker fails partway through setup with a bare "command not found". That is a real gap, and
|
||||
// It does not install anything. Today nothing in `scripts/` installs Docker either — the host installer
|
||||
// `scripts/setup/setup.sh` runs `scripts/setup/setup-dockers.sh`, which invokes `docker compose` without
|
||||
// ever checking it exists, so a fresh host without Docker fails partway through setup with a bare
|
||||
// "command not found". That is a real gap, and
|
||||
// the intended fix is a per-sidecar `setup.sh` that ensures its own dependencies — which is also the
|
||||
// shape a sidecar needs once it lives in its own repository and ships independently.
|
||||
//
|
||||
|
||||
@@ -50,7 +50,7 @@ export const getOwnerHomeDir = (email: string): string => process.env.HOME_DIR ?
|
||||
// non-owner's sessions would run.
|
||||
//
|
||||
// Single-sourced here rather than in the script that used to own the list, because there are now two
|
||||
// callers — `scripts/provision-user-dirs.ts` and the owner's create-account handler — and a skeleton
|
||||
// callers — `scripts/setup/provision-user-dirs.ts` and the owner's create-account handler — and a skeleton
|
||||
// that differs depending on how the account was made is a bug nobody would think to look for.
|
||||
export const USER_DIRS = ['home', 'attachments', 'cache', 'dashboards', 'email_accounts', 'logs', 'sidecar'] as const;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { osUserHome, runAs } from './os-user';
|
||||
// A single `/usr/local/bin/claude` would be less disk and one version to reason about, and the argument for
|
||||
// it is real: the private part of Claude is the credential in `~/.claude`, not the executable. It is still
|
||||
// the wrong shape here. `claude` updates itself — that is why the owner's own install goes through
|
||||
// Anthropic's installer rather than npm (`scripts/setup.sh:853`) — and a root-owned binary is one a member
|
||||
// Anthropic's installer rather than npm (`scripts/setup/setup.sh:861`) — and a root-owned binary is one a member
|
||||
// cannot update, which turns "my agent is a version behind" into a request to the owner. Per-member also
|
||||
// means the account's agent keeps working exactly as the tool ships, with no platform-shaped exception to
|
||||
// explain. Same command the owner ran, run as them, in their home.
|
||||
@@ -29,7 +29,7 @@ import { osUserHome, runAs } from './os-user';
|
||||
// report whether the credential has appeared, so the UI can render the one-line instruction instead of an
|
||||
// agent that fails for reasons nobody can see.
|
||||
|
||||
/** Anthropic's own installer — the same one `scripts/setup.sh` uses for the owner, chosen for auto-update. */
|
||||
/** Anthropic's own installer — the same one `scripts/setup/setup.sh` uses for the owner, chosen for auto-update. */
|
||||
const CLAUDE_INSTALL_URL = 'https://claude.ai/install.sh';
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ import { osUserHome } from './os-user';
|
||||
//
|
||||
// ── What it is ──
|
||||
//
|
||||
// `shell-skel/zshrc` → `~/.zshrc`, and the platform's own `scripts/starship.toml` → `~/.config/starship.toml`
|
||||
// `shell-skel/zshrc` → `~/.zshrc`, and the platform's own `scripts/setup/starship.toml` → `~/.config/starship.toml`
|
||||
// so a member's prompt is the same one the owner's install deploys. That file is the single source for both:
|
||||
// setup.sh copies it for the owner and this copies it for everybody else, so the two cannot drift.
|
||||
//
|
||||
@@ -31,7 +31,7 @@ import { osUserHome } from './os-user';
|
||||
/** Where the templates live, relative to this file. */
|
||||
const SKEL_DIR = join(import.meta.dir, 'shell-skel');
|
||||
/** The prompt config the owner's own install uses — one file, both audiences. */
|
||||
const STARSHIP_SRC = join(import.meta.dir, '../../scripts/starship.toml');
|
||||
const STARSHIP_SRC = join(import.meta.dir, '../../scripts/setup/starship.toml');
|
||||
|
||||
type SudoResult = { ok: boolean; out: string };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user