offscale in the catalogue, and the comments the rename falsified
The plugin half is in gitea.officer.dev/plugins/offscale (95b84ea). This is what
the platform owed it.
- marketplace: offscale now ships assets/icon.png, so the catalogue carries an
iconUrl and a `bare` tile like music's. The lucide `icon` stays as the fallback
for a failed asset publish — a glyph beats the generic box.
- Four schema files cited `headscale_servers` as the shape they copied. The table
is `offscale_servers` as of today, so those sentences named a table that no
longer exists. Same for secret-store.ts's list of purposes.
The crypto purpose moved with it: `decryptSecret('headscale', …)` in the plugin
is now `'offscale'`, and the secret-store row was renamed rather than abandoned,
so the key material is preserved and the change is reversible. Safe only because
offscale_servers held 0 rows — one stored API key and this would have been a
re-encryption, not a rename.
Verified: offscale_servers, uq_offscale_servers_one_active and
uq_offscale_servers_user_url all exist in Postgres and nothing named headscale
does. Dropped the empty table by hand first, because drizzle-kit push treats a
rename as an interactive prompt and would have hung.
tsgo clean. 808 pass / 7 fail — +21 from offscale's first tests, same 7 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import { users } from '../auth/schema';
|
||||
// instance was a shell task on the server rather than something the owner could do from the app.
|
||||
//
|
||||
// It is a REGISTRY, not a single row: the owner adds any number of accounts and switches between them, the
|
||||
// same shape headscale_servers and photos_config use. Uniqueness is on the label rather than the URL,
|
||||
// same shape offscale_servers and photos_config use. Uniqueness is on the label rather than the URL,
|
||||
// because the same instance with a different company is a different account here, and two of those share
|
||||
// a URL AND a token.
|
||||
//
|
||||
|
||||
@@ -6,7 +6,7 @@ import { users } from '../auth/schema';
|
||||
//
|
||||
// A REGISTRY rather than a single row, and here that is not speculative: this machine runs four Jellyfin
|
||||
// instances on separate ports (video, albums, DJ sets, and a second person's), and the owner switches
|
||||
// between them. Same shape as invoiceshelf_accounts and headscale_servers.
|
||||
// between them. Same shape as invoiceshelf_accounts and offscale_servers.
|
||||
//
|
||||
// `accessToken` is encrypted at rest via ../crypto.ts. A Jellyfin access token can read every item, every
|
||||
// watch history and every stream on the instance, so a DB dump must not hand it over. Encryption is confined
|
||||
|
||||
@@ -10,7 +10,7 @@ import { users } from '../auth/schema';
|
||||
// than something the owner could do from the app.
|
||||
//
|
||||
// It is a REGISTRY, not a single row: the owner adds any number of accounts and switches between them, the
|
||||
// same shape headscale_servers uses. Two accounts on the same instance is the normal case (one key per
|
||||
// same shape offscale_servers uses. Two accounts on the same instance is the normal case (one key per
|
||||
// Immich user), which is why the uniqueness below is on the label and not on the URL.
|
||||
//
|
||||
// `api_key` is encrypted at rest via ../crypto.ts. An Immich key can read and delete the entire library, so a
|
||||
|
||||
@@ -117,7 +117,7 @@ function readActive(purpose: string): string | null {
|
||||
/**
|
||||
* The active key for a purpose, created on first use.
|
||||
*
|
||||
* Purposes are plain strings and belong to whoever owns the data they protect: `jwt`, `headscale`,
|
||||
* Purposes are plain strings and belong to whoever owns the data they protect: `jwt`, `offscale`,
|
||||
* `wallet`, `photos`, `jellyfin`, `invoiceshelf`, `vault`, `service-connections`. A plugin asks for its
|
||||
* own and never another's — same rule `service_connections` rows already follow.
|
||||
*
|
||||
|
||||
@@ -9,7 +9,7 @@ import { users } from '../auth/schema';
|
||||
// TWO COLUMNS HOLD SPENDING AUTHORITY AND THEY ARE PROTECTED DIFFERENTLY. This asymmetry is deliberate:
|
||||
//
|
||||
// `config` — node credentials (macaroon, rune, LNDHub password, NWC URI). Encrypted at rest with
|
||||
// the 'wallet' store key via ../crypto.ts, the way headscale_servers.api_key uses its
|
||||
// the 'wallet' store key via ../crypto.ts, the way offscale_servers.api_key uses its
|
||||
// own. It CANNOT be
|
||||
// passphrase-protected: background balance polling needs it without the owner present.
|
||||
//
|
||||
@@ -58,7 +58,7 @@ export const walletWallets = pgTable(
|
||||
(t) => [
|
||||
uniqueIndex('uq_wallet_wallets_user_name').on(t.userId, t.name),
|
||||
// At most one active wallet per owner, enforced by the DB rather than convention — a partial unique
|
||||
// index over active rows only, mirroring uq_headscale_servers_one_active.
|
||||
// index over active rows only, mirroring uq_offscale_servers_one_active.
|
||||
uniqueIndex('uq_wallet_wallets_one_active')
|
||||
.on(t.userId)
|
||||
.where(sql`${t.isActive}`),
|
||||
|
||||
@@ -124,6 +124,10 @@ const CATALOGUE: CatalogueEntry[] = [
|
||||
label: 'Offscale',
|
||||
summary: 'Your tailnet — machines, users, pre-auth keys, access policy and device invites',
|
||||
color: '#818cf8',
|
||||
// Ships artwork as of 2026-08-15, so the tile is `bare` like music's. `icon` stays as the fallback
|
||||
// for the case the assets fail to publish — the glyph is strictly better than the generic box.
|
||||
tile: 'bare',
|
||||
iconUrl: 'https://gitea.officer.dev/plugins/offscale/raw/branch/main/assets/icon.png',
|
||||
icon: 'Network',
|
||||
source: 'https://gitea.officer.dev/plugins/offscale.git',
|
||||
permissions: [
|
||||
|
||||
Reference in New Issue
Block a user