kitsune-kit 0.4.0 → 0.5.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -2
  3. data/CONTRIBUTING.md +83 -0
  4. data/README.md +108 -136
  5. data/SECURITY.md +25 -0
  6. data/bin/kit +4 -1
  7. data/docs/architecture/decisions/0001-product-boundary.md +23 -0
  8. data/docs/architecture/decisions/0002-core-and-interfaces.md +23 -0
  9. data/docs/architecture/decisions/0003-configuration-state-and-secrets.md +23 -0
  10. data/docs/architecture/decisions/0004-supported-platforms.md +19 -0
  11. data/docs/architecture/decisions/0005-operation-semantics.md +19 -0
  12. data/docs/architecture/decisions/0006-tui-backend.md +21 -0
  13. data/docs/architecture/provider-adapters.md +49 -0
  14. data/docs/architecture.md +89 -0
  15. data/docs/commands.md +210 -0
  16. data/docs/configuration.md +210 -0
  17. data/docs/getting-started.md +133 -0
  18. data/docs/providers/digitalocean.md +52 -0
  19. data/docs/releasing.md +43 -0
  20. data/docs/roadmap.md +46 -0
  21. data/docs/security-audit.md +130 -0
  22. data/docs/security.md +116 -0
  23. data/docs/services/postgres.md +78 -0
  24. data/docs/services/redis.md +53 -0
  25. data/docs/testing.md +200 -0
  26. data/docs/troubleshooting.md +123 -0
  27. data/docs/tui.md +57 -0
  28. data/lib/kitsune/kit/adapters/confirming_host_key_verifier.rb +42 -0
  29. data/lib/kitsune/kit/adapters/digitalocean_provider.rb +217 -0
  30. data/lib/kitsune/kit/adapters/fake_clock.rb +34 -0
  31. data/lib/kitsune/kit/adapters/fake_provider.rb +107 -0
  32. data/lib/kitsune/kit/adapters/fake_reporter.rb +34 -0
  33. data/lib/kitsune/kit/adapters/fake_secret_store.rb +35 -0
  34. data/lib/kitsune/kit/adapters/fake_state_store.rb +91 -0
  35. data/lib/kitsune/kit/adapters/fake_transport.rb +56 -0
  36. data/lib/kitsune/kit/adapters/net_ssh_transport.rb +155 -0
  37. data/lib/kitsune/kit/adapters/provider.rb +34 -0
  38. data/lib/kitsune/kit/adapters/transport.rb +22 -0
  39. data/lib/kitsune/kit/adapters/transport_factory.rb +116 -0
  40. data/lib/kitsune/kit/application.rb +170 -0
  41. data/lib/kitsune/kit/cancellation.rb +21 -0
  42. data/lib/kitsune/kit/cli.rb +802 -64
  43. data/lib/kitsune/kit/clock.rb +11 -0
  44. data/lib/kitsune/kit/configuration.rb +493 -0
  45. data/lib/kitsune/kit/errors.rb +98 -0
  46. data/lib/kitsune/kit/events.rb +55 -0
  47. data/lib/kitsune/kit/operations/ensure_dns_records.rb +165 -0
  48. data/lib/kitsune/kit/operations/ensure_server.rb +135 -0
  49. data/lib/kitsune/kit/operations/ensure_service.rb +285 -0
  50. data/lib/kitsune/kit/operations/remote_script.rb +214 -0
  51. data/lib/kitsune/kit/operations/service_backup.rb +71 -0
  52. data/lib/kitsune/kit/operations/service_files.rb +127 -0
  53. data/lib/kitsune/kit/operations/service_firewall.rb +190 -0
  54. data/lib/kitsune/kit/operations/service_state.rb +59 -0
  55. data/lib/kitsune/kit/plan.rb +72 -0
  56. data/lib/kitsune/kit/reporters/human.rb +89 -0
  57. data/lib/kitsune/kit/reporters/json.rb +65 -0
  58. data/lib/kitsune/kit/reporters/reporter.rb +11 -0
  59. data/lib/kitsune/kit/result.rb +28 -0
  60. data/lib/kitsune/kit/run_journal.rb +102 -0
  61. data/lib/kitsune/kit/run_logger.rb +37 -0
  62. data/lib/kitsune/kit/scripts/docker.sh +137 -0
  63. data/lib/kitsune/kit/scripts/firewall.sh +205 -0
  64. data/lib/kitsune/kit/scripts/metrics.sh +54 -0
  65. data/lib/kitsune/kit/scripts/ssh.sh +95 -0
  66. data/lib/kitsune/kit/scripts/swap.sh +86 -0
  67. data/lib/kitsune/kit/scripts/unattended.sh +81 -0
  68. data/lib/kitsune/kit/scripts/user.sh +114 -0
  69. data/lib/kitsune/kit/secret_filter.rb +54 -0
  70. data/lib/kitsune/kit/secret_store.rb +32 -0
  71. data/lib/kitsune/kit/secret_stores/store.rb +12 -0
  72. data/lib/kitsune/kit/service_compose.rb +72 -0
  73. data/lib/kitsune/kit/state_store.rb +158 -0
  74. data/lib/kitsune/kit/state_stores/store.rb +15 -0
  75. data/lib/kitsune/kit/tui/actions.rb +72 -0
  76. data/lib/kitsune/kit/tui/application.rb +35 -0
  77. data/lib/kitsune/kit/tui/controller.rb +162 -0
  78. data/lib/kitsune/kit/tui/renderer.rb +134 -0
  79. data/lib/kitsune/kit/tui/state.rb +18 -0
  80. data/lib/kitsune/kit/tui/store.rb +88 -0
  81. data/lib/kitsune/kit/tui/terminal.rb +95 -0
  82. data/lib/kitsune/kit/version.rb +1 -1
  83. data/lib/kitsune/kit/workflows/apply_plan.rb +145 -0
  84. data/lib/kitsune/kit/workflows/base.rb +31 -0
  85. data/lib/kitsune/kit/workflows/build_plan.rb +33 -0
  86. data/lib/kitsune/kit/workflows/destroy_server.rb +84 -0
  87. data/lib/kitsune/kit/workflows/doctor.rb +225 -0
  88. data/lib/kitsune/kit/workflows/environment_selection.rb +70 -0
  89. data/lib/kitsune/kit/workflows/import_server.rb +100 -0
  90. data/lib/kitsune/kit/workflows/initialize_project.rb +121 -0
  91. data/lib/kitsune/kit/workflows/inspect_environment.rb +44 -0
  92. data/lib/kitsune/kit/workflows/rollback.rb +54 -0
  93. data/lib/kitsune/kit/workflows/support_bundle.rb +82 -0
  94. data/lib/kitsune/kit.rb +41 -2
  95. metadata +122 -79
  96. data/.rspec +0 -3
  97. data/Rakefile +0 -8
  98. data/kitsune-kit-logo.jpg +0 -0
  99. data/lib/kitsune/blueprints/.env.template +0 -31
  100. data/lib/kitsune/blueprints/docker/postgres.yml +0 -27
  101. data/lib/kitsune/blueprints/docker/redis.yml +0 -23
  102. data/lib/kitsune/blueprints/kit.env.template +0 -1
  103. data/lib/kitsune/kit/ansi_color.rb +0 -78
  104. data/lib/kitsune/kit/commands/bootstrap.rb +0 -134
  105. data/lib/kitsune/kit/commands/bootstrap_docker.rb +0 -66
  106. data/lib/kitsune/kit/commands/dns.rb +0 -112
  107. data/lib/kitsune/kit/commands/init.rb +0 -148
  108. data/lib/kitsune/kit/commands/install_docker_engine.rb +0 -146
  109. data/lib/kitsune/kit/commands/postinstall_docker.rb +0 -142
  110. data/lib/kitsune/kit/commands/provision.rb +0 -43
  111. data/lib/kitsune/kit/commands/setup_do_metrics.rb +0 -123
  112. data/lib/kitsune/kit/commands/setup_docker_prereqs.rb +0 -151
  113. data/lib/kitsune/kit/commands/setup_firewall.rb +0 -132
  114. data/lib/kitsune/kit/commands/setup_postgres_docker.rb +0 -246
  115. data/lib/kitsune/kit/commands/setup_redis_docker.rb +0 -241
  116. data/lib/kitsune/kit/commands/setup_swap.rb +0 -151
  117. data/lib/kitsune/kit/commands/setup_unattended.rb +0 -132
  118. data/lib/kitsune/kit/commands/setup_user.rb +0 -189
  119. data/lib/kitsune/kit/commands/ssh.rb +0 -46
  120. data/lib/kitsune/kit/commands/switch_env.rb +0 -42
  121. data/lib/kitsune/kit/defaults.rb +0 -91
  122. data/lib/kitsune/kit/env_loader.rb +0 -41
  123. data/lib/kitsune/kit/options_builder.rb +0 -26
  124. data/lib/kitsune/kit/provisioner.rb +0 -107
  125. data/sig/kitsune/kit.rbs +0 -6
@@ -0,0 +1,19 @@
1
+ # ADR 0005: Plan, apply, rollback, remove and destroy semantics
2
+
3
+ - Status: accepted
4
+ - Date: 2026-08-14
5
+
6
+ ## Decision
7
+
8
+ - `plan` observes and describes changes without mutation.
9
+ - `apply` converges actual state to desired state and verifies postconditions.
10
+ - `rollback` restores captured prior state when restoration is safe and supported.
11
+ - `remove` stops and removes a managed capability while preserving data.
12
+ - `destroy` permanently removes a provider resource.
13
+ - `destroy-data` permanently removes service data and is always separately confirmed.
14
+
15
+ Destructive operations show exact IDs and require the resource name as confirmation in an interactive terminal or an explicit confirmation option in non-interactive mode.
16
+
17
+ ## Consequences
18
+
19
+ The word rollback is never used as a synonym for deleting a server or Docker volume. Operations that cannot restore prior state say so before execution.
@@ -0,0 +1,21 @@
1
+ # ADR 0006: Optional TUI backend
2
+
3
+ - Status: accepted
4
+ - Date: 2026-08-14
5
+
6
+ ## Context
7
+
8
+ Kitsune Kit needs a full-screen terminal interface without making it necessary for automation or conventional CLI use. RatatuiRuby 1.5 provides excellent widgets and headless testing, but currently requires Ruby 3.2.9 or newer, distributes native platform artifacts, and is LGPL-3.0-or-later. Kitsune Kit currently supports the Ruby 3.2 series and must remain installable as one portable Ruby gem.
9
+
10
+ ## Decision
11
+
12
+ - The domain emits presentation-neutral events.
13
+ - `Tui::Store`, `Tui::Renderer`, `Tui::Controller` and `Tui::Terminal` are separate components.
14
+ - The initial renderer uses a small ANSI terminal backend and a deterministic text buffer.
15
+ - Every TUI action invokes the same workflow used by the conventional CLI.
16
+ - A future RatatuiRuby backend may replace only renderer, input and terminal lifecycle components.
17
+ - The TUI remains optional at runtime; non-TTY use never initializes it.
18
+
19
+ ## Consequences
20
+
21
+ The gem has no native TUI dependency and works completely through CLI/JSON. The initial widget set is intentionally smaller than RatatuiRuby, but its state, navigation, worker and snapshots remain reusable if a richer backend is adopted.
@@ -0,0 +1,49 @@
1
+ # Provider and transport adapters
2
+
3
+ Kitsune Kit's adapter API version is `Kitsune::Kit::Adapters::API_VERSION`, currently `1`. It is a small internal extension boundary, not a dynamic plugin system. A breaking signature or semantic change must increment this value and the corresponding JSON/event schemas when affected.
4
+
5
+ ## Provider contract
6
+
7
+ A provider subclasses `Kitsune::Kit::Adapters::Provider` and implements:
8
+
9
+ | Method | Contract |
10
+ | --- | --- |
11
+ | `validate_credentials!` | Return `true`, or raise a typed authentication/provider error. |
12
+ | `validate_server_spec!(spec:)` | Check region, size and image availability without creating anything. |
13
+ | `find_server(name:, tags:)` | Return an exact owned `ServerRecord` or `nil`; never adopt by name alone. |
14
+ | `find_server_by_id(id:)` | Return the exact record or `nil`. |
15
+ | `create_server(spec:)` | Create once and return the provider ID immediately. |
16
+ | `wait_until_ready(id:, timeout:)` | Return an active record with a public IP or raise `TimeoutError`. |
17
+ | `delete_server(id:)` | Delete by exact provider ID. |
18
+ | DNS methods | Find/upsert/delete records while preserving exact IDs and zones. |
19
+
20
+ Provider exceptions must be translated into `Kitsune::Kit::Errors` without copying tokens or raw provider response bodies into messages. Availability errors are configuration errors because the user can correct the desired spec; transient API failures remain retryable provider errors.
21
+ The CLI `--timeout` value configures both DropletKit open/read timeouts and the separate readiness deadline.
22
+
23
+ ## Transport contract
24
+
25
+ A transport subclasses `Kitsune::Kit::Adapters::Transport`. `execute` receives a command and a separate argument array, applies a deadline, and returns `CommandResult` with independent `stdout`, `stderr`, `exit_status` and `duration_ms`. It does not infer success from output. `upload` accepts explicit content, absolute normalized path and restrictive mode. Host-key verification belongs to the connection adapter and must never silently disable verification.
26
+
27
+ ## Verification
28
+
29
+ The same shared examples run against `FakeProvider`/`DigitalOceanProvider` and `FakeTransport`/`NetSshTransport`. They cover credentials, spec validation, lifecycle, DNS, reachability, result shape, uploads and timeout mapping. Adapter-specific tests cover provider error translation, SSH quoting, unsafe paths, host keys and failed uploads.
30
+
31
+ Run them with:
32
+
33
+ ```bash
34
+ bundle exec rspec spec/adapters spec/contracts
35
+ ```
36
+
37
+ Do not add a provider until it can pass these contracts and an opt-in real lifecycle test. The Hetzner
38
+ feasibility/sequencing review and the decision not to create a premature hook system are recorded in
39
+ [Roadmap and extension decisions](../roadmap.md).
40
+
41
+ The state boundary follows the same rule: `StateStores::Store` defines the port, while `StateStore` is the locked atomic filesystem adapter and `Adapters::FakeStateStore` is the deterministic in-memory adapter. Both execute `spec/contracts/state_store_contract.rb`.
42
+
43
+ `SecretStores::Store` and `Reporters::Reporter` are also explicit ports. Environment/fake secret stores and
44
+ human/JSON/fake reporters execute their shared contracts in `spec/adapters`; fakes make complete workflow tests
45
+ independent of the process environment and terminal.
46
+
47
+ `Clock` is the time port for workflow event timestamps, monotonic durations, journals and bounded waits.
48
+ `Adapters::FakeClock` advances deterministically without sleeping; both implementations execute
49
+ `spec/contracts/clock_contract.rb`.
@@ -0,0 +1,89 @@
1
+ # Architecture
2
+
3
+ Kitsune Kit is a desired-state infrastructure tool with two replaceable presentation layers over one domain core.
4
+
5
+ ```text
6
+ Conventional CLI ─┐
7
+ ├─ workflows ─ operations ─ provider / SSH / state / secrets
8
+ Optional TUI ─────┘ │
9
+ └─ domain events ─ human / JSON / TUI / run log
10
+ ```
11
+
12
+ Presentation code does not create provider clients, open SSH connections or implement infrastructure rules. CLI and TUI call the same workflow objects and consume the same event stream.
13
+
14
+ ## Layers
15
+
16
+ ### Configuration and domain values
17
+
18
+ `Configuration` loads/merges typed schema values and validates them before adapters are constructed. `Change`, `Plan`, `Result`, typed errors and versioned events are presentation-neutral values.
19
+
20
+ ### Workflows
21
+
22
+ Workflows coordinate use cases:
23
+
24
+ - initialize/select an environment;
25
+ - build/apply/resume a plan;
26
+ - inspect/doctor/rollback/destroy;
27
+ - generate support diagnostics.
28
+
29
+ They emit events but do not print. Apply uses `RunJournal` to record a compatible plan and step status for safe resume.
30
+
31
+ The event vocabulary includes run/plan start and finish, operation start/progress/success/failure/skip, and warnings. All subscribers receive the same versioned event values; progress currently guarantees operation boundary percentages and can become finer-grained without changing presentations.
32
+
33
+ ### Operations
34
+
35
+ Each operation exposes a non-mutating `plan`, idempotent `apply` and, where meaningful, ownership-aware `rollback`. Operations include server creation, versioned remote scripts, services and DNS.
36
+
37
+ An operation verifies its result before writing a managed marker/state. Destructive replacement appears in the plan and is never applied implicitly.
38
+
39
+ ### Ports and adapters
40
+
41
+ - `Provider`: server-spec validation, exact server/DNS lookup, create/wait/delete and credential validation.
42
+ - `Transport`: reachability, command execution with separate output/status/timing, and safe upload.
43
+ - `StateStores::Store`: read/update/delete and per-environment mutation-lock port; filesystem and in-memory fake adapters share a contract.
44
+ - `SecretStores::Store`: resolves secret values without adding them to domain configuration/state; environment
45
+ and deterministic fake adapters execute the same contract.
46
+ - `Reporters::Reporter`: event-consumer port for human output, stable JSON, TUI state and redacted logs.
47
+ - `Clock`: UTC wall time, monotonic durations and sleeping, with a deterministic nonblocking fake.
48
+
49
+ Real DigitalOcean/Net::SSH adapters and deterministic fakes satisfy shared contracts. This permits full workflow failure injection without a VPS while reserving real behavior for integration/E2E suites.
50
+
51
+ The public adapter contract is versioned as `Kitsune::Kit::Adapters::API_VERSION`. See [Provider and transport adapters](architecture/provider-adapters.md).
52
+
53
+ ## State and ownership
54
+
55
+ State schema version 1 contains environment, timestamps, resource identities, operation history and run journals. The server provider ID is recorded immediately after creation. DNS records are persisted one at a time. Remote resources carry script/config fingerprints and previous-state metadata.
56
+
57
+ State updates acquire a per-environment file lock, validate the whole document, preserve a backup, fsync a restricted temporary file and atomically rename it. Unsupported schemas/environment mismatches are rejected.
58
+
59
+ Names/tags help find resources, but provider IDs plus recorded ownership authorize destructive operations.
60
+
61
+ ## Remote execution
62
+
63
+ Remote setup is stored in versioned Bash files under `lib/kitsune/kit/scripts`. `RemoteScript` fingerprints script bytes plus validated arguments, compares a remote marker, uploads through stdin-safe base64 transport, applies, verifies and records the marker.
64
+
65
+ SSH host verification is explicit. Commands and arguments are distinct at the transport boundary. Root and deploy transports are selected per operation; user bootstrap rollback deliberately uses root access.
66
+
67
+ Safety-critical SSH/firewall operations execute inside one preserved authenticated transport session while their postconditions are tested through separate fresh connections. The preserved session restores the captured policy if either fresh check fails.
68
+
69
+ ## Services
70
+
71
+ `EnsureService` composes smaller collaborators for generated Compose YAML, file transaction/backups, firewall reconciliation, data backup and state transitions. Data lifecycle is deliberately separate from container/config lifecycle.
72
+
73
+ An enabled service in `external` mode is configuration metadata, not an operation. It is visible through the
74
+ CLI but excluded from VPS plans, Docker, firewall, doctor port checks and destructive lifecycle methods.
75
+
76
+ ## TUI
77
+
78
+ The initial TUI is a pure-Ruby ANSI backend with deterministic renderer/store/controller boundaries. It has no native dependency and can be replaced without changing workflows. Headless tests feed events and keys into the store/controller and compare stable rendered buffers.
79
+
80
+ ## Decisions
81
+
82
+ Detailed accepted decisions are in [architecture/decisions](architecture/decisions):
83
+
84
+ - product boundary and direct 0.5 rewrite;
85
+ - core/interface separation;
86
+ - configuration/state/secrets;
87
+ - supported platforms;
88
+ - operation semantics;
89
+ - optional TUI backend.
data/docs/commands.md ADDED
@@ -0,0 +1,210 @@
1
+ # Command reference
2
+
3
+ The authoritative runtime reference is `kit help`, `kit help COMMAND` and the equivalent `kit COMMAND help`.
4
+ This document explains the semantics and automation contract that short help cannot capture.
5
+
6
+ ## Global options
7
+
8
+ Global options may be passed to infrastructure commands:
9
+
10
+ | Option | Meaning |
11
+ | --- | --- |
12
+ | `-e`, `--env NAME` | Select an environment for this invocation. |
13
+ | `--root PATH` | Project root containing `.kitsune/`. Defaults to the current directory. |
14
+ | `--config PATH` | Use an alternative base configuration file. |
15
+ | `--format human|json` | Select human or versioned machine output. |
16
+ | `--no-color` | Disable ANSI styling. `NO_COLOR` is also respected by the entrypoint environment. |
17
+ | `--no-input` | Never prompt; fail with an actionable error when confirmation is required. |
18
+ | `--yes` | Approve reviewed, non-destructive changes. It never confirms permanent data/server destruction. |
19
+ | `--dry-run` | Build/display the plan without applying it. |
20
+ | `--quiet` | Limit human output to important findings and summaries. |
21
+ | `--verbose` | Include additional domain-event detail. |
22
+ | `--debug` | Include technical context/stack information for failures. |
23
+ | `--log` / `--no-log` | Enable/disable restricted redacted local logs. Enabled by default. |
24
+ | `--timeout SECONDS` | Upper bound for each remote step, provider HTTP request and readiness wait; must be positive. |
25
+ | `--trust-host-key SHA256:...` | Trust only this exact first-seen SSH fingerprint. |
26
+
27
+ ## Core workflow
28
+
29
+ ### `kit init [--force]`
30
+
31
+ Creates `.kitsune/config.yml`, a development overlay and environment selection. Existing generated files cause exit 9 unless `--force` is explicit. It also appends runtime artifacts to `.gitignore`.
32
+
33
+ ### `kit doctor`
34
+
35
+ Read-only checks include Ruby/runtime, configuration, SSH-key permissions, security defaults, provider credentials, state schema, exact server ownership, verified SSH access, Ubuntu version, passwordless sudo, Docker/Compose, listening ports and managed drift.
36
+
37
+ Statuses are `pass`, `warn`, or `fail`. Warnings do not make the command fail; any failed check returns exit 1.
38
+
39
+ ### `kit plan`
40
+
41
+ Calculates changes without mutation. Details exclude provider key IDs and secrets. Actions are `create`, `update`, `delete` and `no_change`; plans flag destructive changes.
42
+
43
+ ### `kit apply`
44
+
45
+ Builds a fresh plan, displays it, rejects destructive replacement and asks for confirmation. `--yes --no-input` is the automation form. `--dry-run` guarantees no mutation.
46
+
47
+ Every run and step is journaled. The provider ID is saved immediately after server creation; remote markers and local state are saved after verified operations.
48
+ Human output ends with the redacted local log path and a post-apply `doctor`/zero-change-plan next step. Failed or
49
+ cancelled applies report the last confirmed step, run ID and exact `kit resume RUN_ID` command. JSON carries the
50
+ same guidance as a `warning_emitted` event and never receives an extra human-output line.
51
+
52
+ ### `kit resume [RUN_ID]`
53
+
54
+ Resumes the named run or latest incomplete run. It rejects successful runs and saved operation sets that no longer match configuration. Completed steps are skipped. Confirmation is required (`--yes` in automation).
55
+
56
+ ### `kit status`
57
+
58
+ Displays the selected environment, exact provider server and locally tracked resources. It does not run remote diagnostics; use `doctor` for that.
59
+
60
+ ### `kit rollback`
61
+
62
+ Runs rollback in reverse dependency order for resources recorded as managed. It preserves the Droplet and service data volumes. It is not a universal snapshot restore: provider resources or pre-existing files that Kitsune Kit never owned are not touched.
63
+
64
+ ## Server
65
+
66
+ ```text
67
+ kit server show
68
+ kit server status
69
+ kit server create
70
+ kit server configure
71
+ kit server ssh
72
+ kit server import --provider-id DROPLET_ID --confirm-import SERVER_NAME
73
+ kit server destroy --confirm-destroy SERVER_NAME
74
+ ```
75
+
76
+ - `show`/`status`: inspect server and state.
77
+ - `create`: apply only the server operation.
78
+ - `configure`: apply remote, service and DNS operations after the server operation.
79
+ - `ssh`: verify the managed connection, then replace the process with the system `ssh` client using strict known-host checking.
80
+ - `import`: recover only the server identity after state loss. It requires an exact numeric Droplet ID and configured server-name confirmation, then verifies name, tags, region, size, image, active status and public IP. It never imports remote-resource ownership.
81
+ - `destroy`: delete only the exact recorded provider ID after verifying name/tags, then restore/remove managed DNS and clear state. If provider deletion fails, DNS is left untouched; if DNS cleanup fails afterward, retrying completes cleanup without deleting a second server. It never deletes by name alone.
82
+
83
+ Interactive destruction asks the operator to type the exact server name. Automation must use `--confirm-destroy SERVER_NAME`; `--yes` is insufficient.
84
+ Before an interactive confirmation, Kitsune Kit prints the environment, provider, resource, recorded provider ID and recoverability. JSON confirmation errors include the same safe target context.
85
+
86
+ ## Docker
87
+
88
+ ```text
89
+ kit docker status
90
+ kit docker install
91
+ kit docker uninstall --yes
92
+ ```
93
+
94
+ Uninstall refuses to run while managed services remain. Docker installation uses the official apt repository and creates the `kitsune-private` network.
95
+
96
+ ## Services
97
+
98
+ ```text
99
+ kit service postgres status
100
+ kit service postgres install
101
+ kit service postgres backup
102
+ kit service postgres remove --yes
103
+ kit service postgres destroy-data --backup-before-destroy --confirm-destroy postgres@ENV
104
+ ```
105
+
106
+ Replace `postgres` with `redis` for Redis.
107
+
108
+ - `install`: service must be enabled and its secret environment variable present.
109
+ - `backup`: pauses the service, creates a restricted tar archive of the volume on the server and always unpauses it.
110
+ - `remove`: removes containers/network attachment and firewall rules owned by Kitsune Kit, but keeps the volume and managed files needed for recovery.
111
+ - `destroy-data`: optionally creates a restricted data archive, then removes containers, volume, owned
112
+ configuration backups/files and state. In an interactive terminal it offers the backup and requires typing
113
+ `TYPE@ENV`; automation can request it with `--backup-before-destroy` and must supply `--confirm-destroy`.
114
+ The data archive is retained outside the service directory, but must be copied to independent storage.
115
+
116
+ When an enabled service has `mode: external`, `status` returns only its configured host/port and
117
+ `managed: false`. All local lifecycle actions fail safely; Kitsune Kit never treats an external provider's data as
118
+ a VPS Docker volume.
119
+
120
+ ## DNS
121
+
122
+ ```text
123
+ kit dns list
124
+ kit dns plan
125
+ kit dns apply
126
+ kit dns remove --yes
127
+ ```
128
+
129
+ DNS operations use exact record IDs and store original values. `remove` restores updated records and deletes only records created by Kitsune Kit.
130
+
131
+ ## Environments
132
+
133
+ ```text
134
+ kit env list
135
+ kit env current
136
+ kit env use NAME
137
+ ```
138
+
139
+ `use` validates that `.kitsune/environments/NAME.yml` exists and writes the selection atomically. An explicit `--env`/`KITSUNE_ENV` wins without changing the persisted selection.
140
+
141
+ ## Support
142
+
143
+ ```bash
144
+ kit support bundle
145
+ ```
146
+
147
+ The human-readable command prints the complete redacted bundle after writing it, so it can be reviewed before
148
+ sharing. JSON mode returns the local path without adding non-JSON output. Kitsune Kit never uploads the bundle.
149
+
150
+ Creates a permission-restricted JSON file under `.kitsune/support/` with versions, platform, redacted configuration/state, doctor results and selected redacted logs. The path is printed; Kitsune Kit never uploads it. Inspect it before sharing.
151
+
152
+ ## TUI
153
+
154
+ `kit ui` starts the optional full-screen interface. Bare `kit` does the same only with interactive stdin/stdout; otherwise it prints help. See [TUI](tui.md).
155
+
156
+ ## Version
157
+
158
+ `kit version`, `kit --version` and `kit -v` print the installed Kitsune Kit version without loading project configuration or contacting a provider.
159
+
160
+ ## JSON output
161
+
162
+ Successful workflow documents use schema version 1 and include:
163
+
164
+ ```json
165
+ {
166
+ "schema_version": 1,
167
+ "command": "plan",
168
+ "environment": "production",
169
+ "run_id": "...",
170
+ "status": "success",
171
+ "duration_ms": 12,
172
+ "result": {},
173
+ "warnings": [],
174
+ "events": []
175
+ }
176
+ ```
177
+
178
+ Errors are JSON on standard error:
179
+
180
+ ```json
181
+ {
182
+ "schema_version": 1,
183
+ "status": "failure",
184
+ "error": {
185
+ "code": "configuration_error",
186
+ "message": "...",
187
+ "hint": "...",
188
+ "context": {}
189
+ }
190
+ }
191
+ ```
192
+
193
+ Treat unknown additive keys as forward-compatible, but reject unsupported `schema_version` values in consumers.
194
+
195
+ ## Exit codes
196
+
197
+ | Code | Meaning |
198
+ | ---: | --- |
199
+ | 0 | Success, including a no-change plan/apply. |
200
+ | 1 | Unexpected failure or failed `doctor` check. |
201
+ | 2 | Invalid CLI syntax/unknown command. |
202
+ | 3 | Invalid/missing configuration or unsupported environment. |
203
+ | 4 | Authentication failure. |
204
+ | 5 | Provider API failure. |
205
+ | 6 | SSH/network connection failure. |
206
+ | 7 | Remote command failure. |
207
+ | 8 | Post-change verification failure. |
208
+ | 9 | Unsafe operation or missing confirmation. |
209
+ | 10 | Timeout. |
210
+ | 130 | Interrupted by `SIGINT`. |
@@ -0,0 +1,210 @@
1
+ # Configuration
2
+
3
+ Kitsune Kit configuration schema version 1 is YAML with a fixed structure. Unknown keys at any nesting level,
4
+ malformed sections and future schema versions are rejected instead of being ignored.
5
+
6
+ ## Files and precedence
7
+
8
+ The base file is `.kitsune/config.yml`. The selected overlay is `.kitsune/environments/NAME.yml`. Values are combined in this order, with later sources winning:
9
+
10
+ 1. built-in safe defaults;
11
+ 2. `.kitsune/config.yml`;
12
+ 3. `.kitsune/environments/NAME.yml`;
13
+ 4. supported environment-variable overrides;
14
+ 5. explicit overrides supplied by the internal API.
15
+
16
+ Environment selection itself uses:
17
+
18
+ 1. `--env NAME`;
19
+ 2. `KITSUNE_ENV`;
20
+ 3. `.kitsune/environment`;
21
+ 4. `development`.
22
+
23
+ Names start with a lowercase letter or digit and may then contain lowercase letters, numbers, `_` and `-`; uppercase names and path traversal are rejected so Compose project identity remains stable.
24
+
25
+ Use:
26
+
27
+ ```bash
28
+ kit env list
29
+ kit env current
30
+ kit env use production
31
+ ```
32
+
33
+ ## Complete schema
34
+
35
+ ```yaml
36
+ version: 1
37
+
38
+ provider:
39
+ name: digitalocean
40
+ token_env: DO_API_TOKEN
41
+
42
+ server:
43
+ name: myapp-production
44
+ region: sfo3
45
+ size: s-1vcpu-1gb
46
+ image: ubuntu-24-04-x64
47
+ ssh_key_id: "12345678"
48
+ tags:
49
+ - kitsune-managed
50
+
51
+ ssh:
52
+ user: deploy
53
+ port: 22
54
+ key_path: ~/.ssh/id_ed25519
55
+ allowed_cidrs:
56
+ - 203.0.113.10/32
57
+
58
+ services:
59
+ postgres:
60
+ enabled: false
61
+ mode: managed
62
+ host:
63
+ image: postgres:17
64
+ publish: false
65
+ bind: 127.0.0.1
66
+ allowed_cidrs: []
67
+ port: 5432
68
+ password_env: POSTGRES_PASSWORD
69
+ redis:
70
+ enabled: false
71
+ mode: managed
72
+ host:
73
+ image: redis:7.2
74
+ publish: false
75
+ bind: 127.0.0.1
76
+ allowed_cidrs: []
77
+ port: 6379
78
+ password_env: REDIS_PASSWORD
79
+
80
+ system:
81
+ swap_size_gb: 2
82
+ swap_swappiness: 10
83
+ unattended_upgrades: true
84
+ metrics: false
85
+ metrics_installer_sha256:
86
+
87
+ dns:
88
+ domains: []
89
+ ttl: 3600
90
+ ```
91
+
92
+ ## Field reference
93
+
94
+ ### `provider`
95
+
96
+ | Field | Meaning |
97
+ | --- | --- |
98
+ | `name` | Must currently be `digitalocean`. |
99
+ | `token_env` | Name of the environment variable containing the token. Must look like an uppercase environment-variable name. |
100
+
101
+ ### `server`
102
+
103
+ | Field | Validation |
104
+ | --- | --- |
105
+ | `name` | Lowercase DNS-style resource name, 1–63 characters. |
106
+ | `region` | Non-empty DigitalOcean region slug. Provider validity is checked by the API. |
107
+ | `size` | Non-empty DigitalOcean size slug. |
108
+ | `image` | `ubuntu-22-04-x64` or `ubuntu-24-04-x64`. |
109
+ | `ssh_key_id` | ID of a public key already uploaded to DigitalOcean. |
110
+ | `tags` | Tags used to establish ownership. Keep `kitsune-managed`; a same-name untagged server is not adopted. |
111
+
112
+ Region/size/image differences are immutable in the current model. A plan reports replacement as destructive; Kitsune Kit never silently replaces the server.
113
+
114
+ ### `ssh`
115
+
116
+ | Field | Validation |
117
+ | --- | --- |
118
+ | `user` | Linux username: lowercase letters/numbers plus `_`/`-`, maximum 32 characters. |
119
+ | `port` | Integer from 1 through 65535. |
120
+ | `key_path` | Expanded local path to an existing regular private-key file with mode `0600` or stricter. |
121
+ | `allowed_cidrs` | Valid IPv4 or IPv6 CIDRs permitted through UFW. Empty means the SSH port is not CIDR-restricted by this list. |
122
+
123
+ SSH policy is ordered to keep a verified path open: create/verify the deploy user, install and validate the new policy, open the new firewall route, then remove obsolete managed rules.
124
+
125
+ ### `services.postgres` and `services.redis`
126
+
127
+ | Field | Meaning |
128
+ | --- | --- |
129
+ | `enabled` | Include the service in the desired plan. |
130
+ | `mode` | `managed` installs on the VPS; `external` records an endpoint and forbids local lifecycle actions. |
131
+ | `host` | Required hostname/IP for `external`; must be empty for `managed`. |
132
+ | `image` | Valid Docker image reference. Pin a digest for stronger reproducibility. |
133
+ | `publish` | Publish a host port. Defaults to false. |
134
+ | `bind` | IPv4 bind address used only when publishing. |
135
+ | `allowed_cidrs` | Required and non-empty when `publish` is true. |
136
+ | `port` | Host port, 1–65535. Container ports remain 5432/6379. |
137
+ | `password_env` | Environment-variable name containing the required secret. |
138
+
139
+ Images, binds, ports and secret names reject newline/shell/YAML injection patterns before any SSH connection is opened.
140
+
141
+ External example:
142
+
143
+ ```yaml
144
+ services:
145
+ postgres:
146
+ enabled: true
147
+ mode: external
148
+ host: db.internal.example
149
+ port: 5432
150
+ publish: false
151
+ ```
152
+
153
+ External mode is metadata and a safety boundary, not a database-provisioning integration. `kit service postgres
154
+ status` reports the endpoint without its secret. Install, backup, remove and destroy-data refuse to act; manage
155
+ availability, TLS, backups and destruction with the external provider. External services never add Docker,
156
+ firewall or private-port checks to the VPS plan.
157
+
158
+ ### `system`
159
+
160
+ | Field | Validation |
161
+ | --- | --- |
162
+ | `swap_size_gb` | Integer 0–64. Zero disables swap managed by Kitsune Kit. |
163
+ | `swap_swappiness` | Integer 0–100. |
164
+ | `unattended_upgrades` | Boolean. |
165
+ | `metrics` | Boolean; false by default. |
166
+ | `metrics_installer_sha256` | Required lowercase 64-character SHA256 when metrics is enabled. |
167
+
168
+ Kitsune Kit downloads the DigitalOcean metrics installer to a file, verifies the configured digest and only then executes it. Re-verify the digest whenever the upstream installer changes.
169
+
170
+ ### `dns`
171
+
172
+ | Field | Validation |
173
+ | --- | --- |
174
+ | `domains` | Valid hostnames. Public Suffix List parsing determines the provider zone and record name. |
175
+ | `ttl` | Integer 30–86400 seconds. |
176
+
177
+ Each created/updated record is persisted immediately, including its previous value, so a partially failed run can resume and rollback safely.
178
+
179
+ ## Supported environment-variable overrides
180
+
181
+ | Variable | Field |
182
+ | --- | --- |
183
+ | `KITSUNE_PROVIDER` | `provider.name` |
184
+ | `KITSUNE_SERVER_NAME` | `server.name` |
185
+ | `KITSUNE_REGION` | `server.region` |
186
+ | `KITSUNE_SIZE` | `server.size` |
187
+ | `KITSUNE_IMAGE` | `server.image` |
188
+ | `KITSUNE_SSH_KEY_ID` | `server.ssh_key_id` |
189
+ | `KITSUNE_SSH_USER` | `ssh.user` |
190
+ | `KITSUNE_SSH_PORT` | `ssh.port` |
191
+ | `KITSUNE_SSH_KEY_PATH` | `ssh.key_path` |
192
+ | `KITSUNE_METRICS_INSTALLER_SHA256` | `system.metrics_installer_sha256` |
193
+
194
+ Secrets are indirect: `provider.token_env` and each `password_env` name which environment variable Kitsune Kit reads. Their values are registered with the redactor and never written to state.
195
+ Enabled services require at least 12 bytes and reject empty values and common defaults such as `password`, `postgres`, `redis`, `changeme` and `secret` before any provider or SSH request.
196
+
197
+ ## Schema evolution
198
+
199
+ Version 1 is the direct-rewrite baseline and has no supported predecessor to migrate: the old preview `.env`
200
+ format is intentionally not interpreted. A future schema change must ship an explicit, tested `N -> N+1`
201
+ migrator that preserves a backup before changing configuration or state. Kitsune Kit never guesses at an unknown
202
+ version. Until such a migrator exists, open the project with a compatible Kitsune Kit version and follow that
203
+ release's documented migration path; never copy provider IDs by hand.
204
+
205
+ JSON/event consumers must reject unknown `schema_version` values while tolerating documented additive fields
206
+ within a supported version. Breaking adapter signatures increment `Adapters::API_VERSION`.
207
+
208
+ ## State is not configuration
209
+
210
+ `.kitsune/state/ENV.json` is Kitsune Kit's ownership journal. Do not edit it manually or commit it. Writes use a per-environment lock, atomic rename and `.backup` copy. Losing state removes Kitsune Kit's proof of ownership; it intentionally refuses destructive actions rather than guessing by name.