clouddeck 0.1.0__tar.gz

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 (157) hide show
  1. clouddeck-0.1.0/.env.example +56 -0
  2. clouddeck-0.1.0/.gitleaks.toml +27 -0
  3. clouddeck-0.1.0/CHANGELOG.md +90 -0
  4. clouddeck-0.1.0/CODE_OF_CONDUCT.md +131 -0
  5. clouddeck-0.1.0/CONTRIBUTING.md +181 -0
  6. clouddeck-0.1.0/LICENSE +21 -0
  7. clouddeck-0.1.0/MANIFEST.in +14 -0
  8. clouddeck-0.1.0/PKG-INFO +322 -0
  9. clouddeck-0.1.0/README.md +285 -0
  10. clouddeck-0.1.0/SECURITY.md +205 -0
  11. clouddeck-0.1.0/THIRD_PARTY_NOTICES.md +68 -0
  12. clouddeck-0.1.0/bin/clouddeck +4 -0
  13. clouddeck-0.1.0/clouddeck/__init__.py +3 -0
  14. clouddeck-0.1.0/clouddeck/__main__.py +5 -0
  15. clouddeck-0.1.0/clouddeck/cli.py +250 -0
  16. clouddeck-0.1.0/clouddeck/controller.py +3417 -0
  17. clouddeck-0.1.0/clouddeck/engine.py +37 -0
  18. clouddeck-0.1.0/clouddeck/fmt.py +43 -0
  19. clouddeck-0.1.0/clouddeck/hardware.py +598 -0
  20. clouddeck-0.1.0/clouddeck/job/__init__.py +1 -0
  21. clouddeck-0.1.0/clouddeck/job/__main__.py +3 -0
  22. clouddeck-0.1.0/clouddeck/job/cli.py +153 -0
  23. clouddeck-0.1.0/clouddeck/job/clock.py +20 -0
  24. clouddeck-0.1.0/clouddeck/job/market.py +194 -0
  25. clouddeck-0.1.0/clouddeck/job/preflight.py +196 -0
  26. clouddeck-0.1.0/clouddeck/job/remote.py +190 -0
  27. clouddeck-0.1.0/clouddeck/job/runner.py +731 -0
  28. clouddeck-0.1.0/clouddeck/job/spec.py +400 -0
  29. clouddeck-0.1.0/clouddeck/job/telemetry.py +100 -0
  30. clouddeck-0.1.0/clouddeck/providers/__init__.py +44 -0
  31. clouddeck-0.1.0/clouddeck/providers/akash.py +620 -0
  32. clouddeck-0.1.0/clouddeck/providers/base.py +494 -0
  33. clouddeck-0.1.0/clouddeck/providers/demo.py +329 -0
  34. clouddeck-0.1.0/clouddeck/remote.py +447 -0
  35. clouddeck-0.1.0/clouddeck/remote_client.py +90 -0
  36. clouddeck-0.1.0/clouddeck/sdlgen.py +275 -0
  37. clouddeck-0.1.0/clouddeck/service.py +434 -0
  38. clouddeck-0.1.0/clouddeck/tui/__init__.py +0 -0
  39. clouddeck-0.1.0/clouddeck/tui/app.py +495 -0
  40. clouddeck-0.1.0/clouddeck/tui/clouddeck.tcss +109 -0
  41. clouddeck-0.1.0/clouddeck/tui/compute.py +627 -0
  42. clouddeck-0.1.0/clouddeck/tui/monitor.py +188 -0
  43. clouddeck-0.1.0/clouddeck/tui/picker.py +622 -0
  44. clouddeck-0.1.0/clouddeck/tui/projects.py +265 -0
  45. clouddeck-0.1.0/clouddeck/tui/recovery.py +217 -0
  46. clouddeck-0.1.0/clouddeck/tui/safety.py +120 -0
  47. clouddeck-0.1.0/clouddeck/tui/widgets.py +483 -0
  48. clouddeck-0.1.0/clouddeck/tui/workers.py +421 -0
  49. clouddeck-0.1.0/clouddeck.egg-info/PKG-INFO +322 -0
  50. clouddeck-0.1.0/clouddeck.egg-info/SOURCES.txt +155 -0
  51. clouddeck-0.1.0/clouddeck.egg-info/dependency_links.txt +1 -0
  52. clouddeck-0.1.0/clouddeck.egg-info/entry_points.txt +4 -0
  53. clouddeck-0.1.0/clouddeck.egg-info/requires.txt +13 -0
  54. clouddeck-0.1.0/clouddeck.egg-info/top_level.txt +1 -0
  55. clouddeck-0.1.0/config.example.toml +94 -0
  56. clouddeck-0.1.0/conftest.py +21 -0
  57. clouddeck-0.1.0/ddc.py +10 -0
  58. clouddeck-0.1.0/docker/Dockerfile +19 -0
  59. clouddeck-0.1.0/docs/ARCHITECTURE.md +263 -0
  60. clouddeck-0.1.0/docs/CHECKPOINTS.md +154 -0
  61. clouddeck-0.1.0/docs/CLOUDDECK.md +176 -0
  62. clouddeck-0.1.0/docs/COSTS-AND-SAFETY.md +227 -0
  63. clouddeck-0.1.0/docs/HARDWARE.md +156 -0
  64. clouddeck-0.1.0/docs/JOBS.md +178 -0
  65. clouddeck-0.1.0/docs/PLATFORMS.md +108 -0
  66. clouddeck-0.1.0/docs/QUICKSTART.md +269 -0
  67. clouddeck-0.1.0/docs/prebuilt-image.md +54 -0
  68. clouddeck-0.1.0/examples/cpu-smoke.toml +19 -0
  69. clouddeck-0.1.0/examples/gpu-batch.toml +62 -0
  70. clouddeck-0.1.0/jobremote/boot.sh +23 -0
  71. clouddeck-0.1.0/jobremote/fetch.sh +26 -0
  72. clouddeck-0.1.0/jobremote/step.sh +27 -0
  73. clouddeck-0.1.0/lease/README.md +58 -0
  74. clouddeck-0.1.0/lease/agent.sh +91 -0
  75. clouddeck-0.1.0/lease/bench.sh +43 -0
  76. clouddeck-0.1.0/lease/checkpoint.sh +169 -0
  77. clouddeck-0.1.0/lease/ddc-lib.sh +73 -0
  78. clouddeck-0.1.0/lease/dist.sh +45 -0
  79. clouddeck-0.1.0/lease/restore.sh +134 -0
  80. clouddeck-0.1.0/lease/setup.sh +95 -0
  81. clouddeck-0.1.0/lease/tmux.conf +25 -0
  82. clouddeck-0.1.0/lease/workers.sh +23 -0
  83. clouddeck-0.1.0/pyproject.toml +80 -0
  84. clouddeck-0.1.0/requirements.txt +3 -0
  85. clouddeck-0.1.0/scripts/check_dist.py +87 -0
  86. clouddeck-0.1.0/scripts/ci_local.py +76 -0
  87. clouddeck-0.1.0/scripts/release_allowlist.toml +77 -0
  88. clouddeck-0.1.0/scripts/release_check.py +347 -0
  89. clouddeck-0.1.0/sdl.yaml.tmpl +63 -0
  90. clouddeck-0.1.0/setup.cfg +4 -0
  91. clouddeck-0.1.0/setup.py +23 -0
  92. clouddeck-0.1.0/test_ddc.py +107 -0
  93. clouddeck-0.1.0/tests/conftest.py +62 -0
  94. clouddeck-0.1.0/tests/e2e_helpers.py +551 -0
  95. clouddeck-0.1.0/tests/fake_akash.py +256 -0
  96. clouddeck-0.1.0/tests/fixtures/config.toml +58 -0
  97. clouddeck-0.1.0/tests/fixtures/hardware/bid_screening_400_invalid_attr_key.json +25 -0
  98. clouddeck-0.1.0/tests/fixtures/hardware/bid_screening_samples.json +134 -0
  99. clouddeck-0.1.0/tests/fixtures/hardware/bids_synthetic.json +68 -0
  100. clouddeck-0.1.0/tests/fixtures/hardware/gpu_models_sample.json +156 -0
  101. clouddeck-0.1.0/tests/fixtures/hardware/gpu_prices_sample.json +670 -0
  102. clouddeck-0.1.0/tests/fixtures/hardware/placement_options_sample.json +643 -0
  103. clouddeck-0.1.0/tests/fixtures/hardware/providers_sample.json +5888 -0
  104. clouddeck-0.1.0/tests/fixtures/hardware/sdl_golden/box64.yaml +61 -0
  105. clouddeck-0.1.0/tests/fixtures/hardware/sdl_golden/gpu_a100x4.yaml +67 -0
  106. clouddeck-0.1.0/tests/fixtures/hardware/sdl_golden/gpu_bare.yaml +66 -0
  107. clouddeck-0.1.0/tests/fixtures/hardware/sdl_golden/gpu_model.yaml +67 -0
  108. clouddeck-0.1.0/tests/fixtures/hardware/sdl_golden/plain16.yaml +61 -0
  109. clouddeck-0.1.0/tests/fixtures/hardware/sdl_golden/worker32.yaml +61 -0
  110. clouddeck-0.1.0/tests/fixtures/lease/legacy-checkpoint.sh +102 -0
  111. clouddeck-0.1.0/tests/fixtures/lease/legacy-restore.sh +59 -0
  112. clouddeck-0.1.0/tests/hw_helpers.py +81 -0
  113. clouddeck-0.1.0/tests/job_fakes.py +322 -0
  114. clouddeck-0.1.0/tests/job_helpers.py +70 -0
  115. clouddeck-0.1.0/tests/job_sim.py +188 -0
  116. clouddeck-0.1.0/tests/pty_smoke.py +157 -0
  117. clouddeck-0.1.0/tests/test_controller.py +406 -0
  118. clouddeck-0.1.0/tests/test_controller_followups.py +254 -0
  119. clouddeck-0.1.0/tests/test_e2e_checkpoint.py +318 -0
  120. clouddeck-0.1.0/tests/test_e2e_failure.py +262 -0
  121. clouddeck-0.1.0/tests/test_e2e_provision.py +165 -0
  122. clouddeck-0.1.0/tests/test_e2e_scaling.py +221 -0
  123. clouddeck-0.1.0/tests/test_e2e_shutdown.py +257 -0
  124. clouddeck-0.1.0/tests/test_e2e_tui.py +427 -0
  125. clouddeck-0.1.0/tests/test_hardware_catalog.py +365 -0
  126. clouddeck-0.1.0/tests/test_hardware_sdl.py +327 -0
  127. clouddeck-0.1.0/tests/test_hardware_spec.py +289 -0
  128. clouddeck-0.1.0/tests/test_hardware_tui.py +535 -0
  129. clouddeck-0.1.0/tests/test_isolation_guard.py +24 -0
  130. clouddeck-0.1.0/tests/test_job_hardening.py +497 -0
  131. clouddeck-0.1.0/tests/test_job_runner.py +327 -0
  132. clouddeck-0.1.0/tests/test_job_safety_access.py +211 -0
  133. clouddeck-0.1.0/tests/test_job_safety_orphans.py +281 -0
  134. clouddeck-0.1.0/tests/test_job_safety_spend.py +294 -0
  135. clouddeck-0.1.0/tests/test_job_scripts.py +304 -0
  136. clouddeck-0.1.0/tests/test_job_spec.py +217 -0
  137. clouddeck-0.1.0/tests/test_lease_scripts.py +640 -0
  138. clouddeck-0.1.0/tests/test_packaging.py +50 -0
  139. clouddeck-0.1.0/tests/test_providers.py +443 -0
  140. clouddeck-0.1.0/tests/test_release_check.py +264 -0
  141. clouddeck-0.1.0/tests/test_remote_security.py +571 -0
  142. clouddeck-0.1.0/tests/test_safety_followups.py +431 -0
  143. clouddeck-0.1.0/tests/test_safety_lease_disabled.py +72 -0
  144. clouddeck-0.1.0/tests/test_safety_lease_followups.py +132 -0
  145. clouddeck-0.1.0/tests/test_safety_limits.py +236 -0
  146. clouddeck-0.1.0/tests/test_safety_ownership.py +233 -0
  147. clouddeck-0.1.0/tests/test_safety_provision.py +418 -0
  148. clouddeck-0.1.0/tests/test_safety_state.py +300 -0
  149. clouddeck-0.1.0/tests/test_safety_watchdog.py +420 -0
  150. clouddeck-0.1.0/tests/test_service.py +521 -0
  151. clouddeck-0.1.0/tests/test_tui.py +985 -0
  152. clouddeck-0.1.0/windows/bench.cmd +3 -0
  153. clouddeck-0.1.0/windows/claude-cloud.cmd +2 -0
  154. clouddeck-0.1.0/windows/clouddeck.cmd +3 -0
  155. clouddeck-0.1.0/windows/start.cmd +3 -0
  156. clouddeck-0.1.0/windows/status.cmd +3 -0
  157. clouddeck-0.1.0/windows/stop.cmd +3 -0
@@ -0,0 +1,56 @@
1
+ # CloudDeck / ddc: the name of every setting that comes from a file or from the environment. Placeholders only; never commit real values.
2
+ #
3
+ # CloudDeck does NOT read a ".env" file. This file only documents the names:
4
+ # * Part 1 lists the keys of secrets.env (same KEY=value syntax). `ddc init` creates the real file for you, owner-only
5
+ # (mode 0600 on Linux/macOS), in the secrets directory (default ~/.config/clouddeck/, see Part 2).
6
+ # * Part 2 lists process environment variables (all optional, all commented out here) that move files or change behaviour.
7
+ # Everything else (limits, repos, profiles) lives in config.toml: see config.example.toml.
8
+
9
+ # ---------------------------------------------------------------------------------------------------------------------------------
10
+ # Part 1: keys of secrets.env (read from that FILE only, never from the process environment)
11
+ # ---------------------------------------------------------------------------------------------------------------------------------
12
+
13
+ # Akash Console API key (https://console.akash.network, API Keys). Required. It can spend your Akash balance: use a dedicated,
14
+ # low-balance account. It stays on this PC and is never sent to any rented box.
15
+ AKASH_API_KEY=
16
+
17
+ # Passphrase that encrypts checkpoints (gpg AES256). `ddc install` generates it if missing. It IS copied to the dev box so that the
18
+ # box can encrypt checkpoints. Keep a copy in a password manager: if it is lost, every checkpoint is unrecoverable.
19
+ DDC_BACKUP_PASSPHRASE=
20
+
21
+ # Optional. Claude Code login token for the dev box. Only sent when `push_claude_credentials = true` in config.toml (default: false).
22
+ CLAUDE_CODE_OAUTH_TOKEN=
23
+
24
+ # ---------------------------------------------------------------------------------------------------------------------------------
25
+ # Part 2: process environment variables (all optional)
26
+ # ---------------------------------------------------------------------------------------------------------------------------------
27
+
28
+ # Where things live. Defaults: config in %APPDATA%\clouddeck (Windows) or ~/.config/clouddeck (Linux, macOS, WSL; honours
29
+ # XDG_CONFIG_HOME); secrets in ~/.config/clouddeck; state in %LOCALAPPDATA%\clouddeck (Windows) or ~/.local/state/clouddeck
30
+ # (honours XDG_STATE_HOME). DDC_CONFIG is a file path, the other two are directories.
31
+ # DDC_CONFIG=/path/to/config.toml
32
+ # DDC_SECRETS_DIR=/path/to/secrets-dir
33
+ # DDC_STATE_DIR=/path/to/state-dir
34
+
35
+ # Path of the gpg binary (default: gpg on PATH; on Windows also the gpg bundled with Git for Windows).
36
+ # DDC_GPG=/path/to/gpg
37
+
38
+ # Akash Console API base URL (default https://console-api.akash.network). Only change it to point at a test double.
39
+ # DDC_AKASH_API=https://console-api.akash.network
40
+
41
+ # CloudDeck: akash (default), demo (simulated, touches nothing) or a plugin name; the same as `clouddeck -p NAME`.
42
+ # CLOUDDECK_PROVIDER=demo
43
+ # CloudDeck: any non-empty value shows full tracebacks instead of a one-line error.
44
+ # CLOUDDECK_DEBUG=1
45
+
46
+ # `clouddeck-remote`, the client that `clouddeck serve --tunnel` installs on the dev box (set these on the BOX, not on your PC).
47
+ # URL default: http://127.0.0.1:8765 (the end of the SSH reverse tunnel). Token default: the file ~/.config/clouddeck/token
48
+ # that `clouddeck serve --tunnel` pushed.
49
+ # CLOUDDECK_URL=http://127.0.0.1:8765
50
+ # CLOUDDECK_TOKEN=cdk_example
51
+
52
+ # Test seams, used by the test suite and the simulated provider. Do not set these in normal use.
53
+ # DDC_STATE_URL= git URL used to verify checkpoints
54
+ # DDC_STATE_REMOTE= state repo URL on the lease
55
+ # CLOUDDECK_DEMO_LATENCY=0 seconds of fake latency in the demo provider
56
+ # CLOUDDECK_DEMO_SSH_FAIL=1 make the demo provider's interactive ssh fail
@@ -0,0 +1,27 @@
1
+ title = "CloudDeck gitleaks config"
2
+
3
+ # Start from gitleaks' built-in rules (GitHub/AWS/GCP tokens, private keys, generic API keys, ...) and add project-specific ones.
4
+ [extend]
5
+ useDefault = true
6
+
7
+ [[rules]]
8
+ id = "akash-api-key-assignment"
9
+ description = "Akash console API key assigned a real-looking value"
10
+ regex = '''(?i)AKASH_API_KEY["']?\s*[=:]\s*["']?[A-Za-z0-9._\-]{16,}'''
11
+ keywords = ["akash_api_key"]
12
+
13
+ [rules.allowlist]
14
+ description = "placeholders and obviously fake values"
15
+ regexes = ['''(?i)(your|xxx|example|placeholder|changeme|redacted|dummy|fake|sample|test|\$\{)''']
16
+
17
+ [[rules]]
18
+ id = "akash-address"
19
+ description = "Akash account address (identifies the owner's wallet; not a secret, but must not ship in examples)"
20
+ regex = '''\bakash1[0-9a-z]{38}\b'''
21
+ keywords = ["akash1"]
22
+
23
+ # No broad allowlist. The one entry below is a verified false positive: the fixture is a trimmed capture of Akash's public provider
24
+ # listing, whose attribute keys (capabilities/gpu/vendor/nvidia/<model>, ...) look like generic API keys. It holds no credentials.
25
+ [allowlist]
26
+ description = "public Akash provider attribute keys in the hardware test fixture"
27
+ paths = ['''^tests/fixtures/hardware/providers_sample\.json$''']
@@ -0,0 +1,90 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
4
+ and the project uses [Semantic Versioning](https://semver.org/); while the version is `0.x`, minor releases may change behaviour and say so here.
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] - 2026-10-18
9
+
10
+ First public release (alpha).
11
+
12
+ ### Added
13
+
14
+ * **`ddc`, the controller** (`ddc init`, `install`, `uninstall`, `start`, `attach`, `scale`, `status`, `bench`, `checkpoint`, `verify`,
15
+ `stop`, `heal`, `reconcile`, `watchdog`, `ssh`, `run`): provisions a dev box and disposable workers on Akash through the Console API
16
+ (create, bids, lease, close), with a price cap and `runtimeLimitHours` in every order.
17
+ * **`clouddeck`, a terminal UI** with five tabs (Compute, Workers, Projects, Monitor, Recovery), a command palette, a provider catalogue
18
+ and GPU market view, live utilisation and spend, typed confirmations for destructive actions, a summary-line warning when the Akash API is
19
+ unreachable (the last known data stays on screen), and a simulated provider (`-p demo`).
20
+ Also `clouddeck status`, `doctor`, `providers`, `install`, `serve` and `remote`.
21
+ * **Hardware picker** (`h` on the Compute tab): choose any CPU architecture or GPU vendor, model, memory variant and interface in any count,
22
+ a region, audited providers only or pinned providers, from lists built from Akash's public data at run time (nothing is hard-coded, offline
23
+ falls back to the last saved list), with advertised, screened and confirmed availability kept apart. Needs no new limit: price cap and
24
+ runtime limit still go into every order, and `max_price_usd_hr` (default 2.0, at most 100) is the explicit ceiling for one price cap.
25
+ * **Encrypted checkpoints** (gpg AES256, split into 45 MiB parts) in a private GitHub repository you own, taken periodically and before
26
+ every stop, verified on the PC (part hashes, decryption, entry counts, git HEADs), restored on the next start.
27
+ * **Spending protection:** budget and reserve guard (on by default: $25 / $5), lease and worker caps, hard ceilings that hold with the guard off
28
+ (8 leases, 7 workers, $2.00/h price cap per profile by default, 24 h, 8 per call), `unsafe_disable_limits` as the only override, worst-case accounting that counts
29
+ creations whose outcome is unknown, and a lifetime ledger.
30
+ * **Watchdog** registered by `ddc install` (Windows scheduled task, Linux and WSL systemd user timer or cron): idle shutdown of the dev box
31
+ and workers, runtime and budget enforcement, cleanup of leases that never became ready, alerts (desktop notification, `alert_command`, log).
32
+ * **Ownership safety:** only leases recorded in `state.json` and named `ddc-*` (and owned by your account) can be closed; other
33
+ deployments are read-only.
34
+ * **Failure handling:** atomic state and ledger writes with a backup, damaged state or ledger blocks new leases but never blocks status or stop,
35
+ create requests are sent once and reconciled when the outcome is unknown, `ddc reconcile` repairs and adopts.
36
+ * **Remote controller API** for a terminal on the dev box: loopback only, SSH reverse tunnel, `read` / `fleet` / `admin` scopes, expiring
37
+ hashed tokens, rate and size limits, audit log.
38
+ * **Lease scripts** for the box (checkpoint, restore, activity agent, benchmark, worker fan-out) that fail safe: restore verifies before it
39
+ renames, checkpoints refuse truncated workspaces, foreign repositories and second writers, and pushes are atomic and lease-checked.
40
+ * **Configuration** with validated, safe defaults (`config.example.toml`), custom hardware profiles, projects, named environments, reusable
41
+ workloads, an optional pinned Claude Code install (`install_claude_code`) and opt-in credential push (`push_claude_credentials`, off by default).
42
+ * **Provider plugin contract** (`clouddeck.providers` entry points) with a mandatory spend-guard seam.
43
+ * **`ddc job`** (also `python -m clouddeck.job`): a generic runner for batch workloads on one short-lived lease (role `job`) with minimal paid
44
+ idle time: preflight, bid early / lease late, parallel local preparation, resumable detached steps, sha256-verified transfers, verified
45
+ close, per-phase telemetry. Docs: [docs/JOBS.md](docs/JOBS.md), a prebuilt-image guide, examples under `examples/` (in the repository and the
46
+ sdist, not in the wheel), a toolchain `docker/Dockerfile`, and an old-vs-new simulation (assumed, synthetic inputs) in the test suite.
47
+ Safety rules: artifact retrieval is capped (`max_artifact_gb`) and bound by the job's own cost and wall-clock limits, ssh host keys are kept
48
+ per lease, `env.secrets` refuses `AKASH_*` / `DDC_*`, downloads need a sha256 (or an explicit `allow_unverified`) and an http(s) URL, the GPU
49
+ vendor must be named, one run per job name, and a signal during the close never aborts it.
50
+ * **One creation path for every lease.** `check_budget` is now a thin wrapper around `check_spend(label, n, hours, cap_usd_hr, role)`, and
51
+ `launch_lease` (write-ahead record, one create request, promotion) is used by both `create_box` and `ddc job`: job leases are subject to
52
+ the lease caps, the hard ceilings (including the per-lease price cap), the budget guard and the unknown-outcome handling like any other lease.
53
+ Behaviour and messages of the existing commands are unchanged.
54
+ * **Orphaned job protection:** the runner records a heartbeat; `ddc watchdog` closes an owned job lease whose runner has been silent for
55
+ `job_orphan_minutes` (new optional config key, default 30) or that is past its own deadline, and alerts. The budget stop closes job leases
56
+ too. `ddc status`, `ddc stop`, `ddc reconcile` and the TUI handle role `job` (not resizable from the TUI). A plain `ddc stop` (and the TUI's
57
+ stop-everything) also closes owned job leases, as the user's kill switch; `--only` / `--workers-only` and the watchdog's idle stop keep their
58
+ narrower meaning. `job_orphan_minutes` has a floor of 10.
59
+ * `ssh_args` takes an optional `known_hosts` path (default unchanged); `close_box` no longer books a lease that another process already
60
+ recorded as closed.
61
+ * Documentation: quickstart, architecture, costs and safety, checkpoints, platforms, security policy, contributing guide.
62
+ * CI (tests on Linux for Python 3.11 and 3.13 and on Windows for 3.12, lint, shellcheck, wheel smoke test on Linux and Windows, secret
63
+ scanning, dependency audit), a tag-triggered release workflow (PyPI Trusted Publishing, GitHub release with SHA256SUMS) and a
64
+ release-hygiene checker (`scripts/release_check.py`).
65
+ * Preflight loads the ssh access key the way ssh does before any lease is created (a key other accounts can read, for example after
66
+ copying `secrets.env` and `keys\` to a new Windows PC, is refused locally with the `icacls` / `chmod` fix instead of a paid lease that
67
+ never answers ssh); on Windows `ddc install` makes the keys it generates owner-only. `clouddeck doctor` counts a scheduled watchdog only
68
+ when it runs this config.
69
+
70
+ ### Upgrading from the pre-release `ddc.py`
71
+
72
+ * The command lock is an OS lock on `ddc.flock`; `ddc.lock` is left to the pre-release tool, and `ddc` waits while a pre-release
73
+ `ddc.lock` (holding `<pid> <time>`) younger than 3 h exists. Disable the pre-release watchdog task before the first `ddc` command: the
74
+ pre-release tool does not see `ddc.flock`. `ddc install --migrate` replaces the old watchdog task and also rewrites a
75
+ `%USERPROFILE%\bin\clouddeck.cmd` that points elsewhere (`--dry-run` lists the shims).
76
+ * Scheduled watchdogs (Windows task, systemd, cron) are registered with `--config`, and a watchdog run that cannot load its config or finds
77
+ conflicting state dirs raises an alert instead of failing silently.
78
+ * Every checkpoint first re-sends the lease env, so a changed `state_repo` (for example after the state repository moved to another owner) takes effect without
79
+ restarting the box; restored push repos get `origin` set to their configured `github`; the checkpoint verification clone never prompts
80
+ and fails closed after 600 s.
81
+
82
+ ### Known limitations
83
+
84
+ See [SECURITY.md](SECURITY.md#known-limitations) and [docs/COSTS-AND-SAFETY.md](docs/COSTS-AND-SAFETY.md#what-is-not-bounded). In short:
85
+ the platform runtime limit was observed working once (not a guarantee), the watchdog needs your PC, SSH host keys are trusted on first use, deploy keys are
86
+ not per-lease, checkpoint verification is not a content digest, macOS is unsupported (no watchdog installer), and a native Linux host has not been used against real
87
+ leases (WSL2 has). What was checked against real leases: README, "Verified and not verified".
88
+
89
+ [Unreleased]: https://github.com/trexchery/clouddeck/compare/v0.1.0...HEAD
90
+ [0.1.0]: https://github.com/trexchery/clouddeck/releases/tag/v0.1.0
@@ -0,0 +1,131 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement through GitHub's
63
+ private reporting form for this repository:
64
+ <https://github.com/trexchery/clouddeck/security/advisories/new> (start the title
65
+ with "Conduct:"). Please do not use public issues for conduct reports.
66
+ All complaints will be reviewed and investigated promptly and fairly.
67
+
68
+ All community leaders are obligated to respect the privacy and security of the
69
+ reporter of any incident.
70
+
71
+ ## Enforcement Guidelines
72
+
73
+ Community leaders will follow these Community Impact Guidelines in determining
74
+ the consequences for any action they deem in violation of this Code of Conduct:
75
+
76
+ ### 1. Correction
77
+
78
+ **Community Impact**: Use of inappropriate language or other behavior deemed
79
+ unprofessional or unwelcome in the community.
80
+
81
+ **Consequence**: A private, written warning from community leaders, providing
82
+ clarity around the nature of the violation and an explanation of why the
83
+ behavior was inappropriate. A public apology may be requested.
84
+
85
+ ### 2. Warning
86
+
87
+ **Community Impact**: A violation through a single incident or series of
88
+ actions.
89
+
90
+ **Consequence**: A warning with consequences for continued behavior. No
91
+ interaction with the people involved, including unsolicited interaction with
92
+ those enforcing the Code of Conduct, for a specified period of time. This
93
+ includes avoiding interactions in community spaces as well as external channels
94
+ like social media. Violating these terms may lead to a temporary or permanent
95
+ ban.
96
+
97
+ ### 3. Temporary Ban
98
+
99
+ **Community Impact**: A serious violation of community standards, including
100
+ sustained inappropriate behavior.
101
+
102
+ **Consequence**: A temporary ban from any sort of interaction or public
103
+ communication with the community for a specified period of time. No public or
104
+ private interaction with the people involved, including unsolicited interaction
105
+ with those enforcing the Code of Conduct, is allowed during this period.
106
+ Violating these terms may lead to a permanent ban.
107
+
108
+ ### 4. Permanent Ban
109
+
110
+ **Community Impact**: Demonstrating a pattern of violation of community
111
+ standards, including sustained inappropriate behavior, harassment of an
112
+ individual, or aggression toward or disparagement of classes of individuals.
113
+
114
+ **Consequence**: A permanent ban from any sort of public interaction within the
115
+ community.
116
+
117
+ ## Attribution
118
+
119
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
120
+ version 2.1, available at
121
+ <https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.
122
+
123
+ Community Impact Guidelines were inspired by
124
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
125
+
126
+ For answers to common questions about this code of conduct, see the FAQ at
127
+ <https://www.contributor-covenant.org/faq>. Translations are available at
128
+ <https://www.contributor-covenant.org/translations>.
129
+
130
+ [homepage]: https://www.contributor-covenant.org
131
+ [Mozilla CoC]: https://github.com/mozilla/diversity
@@ -0,0 +1,181 @@
1
+ # Contributing to CloudDeck
2
+
3
+ Thanks for helping. CloudDeck is alpha software that moves real money, so the bar for changes is "boring, tested and safe". Small,
4
+ focused pull requests are easiest to review. Please read the [Code of Conduct](CODE_OF_CONDUCT.md); report security problems privately
5
+ (see [SECURITY.md](SECURITY.md)), not as issues.
6
+
7
+ By contributing you agree that your contribution is licensed under the [MIT license](LICENSE). Much of this code base was written with
8
+ AI assistance and contributions may be too; you are responsible for understanding, testing and standing behind what you submit.
9
+
10
+ ## The one rule
11
+
12
+ > **Tests never spend real money and never touch real state.**
13
+ > No Akash API key, no real lease, no real `secrets.env`, `state.json` or keys directory, no network call to the Akash API (not even a
14
+ > read-only one) from a test.
15
+
16
+ Tests use the simulated provider (`DemoProvider`, `clouddeck -p demo`) and the in-memory Akash and lease fake in `tests/fake_akash.py`
17
+ (the `fake` fixture in `tests/conftest.py`). External responses are saved fixtures. Use neutral names in tests, fixtures and docs
18
+ (`prod-box`, `web-app`, `my-project`, `provider-a.example`), and never real deployment numbers, account addresses, host names or paths.
19
+
20
+ ## Development setup
21
+
22
+ You need Python 3.11+ and git. For the lease-script tests you also want bash, GnuPG, `flock` and `tar` (Linux or WSL; they are
23
+ skipped with a reason elsewhere).
24
+
25
+ ```bash
26
+ git clone https://github.com/trexchery/clouddeck.git && cd clouddeck
27
+ python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
28
+ pip install -e ".[dev]"
29
+ pip install ruff==0.17.1 # the version CI uses
30
+ ```
31
+
32
+ Optional: `pip install -e ".[pty-tests]"` for the terminal smoke test (`python tests/pty_smoke.py`), `pip install shellcheck-py` for
33
+ the lease scripts, `pip install pre-commit && pre-commit install` for the same checks as CI before every commit.
34
+
35
+ ## Running the tests
36
+
37
+ ```bash
38
+ python -m pytest -p no:cacheprovider
39
+ ```
40
+
41
+ The suite is offline and takes a few minutes. Run plain `pytest` or a single file; mixing `tests/...` paths with the root `test_ddc.py`
42
+ in an unusual order can lose the per-test isolation fixture (there are two `conftest.py` files). The TUI tests are timing-sensitive on a
43
+ heavily loaded machine: rerun a lone failure before investigating.
44
+
45
+ The `ddc job` tests (`tests/test_job_*.py`) run the real runner on virtual time against a stubbed Akash API and a fake box
46
+ (`tests/job_fakes.py`, `tests/job_helpers.py`; the `jw` fixture) inside the same isolated state as every other test. `tests/job_sim.py` is the
47
+ old-vs-new simulation with assumed, synthetic inputs (from a checkout with the dev extra, `python tests/job_sim.py` runs it standalone in a throw-away state dir). `tests/test_job_scripts.py` runs the real
48
+ `jobremote/*.sh` scripts under bash and skips cleanly where bash (or curl, or shellcheck) is missing.
49
+
50
+ Opt-in extras:
51
+
52
+ * `CLOUDDECK_TEST_WHEEL=1 python -m pytest tests/test_packaging.py` builds a wheel in a temp copy and runs it in a clean venv (needs
53
+ network for the build backend).
54
+ * `python tests/pty_smoke.py` drives the real TUI through a pseudo-terminal.
55
+
56
+ ### Redirect the environment first (and why)
57
+
58
+ The controller reads its config, state and secrets locations from the environment **when it is imported**. A test run once resolved the
59
+ real directories and overwrote a live `state.json`: the file that records which leases are billing. Two protections exist now:
60
+
61
+ 1. the root `conftest.py` points `DDC_STATE_DIR`, `DDC_SECRETS_DIR` and `DDC_CONFIG` at fresh temp directories before anything is
62
+ imported, and the controller **refuses to load under pytest** unless `DDC_STATE_DIR` and `DDC_SECRETS_DIR` are both set;
63
+ 2. *you*, every time you run pytest, a script that imports `clouddeck`, or the CLI during development, should also redirect the
64
+ environment, so that a mistake cannot reach your real directories.
65
+
66
+ ```bash
67
+ # Linux / macOS / WSL
68
+ S=$(mktemp -d)
69
+ export HOME=$S/home XDG_CONFIG_HOME=$S/xdg/config XDG_STATE_HOME=$S/xdg/state XDG_DATA_HOME=$S/xdg/data TMPDIR=$S/tmp
70
+ export DDC_STATE_DIR=$S/state DDC_SECRETS_DIR=$S/secrets PYTHONDONTWRITEBYTECODE=1
71
+ mkdir -p "$HOME" "$TMPDIR"
72
+ python -m pytest -p no:cacheprovider
73
+ ```
74
+
75
+ ```powershell
76
+ # Windows PowerShell
77
+ $S = Join-Path $env:TEMP ("clouddeck-" + [guid]::NewGuid()); New-Item -ItemType Directory $S\home, $S\local, $S\roaming, $S\tmp | Out-Null
78
+ $env:USERPROFILE = "$S\home"; $env:HOME = "$S\home"; $env:LOCALAPPDATA = "$S\local"; $env:APPDATA = "$S\roaming"
79
+ $env:TEMP = "$S\tmp"; $env:TMP = "$S\tmp"
80
+ $env:DDC_STATE_DIR = "$S\state"; $env:DDC_SECRETS_DIR = "$S\secrets"; $env:PYTHONDONTWRITEBYTECODE = "1"
81
+ python -m pytest -p no:cacheprovider
82
+ ```
83
+
84
+ Do the same in a throwaway shell when you try `ddc` or `clouddeck` by hand, unless you really mean to use your real setup.
85
+
86
+ ## Adding or changing things
87
+
88
+ * **Every behaviour change gets an offline test**, and a bug fix gets a test that fails without the fix. Spending, ownership,
89
+ checkpoint and watchdog paths are the most important to cover: look at `tests/test_safety_*.py` for the pattern.
90
+ * **Keep the compatibility promises** ([ARCHITECTURE.md](docs/ARCHITECTURE.md#compatibility-promises)): the `ddc-` lease prefix and `ours`
91
+ semantics, the `state.json` / `ledger.jsonl` / `custom_profiles.json` formats, the checkpoint layout, the lease-side paths, and the
92
+ `DDC_*`, `AKASH_API_KEY` and `CLOUDDECK_*` names. If a change must break one, say so in the PR and in the changelog.
93
+ * **Fail closed.** When something is unknown (an empty API answer, a damaged file, an unreadable balance), do not assume the safe-looking
94
+ answer; refuse to spend and keep close/stop paths working.
95
+ * **No new runtime dependency** without a discussion: the controller and the lease scripts are standard-library only on purpose.
96
+ * **Lease scripts** (`lease/`): every file in that directory is uploaded to every lease, so no subdirectories; LF line endings; bash
97
+ (`set -euo pipefail`); `shellcheck -x -S warning -P lease lease/*.sh` must be clean; document new environment variables in
98
+ `lease/README.md`. Most tests for them need bash, `gpg`, `flock` and `python3` (Linux/WSL).
99
+ * **Job scripts** (`jobremote/`): the same rules as lease scripts (LF, executable bit in git: `git update-index --chmod=+x`, shellcheck clean);
100
+ `boot.sh` is inlined into the SDL, `step.sh` / `fetch.sh` are uploaded. Anything that creates or closes a lease must go through
101
+ `launch_lease` / `close_box`, never a direct API call (a test greps for it).
102
+ * **A new provider**: see [Extension points](docs/ARCHITECTURE.md#extension-points). It must route spending through `Provider.require`.
103
+ * **Docs**: update the relevant file under `docs/`, the README command table and `config.example.toml` when behaviour or configuration
104
+ changes. Do not document a flag you have not run `--help` on.
105
+
106
+ ## Style
107
+
108
+ * Python: `ruff check .` must pass (configuration in `pyproject.toml`: rules E, F, W, I, B, UP, target 3.11, long lines are allowed).
109
+ `ruff format` is enforced only for `scripts/` and `tests/test_release_check.py`; elsewhere match the surrounding code (the code base
110
+ is compact), and do not reformat files you are not changing.
111
+ * Prefer the shortest change that works; no abstractions for a single caller, no speculative configuration.
112
+ * Error messages say what happened and what to do next. Never print secrets, tokens or key material, not even in debug output.
113
+
114
+ ## Release hygiene checks
115
+
116
+ ```bash
117
+ python scripts/release_check.py --no-history # working tree: tracked and untracked-not-ignored files
118
+ python scripts/release_check.py # plus every commit, every blob, every commit message
119
+ python scripts/release_check.py --strict # also fail on placeholders and commit identities (before publishing)
120
+ ```
121
+
122
+ It fails on real-looking secrets, API keys, account addresses, personal absolute paths, private Claude session links, forbidden file
123
+ names and missing required files. It prints locations and rule names only, never the matched text. With `--strict` it also fails on an
124
+ `OWNER` placeholder left in this project's own GitHub URL and on unfilled release-manager markers (the README's live-verification
125
+ table); placeholders meant for the user, such as `OWNER/your-private-state-repo`, are fine. Private words (names, private project
126
+ names, deployment ids) are never written into the public script: maintainers keep them in `scripts/release_denylist.local.txt`
127
+ (gitignored; one case-insensitive regex per line, `#` comments) or point `CLOUDDECK_RELEASE_DENYLIST` at such a file. The few legacy
128
+ compatibility strings that are allowed live in `scripts/release_allowlist.toml`, and every entry needs a `reason`: keep it small. The
129
+ allowlist is scanned too, except inside its own `contains` values.
130
+
131
+ Secret scanning uses [gitleaks](https://github.com/gitleaks/gitleaks) (CI pins version 8.30.1):
132
+
133
+ ```bash
134
+ gitleaks dir --config .gitleaks.toml --redact --no-banner .
135
+ gitleaks git --config .gitleaks.toml --redact --no-banner --log-opts="--all" .
136
+ ```
137
+
138
+ `pre-commit` runs gitleaks, ruff, shellcheck, `detect-private-key` and the tree-only release check.
139
+
140
+ Building and checking the artifacts (CI does this too):
141
+
142
+ ```bash
143
+ pip install build twine && python -m build && python -m twine check --strict dist/*
144
+ python scripts/check_dist.py dist/*.whl dist/*.tar.gz
145
+ ```
146
+
147
+ Remove `build/`, `dist/` and `*.egg-info/` afterwards; they are ignored by git but should not linger.
148
+
149
+ With `SOURCE_DATE_EPOCH` set to the commit time (`git log -1 --format=%ct`), the wheel is bit-for-bit reproducible. The sdist is
150
+ content-reproducible only: the same members, order and contents, but tar and gzip timestamps differ between builds. Release artifacts
151
+ are built on Linux (`.github/workflows/release.yml`) so the sdist keeps the executable bits of the shell scripts.
152
+
153
+ ## Running the CI gates locally
154
+
155
+ ```bash
156
+ python scripts/ci_local.py # ruff, pytest (redirected env, -n auto with pytest-xdist), release_check --no-history,
157
+ # build + twine check + check_dist, shellcheck if installed
158
+ python scripts/ci_local.py --release # the same with release_check --strict over tree + history, as the release workflow runs it
159
+ python scripts/ci_local.py --no-build # skip the build
160
+ ```
161
+
162
+ It needs `pip install -e ".[dev]" ruff==0.17.1 build twine` (and optionally `pytest-xdist`, `shellcheck-py`). It does not run the
163
+ Windows/Linux matrix, the clean-venv wheel smoke test, actionlint, gitleaks or pip-audit; CI does.
164
+
165
+ ## Releasing
166
+
167
+ Push a `vX.Y.Z` tag on a commit that is green on `main`, after moving the `[Unreleased]` entries of `CHANGELOG.md` into a dated `[X.Y.Z]`
168
+ section, setting the version in `pyproject.toml` and `clouddeck/__init__.py` and filling in the README's live-verification table.
169
+ `.github/workflows/release.yml` checks that the tag, the version and the changelog agree, runs `release_check.py --strict`, builds and
170
+ checks the artifacts, publishes to PyPI through Trusted Publishing when the repository variable `PYPI_PUBLISH` is `true`, and creates the
171
+ GitHub release with the wheel, the sdist, `SHA256SUMS` and that changelog section as notes.
172
+
173
+ ## Pull request checklist
174
+
175
+ - [ ] Offline tests added or updated, and `python -m pytest -p no:cacheprovider` passes (with the redirected environment above)
176
+ - [ ] `ruff check .` is clean (and `shellcheck` for `lease/` changes)
177
+ - [ ] `python scripts/release_check.py --no-history` passes: no secrets, personal paths, account names or real host names
178
+ - [ ] No test, fixture or doc depends on real money, real credentials or real state
179
+ - [ ] Compatibility kept, or the break is called out
180
+ - [ ] Docs, `config.example.toml` and `CHANGELOG.md` updated if behaviour or configuration changed
181
+ - [ ] The change is as small as it can be
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CloudDeck contributors
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.
@@ -0,0 +1,14 @@
1
+ include LICENSE README.md SECURITY.md CONTRIBUTING.md CODE_OF_CONDUCT.md CHANGELOG.md THIRD_PARTY_NOTICES.md
2
+ include .env.example .gitleaks.toml
3
+ include config.example.toml sdl.yaml.tmpl ddc.py conftest.py test_ddc.py requirements.txt
4
+ recursive-include lease *
5
+ recursive-include jobremote *
6
+ recursive-include docker *
7
+ recursive-include examples *
8
+ recursive-include tests *
9
+ recursive-include scripts *
10
+ recursive-include windows *
11
+ recursive-include bin *
12
+ recursive-include docs *
13
+ global-exclude __pycache__ *.py[cod]
14
+ exclude scripts/release_denylist.local*