Replicates what Gitea's own web UI offers, on top of the sidecar's /_api pass-through. Repository browsing (tree, file view with the FileViewer's shiki renderer, README), commits, branches, tags, releases, issues and pull requests both per-repo and cross-repo, notifications, explore/search and organizations. Routes are /gitea/:section plus /gitea/repo/:owner/:name/:tab/:item, all real Links with the URL as the source of truth — no selection channel. Markdown is rendered client-side (react-markdown + remark-gfm + rehype-sanitize, rehype-raw deliberately absent) rather than through the instance's /api/v1/markdown, because consuming that means dangerouslySetInnerHTML and there is no DOMPurify in the tree with installs frozen. The cost is Gitea's #123 and @mention cross-references; relative links and images are resolved instead. The /markdown and /markup allow-list entries stay, so that door is open when a sanitiser lands. retargetUrls rebases instance-minted URLs onto a browser-reachable origin, IN ONE DIRECTION ONLY. This instance answers with two: /user and /repos build from its configured ROOT_URL (http://localhost:9004), /contents from the public host. An unconditional rewrite onto the connection URL therefore broke the second set to match the first, turning working https links into dead loopback ones. Only a private/loopback URL is rewritten now, and only when the target is itself public; when the connection URL is a dial address nothing is touched and the connection screen says why avatars will not load. Also carries the frontend half of the one-instance-many-tokens model: the connection form draws a URL field only for the owner and sends no url key at all for anyone else, ServiceConnection.url is string | null to match officerdb, and the rebase origin comes from the resolved instanceUrl rather than connection.url, which is null for a member. Not verified: no runtime pass since the last four changes, the issues and pull views have never rendered a row (the instance has none), and the member path has never executed (one account). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
158 lines
5.9 KiB
JavaScript
158 lines
5.9 KiB
JavaScript
module.exports = {
|
|
apps: [
|
|
{
|
|
name: 'officer',
|
|
script: 'bun',
|
|
args: 'start',
|
|
watch: false,
|
|
},
|
|
// The Anthropic credential proxy. Despite the old name (`officer-claude`) this process does NOT
|
|
// run agents — it holds the proxy secret and forwards to api.anthropic.com. The process that runs
|
|
// agents is `officer-agent` below.
|
|
{
|
|
name: 'officer-anthropic-proxy',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/claude/index.ts',
|
|
watch: false,
|
|
},
|
|
// The process that actually runs `claude`. It used to be spawned on demand by the main server,
|
|
// which made every agent session a grandchild of `officer` and killed it on every restart. As a PM2
|
|
// peer it survives them. It resolves the owner from the database and the proxy secret from the
|
|
// proxy's state file, so it needs nothing from `officer` in order to start.
|
|
{
|
|
name: 'officer-agent',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/claude/user-instance.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-opencode',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/opencode/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-email',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/email/index.ts',
|
|
watch: false,
|
|
},
|
|
// The only sidecar run by `node` rather than `bun`, and the only one that is not TypeScript: node-pty
|
|
// is a native addon. It also does not use sidecar/connect.ts, and carries its own copy of the
|
|
// reconnect loop.
|
|
{
|
|
name: 'officer-pty',
|
|
script: 'node',
|
|
args: 'src/servers/sidecar/pty/index.mjs',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-vnc',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/vnc/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-music',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/music/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-vault',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/vault/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-slskd',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/slskd/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-headscale',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/headscale/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-transmission',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/transmission/index.ts',
|
|
watch: false,
|
|
},
|
|
// The books. Wraps a self-hosted InvoiceShelf. Instances, their Sanctum tokens and the company each one
|
|
// is pinned to are set by the owner from /invoices/settings and stored encrypted in
|
|
// `invoiceshelf_accounts` — read here, never from the environment, because Bun auto-loads `.env` into
|
|
// every process in this directory and `officer` would hold the token too.
|
|
{
|
|
name: 'officer-invoiceshelf',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/invoiceshelf/index.ts',
|
|
watch: false,
|
|
},
|
|
// Video. Wraps a self-hosted Jellyfin. Servers, and the access token each one is signed in with, are set
|
|
// by the owner from /jellyfin and stored encrypted in `jellyfin_servers` — read here, never from the
|
|
// environment. Video only: Officer's own player owns audio.
|
|
{
|
|
name: 'officer-jellyfin',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/jellyfin/index.ts',
|
|
watch: false,
|
|
},
|
|
// Notes. Wraps a self-hosted Memos. The instance URL and its personal access token are set by the
|
|
// owner from the UI and stored in `service_connections` — read here, never from the environment.
|
|
{
|
|
name: 'officer-memos',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/memos/index.ts',
|
|
watch: false,
|
|
},
|
|
// Code hosting. Wraps a self-hosted Gitea. The instance URL and its personal access token are set by
|
|
// the owner from /gitea and stored in `service_connections` — read here, never from the environment.
|
|
{
|
|
name: 'officer-gitea',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/gitea/index.ts',
|
|
watch: false,
|
|
},
|
|
// Calendar and contacts. Supervises Radicale (CalDAV/CardDAV) on a loopback port and owns the
|
|
// collections under DATA_PATH/dav. Two doors: /dav for phones (DAVx5, iOS, Thunderbird — HTTP Basic
|
|
// against a scoped app password) and /api/caldav for Officer's own UI. The protocol is Radicale's;
|
|
// the platform authenticates and forwards. See docs/nextcloud-replacement.md.
|
|
{
|
|
name: 'officer-caldav',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/caldav/index.ts',
|
|
watch: false,
|
|
},
|
|
// The photo library. Wraps a self-hosted Immich. The instance and its key are set by the owner from
|
|
// /photos/settings and stored encrypted in `photos_config` — read here, never from the environment,
|
|
// because Bun auto-loads `.env` into every process in this directory and `officer` would hold it too.
|
|
{
|
|
name: 'officer-photos',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/photos/index.ts',
|
|
watch: false,
|
|
},
|
|
// The bitcoin wallet. Holds seed material (sealed under an owner passphrase) and node credentials, so
|
|
// it is the one sidecar whose restart has a security-relevant side effect: every wallet relocks.
|
|
// The one place anything leaves this machine to tell the owner something: push (APNs + FCM) and the
|
|
// Discord webhook, behind one interface. A sidecar rather than platform code because the producers
|
|
// are spread across sidecars, and a platform-owned notifier would make every one of them call back in.
|
|
{
|
|
name: 'officer-notify',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/notify/index.ts',
|
|
watch: false,
|
|
},
|
|
{
|
|
name: 'officer-wallet',
|
|
script: 'bun',
|
|
args: 'run src/servers/sidecar/wallet/index.ts',
|
|
watch: false,
|
|
},
|
|
],
|
|
};
|