tags are how the marketplace is browsed rather than scrolled: a list of them on the left, the plugins that carry one on the right. a closed vocabulary (PLUGIN_TAGS in the platform) because free text becomes homelab, home-lab, Home Lab and selfhosted inside a month, and a filter list of synonyms is worse than no filter at all. declared here rather than curated in the catalogue, so a plugin can say what it is and nobody edits a table when one ships. the marketplace caches it on sync, exactly as it already does for label and summary.
66 lines
3.5 KiB
TypeScript
66 lines
3.5 KiB
TypeScript
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'],
|
|
},
|
|
],
|
|
};
|