diff --git a/ecosystem.light.config.cjs b/ecosystem.light.config.cjs index 4718e70b..38dd3d79 100644 --- a/ecosystem.light.config.cjs +++ b/ecosystem.light.config.cjs @@ -26,16 +26,17 @@ module.exports = defineProfile({ 'officer-agent', // spawns `claude` — chat is dead without it 'officer-opencode', // the alternative agent 'officer-pty', // the terminal - // Included even though a light host runs no Gitea: this sidecar fronts a REMOTE instance. Its URL - // and token live in `service_connections`, set from /gitea, so it needs nothing installed here. - // That is what separates it from the sidecars below, which supervise a local daemon or container. - // Same reasoning as the mac light profile, which has included it since it was added. - 'officer-gitea', ], // Excluded by CHOICE rather than by platform limits — every one of these would run on a Linux host. // A light install simply is not running the thing behind it. excluded: { + // Was in the baseline until 2026-08-11, on the reasoning that it fronts a REMOTE instance and so needs + // nothing installed locally. True, and beside the point: a baseline process appears in the Permissions + // screen and the dock whether or not anyone has given it a URL, so a fresh server offered to grant Gitea + // access to an instance that did not exist. It is installable now — `existing` mode, URL and token — which + // makes "is Gitea here" one question with one answer instead of two that disagree. + 'officer-gitea': 'fronts a remote instance; installed from the app store with its URL and token', 'officer-vnc': 'no desktop to mirror on a light install', 'officer-email': 'needs the mbsync/IMAP stack the light profile does not install', 'officer-music': 'the ffprobe indexer works, but a full library index is not a light-install concern', diff --git a/ecosystem.mac.light.config.cjs b/ecosystem.mac.light.config.cjs index 3ef55653..186f0cbd 100644 --- a/ecosystem.mac.light.config.cjs +++ b/ecosystem.mac.light.config.cjs @@ -32,17 +32,18 @@ module.exports = defineProfile({ // The terminal. Runs under node rather than bun — node-pty binds a native addon built against // node's ABI. That detail lives in ecosystem.config.cjs, not here. 'officer-pty', - // Included even though a laptop hosts no Gitea: this sidecar fronts a REMOTE instance. The URL and - // its token live in `service_connections`, set from /gitea, so it points at gitea.pastilhas.dev - // over the network and needs nothing installed here. That is what separates it from the sidecars - // below that supervise a local daemon or container. - 'officer-gitea', ], excluded: { // Cannot run on macOS at all. 'officer-vnc': 'mirrors an Xorg display with x11vnc; macOS has no Xorg', + // Left the baseline on 2026-08-11, on both light profiles together. It genuinely needs nothing installed + // locally — it points at a remote instance over the network — but a baseline process shows up in the dock + // and the Permissions screen whether or not a URL was ever given, so "is Gitea here" had two answers. It + // is an app-store install now: `existing` mode, URL and token, same as any other remote service. + 'officer-gitea': 'fronts a remote instance; installed from the app store with its URL and token', + // Would run, but needs something setup_mac_light.sh deliberately does not install. 'officer-email': 'needs the mbsync/IMAP stack setup_mac_light.sh does not install', 'officer-caldav': 'supervises Radicale, which setup_mac_light.sh does not install', diff --git a/src/servers/app-store/catalogue.ts b/src/servers/app-store/catalogue.ts index 8cd99fa1..cffe09e2 100644 --- a/src/servers/app-store/catalogue.ts +++ b/src/servers/app-store/catalogue.ts @@ -331,6 +331,34 @@ export const CATALOGUE: CatalogueEntry[] = [ composeTemplate: 'radicale', existingFields: [{ key: 'url', label: 'CalDAV URL', type: 'url', required: true }], }, + { + id: 'gitea', + ui: { name: 'Gitea', icon: 'GitBranch', color: '#34d399', rootRoute: '/gitea', routes: ['/gitea'] }, + process: 'officer-gitea', + label: 'Gitea', + summary: 'Repositories, issues and pull requests from your own Gitea account', + // 'none' because there is nothing for the INSTALLER to provision, not because Gitea is single-tenant — + // it is the most per-user service here. The owner's connection carries the instance URL; every member + // adds their own access token from /gitea and acts only as themselves upstream, with Gitea's own + // permissions as the second and real gate. A provisioner could not do that part for them without + // holding an admin token and minting tokens on their behalf, which is more authority than this needs. + members: 'none', + // `existing` only, and there is no compose template. Gitea here is always something the owner already + // runs — on this machine, on another, or hosted. Provisioning one would mean owning the migration, + // backup and upgrade story for a service that is nobody's side feature. + modes: ['existing'], + capability: 'gitea', + existingFields: [ + { key: 'url', label: 'Gitea URL', type: 'url', required: true, placeholder: 'https://gitea.example.com' }, + { + key: 'secret', + label: 'Your access token', + type: 'secret', + required: true, + help: 'A personal access token from your own Gitea account. Members add their own from /gitea.', + }, + ], + }, { id: 'headscale', ui: { name: 'Headscale', icon: 'Network', color: '#818cf8', rootRoute: '/headscale', routes: ['/headscale'] },