@lamalibre/create-lamaste 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE.md +118 -0
  2. package/README.md +114 -0
  3. package/bin/create-lamaste.js +15 -0
  4. package/package.json +54 -0
  5. package/scripts/bundle-vendor.js +110 -0
  6. package/scripts/lamaste-cert-info +62 -0
  7. package/scripts/lamaste-certbot +96 -0
  8. package/scripts/lamaste-priv +693 -0
  9. package/src/index.js +646 -0
  10. package/src/lib/accounts.js +86 -0
  11. package/src/lib/binaries.js +137 -0
  12. package/src/lib/cert-help-page.js +160 -0
  13. package/src/lib/env.js +132 -0
  14. package/src/lib/http-redirect.js +38 -0
  15. package/src/lib/ownership.js +231 -0
  16. package/src/lib/relay-services.js +103 -0
  17. package/src/lib/secrets.js +19 -0
  18. package/src/lib/service-config.js +311 -0
  19. package/src/lib/summary.js +131 -0
  20. package/src/lib/wrappers.js +63 -0
  21. package/src/tasks/gatekeeper.js +233 -0
  22. package/src/tasks/harden.js +293 -0
  23. package/src/tasks/mtls.js +193 -0
  24. package/src/tasks/nginx.js +265 -0
  25. package/src/tasks/node.js +110 -0
  26. package/src/tasks/panel.js +408 -0
  27. package/src/tasks/redeploy.js +438 -0
  28. package/vendor/docs/00-introduction/how-it-works.md +447 -0
  29. package/vendor/docs/00-introduction/quickstart.md +472 -0
  30. package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
  31. package/vendor/docs/01-concepts/authentication.md +464 -0
  32. package/vendor/docs/01-concepts/authorization.md +242 -0
  33. package/vendor/docs/01-concepts/certificates.md +452 -0
  34. package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
  35. package/vendor/docs/01-concepts/mtls.md +490 -0
  36. package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
  37. package/vendor/docs/01-concepts/security-model.md +600 -0
  38. package/vendor/docs/01-concepts/tickets.md +414 -0
  39. package/vendor/docs/01-concepts/tunneling.md +367 -0
  40. package/vendor/docs/02-guides/agent-setup.md +194 -0
  41. package/vendor/docs/02-guides/certificate-management.md +341 -0
  42. package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
  43. package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
  44. package/vendor/docs/02-guides/disaster-recovery.md +464 -0
  45. package/vendor/docs/02-guides/first-tunnel.md +230 -0
  46. package/vendor/docs/02-guides/installation.md +393 -0
  47. package/vendor/docs/02-guides/managing-users.md +255 -0
  48. package/vendor/docs/02-guides/onboarding.md +244 -0
  49. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
  50. package/vendor/docs/02-guides/static-sites.md +364 -0
  51. package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
  52. package/vendor/docs/03-architecture/installer-flow.md +151 -0
  53. package/vendor/docs/03-architecture/installer.md +445 -0
  54. package/vendor/docs/03-architecture/management-flow.md +288 -0
  55. package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
  56. package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
  57. package/vendor/docs/03-architecture/overview.md +541 -0
  58. package/vendor/docs/03-architecture/panel-client.md +416 -0
  59. package/vendor/docs/03-architecture/panel-server.md +695 -0
  60. package/vendor/docs/03-architecture/state-management.md +529 -0
  61. package/vendor/docs/03-architecture/system-overview.md +167 -0
  62. package/vendor/docs/04-api-reference/certificates.md +1141 -0
  63. package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
  64. package/vendor/docs/04-api-reference/onboarding.md +473 -0
  65. package/vendor/docs/04-api-reference/overview.md +404 -0
  66. package/vendor/docs/04-api-reference/services.md +267 -0
  67. package/vendor/docs/04-api-reference/settings.md +343 -0
  68. package/vendor/docs/04-api-reference/sites.md +716 -0
  69. package/vendor/docs/04-api-reference/system.md +268 -0
  70. package/vendor/docs/04-api-reference/tickets.md +618 -0
  71. package/vendor/docs/04-api-reference/tunnels.md +417 -0
  72. package/vendor/docs/04-api-reference/users.md +333 -0
  73. package/vendor/docs/05-operations/backup-and-restore.md +342 -0
  74. package/vendor/docs/05-operations/monitoring.md +285 -0
  75. package/vendor/docs/05-operations/uninstalling.md +344 -0
  76. package/vendor/docs/05-operations/upgrades.md +270 -0
  77. package/vendor/docs/06-reference/config-files.md +1080 -0
  78. package/vendor/docs/06-reference/glossary.md +528 -0
  79. package/vendor/docs/06-reference/installer-flags.md +260 -0
  80. package/vendor/docs/06-reference/ports-and-services.md +244 -0
  81. package/vendor/docs/06-reference/troubleshooting.md +665 -0
  82. package/vendor/docs/_index.json +233 -0
  83. package/vendor/docs/gatekeeper-design.md +953 -0
  84. package/vendor/docs/package.json +15 -0
  85. package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
  86. package/vendor/gatekeeper/dist/boot.d.ts +1 -0
  87. package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
  88. package/vendor/gatekeeper/dist/boot.js +17 -0
  89. package/vendor/gatekeeper/dist/boot.js.map +1 -0
  90. package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
  91. package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
  92. package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
  93. package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
  94. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
  95. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
  96. package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
  97. package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
  98. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
  99. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
  100. package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
  101. package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
  102. package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
  103. package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
  104. package/vendor/gatekeeper/dist/cli/index.js +45 -0
  105. package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
  106. package/vendor/gatekeeper/dist/index.d.ts +9 -0
  107. package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
  108. package/vendor/gatekeeper/dist/index.js +13 -0
  109. package/vendor/gatekeeper/dist/index.js.map +1 -0
  110. package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
  111. package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
  112. package/vendor/gatekeeper/dist/lib/authz.js +42 -0
  113. package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
  114. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
  115. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
  116. package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
  117. package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
  118. package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
  119. package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
  120. package/vendor/gatekeeper/dist/lib/constants.js +79 -0
  121. package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
  122. package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
  123. package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
  124. package/vendor/gatekeeper/dist/lib/grants.js +314 -0
  125. package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
  126. package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
  127. package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
  128. package/vendor/gatekeeper/dist/lib/groups.js +247 -0
  129. package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
  130. package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
  131. package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
  132. package/vendor/gatekeeper/dist/lib/migration.js +79 -0
  133. package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
  134. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
  135. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
  136. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  137. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
  138. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
  139. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  140. package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
  141. package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
  142. package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
  143. package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
  144. package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
  145. package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
  146. package/vendor/gatekeeper/dist/lib/templates.js +151 -0
  147. package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
  148. package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
  149. package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
  150. package/vendor/gatekeeper/dist/lib/types.js +2 -0
  151. package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
  152. package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
  153. package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
  154. package/vendor/gatekeeper/dist/server/index.js +251 -0
  155. package/vendor/gatekeeper/dist/server/index.js.map +1 -0
  156. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
  157. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
  158. package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
  159. package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
  160. package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
  161. package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
  162. package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
  163. package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
  164. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
  165. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
  166. package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
  167. package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
  168. package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
  169. package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
  170. package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
  171. package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
  172. package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
  173. package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
  174. package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
  175. package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
  176. package/vendor/gatekeeper/package.json +49 -0
  177. package/vendor/server/bin/lamaste-server.js +204 -0
  178. package/vendor/server/package.json +31 -0
  179. package/vendor/server/src/commands/certs.js +224 -0
  180. package/vendor/server/src/commands/chisel.js +132 -0
  181. package/vendor/server/src/commands/logs.js +49 -0
  182. package/vendor/server/src/commands/plugins.js +321 -0
  183. package/vendor/server/src/commands/reset-admin.js +478 -0
  184. package/vendor/server/src/commands/restart.js +94 -0
  185. package/vendor/server/src/commands/sites.js +257 -0
  186. package/vendor/server/src/commands/status.js +158 -0
  187. package/vendor/server/src/commands/tunnels.js +365 -0
  188. package/vendor/server/src/commands/uninstall.js +326 -0
  189. package/vendor/server/src/config.js +38 -0
  190. package/vendor/server/src/exec.js +26 -0
  191. package/vendor/server/src/logger.js +39 -0
  192. package/vendor/server/src/ndjson.js +56 -0
  193. package/vendor/server/src/panel-api.js +85 -0
  194. package/vendor/server/src/state.js +45 -0
  195. package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
  196. package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
  197. package/vendor/server-ui/dist/index.html +13 -0
  198. package/vendor/serverd/package.json +44 -0
  199. package/vendor/serverd/src/boot.js +13 -0
  200. package/vendor/serverd/src/index.js +191 -0
  201. package/vendor/serverd/src/lib/app-error.js +14 -0
  202. package/vendor/serverd/src/lib/authelia.js +85 -0
  203. package/vendor/serverd/src/lib/certbot.js +74 -0
  204. package/vendor/serverd/src/lib/chisel-args.js +6 -0
  205. package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
  206. package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
  207. package/vendor/serverd/src/lib/chisel-users.js +109 -0
  208. package/vendor/serverd/src/lib/chisel.js +81 -0
  209. package/vendor/serverd/src/lib/config.js +190 -0
  210. package/vendor/serverd/src/lib/constants.js +8 -0
  211. package/vendor/serverd/src/lib/csr-signing.js +331 -0
  212. package/vendor/serverd/src/lib/enrollment.js +302 -0
  213. package/vendor/serverd/src/lib/files.js +19 -0
  214. package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
  215. package/vendor/serverd/src/lib/invite-page.js +281 -0
  216. package/vendor/serverd/src/lib/ip-utils.js +79 -0
  217. package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
  218. package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
  219. package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
  220. package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
  221. package/vendor/serverd/src/lib/mtls.js +795 -0
  222. package/vendor/serverd/src/lib/nginx.js +1090 -0
  223. package/vendor/serverd/src/lib/pki-paths.js +41 -0
  224. package/vendor/serverd/src/lib/pki-sign.js +103 -0
  225. package/vendor/serverd/src/lib/plugins.js +538 -0
  226. package/vendor/serverd/src/lib/push-install.js +390 -0
  227. package/vendor/serverd/src/lib/revocation.js +112 -0
  228. package/vendor/serverd/src/lib/services.js +131 -0
  229. package/vendor/serverd/src/lib/session.js +151 -0
  230. package/vendor/serverd/src/lib/state-db.js +130 -0
  231. package/vendor/serverd/src/lib/state.js +140 -0
  232. package/vendor/serverd/src/lib/storage.js +63 -0
  233. package/vendor/serverd/src/lib/system-stats.js +58 -0
  234. package/vendor/serverd/src/lib/tickets.js +1556 -0
  235. package/vendor/serverd/src/lib/totp.js +76 -0
  236. package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
  237. package/vendor/serverd/src/lib/user-access-session.js +142 -0
  238. package/vendor/serverd/src/lib/user-access.js +52 -0
  239. package/vendor/serverd/src/middleware/errors.js +63 -0
  240. package/vendor/serverd/src/middleware/mtls.js +120 -0
  241. package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
  242. package/vendor/serverd/src/middleware/role-guard.js +58 -0
  243. package/vendor/serverd/src/middleware/twofa-session.js +65 -0
  244. package/vendor/serverd/src/middleware/user-access-session.js +52 -0
  245. package/vendor/serverd/src/routes/enrollment.js +120 -0
  246. package/vendor/serverd/src/routes/health.js +22 -0
  247. package/vendor/serverd/src/routes/invite.js +163 -0
  248. package/vendor/serverd/src/routes/management/agents.js +182 -0
  249. package/vendor/serverd/src/routes/management/certs.js +911 -0
  250. package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
  251. package/vendor/serverd/src/routes/management/identity.js +107 -0
  252. package/vendor/serverd/src/routes/management/invitations.js +166 -0
  253. package/vendor/serverd/src/routes/management/logs.js +218 -0
  254. package/vendor/serverd/src/routes/management/plugins.js +560 -0
  255. package/vendor/serverd/src/routes/management/services.js +63 -0
  256. package/vendor/serverd/src/routes/management/settings.js +325 -0
  257. package/vendor/serverd/src/routes/management/sites.js +522 -0
  258. package/vendor/serverd/src/routes/management/storage.js +192 -0
  259. package/vendor/serverd/src/routes/management/system.js +201 -0
  260. package/vendor/serverd/src/routes/management/tickets.js +457 -0
  261. package/vendor/serverd/src/routes/management/tunnels.js +709 -0
  262. package/vendor/serverd/src/routes/management/users.js +377 -0
  263. package/vendor/serverd/src/routes/management.js +36 -0
  264. package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
  265. package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
  266. package/vendor/serverd/src/routes/onboarding/index.js +18 -0
  267. package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
  268. package/vendor/serverd/src/routes/onboarding/status.js +12 -0
  269. package/vendor/serverd/src/routes/plugin-router.js +79 -0
  270. package/vendor/serverd/src/routes/user-access.js +389 -0
@@ -0,0 +1,417 @@
1
+ # Tunnels API
2
+
3
+ > Create, list, move, and delete tunnels that expose services on an agent machine through your Lamaste domain.
4
+
5
+ ## In Plain English
6
+
7
+ A tunnel connects a web app running on an agent machine (say, a development server on port 3000) to a public subdomain on your Lamaste domain (like `app.example.com`). When someone visits that URL, the request travels through the tunnel back to that machine.
8
+
9
+ Every tunnel belongs to exactly one **agent** — the enrolled machine that carries it. Only that agent is told about the tunnel, and only that agent's credential may open it on the server. Another agent cannot take over the hostname by binding the same port.
10
+
11
+ ## Authentication
12
+
13
+ All tunnel endpoints require a valid mTLS client certificate and a completed onboarding. See the [API Overview](./overview.md) for details.
14
+
15
+ If onboarding is not complete, all endpoints return `503 Service Unavailable`.
16
+
17
+ **Admin vs agent certificates.** An admin certificate sees and manages every tunnel. An agent certificate sees and manages only the tunnels that agent carries: other agents' tunnels are absent from its list and answer `404` to it.
18
+
19
+ ## Endpoints
20
+
21
+ ### `GET /api/tunnels`
22
+
23
+ Returns tunnels, sorted by creation date (newest first). An agent certificate receives only its own tunnels.
24
+
25
+ **Query parameters:** `limit` (1–500, default 100), `offset` (default 0), `sort` (`createdAt` | `name`), `order` (`asc` | `desc`, default `desc`).
26
+
27
+ ```bash
28
+ curl -s --cert client.p12:password \
29
+ https://203.0.113.42:9292/api/tunnels | jq
30
+ ```
31
+
32
+ **Response (200):**
33
+
34
+ ```json
35
+ {
36
+ "tunnels": [
37
+ {
38
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
39
+ "subdomain": "app",
40
+ "fqdn": "app.example.com",
41
+ "port": 3000,
42
+ "description": "React development server",
43
+ "type": "app",
44
+ "accessMode": "restricted",
45
+ "agentLabel": "laptop",
46
+ "enabled": true,
47
+ "createdAt": "2026-03-13T14:30:00.000Z"
48
+ }
49
+ ],
50
+ "total": 1,
51
+ "limit": 100,
52
+ "offset": 0
53
+ }
54
+ ```
55
+
56
+ | Field | Type | Description |
57
+ | --------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
58
+ | `id` | `string` | UUID v4 identifier |
59
+ | `subdomain` | `string` | The subdomain portion (e.g., `app`) |
60
+ | `fqdn` | `string` | Fully qualified domain name (e.g., `app.example.com`) |
61
+ | `port` | `number` | Port the carrying agent forwards; bound on `127.0.0.1` on both the server and the agent |
62
+ | `description` | `string \| null` | Optional human-readable description |
63
+ | `type` | `string` | `'app'` (default), `'panel'` (agent web panel), or `'plugin'` |
64
+ | `accessMode` | `string` | `'restricted'` (default), `'authenticated'`, or `'public'` — absent on panel tunnels |
65
+ | `agentLabel` | `string` | The agent that carries the tunnel. Absent only on an unassigned legacy tunnel (see below) |
66
+ | `maxBodySizeMb` | `number` | Largest request body nginx accepts, in MiB. Absent on panel tunnels and on tunnels created before the setting existed, which keep nginx's built-in 1 MiB until a body limit is set on them |
67
+ | `enabled` | `boolean` | Whether the tunnel is active (defaults to `true`) |
68
+ | `createdAt` | `string` | ISO 8601 timestamp |
69
+
70
+ ---
71
+
72
+ ### `POST /api/tunnels`
73
+
74
+ Creates a new tunnel. This is a multi-step operation that:
75
+
76
+ 1. Issues a Let's Encrypt TLS certificate for `<subdomain>.<domain>`
77
+ 2. Writes an nginx vhost configuration for the subdomain
78
+ 3. Saves the tunnel to the state file
79
+ 4. Syncs Chisel: re-renders the authfile so the carrying agent may bind the new port (Chisel reloads it without a restart)
80
+
81
+ State is saved before Chisel is synced because the Chisel authfile is rendered from the persisted state. If any step fails, the previous steps are rolled back. The whole workflow — validation included — runs under the panel's single tunnel lock, so two concurrent requests cannot both claim the same subdomain or port.
82
+
83
+ The carrying agent's sync timer picks the tunnel up within about 30 seconds and starts forwarding the port; `lamaste-agent update` on the agent applies it immediately.
84
+
85
+ **Request:**
86
+
87
+ ```json
88
+ {
89
+ "subdomain": "app",
90
+ "port": 3000,
91
+ "agentLabel": "laptop",
92
+ "description": "React development server",
93
+ "accessMode": "restricted",
94
+ "maxBodySizeMb": 10
95
+ }
96
+ ```
97
+
98
+ | Field | Type | Validation | Description |
99
+ | --------------- | --------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
100
+ | `subdomain` | `string` | Lowercase alphanumeric + hyphens, max 63 chars, cannot start/end with hyphen | The subdomain to create |
101
+ | `port` | `integer` | 1024 - 65535, not a [reserved port](#port) | Port the agent forwards |
102
+ | `agentLabel` | `string` | An enrolled, non-revoked agent's label | **Required for admin certificates.** An agent certificate may omit it (defaults to itself) but may not name another agent |
103
+ | `description` | `string` | Max 200 chars, optional (defaults to `""`) | Human-readable description |
104
+ | `type` | `string` | `'app'` (default), `'panel'`, or `'plugin'` | `panel` requires the `panel:expose` capability; `plugin` is admin-only |
105
+ | `accessMode` | `string` | `'restricted'` (default), `'authenticated'`, `'public'` | `authenticated` and `public` are admin-only |
106
+ | `maxBodySizeMb` | `integer` | 1 - 10240 for admins, 1 - 100 for agents; optional (defaults to 10) | Written into the vhost as `client_max_body_size`; larger requests get HTTP 413. Raise it for apps that take big uploads |
107
+
108
+ **Subdomain regex:**
109
+
110
+ ```
111
+ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
112
+ ```
113
+
114
+ **Reserved subdomains** (cannot be used):
115
+
116
+ `panel`, `auth`, `tunnel`, `www`, `mail`, `ftp`, `api`
117
+
118
+ **Reserved prefix:** Subdomains starting with `agent-` are reserved for agent panel tunnels (created via `POST /api/tunnels/expose-panel`). Regular tunnel creation with `agent-` prefixed subdomains is rejected unless `type` is `'panel'`.
119
+
120
+ **Who may create:** an admin certificate, or an agent certificate with `tunnels:write` for a tunnel it carries itself. A **plugin-agent** certificate (`CN=plugin-agent:...`) cannot create tunnels: it belongs to a plugin running on an agent, not to a machine running a Chisel client, so it holds no Chisel credential and could carry nothing. Create a plugin's tunnel with the agent's own certificate or as an administrator.
121
+
122
+ ```bash
123
+ curl -s --cert client.p12:password \
124
+ -X POST \
125
+ -H "Content-Type: application/json" \
126
+ -d '{"subdomain":"app","port":3000,"agentLabel":"laptop","description":"React dev server"}' \
127
+ https://203.0.113.42:9292/api/tunnels | jq
128
+ ```
129
+
130
+ **Response (201):**
131
+
132
+ ```json
133
+ {
134
+ "ok": true,
135
+ "tunnel": {
136
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
137
+ "subdomain": "app",
138
+ "fqdn": "app.example.com",
139
+ "port": 3000,
140
+ "description": "React dev server",
141
+ "type": "app",
142
+ "accessMode": "restricted",
143
+ "agentLabel": "laptop",
144
+ "maxBodySizeMb": 10,
145
+ "enabled": true,
146
+ "createdAt": "2026-03-13T14:30:00.000Z"
147
+ }
148
+ }
149
+ ```
150
+
151
+ **Errors:**
152
+
153
+ | Status | Body | When |
154
+ | ------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
155
+ | 400 | `{"error":"Validation failed","details":{"issues":[...]}}` | Invalid subdomain format, port out of range or reserved |
156
+ | 400 | `{"error":"agentLabel is required: name the agent that will carry the tunnel"}` | Admin certificate did not name the carrying agent |
157
+ | 400 | `{"error":"Failed to create tunnel","details":"Agent 'x' is not enrolled or has been revoked"}` | `agentLabel` does not name an active agent |
158
+ | 400 | `{"error":"Failed to create tunnel","details":"Subdomain 'panel' is reserved"}` | Subdomain is in the reserved list |
159
+ | 400 | `{"error":"Failed to create tunnel","details":"Subdomain 'app' is already in use"}` | Another tunnel uses this subdomain |
160
+ | 400 | `{"error":"Failed to create tunnel","details":"Port 3000 is already in use by another tunnel"}` | Another tunnel uses this port |
161
+ | 400 | `{"error":"Failed to create tunnel","details":"Domain 'app.example.com' is already served by a static site"}` | A static site or site alias already answers on this hostname |
162
+ | 400 | `{"error":"Domain and email must be configured before creating tunnels"}` | Domain not set in config |
163
+ | 403 | `{"error":"Agents can only create tunnels they carry themselves"}` | Agent certificate named another agent |
164
+ | 403 | `{"error":"Plugin-agent certificates cannot create tunnels — use the agent certificate of ..."}` | Plugin-agent certificate |
165
+ | 403 | `{"error":"Only administrators can set a body limit above 100 MiB"}` | Agent certificate asked for `maxBodySizeMb` over 100 |
166
+ | 403 | `{"error":"Only administrators can set tunnel access mode to public or authenticated"}` | Agent certificate asked for a non-restricted mode |
167
+ | 500 | `{"error":"Failed to create tunnel","details":"Certificate issuance failed: ..."}` | certbot failed |
168
+ | 500 | `{"error":"Failed to create tunnel","details":"Nginx configuration failed: ..."}` | nginx vhost write or test failed |
169
+ | 500 | `{"error":"Failed to create tunnel","details":"State persistence failed: ..."}` | Failed to write tunnels.json |
170
+ | 500 | `{"error":"Failed to create tunnel","details":"Chisel reconfiguration failed: ..."}` | Chisel authfile sync failed |
171
+
172
+ ### Creation Flow
173
+
174
+ ```
175
+ Take the tunnel lock (held until the response)
176
+ │
177
+ Validate input
178
+ ├── Check agentLabel names an active agent
179
+ ├── Check subdomain not reserved
180
+ ├── Check subdomain uniqueness
181
+ ├── Check port not reserved
182
+ └── Check port uniqueness
183
+ │
184
+ ▼
185
+ Step 1: certbot issues TLS cert for <subdomain>.<domain>
186
+ │
187
+ ▼
188
+ Step 2: Write nginx vhost for the subdomain
189
+ │ (rollback: remove vhost on failure)
190
+ ▼
191
+ Step 3: Save tunnel to tunnels.json
192
+ │ (rollback: remove vhost on failure)
193
+ ▼
194
+ Step 4: Sync Chisel — authfile grants the owner the port (hot-reloaded)
195
+ │ (rollback: remove tunnel from state, remove vhost)
196
+ ▼
197
+ Return 201 with tunnel object
198
+ ```
199
+
200
+ ---
201
+
202
+ ### `PATCH /api/tunnels/:id`
203
+
204
+ Changes any of: carrying agent, access mode, request body limit, enabled state. All of them are applied in one workflow under the tunnel lock, in that order — ownership first, so a later failure still leaves the tunnel with the agent the caller asked for; access mode and body limit next; the enabled state last, so a tunnel being disabled is never re-enabled by the vhost rewrite before it. Authorization is checked against the tunnel as it is when the lock is taken.
205
+
206
+ - **`enabled`** — when disabled, the tunnel's nginx vhost symlink is removed (the config file is kept) and the owner's Chisel grant for the port is withdrawn (Chisel restarts, which ends sessions still holding it). Re-enabling restores both. Only enabled tunnels appear in an agent's config, and the owner's sync timer drops or adds the remote within about 30 seconds.
207
+ - **`agentLabel`** (admin only) — moves the tunnel to another active agent. The previous owner loses the port grant in the same Chisel sync that gives it to the new owner. Both agents' sync timers apply the move within about 30 seconds; `lamaste-agent update` applies it at once. Agent panel tunnels (`type: 'panel'`) belong to the agent they expose and cannot be moved.
208
+ - **`accessMode`** and **`maxBodySizeMb`** — both live in the tunnel's nginx vhost, which is re-rendered and tested (nginx keeps the previous vhost if the test fails). Gatekeeper watches the tunnel state and picks up an access-mode change on its own. A disabled tunnel stays disabled. `public` and `authenticated` are admin-only, as on creation. Agent panel tunnels have a fixed mTLS vhost and cannot be reconfigured.
209
+
210
+ **Request:**
211
+
212
+ ```json
213
+ {
214
+ "enabled": false,
215
+ "agentLabel": "build-server"
216
+ }
217
+ ```
218
+
219
+ | Field | Type | Validation | Description |
220
+ | --------------- | --------- | -------------------------------------- | --------------------------------------- |
221
+ | `enabled` | `boolean` | Optional | Whether the tunnel should be active |
222
+ | `agentLabel` | `string` | Optional; an active agent; admin only | The agent that should carry the tunnel |
223
+ | `accessMode` | `string` | Optional; non-restricted is admin only | `restricted`, `authenticated`, `public` |
224
+ | `maxBodySizeMb` | `integer` | Optional; 1 - 10240 (agents: 1 - 100) | Largest request body in MiB |
225
+
226
+ At least one field is required; unknown fields are rejected.
227
+
228
+ ```bash
229
+ curl -s --cert client.p12:password \
230
+ -X PATCH \
231
+ -H "Content-Type: application/json" \
232
+ -d '{"agentLabel":"build-server"}' \
233
+ https://203.0.113.42:9292/api/tunnels/a1b2c3d4-e5f6-7890-abcd-ef1234567890 | jq
234
+ ```
235
+
236
+ **Response (200):** `{ "ok": true, "tunnel": { ... } }` with the updated tunnel object.
237
+
238
+ **Errors:**
239
+
240
+ | Status | Body | When |
241
+ | ------ | ----------------------------------------------------------------------------------------------- | ---------------------------------------------- |
242
+ | 400 | `{"error":"Validation failed",...}` | Empty body, unknown field, or bad label format |
243
+ | 400 | `{"error":"Failed to update tunnel","details":"Agent 'x' is not enrolled or has been revoked"}` | `agentLabel` does not name an active agent |
244
+ | 400 | `{"error":"Failed to update tunnel","details":"Agent panel tunnels belong to ..."}` | Tried to move a panel tunnel |
245
+ | 403 | `{"error":"Only administrators can change which agent carries a tunnel"}` | Agent certificate sent `agentLabel` |
246
+ | 403 | `{"error":"Only administrators can set a body limit above 100 MiB"}` | Agent certificate sent `maxBodySizeMb` > 100 |
247
+ | 404 | `{"error":"Tunnel not found"}` | No such tunnel, or it belongs to another agent |
248
+ | 500 | `{"error":"Failed to update tunnel","details":"..."}` | nginx, Chisel, or state operation failed |
249
+
250
+ ---
251
+
252
+ ### `DELETE /api/tunnels/:id`
253
+
254
+ Deletes a tunnel by its UUID: removes the nginx vhost, removes the tunnel from the state file, and syncs Chisel so the owner's grant for the port is withdrawn.
255
+
256
+ The TLS certificate is not deleted (it is harmless to keep and may be reused if the subdomain is recreated).
257
+
258
+ ```bash
259
+ curl -s --cert client.p12:password \
260
+ -X DELETE \
261
+ https://203.0.113.42:9292/api/tunnels/a1b2c3d4-e5f6-7890-abcd-ef1234567890 | jq
262
+ ```
263
+
264
+ **Response (200):** `{ "ok": true }`
265
+
266
+ **Errors:**
267
+
268
+ | Status | Body | When |
269
+ | ------ | ----------------------------------------------------- | ---------------------------------------------- |
270
+ | 404 | `{"error":"Tunnel not found"}` | No such tunnel, or it belongs to another agent |
271
+ | 500 | `{"error":"Failed to delete tunnel","details":"..."}` | nginx, Chisel, or state operation failed |
272
+
273
+ ---
274
+
275
+ ### `GET /api/tunnels/agent-config`
276
+
277
+ Returns the Chisel client configuration for one agent: the enabled tunnels it carries that the Chisel authfile actually grants it, and nothing else. A tunnel on a reserved port or sharing its port with another tunnel (possible only in state written before those checks) is withheld — Chisel would refuse the agent's whole session for it. Used by `lamaste-agent setup`, `update`, and the agent's 30-second sync timer (`lamaste-agent sync`), on both macOS and Linux.
278
+
279
+ **Required capability:** `tunnels:read`
280
+
281
+ **Query parameters:**
282
+
283
+ | Parameter | Description |
284
+ | --------- | ---------------------------------------------------------------------------------------------------------------------- |
285
+ | `agent` | Admin certificates only: the agent whose config to return. Ignored for agent certificates, which always get their own. |
286
+
287
+ ```bash
288
+ curl -s --cert client.p12:password \
289
+ https://203.0.113.42:9292/api/tunnels/agent-config | jq
290
+ ```
291
+
292
+ **Response (200) — agent certificate, or admin with `?agent=`:**
293
+
294
+ ```json
295
+ {
296
+ "domain": "example.com",
297
+ "chiselServerUrl": "https://tunnel.example.com:443",
298
+ "agentLabel": "laptop",
299
+ "chiselArgs": ["client", "https://tunnel.example.com:443", "R:127.0.0.1:3000:127.0.0.1:3000"],
300
+ "chiselCredentialIssuedAt": "2026-09-20T10:12:00.000Z",
301
+ "tunnels": [{ "port": 3000, "subdomain": "app" }]
302
+ }
303
+ ```
304
+
305
+ **Response (200) — admin without `?agent=`:** `{ "domain": "...", "chiselServerUrl": "..." }` only. A Chisel configuration is meaningless without an agent to carry it.
306
+
307
+ | Field | Type | Description |
308
+ | -------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
309
+ | `domain` | `string` | Base domain |
310
+ | `chiselServerUrl` | `string` | Full URL to the Chisel server endpoint |
311
+ | `agentLabel` | `string` | The agent this configuration is for |
312
+ | `chiselArgs` | `string[]` | Chisel client arguments: `client`, the server URL, and one `R:127.0.0.1:<port>:127.0.0.1:<port>` remote per tunnel. No `--tls-skip-verify` (the agent verifies the relay's certificate) and no credential — the agent supplies its own through the service environment, never argv |
313
+ | `chiselCredentialIssuedAt` | `string \| null` | When the agent's current Chisel credential was issued — a timestamp, never the credential. An agent holding a credential with a different issue time (an administrator rotated it) fetches the current one from `GET /api/agents/me/chisel-credential` |
314
+ | `tunnels` | `array` | The agent's enabled tunnels with port and subdomain |
315
+
316
+ **Errors:**
317
+
318
+ | Status | Body | When |
319
+ | ------ | --------------------------------------------- | ------------------------ |
320
+ | 400 | `{"error":"Domain not configured"}` | Domain has not been set |
321
+ | 500 | `{"error":"Failed to generate agent config"}` | Config generation failed |
322
+
323
+ ## Chisel Grants
324
+
325
+ The Chisel server authenticates every client against `/etc/lamalibre/lamaste/chisel-users`. That file is rendered by the panel from two persisted inputs — the per-agent credential store and the tunnel state — and re-rendered whenever either changes:
326
+
327
+ ```json
328
+ {
329
+ "lamaste-no-grants:<random>": [],
330
+ "agent-laptop:<password>": ["^R:127\\.0\\.0\\.1:3000$"],
331
+ "agent-build-server:<password>": []
332
+ }
333
+ ```
334
+
335
+ - Each `agent-<label>` user may reverse-bind exactly the ports of the enabled tunnels it owns. An agent with no tunnels can connect but bind nothing. The patterns are anchored because Chisel matches them unanchored — `^…$` is what keeps port `2001` from also granting `20010`.
336
+ - `lamaste-no-grants` exists so the file is never empty. Chisel disables authentication entirely when its authfile has no users, which would turn a freshly installed server (or one whose agents are all revoked) into an open relay. Its password is random, stored at `chisel-sentinel` (0600), and never handed out.
337
+ - A port that belongs to a Lamaste service ([reserved ports](#port)), or a port that two tunnels claim, is never granted — even if the state file somehow holds such a tunnel.
338
+ - The file is `0640`, owner `lamaste`, group `lamaste-chisel` — the group the Chisel server runs as (`User=nobody`, `Group=lamaste-chisel`). The panel is a member of that group and writes the file itself, without sudo: a `0600` temp file in the same directory, `chgrp`, `chmod 0640`, fsync, rename. Chisel therefore never sees the file with the wrong permissions.
339
+ - Chisel 1.12.0 reloads the authfile on every replace, and the reload applies to new sessions. Additions (a new agent, a new grant) therefore take effect without a restart. A revocation (a removed or rotated password, a withdrawn grant) restarts Chisel with `systemctl try-restart`, because a live session keeps what it already bound — and `try-restart` leaves a deliberately stopped Chisel stopped.
340
+
341
+ ### Why the grants matter to the agent
342
+
343
+ Chisel rejects a client's **whole** session when one remote it requests is not granted. An agent still asking for a disabled or moved tunnel's port would lose all of its other tunnels too. That is why agents run a sync timer that drops such remotes within about 30 seconds — see [Agent Setup](../02-guides/agent-setup.md#how-tunnel-changes-reach-the-agent).
344
+
345
+ ### Upgrading from a version without tunnel ownership
346
+
347
+ On startup the panel reconciles older state. An agent panel tunnel without an `agentLabel` is bound to the agent its hostname names (`agent-<label>`) when that agent is active, and never to any other. Other tunnels without an `agentLabel` are bound to the only active machine agent when exactly one exists (plugin-agent certificates carry no tunnels and are not counted). With several active agents the carrier cannot be known, so those tunnels stay **unassigned**: they are carried by no agent, show as `unassigned` in the panel, are logged at startup, and come back as soon as an administrator moves them to an agent (`PATCH` with `agentLabel`).
348
+
349
+ Tunnels on a reserved port, or two tunnels on the same port, are granted to no agent and left out of every `agent-config`; the panel logs them at startup. Delete each and recreate it on a free port.
350
+
351
+ ### When an agent certificate is revoked
352
+
353
+ Revoking an agent's certificate releases its tunnels: its app and plugin tunnels become unassigned (their hostnames stay reserved and dark until an administrator assigns them), and its `agent-<label>` panel tunnel is removed with its vhost. A later enrollment that reuses the label therefore inherits nothing.
354
+
355
+ ## Validation Rules
356
+
357
+ ### Subdomain
358
+
359
+ - Lowercase letters, digits, and hyphens only
360
+ - Cannot start or end with a hyphen
361
+ - Maximum 63 characters
362
+ - Must not be one of: `panel`, `auth`, `tunnel`, `www`, `mail`, `ftp`, `api`
363
+ - Must not start with `agent-` (reserved for agent panel tunnels, unless `type` is `'panel'`)
364
+ - Must be unique across all tunnels
365
+ - Must not be a hostname a static site or site alias already serves (checked on tunnel creation; site creation and alias changes check the reverse)
366
+
367
+ ### Port
368
+
369
+ - Must be an integer
370
+ - Minimum: 1024 (no privileged ports)
371
+ - Maximum: 65535
372
+ - Must not be a reserved port: `3100` (panel server), `9090` (Chisel server), `9091` (Authelia), `9292` (IP-based panel listener), `9294` (Gatekeeper)
373
+ - Must be unique across all tunnels — Chisel binds the same port on the server and on the agent
374
+
375
+ A tunnel's vhost proxies to `127.0.0.1:<port>` on the relay, so a tunnel on one of Lamaste's own ports would publish that internal service on the internet. For the panel ports that would be an administrator takeover, not a leak: the panel trusts the client-certificate headers nginx sets. The reserved ports are rejected by the request schema, again inside the tunnel workflow, and never granted in the Chisel authfile.
376
+
377
+ ### Agent label
378
+
379
+ - An enrolled, non-revoked agent: `^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$`
380
+
381
+ ## Quick Reference
382
+
383
+ | Method | Path | Description |
384
+ | ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
385
+ | GET | `/api/tunnels` | List tunnels (an agent sees only its own) |
386
+ | POST | `/api/tunnels` | Create a tunnel carried by an agent |
387
+ | PATCH | `/api/tunnels/:id` | Move (admin), access mode, body limit, enable/disable |
388
+ | DELETE | `/api/tunnels/:id` | Delete a tunnel by UUID |
389
+ | GET | `/api/tunnels/agent-config` | One agent's Chisel client config (macOS & Linux) |
390
+ | GET | `/api/tunnels/agent-panel-status` | Check if agent has an exposed panel tunnel |
391
+ | POST | `/api/tunnels/expose-panel` | Create mTLS panel tunnel for requesting agent (refused when a static site or alias serves `agent-<label>.<domain>`) |
392
+ | DELETE | `/api/tunnels/retract-panel` | Remove the panel tunnel for requesting agent |
393
+
394
+ ### curl Cheat Sheet
395
+
396
+ ```bash
397
+ # List tunnels
398
+ curl -s --cert client.p12:password \
399
+ https://203.0.113.42:9292/api/tunnels | jq
400
+
401
+ # Create a tunnel carried by the agent "laptop"
402
+ curl -s --cert client.p12:password \
403
+ -X POST -H "Content-Type: application/json" \
404
+ -d '{"subdomain":"myapp","port":8080,"agentLabel":"laptop"}' \
405
+ https://203.0.113.42:9292/api/tunnels | jq
406
+
407
+ # Move it to another agent
408
+ curl -s --cert client.p12:password \
409
+ -X PATCH -H "Content-Type: application/json" \
410
+ -d '{"agentLabel":"build-server"}' \
411
+ https://203.0.113.42:9292/api/tunnels/<uuid> | jq
412
+
413
+ # Delete tunnel
414
+ curl -s --cert client.p12:password \
415
+ -X DELETE \
416
+ https://203.0.113.42:9292/api/tunnels/<uuid> | jq
417
+ ```