re-enable vault — plugin extraction deferred, this is the same code as before
Reverses 1eecc400 ("switch off Vaultwarden's routers, pending extraction into
a plugin"). That plan is on hold: vault, unlike offscale/email/music, is a
Docker-provisioned service through the old app-store catalogue
(sidecar_installs, composeTemplate: 'vaultwarden') rather than a self-
contained sidecar, and the new plugins/ system has no equivalent yet for
provisioning a container. Design work for that is separate, future, explicitly
deferred by the owner rather than attempted here.
What's actually needed right now is narrower: the owner's own Vaultwarden
instance is already running, untouched, externally. This restores exactly the
code that was there before 2026-08-13 — both routers (owner-authenticated
/api/vault for Officer's own clients, unauthenticated /vaultwarden + root
Bitwarden paths for third-party clients), the websocket notifications hub, the
sidecar's port-capture side-effect import, and the vault schema export. All of
it was left commented rather than deleted specifically so this restoration
would be a straight uncomment, per the original commit's own reasoning.
Verified before touching anything: no data was at risk. vault_tokens/
vault_unlock_keys did not exist in Postgres (dropped by a db:push after the
schema was commented out, before this session), and there is no
sidecar_installs row for vault on this machine — nothing to lose, nothing to
migrate.
Confirmed the auth split resolves cleanly without any core changes:
vaultRouter (/api/vault) does its OWN userMiddleware + owner-only gate
internally, so it needs no special exemption — the totality.ts entries for
'/vault' and 'vault' were already present, unaffected by the mount being
commented out, and needed no changes here. Only the unauthenticated
publicVaultRouter genuinely depends on the EXEMPT_API_PREFIXES/
UNPROTECTED_API_PREFIXES carve-out, which is explicitly documented in-code as
TEMPORARY: it goes away once a first-party browser extension authenticates to
Officer directly instead of to Vaultwarden.
bunx tsgo clean, suite at its 7 pre-existing baseline failures, no new ones —
the code had not drifted while dark.
Still needed, not done here (deliberately left to the owner, same as every
restart this session): set VAULTWARDEN_URL in .env to the real server address,
add an officer-vault PM2 entry (ecosystem.config.cjs has none — this was never
in CORE_PROCESSES and there is no plugin/sidecar_installs row driving it),
bun db:push to recreate vault_tokens/vault_unlock_keys, restart officer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ export * from './service-connections/schema'; // service_connections
|
|||||||
// export * from './jellyfin/schema'; // jellyfin_servers officer-jellyfin
|
// export * from './jellyfin/schema'; // jellyfin_servers officer-jellyfin
|
||||||
// export * from './invoiceshelf/schema'; // invoiceshelf_accounts officer-invoiceshelf
|
// export * from './invoiceshelf/schema'; // invoiceshelf_accounts officer-invoiceshelf
|
||||||
// export * from './soulseek/schema'; // soulseek_favorites, _browse_snapshots, _browse_dirs
|
// export * from './soulseek/schema'; // soulseek_favorites, _browse_snapshots, _browse_dirs
|
||||||
// export * from './vault/schema'; // vault_tokens, vault_unlock_keys officer-vault
|
export * from './vault/schema'; // vault_tokens, vault_unlock_keys officer-vault
|
||||||
// export * from './wallet/schema'; // wallet_wallets, _labels, _frozen_utxos, _chain_cache
|
// export * from './wallet/schema'; // wallet_wallets, _labels, _frozen_utxos, _chain_cache
|
||||||
|
|
||||||
// ── Plugin tables are NOT here ───────────────────────────────────────────────────────────────────
|
// ── Plugin tables are NOT here ───────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+11
-11
@@ -18,7 +18,7 @@ import { chatWebsocket } from './servers/api/chat/websocket';
|
|||||||
import { taskRunnerWebsocket } from './servers/api/tasks/task-executor';
|
import { taskRunnerWebsocket } from './servers/api/tasks/task-executor';
|
||||||
import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor';
|
import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor';
|
||||||
// import { desktopWebsocket } from './servers/api/desktop/websocket';
|
// import { desktopWebsocket } from './servers/api/desktop/websocket';
|
||||||
// import { vaultWebsocket, upgradeVaultWs } from './servers/api/vault/websocket';
|
import { vaultWebsocket, upgradeVaultWs } from './servers/api/vault/websocket';
|
||||||
import officerWeb from './apps/officer-web/index.gen.html';
|
import officerWeb from './apps/officer-web/index.gen.html';
|
||||||
// import { startBrowserRelay } from './servers/api/browser/relay'; // switched off — see below
|
// import { startBrowserRelay } from './servers/api/browser/relay'; // switched off — see below
|
||||||
import { registerSidecar, unregisterSidecar, handleSidecarMessage } from './servers/sidecar-registry';
|
import { registerSidecar, unregisterSidecar, handleSidecarMessage } from './servers/sidecar-registry';
|
||||||
@@ -144,7 +144,7 @@ const handlers: Record<string, any> = {
|
|||||||
'task-runner': taskRunnerWebsocket,
|
'task-runner': taskRunnerWebsocket,
|
||||||
pipeline: pipelineWebsocket,
|
pipeline: pipelineWebsocket,
|
||||||
// desktop: desktopWebsocket,
|
// desktop: desktopWebsocket,
|
||||||
// vault: vaultWebsocket,
|
vault: vaultWebsocket,
|
||||||
sidecar: sidecarWebsocket,
|
sidecar: sidecarWebsocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -407,10 +407,10 @@ const server = serve({
|
|||||||
},
|
},
|
||||||
// Vaultwarden notifications hub: upgrade the WebSocket here (proxied to upstream by vaultWebsocket);
|
// Vaultwarden notifications hub: upgrade the WebSocket here (proxied to upstream by vaultWebsocket);
|
||||||
// everything else on this path (SignalR long-poll negotiate/poll) falls through to the HTTP proxy.
|
// everything else on this path (SignalR long-poll negotiate/poll) falls through to the HTTP proxy.
|
||||||
// '/api/vault/notifications/*': (req, server) => {
|
'/api/vault/notifications/*': (req, server) => {
|
||||||
// if (req.headers.get('upgrade') === 'websocket') return upgradeVaultWs(req, server);
|
if (req.headers.get('upgrade') === 'websocket') return upgradeVaultWs(req, server);
|
||||||
// return honoServer.fetch(req, server);
|
return honoServer.fetch(req, server);
|
||||||
// },
|
},
|
||||||
// A dev server's HMR socket, framed by the Preview panel. Must be in the route table rather than
|
// A dev server's HMR socket, framed by the Preview panel. Must be in the route table rather than
|
||||||
// behind the `/api/*` catch-all: Bun matches this table first, and an upgrade that reaches Hono is
|
// behind the `/api/*` catch-all: Bun matches this table first, and an upgrade that reaches Hono is
|
||||||
// an ordinary GET by then.
|
// an ordinary GET by then.
|
||||||
@@ -441,14 +441,14 @@ const server = serve({
|
|||||||
// entry for the same reason /dav does: only the paths listed here reach hono, and anything else
|
// entry for the same reason /dav does: only the paths listed here reach hono, and anything else
|
||||||
// falls through to the SPA — which answers 200 with the React shell, so a missing line here looks
|
// falls through to the SPA — which answers 200 with the React shell, so a missing line here looks
|
||||||
// like a working endpoint returning nonsense rather than a 404.
|
// like a working endpoint returning nonsense rather than a 404.
|
||||||
// '/vaultwarden/*': honoServer.fetch,
|
'/vaultwarden/*': honoServer.fetch,
|
||||||
// The same proxy at the root, so the extension needs only the bare Officer URL. These four prefixes
|
// The same proxy at the root, so the extension needs only the bare Officer URL. These four prefixes
|
||||||
// are Vaultwarden's alone — nothing in Officer answers on them — so routing them here costs nothing.
|
// are Vaultwarden's alone — nothing in Officer answers on them — so routing them here costs nothing.
|
||||||
// `/api/*` already reaches hono below, where a Bitwarden client header diverts it.
|
// `/api/*` already reaches hono below, where a Bitwarden client header diverts it.
|
||||||
// '/identity/*': honoServer.fetch,
|
'/identity/*': honoServer.fetch,
|
||||||
// '/notifications/*': honoServer.fetch,
|
'/notifications/*': honoServer.fetch,
|
||||||
// '/icons/*': honoServer.fetch,
|
'/icons/*': honoServer.fetch,
|
||||||
// '/events/*': honoServer.fetch,
|
'/events/*': honoServer.fetch,
|
||||||
...spaRoutes,
|
...spaRoutes,
|
||||||
'/api': honoServer.fetch,
|
'/api': honoServer.fetch,
|
||||||
// A CLOSURE, deliberately, and not the bound `honoServer.fetch`.
|
// A CLOSURE, deliberately, and not the bound `honoServer.fetch`.
|
||||||
|
|||||||
+12
-12
@@ -22,8 +22,8 @@ import { dashboardsRouter } from './api/dashboards';
|
|||||||
import { router as fileBrowserRouter } from './api/file-browser/router';
|
import { router as fileBrowserRouter } from './api/file-browser/router';
|
||||||
import { devProxyRouter } from './api/dev-proxy/router';
|
import { devProxyRouter } from './api/dev-proxy/router';
|
||||||
import { pluginsRouter } from './api/plugins/router';
|
import { pluginsRouter } from './api/plugins/router';
|
||||||
// import { vaultRouter } from './api/vault/router';
|
import { vaultRouter } from './api/vault/router';
|
||||||
// import { publicVaultRouter, VAULT_ONLY_PREFIXES, isBitwardenClient } from './api/vault/public-router';
|
import { publicVaultRouter, VAULT_ONLY_PREFIXES, isBitwardenClient } from './api/vault/public-router';
|
||||||
import { agentHandoffRouter } from './api/agent-handoff/router';
|
import { agentHandoffRouter } from './api/agent-handoff/router';
|
||||||
// import { slskdRouter } from './api/slskd/router';
|
// import { slskdRouter } from './api/slskd/router';
|
||||||
// import { transmissionRouter } from './api/transmission/router';
|
// import { transmissionRouter } from './api/transmission/router';
|
||||||
@@ -44,7 +44,7 @@ import { systemMonitorRouter } from './api/system-monitor/system-monitor';
|
|||||||
import { activityRouter } from './api/activity/router';
|
import { activityRouter } from './api/activity/router';
|
||||||
// Vault still hand-rolls its port capture, so it keeps a side-effect import; every other HTTP sidecar
|
// Vault still hand-rolls its port capture, so it keeps a side-effect import; every other HTTP sidecar
|
||||||
// registers its listener when createSidecarProxy runs inside the router this file already imports.
|
// registers its listener when createSidecarProxy runs inside the router this file already imports.
|
||||||
// import './api/vault/sidecar-server'; // side-effect: capture the officer-vault reverse-proxy port
|
import './api/vault/sidecar-server'; // side-effect: capture the officer-vault reverse-proxy port
|
||||||
import { dockRouter } from './api/dock/dock';
|
import { dockRouter } from './api/dock/dock';
|
||||||
import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations';
|
import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations';
|
||||||
import { queueRouter } from './api/queue/queue';
|
import { queueRouter } from './api/queue/queue';
|
||||||
@@ -175,7 +175,7 @@ export const UNPROTECTED_API_PREFIXES: string[] = [
|
|||||||
'/landing-page-data',
|
'/landing-page-data',
|
||||||
'/themes',
|
'/themes',
|
||||||
'/waitlist',
|
'/waitlist',
|
||||||
// '/vault', // switched off with the Vaultwarden mounts — see above
|
'/vault',
|
||||||
'/sidecar',
|
'/sidecar',
|
||||||
'/agent-handoff',
|
'/agent-handoff',
|
||||||
'/dev-proxy',
|
'/dev-proxy',
|
||||||
@@ -205,7 +205,7 @@ export function buildHonoApp(plugins: MountedPlugin[] = []): Hono<{ Variables: H
|
|||||||
// Vaultwarden reverse-proxy — mounted TOP-LEVEL (not under protectedRouter): the Bitwarden client
|
// Vaultwarden reverse-proxy — mounted TOP-LEVEL (not under protectedRouter): the Bitwarden client
|
||||||
// carries its own bearer token, not a platform session JWT, so userMiddleware would 401 it. The
|
// carries its own bearer token, not a platform session JWT, so userMiddleware would 401 it. The
|
||||||
// notifications WebSocket is upgraded at the serve level (server.tsx).
|
// notifications WebSocket is upgraded at the serve level (server.tsx).
|
||||||
// app.route('/api/vault', vaultRouter); // switched off 2026-08-13 — Vaultwarden is a plugin
|
app.route('/api/vault', vaultRouter);
|
||||||
|
|
||||||
// The same Vaultwarden, with NO Officer authentication, so the Bitwarden browser extension can point at
|
// The same Vaultwarden, with NO Officer authentication, so the Bitwarden browser extension can point at
|
||||||
// this host instead of at a second public hostname for Vaultwarden. Deliberately its own mount rather
|
// this host instead of at a second public hostname for Vaultwarden. Deliberately its own mount rather
|
||||||
@@ -213,7 +213,7 @@ export function buildHonoApp(plugins: MountedPlugin[] = []): Hono<{ Variables: H
|
|||||||
// Authorization header for a server-held token, and blending the two would put an unauthenticated branch
|
// Authorization header for a server-held token, and blending the two would put an unauthenticated branch
|
||||||
// inside the authenticated path. Temporary — see public-router.ts for what replaces it and why leaving it
|
// inside the authenticated path. Temporary — see public-router.ts for what replaces it and why leaving it
|
||||||
// open is not a new exposure.
|
// open is not a new exposure.
|
||||||
// app.route('/vaultwarden', publicVaultRouter); // switched off with the above
|
app.route('/vaultwarden', publicVaultRouter);
|
||||||
|
|
||||||
// …and at the ROOT, so the extension can be pointed at the bare Officer URL with no path at all.
|
// …and at the ROOT, so the extension can be pointed at the bare Officer URL with no path at all.
|
||||||
//
|
//
|
||||||
@@ -221,12 +221,12 @@ export function buildHonoApp(plugins: MountedPlugin[] = []): Hono<{ Variables: H
|
|||||||
// for a Bitwarden client. It is deliberately narrow: the four prefixes below belong to Vaultwarden and
|
// for a Bitwarden client. It is deliberately narrow: the four prefixes below belong to Vaultwarden and
|
||||||
// to nothing else here, and `/api/*` is diverted ONLY when the request carries a Bitwarden client
|
// to nothing else here, and `/api/*` is diverted ONLY when the request carries a Bitwarden client
|
||||||
// header. An ordinary Officer request never matches, so nothing that worked before changes.
|
// header. An ordinary Officer request never matches, so nothing that worked before changes.
|
||||||
// for (const prefix of VAULT_ONLY_PREFIXES) app.route(prefix, publicVaultRouter);
|
for (const prefix of VAULT_ONLY_PREFIXES) app.route(prefix, publicVaultRouter);
|
||||||
//
|
|
||||||
// app.use('/api/*', async (ctx, next) => {
|
app.use('/api/*', async (ctx, next) => {
|
||||||
// if (!isBitwardenClient(ctx.req.raw.headers)) return next();
|
if (!isBitwardenClient(ctx.req.raw.headers)) return next();
|
||||||
// return publicVaultRouter.fetch(ctx.req.raw, ctx.env);
|
return publicVaultRouter.fetch(ctx.req.raw, ctx.env);
|
||||||
// });
|
});
|
||||||
app.get('/api/integrations/google/callback', googleCallbackHandler);
|
app.get('/api/integrations/google/callback', googleCallbackHandler);
|
||||||
|
|
||||||
// Agent-to-agent handoff — mounted TOP-LEVEL for the same reason the vault is: the caller is a Claude
|
// Agent-to-agent handoff — mounted TOP-LEVEL for the same reason the vault is: the caller is a Claude
|
||||||
|
|||||||
Reference in New Issue
Block a user