diff --git a/api/router.ts b/api/router.ts new file mode 100644 index 0000000..e0472e0 --- /dev/null +++ b/api/router.ts @@ -0,0 +1,30 @@ +import { createSidecarProxy } from '@@/sidecar/create-proxy'; +import { mountPrefix } from '@@/plugins/manifest'; +import { manifest } from '../manifest'; + +/** + * `/api/av-club/*` — authenticate, then forward to the officer-av-club sidecar. + * + * No routes of its own and no media knowledge: the whole contract is `../sidecar/index.ts`, and the + * reasoning behind what it will and will not accept is `../docs/SECURITY.md`. + * + * ── The prefix is derived ── + * + * `mountPrefix` returns `/av-club` for this first-party publisher. Writing `/api/av-club` literally + * would be right by coincidence and wrong the moment anybody else publishes this plugin. + * + * ── Why the timeout is long ── + * + * A 4K transcode is minutes of work, and the sidecar already sets `idleTimeout: 255` because Bun's + * ten-second default was killing encodes mid-flight. A short timeout on THIS hop would reintroduce the + * same failure one layer up: the platform hanging up while ffmpeg is still working, which the caller + * sees as a dead request rather than as a limit. Same reasoning as transcribe, whose whisper runs are + * long for the same kind of reason. + */ +const proxy = createSidecarProxy({ + name: 'av-club', + prefix: `/api${mountPrefix({ appName: 'av-club', manifest })}`, + timeoutSeconds: 1800, +}); + +export const router = proxy.router; diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 236c114..0000000 --- a/bun.lock +++ /dev/null @@ -1,88 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "av-club", - "dependencies": { - "sharp": "^0.35.3", - }, - "devDependencies": { - "@types/bun": "latest", - }, - }, - }, - "packages": { - "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], - - "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="], - - "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "os": "freebsd" }, "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.2" }, "os": "linux", "cpu": "ppc64" }, "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.2" }, "os": "linux", "cpu": "none" }, "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.3", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w=="], - - "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "cpu": "none" }, "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="], - - "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], - - "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], - - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - - "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - } -} diff --git a/docs/SHARP.md b/docs/SHARP.md index f1d70f0..e66dcf8 100644 --- a/docs/SHARP.md +++ b/docs/SHARP.md @@ -1,6 +1,6 @@ # sharp — the operation vocabulary -**Generated from `src/sharp/vocabulary.ts` by `scripts/gen-docs.ts`. Do not edit by hand — +**Generated from `sidecar/sharp/vocabulary.ts` by `scripts/gen-docs.ts`. Do not edit by hand — run `bun run docs`.** Every operation is named what sharp names it and takes sharp's own arguments, so diff --git a/manifest.ts b/manifest.ts new file mode 100644 index 0000000..86a2024 --- /dev/null +++ b/manifest.ts @@ -0,0 +1,49 @@ +import type { PluginManifest } from '@@/plugins/manifest'; + +/** + * av-club — sharp, ffmpeg and ffprobe over HTTP, for whoever needs to turn an upload into something a + * browser can serve. + * + * ── No screens, deliberately ── + * + * This is an API and a body of documentation, not a place to click. Its callers are a commander watching + * an upload folder, an agent, or a person with curl. A dock tile would advertise a page that has nothing + * to show — the work happens where the files are, which is not here. + * + * ── One permission, and it is not `personal` ── + * + * Every other permission's paths act on the caller's own data. These act on assets av-club itself issued + * ids for, in its own store, shared by every caller — so it is an ordinary grantable capability rather + * than a per-account one, and an account without it cannot reach the encoder at all. + */ +export const manifest: PluginManifest = { + publisher: 'officerdev', + version: '2026.08.28.01', + platform: '>=1.0.0', + label: 'AV Club', + summary: 'Convert and treat images, video and audio — sharp, ffmpeg and ffprobe behind one API', + tags: ['Development', 'Personal'], + icon: 'Clapperboard', + color: '#8b5cf6', + /* + Declared HERE rather than in a package.json, and that is not a style choice. + + A package.json inside a plugin makes a package boundary, and Bun then stops walking up for the + platform's tsconfig — so `@@/sidecar/connect` stops resolving and the sidecar dies on its first + import. That is exactly how this plugin failed on the day it was extracted: it arrived carrying its + own package.json and bun.lock, and both the router and the sidecar could not find the platform. + + `npm-deps.ts` collects these across every plugin into `plugins/node_modules/`, so atelier's copy of + sharp is the one used here too rather than a second 50 MB of platform binaries. + */ + npmDependencies: { + sharp: '^0.35.3', + }, + permissions: [ + { + key: 'av-club', + label: 'AV Club', + description: 'Convert and inspect media through sharp, ffmpeg and ffprobe', + }, + ], +}; diff --git a/package.json b/package.json deleted file mode 100644 index b48187f..0000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "av-club", - "version": "0.0.1", - "private": true, - "type": "module", - "description": "Turning whatever someone uploads into assets a website can actually serve", - "scripts": { - "dev": "bun --hot src/server.ts", - "start": "bun src/server.ts", - "docs": "bun run ./scripts/gen-docs.ts", - "install:tools": "./scripts/install.sh", - "verify": "./scripts/verify.sh" - }, - "dependencies": { - "sharp": "^0.35.3" - }, - "devDependencies": { - "@types/bun": "latest" - } -} diff --git a/scripts/gen-docs.ts b/scripts/gen-docs.ts index 9d38e58..1abef1a 100644 --- a/scripts/gen-docs.ts +++ b/scripts/gen-docs.ts @@ -1,5 +1,5 @@ #!/usr/bin/env bun -// Generate docs/SHARP.md from src/sharp/vocabulary.ts. +// Generate docs/SHARP.md from sidecar/sharp/vocabulary.ts. // // The vocabulary table is what the validator enforces. Writing the reference by hand would mean two // descriptions of one thing, and the doc would quietly stop being true the first time a bound @@ -7,7 +7,7 @@ // // Run: bun run docs -import { INPUT_OPTIONS, OPERATIONS, OUTPUT_FORMATS, type Param } from "../src/sharp/vocabulary"; +import { INPUT_OPTIONS, OPERATIONS, OUTPUT_FORMATS, type Param } from "../sidecar/sharp/vocabulary"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; @@ -44,7 +44,7 @@ function paramTable(params: Record): string { const lines: string[] = [ "# sharp — the operation vocabulary", "", - "**Generated from `src/sharp/vocabulary.ts` by `scripts/gen-docs.ts`. Do not edit by hand —", + "**Generated from `sidecar/sharp/vocabulary.ts` by `scripts/gen-docs.ts`. Do not edit by hand —", "run `bun run docs`.**", "", "Every operation is named what sharp names it and takes sharp's own arguments, so", diff --git a/src/assets/store.ts b/sidecar/assets/store.ts similarity index 100% rename from src/assets/store.ts rename to sidecar/assets/store.ts diff --git a/src/config.ts b/sidecar/config.ts similarity index 100% rename from src/config.ts rename to sidecar/config.ts diff --git a/src/errors.ts b/sidecar/errors.ts similarity index 100% rename from src/errors.ts rename to sidecar/errors.ts diff --git a/src/ffmpeg/run.ts b/sidecar/ffmpeg/run.ts similarity index 100% rename from src/ffmpeg/run.ts rename to sidecar/ffmpeg/run.ts diff --git a/src/ffmpeg/vocabulary.ts b/sidecar/ffmpeg/vocabulary.ts similarity index 100% rename from src/ffmpeg/vocabulary.ts rename to sidecar/ffmpeg/vocabulary.ts diff --git a/src/server.ts b/sidecar/index.ts similarity index 77% rename from src/server.ts rename to sidecar/index.ts index 3d32ff9..43172e8 100644 --- a/src/server.ts +++ b/sidecar/index.ts @@ -6,6 +6,8 @@ import { ffprobe } from "./probe/ffprobe"; import { runSharp, type SharpRequest } from "./sharp/run"; import { runFfmpeg, type FfmpegRequest } from "./ffmpeg/run"; import sharp from "sharp"; +import { createSidecarConnector } from "@@/sidecar/connect"; +import { API_URL } from "@@/officer-url.mjs"; /** * av-club — ffmpeg, sharp and ffprobe over HTTP. @@ -26,6 +28,17 @@ const TOKEN = requireToken(); * properly is three lines. */ function authorised(request: Request): boolean { + /* + The platform, having already authenticated somebody. + + `createSidecarProxy` forwards `X-Officer-User` after the account gate and the `av-club` permission + gate have both passed, and this server binds LOOPBACK — so that header is only settable by something + already on this machine, which is the same trust boundary caldav's DAV door relies on. Requiring the + bearer token as well would mean shipping av-club's secret to every caller for no gain: the platform + is the thing deciding who may encode, and it has already decided. + */ + if (request.headers.get("x-officer-user")) return true; + const header = request.headers.get("authorization"); if (!header?.startsWith("Bearer ")) return false; @@ -181,12 +194,47 @@ async function route(request: Request): Promise { const server = Bun.serve({ port: PORT, + // Loopback, because `X-Officer-User` above is trusted and a header is only as good as who can set it. + hostname: "127.0.0.1", // Bun's default is 10 seconds, which silently kills a long encode mid-flight. idleTimeout: 255, maxRequestBodySize: MAX_UPLOAD_BYTES, fetch: (request) => route(request).catch(toErrorResponse), }); +/* + Register with the platform, and report the port it can be reached on. + + The port is still `AV_CLUB_PORT` rather than an ephemeral one: this service is also usable directly — + by a script on the box, or by fofos/platform, which is its stated first consumer and is not Officer. + Being a plugin is an additional way in, not a replacement for the one it already had. +*/ +const connection = createSidecarConnector({ + apiUrl: `${API_URL}/api/sidecar/register`, + name: "av-club", + handles: ["av-club"], + onCommand(_cmd, reply) { + reply({ type: "error", id: (_cmd as { id: string }).id, error: "av-club takes no commands; use its HTTP API" }); + }, + onConnected() { + connection.send({ type: "av-club:server", port: PORT }); + console.log(`[av-club] reported port ${PORT} to the API`); + }, +}); + +function shutdown(signal: string) { + console.log(`[av-club] ${signal} received, shutting down...`); + try { + server.stop(true); + } catch { + /* already stopped */ + } + connection.destroy(); + process.exit(0); +} +process.on("SIGTERM", () => shutdown("SIGTERM")); +process.on("SIGINT", () => shutdown("SIGINT")); + console.log(`av-club on ${server.url}`); console.log(` assets ${ASSETS_DIR}`); console.log(` max upload ${(MAX_UPLOAD_BYTES / 1_048_576).toFixed(0)} MB`); diff --git a/src/probe/ffprobe.ts b/sidecar/probe/ffprobe.ts similarity index 100% rename from src/probe/ffprobe.ts rename to sidecar/probe/ffprobe.ts diff --git a/src/sharp/decodable.ts b/sidecar/sharp/decodable.ts similarity index 100% rename from src/sharp/decodable.ts rename to sidecar/sharp/decodable.ts diff --git a/src/sharp/run.ts b/sidecar/sharp/run.ts similarity index 100% rename from src/sharp/run.ts rename to sidecar/sharp/run.ts diff --git a/src/sharp/validate.ts b/sidecar/sharp/validate.ts similarity index 100% rename from src/sharp/validate.ts rename to sidecar/sharp/validate.ts diff --git a/src/sharp/vocabulary.ts b/sidecar/sharp/vocabulary.ts similarity index 100% rename from src/sharp/vocabulary.ts rename to sidecar/sharp/vocabulary.ts diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 129d371..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ESNext"], - "target": "ESNext", - "module": "Preserve", - "moduleDetection": "force", - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true - } -}