@localctl/cli 0.2.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/assets/manifests/namespace-addons.yaml +4 -0
  4. package/assets/manifests/tls-store.yaml +8 -0
  5. package/bin/localctl.js +2 -0
  6. package/package.json +54 -0
  7. package/schema/app.schema.json +127 -0
  8. package/src/commands/addons.js +56 -0
  9. package/src/commands/doctor.js +43 -0
  10. package/src/commands/down.js +35 -0
  11. package/src/commands/exec.js +15 -0
  12. package/src/commands/generateManifests.js +12 -0
  13. package/src/commands/hosts.js +23 -0
  14. package/src/commands/logs.js +45 -0
  15. package/src/commands/new.js +55 -0
  16. package/src/commands/profiles.js +180 -0
  17. package/src/commands/prune.js +49 -0
  18. package/src/commands/registryInfo.js +18 -0
  19. package/src/commands/reload.js +44 -0
  20. package/src/commands/secrets.js +75 -0
  21. package/src/commands/setup.js +36 -0
  22. package/src/commands/status.js +136 -0
  23. package/src/commands/uninstall.js +105 -0
  24. package/src/commands/up.js +123 -0
  25. package/src/index.js +36 -0
  26. package/src/lib/addonDirs.js +43 -0
  27. package/src/lib/addonLoader.js +190 -0
  28. package/src/lib/addons/cluster/logging/addon.yaml +46 -0
  29. package/src/lib/addons/cluster/logging/config/all-logs.json +21 -0
  30. package/src/lib/addons/cluster/monitoring/addon.yaml +33 -0
  31. package/src/lib/addons/mongo/addon.yaml +18 -0
  32. package/src/lib/addons/postgres/addon.yaml +39 -0
  33. package/src/lib/addons/redis/addon.yaml +29 -0
  34. package/src/lib/appSecrets.js +60 -0
  35. package/src/lib/clusterAddons.js +59 -0
  36. package/src/lib/clusterProvision.js +170 -0
  37. package/src/lib/configSchema.js +103 -0
  38. package/src/lib/constants.js +16 -0
  39. package/src/lib/engine.js +20 -0
  40. package/src/lib/exec.js +77 -0
  41. package/src/lib/hostsFile.js +79 -0
  42. package/src/lib/loadConfig.js +51 -0
  43. package/src/lib/logger.js +9 -0
  44. package/src/lib/manifestGen.js +210 -0
  45. package/src/lib/paths.js +41 -0
  46. package/src/lib/podman.js +72 -0
  47. package/src/lib/profileStore.js +134 -0
  48. package/src/lib/projectContext.js +32 -0
  49. package/src/lib/prompt.js +97 -0
  50. package/src/lib/toolInstall.js +30 -0
  51. package/src/lib/ui.js +50 -0
  52. package/src/lib/workloadReadiness.js +35 -0
  53. package/templates/Tiltfile.tmpl +35 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Moreira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,431 @@
1
+ # localctl
2
+
3
+ 📖 **[Full documentation site](https://psilvmoreira.github.io/localctl/)** — same content as
4
+ below, plus diagrams, search, and a nicer read. Update the `USERNAME`/`repo_url` placeholders in
5
+ `mkdocs.yml` and this line once this repo has a real GitHub remote (see [Docs
6
+ Site](#docs-site) below for how it's built/deployed).
7
+
8
+ A local Kubernetes dev platform for macOS and Windows: one shared cluster, one `*.local.test`
9
+ subdomain per app, config-driven deploys, live-reload via Tilt, HTTPS everywhere, and a single
10
+ CLI — `localctl` — that owns the whole lifecycle from first install to full teardown.
11
+
12
+ Point it at any app repo, describe the app in a small JSON file, and `localctl app up` builds it,
13
+ deploys it, wires up its subdomain and TLS certificate, and live-syncs your code on every save.
14
+
15
+ ```
16
+ $ localctl app up
17
+ ok orders-api -> https://orders.local.test
18
+ ```
19
+
20
+ ## Table of contents
21
+
22
+ - [Features](#features)
23
+ - [Requirements](#requirements)
24
+ - [Installing the CLI](#installing-the-cli)
25
+ - [Quickstart: deploy your first app](#quickstart-deploy-your-first-app)
26
+ - [CLI reference](#cli-reference)
27
+ - [App configuration](#app-configuration)
28
+ - [Multiple projects](#multiple-projects)
29
+ - [Architecture](#architecture)
30
+ - [Debugging](#debugging)
31
+ - [Adding tools (databases, logging, more)](#adding-tools-databases-logging-more)
32
+ - [Uninstalling](#uninstalling)
33
+ - [Troubleshooting](#troubleshooting)
34
+ - [Repo layout](#repo-layout)
35
+ - [Documentation index](#documentation-index)
36
+ - [Docs Site](#docs-site)
37
+
38
+ ## Features
39
+
40
+ - **One cluster, one CLI.** A single k3d (k3s-in-Docker) cluster runs every app you're developing
41
+ locally. No per-project Kubernetes setup, no VM sprawl.
42
+ - **A real subdomain per app.** `myapp.local.test`, `orders.local.test`, whatever you name it —
43
+ routed by Traefik, no port-juggling, no `localhost:3000` vs `localhost:3001` confusion.
44
+ - **HTTPS by default.** A locally-trusted wildcard certificate (via `mkcert`) is wired into the
45
+ cluster's ingress automatically. Every app gets a green padlock, not a browser warning.
46
+ - **Config-driven, not YAML-driven.** Each app repo gets one `.local/config.json`. `localctl`
47
+ generates every Kubernetes manifest from it — you never hand-write or hand-edit Kubernetes YAML.
48
+ - **Fast inner loop.** Tilt builds your image, pushes it to a local registry, deploys it, and
49
+ live-syncs changed files straight into the running container — no full rebuild for every edit.
50
+ - **Debugger-friendly.** Declare a debug port in config and it's forwarded to `localhost`
51
+ automatically. Node, Python, Go, Java, and .NET patterns are documented and ready to copy.
52
+ - **Cross-platform, including Podman.** Works with Docker Desktop, Podman Desktop, or Rancher
53
+ Desktop. Podman-specific quirks (BuildKit protocol, insecure-registry trust) are detected and
54
+ handled automatically — you don't need to know they exist.
55
+ - **Real Helm charts, zero JS.** Per-app databases (Postgres, Redis, MongoDB via Bitnami's own
56
+ charts) and cluster-wide addons (a Grafana/Loki logging stack, a Prometheus metrics stack) are
57
+ each just one `addon.yaml` file pointing at a published chart — adding a new one is dropping in
58
+ a folder, no code required. Each dependency also gets a `wait-for-<type>` init container
59
+ automatically, so your app never races one that isn't ready yet.
60
+ - **Real readiness, not just "the process started."** Every app gets a TCP (or HTTP, if you set a
61
+ path) readiness/liveness probe by default — `kubectl rollout status` succeeding actually means
62
+ it's serving.
63
+ - **Your own secrets, never plaintext.** API keys and tokens you supply (not addon-generated
64
+ credentials) go through the same real-Secret/`secretKeyRef` pipeline — `localctl app secrets
65
+ set/show` to write and read them, `.local/config.json` only ever holds the key name.
66
+ - **Clean install, clean uninstall.** Nothing is installed with `sudo`-requiring global state where
67
+ avoidable, and `localctl uninstall` reverses everything `localctl setup` did.
68
+ - **More than one project when you need it.** `default` (one shared cluster) is all most people
69
+ ever need, but `localctl profiles new` can spin up an isolated project - either its own separate
70
+ cluster, or just a namespace-scoped slice of an existing one - and directory-based resolution
71
+ (walk up for the nearest `.local/project.json`, same idea as `.git`/`.claude`) means `app`
72
+ commands just know which project they're in.
73
+
74
+ ## Requirements
75
+
76
+ - **macOS** or **Windows** (PowerShell)
77
+ - One running container engine: **Docker Desktop**, **Podman Desktop**, or **Rancher Desktop**
78
+ - **Git**, to clone this repo and your app repos
79
+ - **Homebrew** (macOS) or **winget** (Windows) — used once to install missing tools
80
+
81
+ Everything else (`k3d`, `kubectl`, `tilt`, `mkcert`, `helm`, Node.js) is installed automatically if it's
82
+ missing.
83
+
84
+ ## Installing the CLI
85
+
86
+ **From npm (recommended):**
87
+
88
+ ```
89
+ npm install -g @localctl/cli
90
+ localctl setup
91
+ ```
92
+
93
+ Pre-releases (from the `beta` branch) are published under the `beta` dist-tag: `npm install -g @localctl/cli@beta`.
94
+
95
+ **From source** (to hack on `localctl` itself):
96
+
97
+ 1. Clone this repo somewhere permanent (it acts as the control plane for every app you run
98
+ locally, so keep it around — don't nest it inside an app repo):
99
+
100
+ ```
101
+ git clone <this-repo-url> ~/dev/localctl
102
+ cd ~/dev/localctl
103
+ ```
104
+
105
+ 2. Install `localctl` as a standard global npm package:
106
+
107
+ ```
108
+ cd cli
109
+ npm install
110
+ npm install -g .
111
+ ```
112
+
113
+ > **Hit `EACCES`?** That means npm's global install directory isn't writable by your user —
114
+ > common on macOS when Node wasn't installed through a version manager. Fix it once, for every
115
+ > future global npm install, not just this one ([npm's own documented fix](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)):
116
+ > ```
117
+ > mkdir ~/.npm-global
118
+ > npm config set prefix ~/.npm-global
119
+ > echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc # or ~/.bash_profile
120
+ > source ~/.zshrc
121
+ > ```
122
+ > Then re-run `npm install -g .` from `cli/`.
123
+
124
+ 3. Run the actual infra setup:
125
+
126
+ ```
127
+ localctl setup
128
+ ```
129
+
130
+ This detects your container engine, installs `k3d`/`kubectl`/`tilt`/`mkcert`/`helm` if missing,
131
+ creates the local cluster, starts the local image registry, generates and trusts the
132
+ `*.local.test` TLS certificate, and syncs your hosts file.
133
+
134
+ You'll see up to two password prompts during this step — both expected, both one-time:
135
+ - **mkcert** asking to trust its local certificate authority (macOS Touch ID/password prompt)
136
+ - **sudo/Administrator** asking to write the `*.local.test` entries to your hosts file
137
+
138
+ 4. Confirm it worked:
139
+
140
+ ```
141
+ localctl doctor
142
+ ```
143
+
144
+ Every line should read `[ok]`.
145
+
146
+ From here on, `localctl` is a normal globally-installed command:
147
+
148
+ ```
149
+ localctl setup # re-check or repair the infra at any time — fully idempotent
150
+ localctl uninstall # tear the infra back down (see Uninstalling below)
151
+ ```
152
+
153
+ <details>
154
+ <summary>Alternative: install without touching npm's global config</summary>
155
+
156
+ If you'd rather not change npm's global prefix, `bin/bootstrap.sh` (macOS) / `bootstrap.ps1`
157
+ (Windows) install `localctl` as a self-contained PATH shim in `~/.localctl/bin` instead of a real
158
+ npm global package — no `npm install -g`, no `EACCES` possible, same `localctl` command either
159
+ way:
160
+
161
+ ```
162
+ ./bin/bootstrap.sh # macOS — installs the shim, then runs `localctl setup` for you
163
+ ./bin/bootstrap.ps1 # Windows (PowerShell)
164
+ ```
165
+
166
+ Uninstall the shim version with `./bin/uninstall.sh` / `uninstall.ps1` instead of
167
+ `npm uninstall -g` (see [Uninstalling](#uninstalling)).
168
+ </details>
169
+
170
+ ## Quickstart: deploy your first app
171
+
172
+ Try it immediately with the bundled example, no app of your own required:
173
+
174
+ ```
175
+ cd examples/node-app
176
+ localctl app up
177
+ ```
178
+
179
+ `localctl app up` runs in the background by default — it prints the app's real URL once the
180
+ deployment is ready and hands control of the terminal straight back to you, so closing that
181
+ terminal doesn't stop the app:
182
+
183
+ ```
184
+ $ localctl app up
185
+ > Waiting for the deployment to become ready...
186
+ deployment "node-app" successfully rolled out
187
+
188
+ ok node-app -> https://node-app.local.test
189
+ debug: localhost:9229
190
+ logs: localctl app logs node-app -f
191
+ status: localctl app status
192
+ stop: localctl app down
193
+ ```
194
+
195
+ Open <https://node-app.local.test>. Live-sync keeps working in the background exactly as if it
196
+ were running in the foreground — edit a file under any path listed in `"sync"` and it updates
197
+ without a full rebuild, as long as something inside the container watches for the change and
198
+ restarts (`nodemon`, `uvicorn --reload`, etc. — see [Debugging](#debugging) and
199
+ `examples/node-app/Dockerfile` for a working pattern).
200
+
201
+ Want the interactive Tilt UI instead (build logs streaming live in your terminal)? Add `-f`:
202
+
203
+ ```
204
+ localctl app up -f
205
+ ```
206
+
207
+ To use it with your own app:
208
+
209
+ 1. From inside your app's repo:
210
+
211
+ ```
212
+ localctl app new
213
+ ```
214
+
215
+ This scaffolds `.local/config.json` and a generic `Tiltfile`. `localctl app new` asks for an app
216
+ name and port; edit `.local/config.json` afterward for anything else (env vars, database
217
+ dependencies, debug settings — see [App configuration](#app-configuration)).
218
+
219
+ 2. Deploy it: `localctl app up` (see above).
220
+
221
+ 3. Check on it any time. Run from inside the app's own repo (or `-a <name>` from anywhere) for
222
+ that app's detail:
223
+
224
+ ```
225
+ $ localctl app status
226
+ node-app
227
+ namespace: node-app
228
+ ready: 1/1
229
+ url: https://node-app.local.test
230
+ debug: localhost:9229
231
+ dev loop: running (pid 4815)
232
+ dependencies:
233
+ node-app-postgres 1/1
234
+ ```
235
+
236
+ From anywhere else (or with `-A`/`--all`), it's a table of every app:
237
+
238
+ ```
239
+ $ localctl app status
240
+ APP NAMESPACE READY URL DEV LOOP
241
+ node-app node-app 1/1 https://node-app.local.test running (pid 4815)
242
+ ```
243
+
244
+ 4. When you're done:
245
+
246
+ ```
247
+ localctl app down
248
+ ```
249
+
250
+ Stops the background dev loop (if any) and tears down just this app; the shared cluster keeps
251
+ running for everything else.
252
+
253
+ ## CLI reference
254
+
255
+ | Command | Purpose |
256
+ |---|---|
257
+ | `localctl setup` | Install/repair the `default` project's infra (container engine, cluster, TLS, hosts). Idempotent. |
258
+ | `localctl uninstall [-y]` | Remove everything `setup`/`profiles` set up: every project's cluster, registries, hosts entries, certs/state. |
259
+ | `localctl doctor` | Quick health check of the active project's environment. |
260
+ | `localctl app new` | Scaffold `.local/config.json` + `Tiltfile` in the current app repo. |
261
+ | `localctl app up [-f]` | Build, deploy, and live-reload the app in the current directory. Runs detached in the background by default (survives closing the terminal) and prints the app's URL once ready; `-f`/`--foreground` runs the interactive Tilt UI instead. |
262
+ | `localctl app down` | Stop the background dev loop (if any) and tear down the app deployed from the current directory. |
263
+ | `localctl app reload [-a <name>]` | Force an immediate rebuild/redeploy of a running background dev loop, without waiting on a file change. |
264
+ | `localctl app status [-a <name>] [-A]` | App detail (readiness, URL, debug port, dependencies, dev-loop state) when run inside an app repo or with `-a <name>`; a table of every app otherwise, or always with `-A`/`--all`. |
265
+ | `localctl app logs <name> [-f]` | Tail logs for a deployed app. |
266
+ | `localctl app exec [cmd...]` | Shell into the running app's pod (`kubectl exec`); defaults to `sh`. |
267
+ | `localctl app prune [-y]` | Permanently delete an already-torn-down app's leftover data (dependency PVCs, secrets) - `app down` deliberately leaves these alone. |
268
+ | `localctl app secrets set <KEY> [value]` | Set your own secret value (hidden prompt if not given inline) - stored as a real k8s Secret, never in `.local/config.json`. |
269
+ | `localctl app secrets list` / `show [KEY]` / `unset <KEY>` | List which keys are set, reveal actual value(s), or remove one. |
270
+ | `localctl hosts sync` / `list` | Manage the `*.local.test` entries in your hosts file. |
271
+ | `localctl addons list` / `enable <name>` / `disable <name>` | Manage cluster-wide addons (e.g. `logging`, `monitoring`). |
272
+ | `localctl addons status` | Every addon instance in the active project's cluster — per-app dependencies and cluster-wide addons — shown separately from your actual apps. |
273
+ | `localctl profiles new <name>` | Create a project - its own cluster (Main project), or namespace-scoped under an existing one. |
274
+ | `localctl profiles list` | List every project this machine knows about, nested under its Main (cluster) project. |
275
+ | `localctl profiles switch <name>` | Make a project active - brings its cluster up if needed, applies its exclusive-teardown setting. |
276
+ | `localctl profiles status` | Show the currently active project. |
277
+
278
+ Run `localctl <command> --help` for options on any of these.
279
+
280
+ ## App configuration
281
+
282
+ Everything about how an app deploys locally lives in that app repo's `.local/config.json`:
283
+
284
+ ```json
285
+ {
286
+ "name": "orders-api",
287
+ "subdomain": "orders",
288
+ "port": 3000,
289
+ "build": { "context": ".", "dockerfile": "Dockerfile" },
290
+ "env": { "NODE_ENV": "development" },
291
+ "debug": { "enabled": true, "port": 9229, "type": "node" },
292
+ "dependencies": [{ "type": "postgres", "database": "orders" }],
293
+ "sync": [{ "localPath": "./src", "remotePath": "/app/src" }],
294
+ "tls": true
295
+ }
296
+ ```
297
+
298
+ This resolves to `https://orders.local.test`, with a dedicated Postgres instance deployed
299
+ alongside it. Point your editor's JSON schema support at `schema/app.schema.json` for
300
+ autocomplete and validation as you type — `localctl app new` sets this up automatically via
301
+ `"$schema"`.
302
+
303
+ Full field-by-field reference: **[docs/config-schema.md](./docs/config-schema.md)**.
304
+
305
+ ## Multiple projects
306
+
307
+ `default` is one shared cluster for everything - fine until you need real isolation (a second
308
+ client's stack, a "staging-like" environment, or just apps you don't want sharing a cluster with
309
+ your main work). From the folder you want it to apply to:
310
+
311
+ ```
312
+ localctl profiles new staging
313
+ ```
314
+
315
+ Choose either your own cluster (a Main project) or a namespace-scoped slice of an existing one, and
316
+ it writes `.local/project.json` there - every app under that folder, and any subfolder below it,
317
+ now resolves to `staging` automatically, the same directory walk-up `.git`/`.claude` use.
318
+ `localctl profiles list` / `status` / `switch <name>` manage which project is current.
319
+
320
+ Full mechanism (exclusive vs. concurrent clusters, namespace scoping, port handling):
321
+ **[docs/architecture.md#multi-project-support](./docs/architecture.md#multi-project-support)**.
322
+
323
+ ## Architecture
324
+
325
+ k3d cluster (Traefik ingress + local registry) → Tilt (build, push, live-sync) → `localctl`
326
+ (config validation, manifest generation, infra lifecycle) → your app repo's
327
+ `.local/config.json`. Nothing about an app's Kubernetes shape is hand-written; it's all generated
328
+ from that one file by `cli/src/lib/manifestGen.js`.
329
+
330
+ Full breakdown of every component and why it was chosen (including the Podman-specific handling):
331
+ **[docs/architecture.md](./docs/architecture.md)**.
332
+
333
+ ## Debugging
334
+
335
+ Set `"debug": { "enabled": true, "port": <port>, "type": "<node|python|go|java|dotnet>" }` in
336
+ `.local/config.json` and the port is forwarded to `localhost` automatically while `localctl app up` is
337
+ running — attach your debugger like you would to any local process. Per-language Dockerfile and
338
+ `launch.json` patterns (including the live-reload watcher each language needs — `nodemon`,
339
+ `uvicorn --reload`, `air`, Spring Boot DevTools): **[docs/debugging.md](./docs/debugging.md)**.
340
+
341
+ ## Adding tools (databases, logging, more)
342
+
343
+ Every addon is one folder with one `addon.yaml` pointing at a real, published Helm chart — no JS.
344
+
345
+ - **Per-app dependencies** (Postgres, Redis, MongoDB today, via Bitnami's own charts) are declared
346
+ in an app's `"dependencies"` array and deployed alongside it, isolated per app, with connection
347
+ details (host, port, credentials via a real k8s Secret) auto-injected into its container.
348
+ - **Cluster-wide addons**: `logging` (Grafana Labs' own loki-stack chart, at
349
+ `https://grafana.local.test` once enabled, dashboard auto-provisioned from a file in the addon's
350
+ `config/` folder) and `monitoring` (prometheus-community's own Prometheus chart, at
351
+ `https://monitoring.local.test`) - shared infrastructure turned on once via `localctl addons
352
+ enable <name>`.
353
+
354
+ Adding a new one — a message broker, a tracing backend, whatever's next — means finding its Helm
355
+ chart and writing one `addon.yaml`: **[docs/adding-tools.md](./docs/adding-tools.md)**.
356
+
357
+ ## Uninstalling
358
+
359
+ ```
360
+ localctl uninstall # every project's cluster/registry, Podman VM config, hosts entries, certs/state
361
+ npm uninstall -g @localctl/cli # + the CLI itself, if you installed it via npm
362
+ ./bin/uninstall.sh # or uninstall.ps1 # + the CLI itself, if you installed it via bin/bootstrap.sh's shim
363
+ ```
364
+
365
+ Prompts for confirmation (skip with `-y`/`--yes`); every step is best-effort and safe to re-run.
366
+ Deliberately left untouched: `k3d`/`kubectl`/`tilt`/`mkcert`/`node` themselves and mkcert's root CA
367
+ trust (other projects on your machine may depend on either) — both are printed with the exact
368
+ command to remove them yourself if you want them gone too.
369
+
370
+ Full details: **[docs/uninstalling.md](./docs/uninstalling.md)**.
371
+
372
+ ## Troubleshooting
373
+
374
+ Hit something unexpected — a Podman-specific build/push error, a port conflict, a certificate
375
+ warning, `ImagePullBackOff`, stale live-sync? Check
376
+ **[docs/troubleshooting.md](./docs/troubleshooting.md)** first; it covers every issue found while
377
+ building and testing this repo, with the actual cause and fix for each.
378
+
379
+ ## Repo layout
380
+
381
+ ```
382
+ localctl/
383
+ ├── bin/ thin installer stubs (macOS + Windows) - install Node/the CLI, then
384
+ │ hand off to `localctl setup`/`localctl uninstall`
385
+ ├── cluster/ k3d cluster definition + cluster-level manifests (TLS store, namespaces)
386
+ ├── cli/ the localctl Node.js CLI
387
+ │ ├── src/commands/ one file per CLI subcommand (setup, uninstall, up, new, profiles, ...)
388
+ │ ├── src/lib/ config validation, manifest generation, project/cluster/Podman/hosts handling
389
+ │ │ └── addons/ per-app (postgres/redis/mongo) + cluster/ (logging, monitoring) addons
390
+ │ └── templates/ generic Tiltfile copied into app repos by `localctl app new`
391
+ ├── schema/ JSON Schema for .local/config.json (editor autocomplete)
392
+ ├── examples/ ready-to-run sample apps (Node.js + Postgres, Python + Redis, .NET)
393
+ └── docs/ full documentation - also published as the docs site, see below
394
+ ```
395
+
396
+ ## Documentation index
397
+
398
+ - [docs/index.md](./docs/index.md) — documentation site home
399
+ - [docs/getting-started.md](./docs/getting-started.md) — the install/quickstart flow, in more detail
400
+ - [docs/architecture.md](./docs/architecture.md) — every component and why it was chosen, with diagrams
401
+ - [docs/config-schema.md](./docs/config-schema.md) — full `.local/config.json` reference
402
+ - [docs/adding-tools.md](./docs/adding-tools.md) — add new per-app or cluster-wide addons
403
+ - [docs/debugging.md](./docs/debugging.md) — attach a debugger, per language
404
+ - [docs/uninstalling.md](./docs/uninstalling.md) — full teardown reference
405
+ - [docs/troubleshooting.md](./docs/troubleshooting.md) — known issues and fixes
406
+
407
+ ## Docs Site
408
+
409
+ Everything under `docs/` is also published as a [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)
410
+ site — search, dark/light mode, and the Mermaid diagrams in [Architecture](./docs/architecture.md)
411
+ rendered properly (GitHub renders Mermaid too, but the site's theme is nicer for reading).
412
+
413
+ **One-time setup**, once this repo has a real GitHub remote:
414
+
415
+ 1. In `mkdocs.yml`, replace every `USERNAME` with your GitHub username (or org), and in this
416
+ README's link at the top.
417
+ 2. Push to GitHub, then in the repo's **Settings → Pages**, set **Source** to **Deploy from a
418
+ branch** and **Branch** to **`gh-pages`** / **`/ (root)`**. (The first push of
419
+ `.github/workflows/docs.yml` below creates that branch for you — the option won't appear in the
420
+ dropdown until after that first run.)
421
+
422
+ From then on, **every push to `master`** rebuilds and redeploys the site automatically
423
+ (`.github/workflows/docs.yml`) — nothing to run by hand.
424
+
425
+ To preview locally before pushing:
426
+
427
+ ```bash
428
+ python3 -m venv .venv && source .venv/bin/activate
429
+ pip install mkdocs-material
430
+ mkdocs serve # http://127.0.0.1:8000, live-reloads on save
431
+ ```
@@ -0,0 +1,4 @@
1
+ apiVersion: v1
2
+ kind: Namespace
3
+ metadata:
4
+ name: observability
@@ -0,0 +1,8 @@
1
+ apiVersion: traefik.io/v1alpha1
2
+ kind: TLSStore
3
+ metadata:
4
+ name: default
5
+ namespace: kube-system
6
+ spec:
7
+ defaultCertificate:
8
+ secretName: local-dev-tls
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../src/index.js');
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@localctl/cli",
3
+ "version": "0.2.0",
4
+ "description": "localctl - local Kubernetes dev platform CLI",
5
+ "keywords": [
6
+ "kubernetes",
7
+ "k3d",
8
+ "tilt",
9
+ "local-development",
10
+ "cli",
11
+ "devtools"
12
+ ],
13
+ "homepage": "https://psilvmoreira.github.io/localctl/",
14
+ "bugs": {
15
+ "url": "https://github.com/psilvmoreira/localctl/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/psilvmoreira/localctl.git",
20
+ "directory": "cli"
21
+ },
22
+ "author": "Pedro Moreira",
23
+ "license": "MIT",
24
+ "bin": {
25
+ "localctl": "bin/localctl.js"
26
+ },
27
+ "main": "src/index.js",
28
+ "files": [
29
+ "bin/",
30
+ "src/",
31
+ "templates/",
32
+ "assets/",
33
+ "schema/"
34
+ ],
35
+ "scripts": {
36
+ "sync-assets": "node scripts/sync-assets.js",
37
+ "prepack": "node scripts/sync-assets.js",
38
+ "test": "node scripts/smoke-test.js",
39
+ "test:pack": "node scripts/pack-test.js"
40
+ },
41
+ "dependencies": {
42
+ "chalk": "^4.1.2",
43
+ "commander": "^11.1.0",
44
+ "js-yaml": "^4.1.0",
45
+ "zod": "^3.23.8"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public",
52
+ "provenance": true
53
+ }
54
+ }
@@ -0,0 +1,127 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "localctl app config",
4
+ "type": "object",
5
+ "required": ["name", "port", "build"],
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "$schema": { "type": "string" },
9
+ "name": {
10
+ "type": "string",
11
+ "pattern": "^[a-z0-9-]+$",
12
+ "description": "Lowercase alphanumeric + dashes. Used as Deployment/Service name and Docker image name."
13
+ },
14
+ "subdomain": {
15
+ "type": "string",
16
+ "pattern": "^[a-z0-9-]+$",
17
+ "description": "Defaults to name. App is reachable at <subdomain>.local.test"
18
+ },
19
+ "namespace": {
20
+ "type": "string",
21
+ "description": "Defaults to name."
22
+ },
23
+ "port": { "type": "integer", "description": "Port the app listens on inside the container." },
24
+ "build": {
25
+ "type": "object",
26
+ "properties": {
27
+ "context": { "type": "string", "default": "." },
28
+ "dockerfile": { "type": "string", "default": "Dockerfile" }
29
+ }
30
+ },
31
+ "env": {
32
+ "type": "object",
33
+ "additionalProperties": { "type": "string" }
34
+ },
35
+ "secrets": {
36
+ "type": "array",
37
+ "description": "Names only - values are never stored here. Set with \"localctl app secrets set <KEY>\"; injected via secretKeyRef, same as an addon's credentials. A name can't also appear in \"env\".",
38
+ "items": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" }
39
+ },
40
+ "replicas": { "type": "integer", "minimum": 1, "default": 1 },
41
+ "resources": {
42
+ "type": "object",
43
+ "properties": {
44
+ "requests": {
45
+ "type": "object",
46
+ "properties": { "cpu": { "type": "string" }, "memory": { "type": "string" } }
47
+ },
48
+ "limits": {
49
+ "type": "object",
50
+ "properties": { "cpu": { "type": "string" }, "memory": { "type": "string" } }
51
+ }
52
+ }
53
+ },
54
+ "debug": {
55
+ "type": "object",
56
+ "properties": {
57
+ "enabled": { "type": "boolean", "default": false },
58
+ "port": { "type": "integer" },
59
+ "type": { "type": "string", "enum": ["node", "python", "go", "java", "dotnet"] }
60
+ }
61
+ },
62
+ "probes": {
63
+ "type": "object",
64
+ "description": "Defaults to a plain TCP check on \"port\" for both if omitted entirely. Set \"path\" to use an HTTP GET check instead.",
65
+ "properties": {
66
+ "readiness": {
67
+ "type": "object",
68
+ "properties": {
69
+ "path": { "type": "string" },
70
+ "initialDelaySeconds": { "type": "integer" },
71
+ "periodSeconds": { "type": "integer" }
72
+ }
73
+ },
74
+ "liveness": {
75
+ "type": "object",
76
+ "properties": {
77
+ "path": { "type": "string" },
78
+ "initialDelaySeconds": { "type": "integer" },
79
+ "periodSeconds": { "type": "integer" }
80
+ }
81
+ }
82
+ }
83
+ },
84
+ "dependencies": {
85
+ "type": "array",
86
+ "items": {
87
+ "type": "object",
88
+ "required": ["type"],
89
+ "properties": {
90
+ "type": { "type": "string", "enum": ["postgres", "redis", "mongo"] },
91
+ "version": { "type": "string" },
92
+ "database": { "type": "string" },
93
+ "storage": { "type": "string", "description": "e.g. 1Gi" }
94
+ }
95
+ }
96
+ },
97
+ "sidecars": {
98
+ "type": "array",
99
+ "description": "Extra containers in the same pod as your app (the k8s sidecar pattern) - reachable from your app via localhost:<port>, no Service needed.",
100
+ "items": {
101
+ "type": "object",
102
+ "required": ["name", "image"],
103
+ "properties": {
104
+ "name": { "type": "string", "pattern": "^[a-z0-9-]+$" },
105
+ "image": { "type": "string" },
106
+ "port": { "type": "integer" },
107
+ "command": { "type": "array", "items": { "type": "string" } },
108
+ "args": { "type": "array", "items": { "type": "string" } },
109
+ "env": { "type": "object", "additionalProperties": { "type": "string" } }
110
+ }
111
+ }
112
+ },
113
+ "sync": {
114
+ "type": "array",
115
+ "description": "Paths live-synced into the running container by Tilt without a full rebuild.",
116
+ "items": {
117
+ "type": "object",
118
+ "required": ["localPath", "remotePath"],
119
+ "properties": {
120
+ "localPath": { "type": "string" },
121
+ "remotePath": { "type": "string" }
122
+ }
123
+ }
124
+ },
125
+ "tls": { "type": "boolean", "default": true }
126
+ }
127
+ }