The install layout a machine should have, seasoned owner or not:
~/officerdev/
platform/ the app
data/ DATA_PATH
dockers/ services the app store provisioned
capabilities/ the file-based item store
One root, everything under it. OFFICER_ROOT derives from DATA_PATH rather than being a second variable
that has to agree with the first.
Deliberately not `~/dockers`, where a seasoned user already keeps their own estate — 47 services on this
machine. That separation buys two things. Containers the app store created are distinguishable from the
user's own structurally, rather than by a naming convention we would have to enforce and they could
break. And we never reason about someone else's compose files: the store does not scan, adopt or modify
anything outside its own directory.
That also simplifies "I already have one of these" — it is answered by the user giving a URL, never by
us finding a directory and guessing whose it is. An earlier draft had the installer adopting existing
directories, which meant reading, and potentially writing over, services Officer did not create.
This development machine predates the convention and derives an ugly-but-correct path, since the project
sits inside ~/dockers/officer.dev. Still isolated, still one root. New installs get the clean shape.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
3.2 KiB
TypeScript
61 lines
3.2 KiB
TypeScript
import { dirname, join } from 'node:path';
|
|
import { DATA_PATH } from '../data-path';
|
|
|
|
// Where the app store puts the containers it provisions.
|
|
//
|
|
// ── The install layout ──
|
|
//
|
|
// A machine that runs Officer is meant to look like this, whether the owner is seasoned or not:
|
|
//
|
|
// ~/officerdev/
|
|
// platform/ the app
|
|
// data/ DATA_PATH — managed homes, attachments, job logs
|
|
// dockers/ services the app store provisioned <- this file
|
|
// capabilities/ the file-based item store
|
|
//
|
|
// One root, everything under it, nothing scattered. `OFFICER_ROOT` is derived from `DATA_PATH` rather
|
|
// than configured separately, because a second environment variable that must agree with the first is a
|
|
// second thing to get wrong — and on a correct install `data/` is always a direct child of the root.
|
|
//
|
|
// (This development machine predates the convention and has it inverted: the whole project sits inside
|
|
// `~/dockers/officer.dev/`, so the root derives to `officer.dev` and the app store's directory would be
|
|
// `~/dockers/officer.dev/dockers`. Which is ugly, and correct — it is still isolated, still under one
|
|
// root, and still not mixed in with anything else. New installs get the clean shape.)
|
|
//
|
|
// ── Why this is not `~/dockers` ──
|
|
//
|
|
// That is where a seasoned user already keeps their own estate — 47 services on this machine alone. Two
|
|
// reasons to stay out of it:
|
|
//
|
|
// 1. **Isolation.** Containers the app store created and containers the user manages must be
|
|
// distinguishable without inspecting them. A separate root makes that structural rather than a
|
|
// naming convention we would have to enforce and they could break.
|
|
// 2. **We never reason about someone else's compose files.** The app store does not scan, adopt or
|
|
// modify anything outside its own directory. "I already have one of these" is answered by the user
|
|
// giving a URL (`mode: 'existing'`), never by us finding a directory and guessing it is theirs.
|
|
//
|
|
// So this directory is exclusively ours to write, and everything in it was put there by an install.
|
|
|
|
/**
|
|
* The install root — the parent of `data/`. On a conventional install, `~/officerdev`.
|
|
*
|
|
* Derived, not configured: see above.
|
|
*/
|
|
export const OFFICER_ROOT = dirname(DATA_PATH);
|
|
|
|
/** Where provisioned services live, one directory each. Created on first install, not at boot. */
|
|
export const DOCKERS_DIR = join(OFFICER_ROOT, 'dockers');
|
|
|
|
/**
|
|
* This service's own directory: `<root>/dockers/<id>/`, holding `docker-compose.yaml` and — because the
|
|
* templates use relative bind mounts rather than named volumes — its data and configuration too.
|
|
*
|
|
* That is the convention the owner already uses everywhere: `./data`, `./database`, `./storage` beside
|
|
* the compose file, so both the app and a human can see exactly what a service is keeping and where.
|
|
* A named volume hides it behind `docker volume inspect`, which is the opposite of the point.
|
|
*/
|
|
export const serviceDir = (sidecarId: string): string => join(DOCKERS_DIR, sidecarId);
|
|
|
|
/** The compose file the installer renders and `docker compose` is run against. */
|
|
export const composeFile = (sidecarId: string): string => join(serviceDir(sidecarId), 'docker-compose.yaml');
|