a chat under the lists, and the skill that makes it worth having
the layout gains a third node across the bottom, a third of the height. a conversation reads badly in a narrow column and the two lists do not, so the chat gets width rather than a column of its own. it is officerdev/chat, the platform's own panel, not a simplified one of ours. seedAppRegistry merges the platform's panels and every plugin's into one registry, so naming it is all it takes — and a second chat maintained here would be the one that falls behind. the skill is deliberately NOT scoped to these routes. that was considered and dropped once it was clear what a skill costs: compileSkills emits a name, a description and a path, and the body is only read if the agent decides it is relevant. two lines in every conversation. scoping would have meant a platform change to buy back nothing. (affordances are the opposite — loaded in full, every conversation — so this plugin ships none. by docs/affordances.md's own test the 404 quirk does not qualify: it CAN be fixed instead, and the platform route already fixes it.) what the skill carries: the routes, that meta.nginx_online is the field for "is it down", that renewal is at 30 days so under 14 is a real failure worth raising unprompted and nothing else will raise it, that the 404 has already been interpreted before it reaches you, and that disabling a host takes a domain off the internet so it is confirmed and never inferred. plus what is not built, so it does not get offered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
name: Proxy Manager
|
||||||
|
description: Use when asked which domains this machine serves, where a hostname forwards to, whether a site is up, or when a TLS certificate expires — and before taking a proxy host in or out of service. Carries the routes, the one 404 that means something else, and what this surface deliberately cannot do.
|
||||||
|
version: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
Officer proxies through **Nginx Proxy Manager**, and this plugin is how you reach it. Everything below is
|
||||||
|
`/api/proxy-manager/...`, authenticated as the signed-in account like any other platform route.
|
||||||
|
|
||||||
|
**Go through the platform, never at NPM directly.** The admin credential lives encrypted on the server and
|
||||||
|
is never handed out — not to you, not to the owner. A route here already holds it, already translates NPM's
|
||||||
|
odder answers, and is the only path that works for an account that is not the owner.
|
||||||
|
|
||||||
|
## What is running, and where it goes
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/proxy-manager/hosts proxy hosts, sorted by first domain
|
||||||
|
GET /api/proxy-manager/hosts/:id one host, with its certificate and access list expanded
|
||||||
|
GET /api/proxy-manager/redirections redirection hosts
|
||||||
|
GET /api/proxy-manager/streams TCP/UDP streams
|
||||||
|
GET /api/proxy-manager/dead-hosts domains served deliberately as nothing (NPM calls them 404 hosts)
|
||||||
|
GET /api/proxy-manager/access-lists with their items and clients
|
||||||
|
GET /api/proxy-manager/status is a connection configured, and does it answer
|
||||||
|
```
|
||||||
|
|
||||||
|
A host carries `domain_names`, `forward_scheme`, `forward_host`, `forward_port`, `enabled`, `ssl_forced`,
|
||||||
|
`certificate_id` (0 means none), `access_list_id`, and `meta.nginx_online` — which is `false` when nginx
|
||||||
|
itself reports the site down, and is the field to check when somebody says a site is broken.
|
||||||
|
|
||||||
|
**This is the honest answer to "what domains does this machine serve".** Better than any document: a
|
||||||
|
hand-maintained list in a repository is right on the day it is written, and this is what nginx is actually
|
||||||
|
configured with right now.
|
||||||
|
|
||||||
|
## Certificates
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/proxy-manager/certificates
|
||||||
|
```
|
||||||
|
|
||||||
|
Sorted by expiry rather than name, with `daysUntilExpiry` computed server-side. That ordering is the point:
|
||||||
|
a list by name is a reference, a list by expiry answers the only question anyone has.
|
||||||
|
|
||||||
|
**Read the numbers the way Let's Encrypt behaves.** Renewal runs at 30 days, so under 30 is normal and not
|
||||||
|
worth mentioning. Under 14 means renewal has actually failed and the domain goes dark at zero — that is
|
||||||
|
worth raising unprompted. `expires_on` is null for some uploaded certificates, and `daysUntilExpiry` is
|
||||||
|
then null too; that is unknown, not urgent.
|
||||||
|
|
||||||
|
**Nothing warns anybody.** No notification exists yet. If you notice a certificate inside 14 days, say so
|
||||||
|
in the conversation — there is no other mechanism that will.
|
||||||
|
|
||||||
|
## The one write
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/proxy-manager/hosts/:id/enable
|
||||||
|
POST /api/proxy-manager/hosts/:id/disable
|
||||||
|
```
|
||||||
|
|
||||||
|
Taking a host in or out of service. **It is the only write this surface has**, deliberately: it is
|
||||||
|
reversible in one click, and creating, editing and deleting hosts are not. Do not look for a create route —
|
||||||
|
there isn't one, and its absence is a decision rather than a gap.
|
||||||
|
|
||||||
|
**Disabling a host takes a domain off the internet.** Confirm before you do it, name the domains, and do not
|
||||||
|
infer it from something adjacent. "Is `foo.example.com` up?" is a question about `meta.nginx_online`, not an
|
||||||
|
instruction to change anything.
|
||||||
|
|
||||||
|
For anything this cannot do — a new proxy host, a certificate to issue, an access list to edit — say so and
|
||||||
|
point at NPM's own UI. Do not attempt it another way.
|
||||||
|
|
||||||
|
## The 404 that is not a 404
|
||||||
|
|
||||||
|
**NPM answers an unauthenticated read with 404, not 401.** Its router only registers a resource once the
|
||||||
|
request carries a token, so "not signed in" and "no such endpoint" are the same response.
|
||||||
|
|
||||||
|
The plugin already knows this: it re-mints its token and retries once, and if a 404 survives that it reports
|
||||||
|
the credential rather than the route. So a failure reaching you through these routes has already been
|
||||||
|
interpreted — **read the message, do not re-diagnose it as a missing endpoint.**
|
||||||
|
|
||||||
|
It is not a rule about the whole API, only about the routes that need it. `GET /nginx/certificates/dns-providers`,
|
||||||
|
for instance, answers a proper 403.
|
||||||
|
|
||||||
|
## Two states that are not failures
|
||||||
|
|
||||||
|
- **409 with `unconfigured`** — no NPM connection is stored. Nothing is broken; the owner has not connected
|
||||||
|
one. Only the owner can, at `/proxy-manager?config=1`.
|
||||||
|
- **403 on anything under `/_config`** — the connection settings are owner-only, including the test. It is
|
||||||
|
an NPM admin login and NPM has no read-only role, so anyone holding it can redirect any hostname this
|
||||||
|
machine serves. Reading the hosts and certificates is grantable; the credential is not.
|
||||||
|
|
||||||
|
## What is coming, so you do not promise it
|
||||||
|
|
||||||
|
Handing DNS and certificates over properly — a domain registry, provider API keys in the secret store, and
|
||||||
|
one request that turns *"expose sub.example.com on the private network over HTTPS"* into a DNS record, a
|
||||||
|
certificate and a proxy host. **None of it is built.** `plugins/proxy-manager/README.md` has the design.
|
||||||
|
Until it exists, do not offer to create a host or issue a certificate.
|
||||||
+31
-5
@@ -1,13 +1,39 @@
|
|||||||
import type { LayoutNode } from 'officerdev';
|
import type { LayoutNode } from 'officerdev';
|
||||||
|
|
||||||
// Hosts wide, certificates beside it. Certificates is a glance rather than a workspace — you look at it
|
// Hosts wide, certificates beside it, a chat across the bottom.
|
||||||
// when something is red — so it gets the narrower half.
|
//
|
||||||
|
// ── Why the chat panel is the platform's and not one of ours ──
|
||||||
|
//
|
||||||
|
// `officerdev/chat` is a panel app in the shared registry — `seedAppRegistry` merges the platform's panels
|
||||||
|
// and every plugin's into ONE registry, so naming it here is all it takes. Shipping a second, simpler chat
|
||||||
|
// from this plugin would mean maintaining a second chat, and the first one to fall behind would be ours.
|
||||||
|
//
|
||||||
|
// It gets a third of the height rather than a column, because a conversation reads badly in a narrow
|
||||||
|
// column and the two lists do not: they are scannable at any width, and a chat is not.
|
||||||
|
//
|
||||||
|
// ── What makes the chat useful HERE ──
|
||||||
|
//
|
||||||
|
// Nothing in this layout. The agent gets the `Proxy Manager` skill the same way it gets every other one —
|
||||||
|
// from this plugin's `skills/` directory, offered in every conversation on the machine, costing two lines
|
||||||
|
// until something actually needs it. A chat in this panel is an ordinary chat that happens to be sitting
|
||||||
|
// underneath the thing it is being asked about.
|
||||||
export const defaultLayout: LayoutNode = {
|
export const defaultLayout: LayoutNode = {
|
||||||
type: 'group',
|
type: 'group',
|
||||||
id: 'proxy-manager-root',
|
id: 'proxy-manager-root',
|
||||||
direction: 'horizontal',
|
direction: 'vertical',
|
||||||
children: [
|
children: [
|
||||||
{ node: { type: 'panel', id: 'proxy-manager-hosts', appType: 'proxy-manager-hosts' }, size: 62 },
|
{
|
||||||
{ node: { type: 'panel', id: 'proxy-manager-certificates', appType: 'proxy-manager-certificates' }, size: 38 },
|
node: {
|
||||||
|
type: 'group',
|
||||||
|
id: 'proxy-manager-lists',
|
||||||
|
direction: 'horizontal',
|
||||||
|
children: [
|
||||||
|
{ node: { type: 'panel', id: 'proxy-manager-hosts', appType: 'proxy-manager-hosts' }, size: 62 },
|
||||||
|
{ node: { type: 'panel', id: 'proxy-manager-certificates', appType: 'proxy-manager-certificates' }, size: 38 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
size: 66,
|
||||||
|
},
|
||||||
|
{ node: { type: 'panel', id: 'proxy-manager-chat', appType: 'officerdev/chat' }, size: 34 },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user