`<root>/website` is now checked, and the block says which of three things it is: not there, there but not a checkout, or a checkout with its branch and its head commit. the middle one is the state worth being loud about — cloning will refuse, and the reason is something already sitting in the way. SSH, hardcoded. the repo is private, so https asks for a username with nobody at a terminal to answer, and the SSH remote is already authenticated by the owner's key. hardcoded because this plugin administers exactly one website: a field for it would be a setting nobody changes and one more thing that can point somewhere wrong. when the checkout's origin is NOT that URL, the block says so — shown always it is noise, shown on a mismatch it answers why an update pulled something unexpected. NOTHING HERE MAY HANG. these run inside an HTTP request, so git gets GIT_TERMINAL_PROMPT=0 and ssh gets BatchMode=yes. without them a key that lost access does not fail, it BLOCKS forever waiting for a password at a terminal that does not exist, and the panel spins until somebody restarts the server. there is a five-minute kill on top for everything else that can wedge. update is `--ff-only`, never a merge. a merge commit made by a button in a checkout nobody is watching is a thing somebody finds days later; refusing is recoverable and legible. it also refuses on a dirty tree rather than failing halfway — and dirty is `--untracked-files=no`, because a build artefact or an uncommitted .env is not a reason to make Update permanently unavailable. clone takes FULL history, not --depth 1. this is a working checkout somebody will pull into and read the log of, and shallow turns both into a support question. it sits inside Root location rather than in a block of its own: the path and the thing at the path are one question, and split apart somebody could change the root in one place and read a status about the old one in the other, both looking equally current. hidden while editing, because it describes what is SAVED. git's own output is shown verbatim on failure. "Permission denied (publickey)" says exactly what is wrong; anything written in its place would be a guess at which of a dozen causes it was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.9 KiB
TypeScript
65 lines
2.9 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
/**
|
|
* Fofos — the fofos website's admin, inside Officer.
|
|
*
|
|
* ── What it replaces ──
|
|
*
|
|
* A server-rendered admin on its own subdomain with its own argon2 passwords, its own session cookie
|
|
* and its own `/admin/setup` bootstrap. Every part of that duplicated something Officer already does,
|
|
* and none of it could be shared: giving somebody access to the machine still meant giving them
|
|
* access to the admin, separately, by hand. That is the whole reason this is a plugin.
|
|
*
|
|
* ── It holds no content ──
|
|
*
|
|
* fofos owns its own database and its own media store, and this reads and writes them over HTTP. The
|
|
* only thing in Officer's database is the connection — a URL and a token.
|
|
*
|
|
* That is a deliberate property rather than a convenience. **Deleting this plugin cannot lose a
|
|
* photograph or a line of the client's copy**, with the "delete its data" checkbox ticked or not,
|
|
* because none of it was ever here. The alternative — a `db/schema.ts` carrying fofos's own tables —
|
|
* would have put every one of them under an installer that is allowed to drop them.
|
|
*
|
|
* ── The permission is grantable; the credential is not ──
|
|
*
|
|
* Reading and editing the site is exactly what a member might be brought in to do. The TOKEN is a
|
|
* different matter: it is unrestricted write authority over everything the public sees, so `/_config`
|
|
* is `ownerOnly` — the split that field exists for, and the same shape proxy-manager and Jellyfin use
|
|
* for the same reason.
|
|
*
|
|
* ── No `screens` ──
|
|
*
|
|
* One screen at the mount prefix, labelled `label`. The nine sections are `?section=` on that one
|
|
* screen rather than nine routes: they are panels of one workspace, and `screens` is for a plugin
|
|
* contributing genuinely separate destinations.
|
|
*/
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '2026.08.29.10',
|
|
platform: '>=1.0.0',
|
|
|
|
label: 'Fofos',
|
|
summary: "The fofos website's content — pages, galleries, drops and collabs",
|
|
/** one website's admin, and useless without that website. */
|
|
tags: ['Personal'],
|
|
icon: 'PenLine',
|
|
color: '#c2410c',
|
|
|
|
// Nothing to declare. It talks HTTP to fofos and stores one row; there is no host binary anywhere
|
|
// on the path, and fofos itself is a separate deployment rather than something this installs.
|
|
|
|
permissions: [
|
|
{
|
|
key: 'fofos-admin',
|
|
label: 'Fofos',
|
|
description: 'Edit the fofos website — pages, galleries, drops, collabs and the messages people send',
|
|
|
|
// Where the fofos URL and token are set, and the probe that checks them. Prefix-matched, so
|
|
// `/_config/test` is covered too — deliberately, because that request carries the token in its
|
|
// BODY. A member who could call it would have an oracle pointed at a credential that can
|
|
// rewrite the whole public site.
|
|
ownerOnly: ['/_config'],
|
|
},
|
|
],
|
|
};
|