import type { PluginManifest } from '@@/plugins/manifest'; // Offscale — Headscale, plus the Companion that ships beside it. // // Not a rename of Headscale and not a fork: the server underneath is stock, and the Companion adds what // Headscale itself does not do — the invite flow being the first of them. The distinct name marks a // distinct product rather than a badge on someone else's. // // The first real plugin, extracted from the platform on 2026-08-15. Everything it needs is here: // // api/router.ts a thin auth-gated proxy — no Headscale knowledge, and it must never grow any // sidecar/ the whole Headscale contract, holding the admin API keys // db/ offscale_servers, and the only table this plugin owns // web/ panels and a layout; the shell renders the Workspace export const manifest: PluginManifest = { publisher: 'officerdev', version: '1.0.0', platform: '>=1.0.0', label: 'Offscale', summary: 'Your tailnet — machines, users, pre-auth keys, access policy and device invites', /** the tailnet: who reaches what. */ tags: ['Server Management', 'Homelab', 'Security'], icon: 'Network', color: '#818cf8', // One permission gating the whole surface, grantable per role at read or write like every other. // // `[open]` What a member's grant MEANS here is this plugin's own job and is not finished. The queries // still scope by the caller (`listOffscaleServers(userId)`), so a granted member would see their own // empty server list rather than the owner's, and could register a Headscale of their own. The model in // ./PLUGIN.md is one shared resource: read sees what the owner sees, write can change it. // That is a change inside these queries, not a flag on the manifest. // // Worth knowing while it is unfinished: the stored credential is a Headscale ADMIN api key that can // delete every node on a tailnet, and there is no read-only version of it — so `write` here is close to // full control of the tailnet, which is the owner's decision to make deliberately. // The Console shells into a machine by spawning `ssh` (sidecar/ssh.ts), and so does the reachability // probe behind /ssh-test. This manifest said "Offscale needed nothing" until 2026-08-16, which meant a // host without an ssh client passed the install checks and then failed at runtime with a spawn error — // the one failure mode `osDependencies` exists to convert into a refusal at install time. PLUGIN.md had // listed it as a host dependency all along; the manifest was the file that was wrong. // // Present on effectively every Linux install, which is exactly why it was missed: the declaration costs // nothing on the machines that have it and is the whole difference on the machines that do not. osDependencies: [ { binary: 'ssh', reason: 'Opens the Console shell to a machine, and probes reachability before you try.', packages: { apt: 'openssh-client', pacman: 'openssh', dnf: 'openssh-clients', brew: 'openssh' }, }, ], permissions: [ { key: 'offscale', label: 'Offscale', description: 'The tailnet: machines, routes, keys and ACLs', // Two POSTs that are really reads — a reachability probe and a policy DRAFT that never saves. // Without declaring them a read-level account meets a broken feature where a withheld permission // should be. Inert while ownerOnly, and correct the moment that changes. readOnlyWrites: ['/ssh-test', '/policy/assist'], }, ], };