feat(deploy): gör 0pt körbar utanför Vercel — standalone + bevisad Coolify-image #40

Merged
admin merged 1 commits from feat/coolify-standalone-deployable into main 2026-09-08 20:48:45 +00:00
Owner

Varför

0pt.app svarar i dag HTTP 402 DEPLOYMENT_DISABLED — Vercels betalningsspärr — och är den enda egendomen som både är önskad och genuint trasig utan någon självhostad ersättare igång. Repot hade en Dockerfile, men appen gick inte att köra meningsfullt utanför Vercel: next.config.mjs satte inte output: "standalone" och satte images.unoptimized.

Referensimplementation är sax3l/siax-io, som redan kör självhostat på flottan. Dess Dockerfile återanvänds i form i stället för att uppfinna ett nytt mönster.

⚠️ DEN HÄR PR:EN ÄR AVSIKTLIGT INTE MERGAD — läs "Blockerare" längst ner innan du mergar.

Vad som faktiskt blockerade självhostning

Uppdraget bad mig anta att det var två inställningar. Det var fem:

# Blockerare Åtgärd
1 Ingen output: "standalone" Satt. Runtime kör node server.js.
2 images.unoptimized: true Borttaget — se sharp-avsnittet, det räckte inte att ta bort raden.
3 next build spårar inte sharp in i standalone-utdatan Eget symlänkfritt npm-steg i Dockerfile.
4 --ignore-workspace i Dockerfile byggde med ett annat kommando än CI Borttaget; motiveringen var inaktuell sedan P1-21.
5 NODE_ENV=production vid byggtid (Coolify injicerar det) fick pnpm att hoppa över devDependencies som next build kräver --prod=false.

Sharp: varför det inte var en enradsändring

Att ta bort images.unoptimized utan mer hade gett en container som svarar fel på varje /_next/image-begäran. Nexts bildoptimerare laddar sharp med en runtime-require, inte ett statiskt import.

  • VERIFIERAT: ett bygge med enbart output: "standalone" gav ett .next/standalone/node_modules som innehöll bara next/, react/, react-dom/.
  • VERIFIERAT (kontrolltest, inte antagande): den uppenbara lösningen outputFileTracingIncludes med "./node_modules/@img/**/*" får Turbopack att panika på pnpms katalogsymlänkar — reading file ".../@img/sharp-libvips-linux-x64" — Is a directory (os error 21) — och bygget faller med exit 1.

Därför ett eget FROM node:22-alpine AS sharp-steg: npm lägger ett platt, symlänkfritt träd som runtime-steget kan kopiera rakt av. Versionen läses ur pnpm-lock.yaml (0.35.4) så den aldrig glider, och steget faller hårt om utläsningen ger tomt i stället för att tyst skeppa en image vars bildoptimering är trasig.

Vercel-beroenden — fullständig genomgång

Beroende Status Ersätts av
vercel.json Finns inte (VERIFIED: find) — inga rewrites/redirects/crons/function-config att flytta
@vercel/*-paket Noll i package.json (VERIFIED) @vercel/analytics togs redan bort tidigare
VERCEL_*-env-läsningar Noll i all spårad kod (VERIFIED: git grep) —
Vercel KV / Blob Används inte —
Vercel Postgres Mjukt beroende: lib/db.ts läser DATABASE_URL → POSTGRES_URL → POSTGRES_PRISMA_URL Sätt DATABASE_URL. De två Vercel-namnen är ofarliga fallbacks, de behöver inte tas bort.
Edge runtime Noll export const runtime = "edge" (VERIFIED) Middleware kör självhostat — bevisat med 401 nedan
Bildoptimering Var kringgången med unoptimized Självhostad next/image + sharp, bevisad nedan
Objektlagring R2_* = Cloudflare R2, inte Vercel Oförändrat. Kan flyttas till ST0RE separat.

Ingenting av detta krävde en ersättningstjänst. Migrationen är i praktiken kostnadsfri på beroendesidan.

Miljövariabler för Coolify (endast namn, aldrig värden)

Måste sättas för en fungerande deploy:

  • DATABASE_URL — Postgres. Utan den svarar /api/ready 503 (avsiktligt).
  • AUTH_SECRET — sessionssignering, lång slumpsträng.
  • INTERNAL_TOKEN — service-till-service, skickas som X-Internal-Token.
  • NEXT_PUBLIC_APP_URL — publik bas-URL, används för absoluta länkar/callbacks.
  • CL0UD_URL, N0D_URL, AUD0_URL — under NODE_ENV=production är en osatt URL ett hårt bootfel (DEPENDENCY_UNAVAILABLE); produktion faller aldrig tyst tillbaka på mockar.

Sätts av Dockerfilen — sätt dem INTE i Coolify: NODE_ENV, PORT, HOSTNAME, NEXT_TELEMETRY_DISABLED.

Valfria (funktionen är avstängd/501 tills de sätts):

  • Auth/IdP: ZITADEL_ISSUER (default https://id-customers.siax.io), ZITADEL_TENANT_CLAIM, AUTH_URL
  • Tjänstetokens: CL0UD_SERVICE_TOKEN, N0D_SERVICE_TOKEN, AUD0_SERVICE_TOKEN, ST0RE_SERVICE_TOKEN
  • Plattform: ST0RE_URL, INF0_URL, ENGINE_URL, REDIS_URL
  • Betalning: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
  • Data-OS: OPENSEARCH_URL, CLICKHOUSE_URL, CLICKHOUSE_AUTH, OPENMETADATA_URL, OPENMETADATA_TOKEN, FEAST_SERVER_URL
  • Lagring/OCR/övrigt: R2_ENDPOINT, R2_BUCKET, R2_ACCESS_KEY_ID, R2_ACCOUNT_ID, R2_SECRET_ACCESS_KEY, DOC_INTELLIGENCE_ENDPOINT, DOC_INTELLIGENCE_KEY, DOC_INTELLIGENCE_API_VERSION, GRANT_ENC_KEY, ODOO_BASE_URL, RESEND_API_KEY

Sätt ALDRIG i produktion: AUTH_ENFORCE=false (appen vägrar boota — fail-closed), DEV_LOGIN_KEY, OPT_LOCAL_BLOB_DIR.

Probe-inställning i Coolify: peka health/liveness på /api/health (200 så länge processen lever). Peka inte en readiness-probe dit — använd /api/ready, som faller stängt (503) tills Postgres är nåbar. Att blanda ihop dem får Coolify att döda en container som fungerar precis som avsett.

Bevis — det byggdes och kördes faktiskt

Uppdraget var uttryckligt: "En Dockerfile som aldrig byggts är inte en leverans." Alla siffror nedan är körda kommandon.

Bygge utan en enda Vercel-variabel (env -i, helt tom miljö):

pnpm install --frozen-lockfile   -> exit 0
NODE_ENV=production next build   -> exit 0
docker build --no-cache          -> exit 0
  "sharp pinnad fran pnpm-lock.yaml: 0.35.4"
  "sharp laddar OK i byggsteget"

Containern kördes och svarade:

docker run -d -p 13012:3000      -> klar efter 1s
GET /api/health   -> HTTP 200  {"ok":true,"service":"0pt-web",...}
GET /api/ready    -> HTTP 503  (utan DATABASE_URL — faller stängt, korrekt)
GET /             -> HTTP 200  86 001 bytes
GET /api/0pt/sources utan Bearer -> HTTP 401 OPT_UNAUTHENTICATED
docker inspect .State.Health.Status -> "healthy"

Bildoptimeringen fungerar — det som unoptimized dolde:

GET /_next/image?url=/images/mono-3.png&w=1920&q=75
  Accept: avif -> HTTP 200  image/avif   51 161 bytes
  Accept: webp -> HTTP 200  image/webp   86 390 bytes
  Källa:          1 488 706 bytes PNG   => 29x mindre
file(1): "ISO Media, AVIF Image" / "RIFF ... Web/P image, VP8, 1920x1280"
node -e require('sharp') i imagen -> "sharp runtime OK, libvips 8.18.6"

Imagens form:

uid=1000(node) icke-root · npm/npx strippat (bara corepack kvar)
node v22.23.2 (matchar CI) · musl-binärer @img/sharp-linuxmusl-x64
X-Powered-By frånvarande
Storlek: 1,33 GB (före) -> 423 MB (efter)

Grindar

Grind Utfall Not
typecheck (blockerande) exit 0
test (blockerande) exit 0 930 passerade, 174 hoppade
build (blockerande) exit 0
lint (rådgivande, continue-on-error) exit 1 Identiskt på ren main: 17 problem (12 fel, 5 varningar). Pre-existerande.
prettier exit 1 Identiskt på ren main: 429 filer. Är inget CI-steg.

Ingen grind, assertion eller lintregel har försvagats, hoppats över eller lossats. Inga skyddade sökvägar rörda (.gitea/workflows/** och .gitignore är orörda).

Blockerare: en merge här ÄR en produktionsdeploy

.gitea/workflows/cd.yml kör vid varje push till main:

curl -X POST https://cloud.siax.io/api/v1/deploy?uuid=${COOLIFY_APP_ID}

VERIFIERAT via Gitea-API: vars/COOLIFY_APP_ID innehåller en riktig 24-teckens UUID (inte längre platshållaren TODO-no-coolify-app-provisioned-yet) och secret COOLIFY_TOKEN finns sedan 2026-08-31. Båda vaktvillkoren passerar.

Att merga den här PR:en avfyrar alltså en skarp ombyggnad och omdeploy mot cloud.siax.io. Mitt uppdrag förbjuder uttryckligen att deploya eller trigga Coolify, så jag har inte mergat. Det beslutet är ditt, Simon.

Sidofynd, utanför scope här: CHANGELOG.md på main påstår att säkerhetsheaders (CSP/HSTS/X-Frame-Options) är gjorda, men origin/main innehåller noll sådan kod — implementationen ligger på den omergade grenen ci/security-headers-and-scanning (8d2ff73). Bakom Traefik utan Vercel framför får appen alltså inga sådana headers förrän den grenen mergas.

🤖 Generated with Claude Code

## Varför `0pt.app` svarar i dag **HTTP 402 DEPLOYMENT_DISABLED** — Vercels betalningsspärr — och är den enda egendomen som både är önskad och genuint trasig utan någon självhostad ersättare igång. Repot hade en Dockerfile, men appen gick inte att köra meningsfullt utanför Vercel: `next.config.mjs` satte inte `output: "standalone"` och satte `images.unoptimized`. Referensimplementation är **`sax3l/siax-io`**, som redan kör självhostat på flottan. Dess Dockerfile återanvänds i form i stället för att uppfinna ett nytt mönster. > ⚠️ **DEN HÄR PR:EN ÄR AVSIKTLIGT INTE MERGAD — läs "Blockerare" längst ner innan du mergar.** ## Vad som faktiskt blockerade självhostning Uppdraget bad mig anta att det var två inställningar. Det var fem: | # | Blockerare | Åtgärd | |---|---|---| | 1 | Ingen `output: "standalone"` | Satt. Runtime kör `node server.js`. | | 2 | `images.unoptimized: true` | Borttaget — se sharp-avsnittet, det räckte **inte** att ta bort raden. | | 3 | `next build` spårar **inte** sharp in i standalone-utdatan | Eget symlänkfritt npm-steg i Dockerfile. | | 4 | `--ignore-workspace` i Dockerfile byggde med ett **annat** kommando än CI | Borttaget; motiveringen var inaktuell sedan P1-21. | | 5 | `NODE_ENV=production` vid byggtid (Coolify injicerar det) fick pnpm att hoppa över devDependencies som `next build` kräver | `--prod=false`. | ## Sharp: varför det inte var en enradsändring Att ta bort `images.unoptimized` utan mer hade gett en container som svarar **fel på varje `/_next/image`-begäran**. Nexts bildoptimerare laddar `sharp` med en runtime-`require`, inte ett statiskt import. - **VERIFIERAT:** ett bygge med enbart `output: "standalone"` gav ett `.next/standalone/node_modules` som innehöll **bara** `next/`, `react/`, `react-dom/`. - **VERIFIERAT (kontrolltest, inte antagande):** den uppenbara lösningen `outputFileTracingIncludes` med `"./node_modules/@img/**/*"` får **Turbopack att panika** på pnpms katalogsymlänkar — `reading file ".../@img/sharp-libvips-linux-x64" — Is a directory (os error 21)` — och bygget faller med **exit 1**. Därför ett eget `FROM node:22-alpine AS sharp`-steg: npm lägger ett platt, symlänkfritt träd som runtime-steget kan kopiera rakt av. Versionen läses ur `pnpm-lock.yaml` (0.35.4) så den aldrig glider, och steget **faller hårt** om utläsningen ger tomt i stället för att tyst skeppa en image vars bildoptimering är trasig. ## Vercel-beroenden — fullständig genomgång | Beroende | Status | Ersätts av | |---|---|---| | `vercel.json` | **Finns inte** (VERIFIED: `find`) | — inga rewrites/redirects/crons/function-config att flytta | | `@vercel/*`-paket | **Noll** i `package.json` (VERIFIED) | `@vercel/analytics` togs redan bort tidigare | | `VERCEL_*`-env-läsningar | **Noll** i all spårad kod (VERIFIED: `git grep`) | — | | Vercel KV / Blob | Används inte | — | | Vercel Postgres | **Mjukt beroende**: `lib/db.ts` läser `DATABASE_URL` → `POSTGRES_URL` → `POSTGRES_PRISMA_URL` | Sätt `DATABASE_URL`. De två Vercel-namnen är ofarliga fallbacks, de behöver inte tas bort. | | Edge runtime | **Noll** `export const runtime = "edge"` (VERIFIED) | Middleware kör självhostat — bevisat med 401 nedan | | Bildoptimering | Var kringgången med `unoptimized` | Självhostad `next/image` + sharp, bevisad nedan | | Objektlagring | `R2_*` = Cloudflare R2, **inte** Vercel | Oförändrat. Kan flyttas till ST0RE separat. | Ingenting av detta krävde en ersättningstjänst. Migrationen är i praktiken kostnadsfri på beroendesidan. ## Miljövariabler för Coolify (endast namn, aldrig värden) **Måste sättas för en fungerande deploy:** - `DATABASE_URL` — Postgres. Utan den svarar `/api/ready` 503 (avsiktligt). - `AUTH_SECRET` — sessionssignering, lång slumpsträng. - `INTERNAL_TOKEN` — service-till-service, skickas som `X-Internal-Token`. - `NEXT_PUBLIC_APP_URL` — publik bas-URL, används för absoluta länkar/callbacks. - `CL0UD_URL`, `N0D_URL`, `AUD0_URL` — under `NODE_ENV=production` är en **osatt** URL ett hårt bootfel (`DEPENDENCY_UNAVAILABLE`); produktion faller aldrig tyst tillbaka på mockar. **Sätts av Dockerfilen — sätt dem INTE i Coolify:** `NODE_ENV`, `PORT`, `HOSTNAME`, `NEXT_TELEMETRY_DISABLED`. **Valfria (funktionen är avstängd/501 tills de sätts):** - Auth/IdP: `ZITADEL_ISSUER` (default `https://id-customers.siax.io`), `ZITADEL_TENANT_CLAIM`, `AUTH_URL` - Tjänstetokens: `CL0UD_SERVICE_TOKEN`, `N0D_SERVICE_TOKEN`, `AUD0_SERVICE_TOKEN`, `ST0RE_SERVICE_TOKEN` - Plattform: `ST0RE_URL`, `INF0_URL`, `ENGINE_URL`, `REDIS_URL` - Betalning: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` - Data-OS: `OPENSEARCH_URL`, `CLICKHOUSE_URL`, `CLICKHOUSE_AUTH`, `OPENMETADATA_URL`, `OPENMETADATA_TOKEN`, `FEAST_SERVER_URL` - Lagring/OCR/övrigt: `R2_ENDPOINT`, `R2_BUCKET`, `R2_ACCESS_KEY_ID`, `R2_ACCOUNT_ID`, `R2_SECRET_ACCESS_KEY`, `DOC_INTELLIGENCE_ENDPOINT`, `DOC_INTELLIGENCE_KEY`, `DOC_INTELLIGENCE_API_VERSION`, `GRANT_ENC_KEY`, `ODOO_BASE_URL`, `RESEND_API_KEY` **Sätt ALDRIG i produktion:** `AUTH_ENFORCE=false` (appen vägrar boota — fail-closed), `DEV_LOGIN_KEY`, `OPT_LOCAL_BLOB_DIR`. **Probe-inställning i Coolify:** peka health/liveness på `/api/health` (200 så länge processen lever). Peka **inte** en readiness-probe dit — använd `/api/ready`, som faller stängt (503) tills Postgres är nåbar. Att blanda ihop dem får Coolify att döda en container som fungerar precis som avsett. ## Bevis — det byggdes och kördes faktiskt Uppdraget var uttryckligt: *"En Dockerfile som aldrig byggts är inte en leverans."* Alla siffror nedan är körda kommandon. **Bygge utan en enda Vercel-variabel** (`env -i`, helt tom miljö): ``` pnpm install --frozen-lockfile -> exit 0 NODE_ENV=production next build -> exit 0 docker build --no-cache -> exit 0 "sharp pinnad fran pnpm-lock.yaml: 0.35.4" "sharp laddar OK i byggsteget" ``` **Containern kördes och svarade:** ``` docker run -d -p 13012:3000 -> klar efter 1s GET /api/health -> HTTP 200 {"ok":true,"service":"0pt-web",...} GET /api/ready -> HTTP 503 (utan DATABASE_URL — faller stängt, korrekt) GET / -> HTTP 200 86 001 bytes GET /api/0pt/sources utan Bearer -> HTTP 401 OPT_UNAUTHENTICATED docker inspect .State.Health.Status -> "healthy" ``` **Bildoptimeringen fungerar — det som `unoptimized` dolde:** ``` GET /_next/image?url=/images/mono-3.png&w=1920&q=75 Accept: avif -> HTTP 200 image/avif 51 161 bytes Accept: webp -> HTTP 200 image/webp 86 390 bytes Källa: 1 488 706 bytes PNG => 29x mindre file(1): "ISO Media, AVIF Image" / "RIFF ... Web/P image, VP8, 1920x1280" node -e require('sharp') i imagen -> "sharp runtime OK, libvips 8.18.6" ``` **Imagens form:** ``` uid=1000(node) icke-root · npm/npx strippat (bara corepack kvar) node v22.23.2 (matchar CI) · musl-binärer @img/sharp-linuxmusl-x64 X-Powered-By frånvarande Storlek: 1,33 GB (före) -> 423 MB (efter) ``` ## Grindar | Grind | Utfall | Not | |---|---|---| | `typecheck` (blockerande) | **exit 0** | | | `test` (blockerande) | **exit 0** | 930 passerade, 174 hoppade | | `build` (blockerande) | **exit 0** | | | `lint` (rådgivande, `continue-on-error`) | exit 1 | **Identiskt på ren main**: 17 problem (12 fel, 5 varningar). Pre-existerande. | | prettier | exit 1 | **Identiskt på ren main**: 429 filer. Är inget CI-steg. | Ingen grind, assertion eller lintregel har försvagats, hoppats över eller lossats. Inga skyddade sökvägar rörda (`.gitea/workflows/**` och `.gitignore` är orörda). ## Blockerare: en merge här ÄR en produktionsdeploy `.gitea/workflows/cd.yml` kör vid varje push till `main`: ``` curl -X POST https://cloud.siax.io/api/v1/deploy?uuid=${COOLIFY_APP_ID} ``` **VERIFIERAT via Gitea-API:** `vars/COOLIFY_APP_ID` innehåller en riktig 24-teckens UUID (inte längre platshållaren `TODO-no-coolify-app-provisioned-yet`) och secret `COOLIFY_TOKEN` finns sedan 2026-08-31. **Båda vaktvillkoren passerar.** Att merga den här PR:en avfyrar alltså en skarp ombyggnad och omdeploy mot `cloud.siax.io`. Mitt uppdrag förbjuder uttryckligen att deploya eller trigga Coolify, så jag har **inte** mergat. **Det beslutet är ditt, Simon.** Sidofynd, utanför scope här: `CHANGELOG.md` på `main` påstår att säkerhetsheaders (CSP/HSTS/X-Frame-Options) är gjorda, men `origin/main` innehåller **noll** sådan kod — implementationen ligger på den omergade grenen `ci/security-headers-and-scanning` (`8d2ff73`). Bakom Traefik utan Vercel framför får appen alltså inga sådana headers förrän den grenen mergas. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
admin added 1 commit 2026-09-07 16:51:39 +00:00
feat(deploy): gör 0pt körbar utanför Vercel (standalone + bevisad Docker-image)
Security Scan (SIAX Cloud) / security (push) Successful in 17s
CI (SIAX Cloud) / security (push) Successful in 26s
CI (SIAX Cloud) / integration (push) Successful in 30s
CI (SIAX Cloud) / quality (push) Successful in 37s
CI (SIAX Cloud) / security (pull_request) Successful in 15s
CI (SIAX Cloud) / integration (pull_request) Successful in 25s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 26s
CI (SIAX Cloud) / quality (pull_request) Successful in 50s
d428dd5018
0pt.app svarar i dag HTTP 402 DEPLOYMENT_DISABLED (Vercels betalningsspärr) och
har ingen självhostad ersättare igång. Det här gör appen faktiskt byggbar och
körbar på Coolify. Referens: sax3l/siax-io, som redan kör självhostat på flottan
— dess Dockerfile återanvänds i form i stället för att uppfinna ett eget mönster.

next.config.mjs:
  * output: "standalone" — runtime-imagen kör `node server.js` i stället för att
    släpa med pnpm, corepack och hela node_modules-trädet.
  * images.unoptimized: true BORTTAGET. Det var v0-generatorns standardvärde, inte
    ett val: next.config.mjs var orörd sedan "Initial commit from v0". Självhostat
    är rätt lösning att låta Next optimera själv.
  * poweredByHeader: false.

Dockerfile (fyra steg, samma form som siax-io):
  * node:24-alpine -> node:22-alpine. Båda CI-workflowsen bygger på node 22;
    att bygga och köra på samma major som grinden testar tar bort en version-skew.
  * libc6-compat (sharp), icke-root `node`, HEALTHCHECK mot /api/health,
    npm/npx strippat ur runtime-imagen (SIO-019-mönstret).
  * --ignore-workspace borttaget: motiveringen i kommentaren var inaktuell sedan
    P1-21 (pnpm-workspace.yaml har ett giltigt packages:-fält och
    packages/schemas är en riktig medlem). Containerinstallationen blir därmed
    bit för bit samma kommando som CI kör.
  * --prod=false: Coolify injicerar NODE_ENV=production vid byggtid, och pnpm
    hoppar då över devDependencies som next build kräver.

Sharp levereras i ett eget npm-steg, och det är inte godtyckligt:
next build spårar INTE sharp in i .next/standalone (verifierat: standalone-
utdatans node_modules innehöll bara next/, react/, react-dom/). Den uppenbara
lösningen outputFileTracingIncludes fungerar bevisligen inte — globben
"./node_modules/@img/**/*" får Turbopack att panika på pnpms katalogsymlänkar
("Is a directory (os error 21)") och bygget faller med exit 1. Reproducerat, inte
antaget. npm-steget lägger ett platt, symlänkfritt träd som runtime-steget kan
kopiera rakt av, pinnat till versionen i pnpm-lock.yaml, och faller hårt om
versionen inte går att läsa ut i stället för att tyst skeppa trasig optimering.

BEVISAT, inte påstått (fullständiga exitkoder i PR-beskrivningen):
  * pnpm install --frozen-lockfile -> 0; next build med HELT TOM env -> 0
  * docker build --no-cache -> 0; container startar, /api/health -> HTTP 200
  * /api/ready utan DATABASE_URL -> HTTP 503 (faller stängt, som avsett)
  * /api/0pt/* utan Bearer -> HTTP 401 (middleware kör självhostat)
  * /_next/image -> HTTP 200 image/avif, 1 488 706 B PNG -> 51 161 B (29x)
  * sharp laddar i runtime-imagen (libvips 8.18.6, musl-binärer)
  * icke-root uid=1000, HEALTHCHECK "healthy", image 1,33 GB -> 423 MB
  * typecheck 0, test 0 (930 passerade). lint/prettier faller identiskt på ren
    main => pre-existerande, inte infört här. Ingen grind har rörts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CODEOWNERS rules requested review from sax3l 2026-09-07 16:51:39 +00:00
admin merged commit into main 2026-09-08 20:48:45 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/0pt#40