{"_id":"@adaptive-ds/caddy-projects","_rev":"4-c76f57903c9c7056f71804d4801ad479","name":"@adaptive-ds/caddy-projects","dist-tags":{"latest":"0.4.2"},"versions":{"0.1.0":{"name":"@adaptive-ds/caddy-projects","version":"0.1.0","keywords":["typescript","caddy","reverse-proxy","cli","daemon"],"license":"MIT","_id":"@adaptive-ds/caddy-projects@0.1.0","maintainers":[{"name":"david1gp","email":"david1gruppenplan@gmail.com"}],"homepage":"https://github.com/david1gp/caddy-projects","bugs":{"url":"https://github.com/david1gp/caddy-projects/issues"},"bin":{"caddy-projects":"dist/cli.js","caddy-projectsd":"dist/daemon.js"},"dist":{"shasum":"98c39e9220a7043535f8cbd987df72ce003e9313","tarball":"https://registry.npmjs.org/@adaptive-ds/caddy-projects/-/caddy-projects-0.1.0.tgz","fileCount":195,"integrity":"sha512-LYqRjzaW0c6ChjzKpmJrJRqZpz7knMfHgnaoK/fSXQqdlucD3zK0SxdFaCiHloM54qxqaScyCLTPmkLaX/x+1w==","signatures":[{"sig":"MEUCICtwKt8lRMSShS0uIWxjMi1MtAF6JaA2bRfkpPQPT1JFAiEAp/DJjIWMr5FOIJ2aTcv7c/EvGP2BBtJhWHINUNII1qA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":201778},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./*.js":{"types":"./dist/*.d.ts","import":"./dist/*.js"}},"gitHead":"ffcfd4f9245b857db6c3d8c7e2a7c3958a95b5a3","imports":{"#result":"@adaptive-ds/result","#git-store":"@adaptive-ds/git-store"},"private":false,"scripts":{"test":"bun test","build":"rm -rf ./dist && bun tsc -p ./tsconfig.lib.json","clean":"rm -rf ./dist","reset":"rm -rf ./node_modules","format":"biome format --write src package.json tsconfig.json tsconfig.lib.json biome.json","test:w":"bun test --watch","update":"bun update","dev:cli":"bun run src/cli.ts","release":"bash ./ops/release.sh","dev:daemon":"bun run src/daemon.ts","update:commit":"bun update && git add package.json && bun run tsc && git commit -m \"chore(deps): update dependencies\" && git push"},"_npmUser":{"name":"david1gp","email":"david1gruppenplan@gmail.com"},"prettier":{"semi":false,"printWidth":120,"trailingComma":"all"},"repository":{"url":"git+https://github.com/david1gp/caddy-projects.git","type":"git"},"_npmVersion":"10.9.4","description":"API + CLI to manage Caddy projects: JSON project definitions, generated Caddy config, git-tracked history.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"valibot":"^1.2.0","@stricli/core":"^1.2.5","@adaptive-ds/result":"link:@adaptive-ds/result","@adaptive-ds/git-store":"link:@adaptive-ds/git-store"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.13","typescript":"^6.0.3","@biomejs/biome":"^2.5.2"},"_npmOperationalInternal":{"tmp":"tmp/caddy-projects_0.1.0_1785312006990_0.4178545932216755","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@adaptive-ds/caddy-projects","version":"0.3.0","keywords":["typescript","caddy","reverse-proxy","cli","daemon"],"license":"MIT","_id":"@adaptive-ds/caddy-projects@0.3.0","maintainers":[{"name":"david1gp","email":"david1gruppenplan@gmail.com"}],"homepage":"https://github.com/david1gp/caddy-projects","bugs":{"url":"https://github.com/david1gp/caddy-projects/issues"},"bin":{"caddy-projects":"dist/cli.js","caddy-projectsd":"dist/daemon.js"},"dist":{"shasum":"352908d9271b04237d64c58ca1eab97d34ca7422","tarball":"https://registry.npmjs.org/@adaptive-ds/caddy-projects/-/caddy-projects-0.3.0.tgz","fileCount":203,"integrity":"sha512-1QcKDDrj+Q9HZrOzTaQnilmljuS8nJC6Zu18ubwvjrLFBZXkE0LF3+2S3+eOx//2EWemXE75hUi/pdXq903jIg==","signatures":[{"sig":"MEUCIFmzAEWtmZ1Wsa6O8ZalV1Q1pgyL7tr/0crwRzFN/kOHAiEAuQ67JILkeY2m8vRCZWt/NTaJLy5L206lkq19E0bhCxo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@adaptive-ds%2fcaddy-projects@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":212434},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./*.js":{"types":"./dist/*.d.ts","import":"./dist/*.js"}},"gitHead":"91c0837a806d9635ebd7095fdbc4d1cd017660ce","imports":{"#result":"@adaptive-ds/result","#git-store":"@adaptive-ds/git-store"},"private":false,"scripts":{"test":"bun test","build":"rm -rf ./dist && bun tsc -p ./tsconfig.lib.json","clean":"rm -rf ./dist","reset":"rm -rf ./node_modules","format":"biome format --write src package.json tsconfig.json tsconfig.lib.json biome.json","test:w":"bun test --watch","update":"bun update","dev:cli":"bun run src/cli.ts","release":"bash ./ops/release.sh","dev:daemon":"bun run src/daemon.ts","update:commit":"bun update && git add package.json && bun run tsc && git commit -m \"chore(deps): update dependencies\" && git push"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:703cf829-5b55-435e-a37e-ec81ad6159df"}},"prettier":{"semi":false,"printWidth":120,"trailingComma":"all"},"repository":{"url":"git+https://github.com/david1gp/caddy-projects.git","type":"git"},"_npmVersion":"12.0.2","description":"API + CLI to manage Caddy projects: JSON project definitions, generated Caddy config, git-tracked history.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"valibot":"^1.2.0","@stricli/core":"^1.2.5","@adaptive-ds/result":"^0.6.0","@adaptive-ds/git-store":"^0.1.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.13","typescript":"^6.0.3","@biomejs/biome":"^2.5.2"},"_npmOperationalInternal":{"tmp":"tmp/caddy-projects_0.3.0_1785497429207_0.5135136022309281","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@adaptive-ds/caddy-projects","version":"0.4.1","keywords":["typescript","caddy","reverse-proxy","cli","daemon"],"license":"MIT","_id":"@adaptive-ds/caddy-projects@0.4.1","maintainers":[{"name":"david1gp","email":"david1gruppenplan@gmail.com"}],"homepage":"https://github.com/david1gp/caddy-projects","bugs":{"url":"https://github.com/david1gp/caddy-projects/issues"},"bin":{"caddy-projects":"dist/cli.js","caddy-projectsd":"dist/daemon.js"},"dist":{"shasum":"eb67bc2a68f6e52049990f9efa8071abbf240fc8","tarball":"https://registry.npmjs.org/@adaptive-ds/caddy-projects/-/caddy-projects-0.4.1.tgz","fileCount":207,"integrity":"sha512-dGVPzEHLg0urO5Eok3ex85R58rY/p5kFnU6I8pyVyq2ngupa5NbFEuL+OaDnsYp+qGqLy0smZB8EeR3f5kTiTg==","signatures":[{"sig":"MEQCID7hQoUGPC+e5Q4Vne+sYND6r7xXsdtNS2+6P3bVVackAiAXscGheK2a7OJ/WmOg1BGbhLG+uhPTUlUehajTeXUABg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@adaptive-ds%2fcaddy-projects@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":222371},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./*.js":{"types":"./dist/*.d.ts","import":"./dist/*.js"}},"gitHead":"6994b6012d56b2e375039f926a068df6528d295b","imports":{"#result":"@adaptive-ds/result","#git-store":"@adaptive-ds/git-store"},"private":false,"scripts":{"test":"bun test","build":"rm -rf ./dist && bun tsc -p ./tsconfig.lib.json","clean":"rm -rf ./dist","reset":"rm -rf ./node_modules","format":"biome format --write src package.json tsconfig.json tsconfig.lib.json biome.json","test:w":"bun test --watch","update":"bun update","dev:cli":"bun run src/cli.ts","release":"bash ./ops/release.sh","dev:daemon":"bun run src/daemon.ts","update:commit":"bun update && git add package.json && bun run tsc && git commit -m \"chore(deps): update dependencies\" && git push"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:703cf829-5b55-435e-a37e-ec81ad6159df"}},"prettier":{"semi":false,"printWidth":120,"trailingComma":"all"},"repository":{"url":"git+https://github.com/david1gp/caddy-projects.git","type":"git"},"_npmVersion":"12.0.2","description":"API + CLI to manage Caddy projects: JSON project definitions, generated Caddy config, git-tracked history.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"valibot":"^1.2.0","@stricli/core":"^1.2.5","@adaptive-ds/result":"^0.6.0","@adaptive-ds/git-store":"^0.1.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.13","typescript":"^6.0.3","@biomejs/biome":"^2.5.2"},"_npmOperationalInternal":{"tmp":"tmp/caddy-projects_0.4.1_1786519940718_0.6122787913731773","host":"s3://npm-registry-packages-npm-production"}},"0.4.2":{"name":"@adaptive-ds/caddy-projects","version":"0.4.2","scripts":{"build":"rm -rf ./dist && bun tsc -p ./tsconfig.lib.json","test":"bun test","test:w":"bun test --watch","release":"bash ./ops/release.sh","update":"bun update","update:commit":"bun update && git add package.json && bun run tsc && git commit -m \"chore(deps): update dependencies\" && git push","clean":"rm -rf ./dist","reset":"rm -rf ./node_modules","format":"biome format --write src package.json tsconfig.json tsconfig.lib.json biome.json","dev:daemon":"bun run src/daemon.ts","dev:cli":"bun run src/cli.ts"},"dependencies":{"@adaptive-ds/git-store":"^0.1.0","@adaptive-ds/result":"^0.6.0","@stricli/core":"^1.2.5","valibot":"^1.2.0"},"devDependencies":{"@biomejs/biome":"^2.5.2","@types/bun":"^1.3.13","typescript":"^6.0.3"},"prettier":{"semi":false,"printWidth":120,"trailingComma":"all"},"type":"module","main":"dist/index.js","bin":{"caddy-projects":"dist/cli.js","caddy-projectsd":"dist/daemon.js"},"private":false,"license":"MIT","description":"API + CLI to manage Caddy projects: JSON project definitions, generated Caddy config, git-tracked history.","keywords":["typescript","caddy","reverse-proxy","cli","daemon"],"homepage":"https://github.com/david1gp/caddy-projects","repository":{"type":"git","url":"git+https://github.com/david1gp/caddy-projects.git"},"bugs":{"url":"https://github.com/david1gp/caddy-projects/issues"},"types":"./dist/index.d.ts","imports":{"#result":"@adaptive-ds/result","#git-store":"@adaptive-ds/git-store"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./*.js":{"types":"./dist/*.d.ts","import":"./dist/*.js"}},"gitHead":"160397f1ced1fbf61b50509435b1f62acdce387a","_id":"@adaptive-ds/caddy-projects@0.4.2","_nodeVersion":"24.18.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-hkpNAfmMtYPH4dpYB936ve0JSWtJgZ9lGL13bH9tn15p/KsFR98F+TJI+gLUFHYCyJZdRbtSV9td9KbdNB5qZQ==","shasum":"d2cfd924645d2f41c7886da8d24b0b8b1f1a6f1e","tarball":"https://registry.npmjs.org/@adaptive-ds/caddy-projects/-/caddy-projects-0.4.2.tgz","fileCount":211,"unpackedSize":223090,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@adaptive-ds%2fcaddy-projects@0.4.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCaViKa1j3slfwa42fUQkvVWuaO4d1GuwMHD8NLgYKuBAIhAIuwbaOlLND3CszyYRLlVDCpFZlKe5fCybxl7i5vnisy"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:703cf829-5b55-435e-a37e-ec81ad6159df"}},"directories":{},"maintainers":[{"name":"david1gp","email":"david1gruppenplan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/caddy-projects_0.4.2_1786521514266_0.7912831642355793"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-29T08:00:06.826Z","modified":"2026-08-12T07:58:34.750Z","0.1.0":"2026-07-29T08:00:07.143Z","0.3.0":"2026-07-31T11:30:29.375Z","0.4.1":"2026-08-12T07:32:20.872Z","0.4.2":"2026-08-12T07:58:34.420Z"},"bugs":{"url":"https://github.com/david1gp/caddy-projects/issues"},"license":"MIT","homepage":"https://github.com/david1gp/caddy-projects","keywords":["typescript","caddy","reverse-proxy","cli","daemon"],"repository":{"type":"git","url":"git+https://github.com/david1gp/caddy-projects.git"},"description":"API + CLI to manage Caddy projects: JSON project definitions, generated Caddy config, git-tracked history.","maintainers":[{"name":"david1gp","email":"david1gruppenplan@gmail.com"}],"readme":"# @adaptive-ds/caddy-projects\n\nAPI + CLI to manage Caddy reverse-proxy and static \"projects\": JSON definitions, generated Caddy JSON config, validated with `caddy validate`, reloaded via the admin API, and every change committed to a git repo.\n\n## Why\n\nOne server, one Caddy instance, several developers. Editing a shared `Caddyfile` by hand has a few sharp edges:\n\n- **No isolation.** Everyone edits the same file, so anyone can break or clobber anyone else's sites.\n- **No audit trail.** When a site breaks, nothing says who changed what, when, or how to get back.\n- **Easy to take everything down.** A typo is only caught when Caddy reloads — by then the whole config is already broken.\n- **Repetitive.** Every site repeats the same reverse-proxy / docs / OIDC boilerplate.\n\nThis project replaces hand-edited Caddyfiles with a small API:\n\n- **Per-user scoping.** Each user only sees and edits their own projects, plus anything explicitly marked `shared` or `template`. No user can read or mutate another user's private entries.\n- **Git-backed history.** Every create/edit/delete is a commit (optionally pushed), so the config's history is a normal `git log` you can diff, blame, and revert.\n- **Validate before reload.** Generated config is checked with `caddy validate` first. If it fails, Caddy is never reloaded and the git write is reverted — a bad entry cannot take the server down.\n- **A tiny schema instead of Caddy JSON.** You declare a port, domains and a path; the boilerplate (reverse proxy, static serving, markdown docs, OIDC gate, `Routed` header) is generated.\n\n## How it works\n\n```\nCLI  --unix socket-->  daemon  -->  git repo (project JSON, one commit per change)\n                          |\n                          +-->  generate Caddy JSON\n                          +-->  caddy validate          (abort + revert on failure)\n                          +-->  POST /load to admin API (zero-downtime reload)\n```\n\n**Identity comes from the socket, not the request.** The daemon listens on one unix socket per user at `<socket-dir>/<username>.sock`, mode `0600` and owned by that user. The username is implied by which socket a request arrived on, so it cannot be spoofed by sending a different header — the kernel's file permissions do the authentication. No tokens, no passwords, nothing to configure per user.\n\n**Every mutation runs the same pipeline**, in this order:\n\n1. Write the project JSON into the git repo and commit it.\n2. Read all projects, generate the full Caddy JSON.\n3. Validate it by shelling out to `caddy validate`.\n4. Reload via `POST /load` on the Caddy admin API.\n\nIf step 3 or 4 fails, the git change is reverted with a follow-up commit and the API returns the error. The live config is only ever replaced by something Caddy already accepted.\n\n**Project data is the source of truth.** The generated Caddy config is disposable — it is rebuilt from all project files on every change, and routes are sorted deterministically so the output diffs cleanly. `GET /config` returns the full generated JSON for debugging.\n\n## Install\n\n```bash\nbun install\nbun run build\n```\n\nBinaries after build: `caddy-projects` (CLI), `caddy-projectsd` (daemon).\n\n## Daemon\n\n```bash\nbun run src/daemon.ts \\\n  --repo ~/c/adaptive/caddy-projects-history \\\n  --socket-dir /run/caddy-projects \\\n  --users leo,david \\\n  --admin-url http://localhost:2019 \\\n  --caddy-bin caddy \\\n  --no-push\n```\n\n| flag | default | notes |\n| --- | --- | --- |\n| `--repo` | `~/c/adaptive/caddy-projects-history` | git repo holding project JSON |\n| `--socket-dir` | `/run/caddy-projects` (or `$XDG_RUNTIME_DIR`/`/tmp` when not root) | one socket per user |\n| `--users` | current user | comma separated; only honoured when running as root |\n| `--admin-url` | `http://localhost:2019` | Caddy admin API |\n| `--caddy-bin` | `caddy` | binary used for `caddy validate` |\n| `--no-push` | off | do not push commits to `origin` |\n| `--skip-validate` | off | skip `caddy validate` (testing only) |\n| `--skip-reload` | off | generate and validate but never reload Caddy (dry run) |\n\nEnv (OIDC; omit issuer to skip oidc app entirely):\n\n- `CADDY_PROJECTS_OIDC_ISSUER`\n- `CADDY_PROJECTS_OIDC_CLIENT_ID`\n- `CADDY_PROJECTS_OIDC_CLIENT_SECRET`\n- `CADDY_PROJECTS_OIDC_COOKIE_SECRET`\n- `CADDY_PROJECTS_OIDC_PROVIDER` (default `zitadel`)\n\nListens on one unix socket per user: `<socket-dir>/<username>.sock` (mode `0600`, chown to user when root).\n\n### systemd example\n\n```ini\n[Unit]\nDescription=caddy-projects daemon\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nExecStart=/usr/local/bin/caddy-projectsd --repo /var/lib/caddy-projects --socket-dir /run/caddy-projects --users leo,david\nRestart=on-failure\nRuntimeDirectory=caddy-projects\nRuntimeDirectoryMode=0755\nEnvironment=CADDY_PROJECTS_OIDC_ISSUER=https://auth.example\nEnvironment=CADDY_PROJECTS_OIDC_CLIENT_ID=...\nEnvironment=CADDY_PROJECTS_OIDC_CLIENT_SECRET=...\nEnvironment=CADDY_PROJECTS_OIDC_COOKIE_SECRET=...\n\n[Install]\nWantedBy=multi-user.target\n```\n\n## CLI\n\nTalks to the daemon over the current user's socket. Resolution order: `--socket`, then `CADDY_PROJECTS_SOCKET`, then `/run/caddy-projects/$USER.sock`. Built with `@stricli/core` — every command and flag has help text via `caddy-projects <command> --help`.\n\n```bash\ncaddy-projects list [--mine] [--templates] [--json]\ncaddy-projects get <name> [--json]\ncaddy-projects create --name app --domain app.example.com [--port 3000] \\\n  [--path /srv/app] [--kind proxy|static] [--access internal|external] \\\n  [--docs|--no-docs] [--browse|--no-browse] [--shared|--no-shared] \\\n  [--template|--no-template] [--disabled|--enabled] \\\n  [--header-up Host=127.0.0.1:3000]\ncaddy-projects edit app [--port 3001] [same flags as create]\ncaddy-projects delete <name>\ncaddy-projects delete --port 3000\ncaddy-projects docs [<name>] <path.md> # public review URLs (HTML at /docs/*.md); name from cwd if omitted\ncaddy-projects config                  # summary table of server blocks\ncaddy-projects config all [--pretty]   # full generated Caddy JSON\ncaddy-projects config <selector>       # one block by project name, domain, or port\ncaddy-projects config --json           # summary as JSON\ncaddy-projects history [--name app] [--limit 20]\ncaddy-projects regenerate\ncaddy-projects --help\ncaddy-projects create --help\ncaddy-projects --version\n```\n\n- `--port` is optional on create: when omitted the daemon assigns the lowest free port in range (default 3000–3999) and the CLI prints `created <name> (port N)`.\n- Boolean project fields have both enable and disable flags (`--docs` / `--no-docs`, etc.). Only flags you pass are sent on `edit` (true partial PATCH).\n- `delete` accepts either a positional name or `--port <n>` (not both).\n- `docs <name> guide/intro.md` prints one `https://…/docs/…` URL per project domain (use `--json` for `{ \"urls\": [...] }`, `--http` for http scheme). With a single argument (`docs guide/intro.md`), the project name is inferred by matching the current directory against each project's `path` (longest match wins).\n- `config` with no arg prints a summary table of blocks actually in the generated config; `config all` dumps full JSON; `config <selector>` prints matching route(s) (pretty by default). Selector is project name, domain, or port.\n- Request bodies are validated client-side before the API call (e.g. bad `--port abc` fails with a clear error).\n\n## API\n\nAll routes are JSON over the user's unix socket. The acting user is bound from the socket, never from the request body.\n\n| method | path | notes |\n| --- | --- | --- |\n| `GET` | `/health` | `{ ok: true, user }` |\n| `GET` | `/projects` | visible to user; `?mine=1`, `?templates=1` |\n| `POST` | `/projects` | create; `port` optional (auto-assign); 409 on duplicate name, domain, or port |\n| `GET` | `/projects/:name` | 404 if not visible |\n| `PUT` | `/projects/:name` | full replace (`port` required) |\n| `PATCH` | `/projects/:name` | partial merge |\n| `DELETE` | `/projects/:name` | own projects only |\n| `DELETE` | `/projects/by-port/:port` | delete own project that owns that port |\n| `GET` | `/projects/:name/docs` | public docs review URLs; required `?path=guide/intro.md`; optional `?scheme=http`; `{ urls: string[] }`; 400 if docs off / bad path |\n| `GET` | `/config` | full generated Caddy JSON; `?pretty=1`; `?summary=1` summary rows; `?select=<name\\|domain\\|port>` matching route(s) (404 if none); visibility-scoped |\n| `POST` | `/regenerate` | force regenerate + validate + reload |\n| `GET` | `/history` | commits; `?name=`, `?limit=` |\n\nSuccess responses are `{ success: true, data }`. Errors return a `ResultErr` (`{ success: false, op, errorMessage }`) with status 400 (validation/conflict), 404 (not found) or 500. Port/domain conflicts use 409.\n\n**Port uniqueness.** Ports are unique across all users (one shared machine). Disabled and template projects are ignored for collision checks. On create, omit `port` to get the lowest free port in the configured range (default 3000–3999 via `ProjectsRegenerateOptions.portRange`); the assigned port is in the returned project object (`data.port`).\n\n```bash\ncurl --unix-socket /run/caddy-projects/$USER.sock http://localhost/projects\n# create without port → auto-assign\ncurl --unix-socket /run/caddy-projects/$USER.sock -X POST http://localhost/projects \\\n  -d '{\"name\":\"auto\",\"domains\":[\"auto.test\"],\"kind\":\"proxy\",\"access\":\"external\",\"docs\":false}'\n# delete by port\ncurl --unix-socket /run/caddy-projects/$USER.sock -X DELETE http://localhost/projects/by-port/3000\n```\n\n## Project JSON\n\n| field | type | notes |\n| --- | --- | --- |\n| `port` | number 1..65535 | upstream port; required when stored; **optional on create** (auto-assigned); unique across all users |\n| `domains` | string[] | >=1 hostnames; unique across all users (active projects) |\n| `name` | slug | `^[a-z0-9][a-z0-9-]*$` |\n| `path` | string | absolute path; `\"\"` allowed |\n| `user` | string | linux username (set by server) |\n| `access` | `internal` \\| `external` | internal => OIDC gate when configured |\n| `kind` | `proxy` \\| `static` | reverse_proxy vs file_server |\n| `docs` | boolean | default true: serve `path/docs/*.md` at `/docs/*` |\n| `browse` | boolean | static only; default false |\n| `headerUp` | record | reverse_proxy `header_up`; default `{}` |\n| `shared` | boolean | visible to all users; default false |\n| `template` | boolean | not emitted into caddy config; default false |\n| `disabled` | boolean | omit from caddy config; default false |\n\nStorage: `projects/<user>/<name>.json` in the git history repo.\n\nVisibility: own projects, or any with `shared` / `template`. Mutations only on own projects.\n\n## Caddy config\n\n`caddyConfigGenerate` builds Caddy JSON (apps.http + optional apps.oidc). Mutating API routes write git then generate → validate → reload; on regenerate failure the git change is reverted. `POST /regenerate` forces the same pipeline without a project mutation.\n\n**Note:** `access: \"internal\"` requires the [caddy-oidc](https://github.com/relvacode/caddy-oidc) plugin compiled into your caddy binary. Validation of configs that include the `oidc` handler will fail on stock caddy.\n\n## OIDC setup\n\nCustom Caddy build + Zitadel client provisioning scripts live under [`ops/`](ops/README.md):\n\n- `ops/caddy_oidc_install.sh` — build/install Caddy with `caddy-oidc` via xcaddy\n- `ops/provision_leo_server_oidc.sh` / `provision_localhost_oidc.sh` / `provision_david_server_oidc.sh` — idempotent Zitadel OIDC clients\n- See [ops/README.md](ops/README.md) for env vars (`CADDY_PROJECTS_OIDC_*`) and how to wire the daemon\n\n## Dev\n\n```bash\nbun test\nbun run dev:daemon\nbun run dev:cli -- list\n```\n","readmeFilename":"README.md"}