msks 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 (315) hide show
  1. msks-0.1.0/.github/actionlint.yaml +10 -0
  2. msks-0.1.0/.github/workflows/ci.yml +73 -0
  3. msks-0.1.0/.github/workflows/guest-assets.yml +107 -0
  4. msks-0.1.0/.github/workflows/lint.yml +80 -0
  5. msks-0.1.0/.github/workflows/release.yml +248 -0
  6. msks-0.1.0/.github/workflows/smoke-debian.yml +138 -0
  7. msks-0.1.0/.github/workflows/smoke-egress.yml +148 -0
  8. msks-0.1.0/.github/workflows/smoke-nixos.yml +178 -0
  9. msks-0.1.0/.gitignore +30 -0
  10. msks-0.1.0/AGENTS.md +406 -0
  11. msks-0.1.0/PKG-INFO +494 -0
  12. msks-0.1.0/README.md +471 -0
  13. msks-0.1.0/alembic.ini +38 -0
  14. msks-0.1.0/devenv.lock +83 -0
  15. msks-0.1.0/devenv.nix +705 -0
  16. msks-0.1.0/devenv.yaml +10 -0
  17. msks-0.1.0/docs/ROADMAP.md +208 -0
  18. msks-0.1.0/docs/boot-speed.md +141 -0
  19. msks-0.1.0/docs/changes.md +492 -0
  20. msks-0.1.0/docs/cli.md +1779 -0
  21. msks-0.1.0/docs/config.md +302 -0
  22. msks-0.1.0/docs/images.md +721 -0
  23. msks-0.1.0/docs/llm.md +195 -0
  24. msks-0.1.0/docs/networking.md +708 -0
  25. msks-0.1.0/docs/secrets.md +442 -0
  26. msks-0.1.0/docs/spikes/194-egress-interceptor.md +185 -0
  27. msks-0.1.0/docs/spikes/205-nixos-appliance-spike-1.md +114 -0
  28. msks-0.1.0/docs/spikes/210-nixos-appliance-spike-2.md +139 -0
  29. msks-0.1.0/docs/spikes/211-nixos-appliance-spike-3.md +142 -0
  30. msks-0.1.0/docs/spikes/spike194.py +496 -0
  31. msks-0.1.0/docs/storage.md +314 -0
  32. msks-0.1.0/flake.lock +27 -0
  33. msks-0.1.0/flake.nix +124 -0
  34. msks-0.1.0/import-graph.toml +231 -0
  35. msks-0.1.0/nix/agent-toolchain.nix +223 -0
  36. msks-0.1.0/nix/guest-debian.nix +1589 -0
  37. msks-0.1.0/nix/guest-nixos-configuration.nix +580 -0
  38. msks-0.1.0/nix/guest-nixos.nix +573 -0
  39. msks-0.1.0/nix/guest-pi-extension.ts +146 -0
  40. msks-0.1.0/nix/guest.nix +38 -0
  41. msks-0.1.0/nix/image-archive.nix +72 -0
  42. msks-0.1.0/nix/module.nix +314 -0
  43. msks-0.1.0/nix/msks-client-pkg.nix +79 -0
  44. msks-0.1.0/nix/msks-pkg.nix +97 -0
  45. msks-0.1.0/nix/pi-shrinkwrap-integrity.json +7 -0
  46. msks-0.1.0/nix/pi-shrinkwrap-patch.py +88 -0
  47. msks-0.1.0/nix/source.nix +28 -0
  48. msks-0.1.0/nix/textual-pkg.nix +67 -0
  49. msks-0.1.0/pyproject.toml +136 -0
  50. msks-0.1.0/scripts/build-guest.sh +70 -0
  51. msks-0.1.0/scripts/check_deferred_imports.py +312 -0
  52. msks-0.1.0/scripts/check_import_cycles.py +213 -0
  53. msks-0.1.0/scripts/check_import_graph.py +597 -0
  54. msks-0.1.0/scripts/covgaps.py +184 -0
  55. msks-0.1.0/scripts/demo-vm.sh +68 -0
  56. msks-0.1.0/scripts/dev-daemon.sh +186 -0
  57. msks-0.1.0/scripts/dev-workspace.sh +92 -0
  58. msks-0.1.0/scripts/fuzz-egress.py +3970 -0
  59. msks-0.1.0/scripts/image_meta.py +112 -0
  60. msks-0.1.0/scripts/jscpd-gate.sh +40 -0
  61. msks-0.1.0/scripts/perf-boot.py +363 -0
  62. msks-0.1.0/scripts/preflight.sh +185 -0
  63. msks-0.1.0/scripts/release_notes.py +109 -0
  64. msks-0.1.0/scripts/xenon-gate.sh +56 -0
  65. msks-0.1.0/src/msks/msks/__init__.py +1 -0
  66. msks-0.1.0/src/msks/msks/agecrypto.py +931 -0
  67. msks-0.1.0/src/msks/msks/app.py +66 -0
  68. msks-0.1.0/src/msks/msks/client/__init__.py +7 -0
  69. msks-0.1.0/src/msks/msks/client/agent.py +451 -0
  70. msks-0.1.0/src/msks/msks/client/cli.py +1106 -0
  71. msks-0.1.0/src/msks/msks/client/config.py +1090 -0
  72. msks-0.1.0/src/msks/msks/client/console.py +321 -0
  73. msks-0.1.0/src/msks/msks/client/context.py +49 -0
  74. msks-0.1.0/src/msks/msks/client/create.py +189 -0
  75. msks-0.1.0/src/msks/msks/client/egress.py +291 -0
  76. msks-0.1.0/src/msks/msks/client/env.py +154 -0
  77. msks-0.1.0/src/msks/msks/client/forward.py +383 -0
  78. msks-0.1.0/src/msks/msks/client/images.py +573 -0
  79. msks-0.1.0/src/msks/msks/client/resize.py +47 -0
  80. msks-0.1.0/src/msks/msks/client/rest.py +357 -0
  81. msks-0.1.0/src/msks/msks/client/rsync.py +264 -0
  82. msks-0.1.0/src/msks/msks/client/secrets.py +370 -0
  83. msks-0.1.0/src/msks/msks/client/ssh.py +1337 -0
  84. msks-0.1.0/src/msks/msks/client/tabular.py +100 -0
  85. msks-0.1.0/src/msks/msks/client/term_popup.py +589 -0
  86. msks-0.1.0/src/msks/msks/client/tui/__init__.py +13 -0
  87. msks-0.1.0/src/msks/msks/client/tui/consent.py +582 -0
  88. msks-0.1.0/src/msks/msks/client/tui/consent_ui.py +1566 -0
  89. msks-0.1.0/src/msks/msks/client/tui/data.py +301 -0
  90. msks-0.1.0/src/msks/msks/client/tui/decide_app.py +707 -0
  91. msks-0.1.0/src/msks/msks/client/tui/follow.py +161 -0
  92. msks-0.1.0/src/msks/msks/client/tui/forms.py +521 -0
  93. msks-0.1.0/src/msks/msks/client/tui/importer.py +324 -0
  94. msks-0.1.0/src/msks/msks/client/tui/link.py +259 -0
  95. msks-0.1.0/src/msks/msks/client/tui/main_app.py +351 -0
  96. msks-0.1.0/src/msks/msks/client/tui/main_screen.py +440 -0
  97. msks-0.1.0/src/msks/msks/client/tui/rows.py +395 -0
  98. msks-0.1.0/src/msks/msks/client/tui/secrets.py +1366 -0
  99. msks-0.1.0/src/msks/msks/client/tui/unlock.py +222 -0
  100. msks-0.1.0/src/msks/msks/client/tui/workspace.py +943 -0
  101. msks-0.1.0/src/msks/msks/client/unlock.py +140 -0
  102. msks-0.1.0/src/msks/msks/client/volumes.py +273 -0
  103. msks-0.1.0/src/msks/msks/client/wintitle.py +72 -0
  104. msks-0.1.0/src/msks/msks/client/workspaces.py +489 -0
  105. msks-0.1.0/src/msks/msks/client/wsauth.py +68 -0
  106. msks-0.1.0/src/msks/msks/client/wsdial.py +88 -0
  107. msks-0.1.0/src/msks/msks/config.py +597 -0
  108. msks-0.1.0/src/msks/msks/configio.py +65 -0
  109. msks-0.1.0/src/msks/msks/conformance.py +860 -0
  110. msks-0.1.0/src/msks/msks/conformance_args.py +66 -0
  111. msks-0.1.0/src/msks/msks/consent/__init__.py +1 -0
  112. msks-0.1.0/src/msks/msks/consent/coordinator.py +841 -0
  113. msks-0.1.0/src/msks/msks/consent/deciders.py +49 -0
  114. msks-0.1.0/src/msks/msks/events.py +111 -0
  115. msks-0.1.0/src/msks/msks/identity.py +516 -0
  116. msks-0.1.0/src/msks/msks/imagestore.py +863 -0
  117. msks-0.1.0/src/msks/msks/interceptor/__init__.py +27 -0
  118. msks-0.1.0/src/msks/msks/interceptor/ca.py +209 -0
  119. msks-0.1.0/src/msks/msks/interceptor/egress.py +242 -0
  120. msks-0.1.0/src/msks/msks/interceptor/engine.py +528 -0
  121. msks-0.1.0/src/msks/msks/interceptor/manager.py +503 -0
  122. msks-0.1.0/src/msks/msks/interceptor/probe.py +323 -0
  123. msks-0.1.0/src/msks/msks/llm.py +1070 -0
  124. msks-0.1.0/src/msks/msks/microvm/__init__.py +76 -0
  125. msks-0.1.0/src/msks/msks/microvm/chapi.py +75 -0
  126. msks-0.1.0/src/msks/msks/microvm/driver.py +71 -0
  127. msks-0.1.0/src/msks/msks/microvm/errors.py +14 -0
  128. msks-0.1.0/src/msks/msks/microvm/local.py +900 -0
  129. msks-0.1.0/src/msks/msks/migrations/env.py +62 -0
  130. msks-0.1.0/src/msks/msks/migrations/script.py.mako +22 -0
  131. msks-0.1.0/src/msks/msks/migrations/versions/0001_initial.py +47 -0
  132. msks-0.1.0/src/msks/msks/migrations/versions/0002_workspace_artifacts.py +40 -0
  133. msks-0.1.0/src/msks/msks/migrations/versions/0003_workspace_egress.py +29 -0
  134. msks-0.1.0/src/msks/msks/migrations/versions/0004_workspace_egress_slice.py +24 -0
  135. msks-0.1.0/src/msks/msks/migrations/versions/0005_workspace_user_data.py +24 -0
  136. msks-0.1.0/src/msks/msks/migrations/versions/0006_workspace_identity.py +28 -0
  137. msks-0.1.0/src/msks/msks/migrations/versions/0007_egress_consent.py +86 -0
  138. msks-0.1.0/src/msks/msks/migrations/versions/0008_placeholders.py +63 -0
  139. msks-0.1.0/src/msks/msks/migrations/versions/0009_workspace_login_user.py +25 -0
  140. msks-0.1.0/src/msks/msks/migrations/versions/0010_workspace_name.py +38 -0
  141. msks-0.1.0/src/msks/msks/migrations/versions/0011_workspace_llm_token.py +27 -0
  142. msks-0.1.0/src/msks/msks/migrations/versions/0012_secret_audit_workspace_index.py +26 -0
  143. msks-0.1.0/src/msks/msks/migrations/versions/0013_daemon_wide_placeholders.py +233 -0
  144. msks-0.1.0/src/msks/msks/migrations/versions/0014_drop_workspace_llm_token.py +49 -0
  145. msks-0.1.0/src/msks/msks/migrations/versions/0015_drop_workspace_privkey.py +52 -0
  146. msks-0.1.0/src/msks/msks/migrations/versions/0016_secret_blobs.py +47 -0
  147. msks-0.1.0/src/msks/msks/model/__init__.py +13 -0
  148. msks-0.1.0/src/msks/msks/model/db.py +58 -0
  149. msks-0.1.0/src/msks/msks/model/egress_consent.py +754 -0
  150. msks-0.1.0/src/msks/msks/model/model.py +915 -0
  151. msks-0.1.0/src/msks/msks/model/secrets.py +144 -0
  152. msks-0.1.0/src/msks/msks/model/tokens.py +20 -0
  153. msks-0.1.0/src/msks/msks/model/workspaces.py +121 -0
  154. msks-0.1.0/src/msks/msks/net/__init__.py +16 -0
  155. msks-0.1.0/src/msks/msks/net/alloc.py +82 -0
  156. msks-0.1.0/src/msks/msks/net/conntrack.py +57 -0
  157. msks-0.1.0/src/msks/msks/net/dhcp.py +307 -0
  158. msks-0.1.0/src/msks/msks/net/dns.py +564 -0
  159. msks-0.1.0/src/msks/msks/net/dnsmsg.py +333 -0
  160. msks-0.1.0/src/msks/msks/net/loopio.py +83 -0
  161. msks-0.1.0/src/msks/msks/net/manager.py +1202 -0
  162. msks-0.1.0/src/msks/msks/net/nfq.py +500 -0
  163. msks-0.1.0/src/msks/msks/net/nfqbind.py +354 -0
  164. msks-0.1.0/src/msks/msks/net/nft.py +853 -0
  165. msks-0.1.0/src/msks/msks/net/taps.py +74 -0
  166. msks-0.1.0/src/msks/msks/persist.py +737 -0
  167. msks-0.1.0/src/msks/msks/secretcache.py +238 -0
  168. msks-0.1.0/src/msks/msks/secretstore.py +155 -0
  169. msks-0.1.0/src/msks/msks/server/__init__.py +7 -0
  170. msks-0.1.0/src/msks/msks/server/api/__init__.py +186 -0
  171. msks-0.1.0/src/msks/msks/server/api/boot.py +172 -0
  172. msks-0.1.0/src/msks/msks/server/api/console.py +224 -0
  173. msks-0.1.0/src/msks/msks/server/api/deps.py +75 -0
  174. msks-0.1.0/src/msks/msks/server/api/egress.py +266 -0
  175. msks-0.1.0/src/msks/msks/server/api/events.py +204 -0
  176. msks-0.1.0/src/msks/msks/server/api/forward.py +118 -0
  177. msks-0.1.0/src/msks/msks/server/api/images.py +380 -0
  178. msks-0.1.0/src/msks/msks/server/api/rows.py +143 -0
  179. msks-0.1.0/src/msks/msks/server/api/schemas.py +256 -0
  180. msks-0.1.0/src/msks/msks/server/api/secrets.py +924 -0
  181. msks-0.1.0/src/msks/msks/server/api/streams.py +136 -0
  182. msks-0.1.0/src/msks/msks/server/api/tokens.py +38 -0
  183. msks-0.1.0/src/msks/msks/server/api/volumes.py +464 -0
  184. msks-0.1.0/src/msks/msks/server/api/workspaces.py +374 -0
  185. msks-0.1.0/src/msks/msks/server/doctor.py +868 -0
  186. msks-0.1.0/src/msks/msks/server/hostsetup.py +935 -0
  187. msks-0.1.0/src/msks/msks/server/localsocket.py +274 -0
  188. msks-0.1.0/src/msks/msks/server/main.py +335 -0
  189. msks-0.1.0/src/msks/msks/server/reload.py +131 -0
  190. msks-0.1.0/src/msks/msks/server/tls.py +278 -0
  191. msks-0.1.0/src/msks/msks/server/watcher.py +256 -0
  192. msks-0.1.0/src/msks/msks/settings.py +783 -0
  193. msks-0.1.0/src/msks/msks/spec/__init__.py +29 -0
  194. msks-0.1.0/src/msks/msks/spec/egress.py +401 -0
  195. msks-0.1.0/src/msks/msks/spec/failures.py +24 -0
  196. msks-0.1.0/src/msks/msks/spec/images.py +53 -0
  197. msks-0.1.0/src/msks/msks/spec/probe.py +57 -0
  198. msks-0.1.0/src/msks/msks/spec/sockets.py +78 -0
  199. msks-0.1.0/src/msks/msks/spec/time.py +20 -0
  200. msks-0.1.0/src/msks/msks/spec/tokens.py +38 -0
  201. msks-0.1.0/src/msks/msks/spec/version.py +9 -0
  202. msks-0.1.0/src/msks/msks/spec/vm.py +104 -0
  203. msks-0.1.0/src/msks/msks/storage.py +211 -0
  204. msks-0.1.0/src/msks/tests/agefixtures/ed25519 +7 -0
  205. msks-0.1.0/src/msks/tests/agefixtures/ed25519.age +5 -0
  206. msks-0.1.0/src/msks/tests/agefixtures/ed25519.pub +1 -0
  207. msks-0.1.0/src/msks/tests/agefixtures/ed25519_enc +8 -0
  208. msks-0.1.0/src/msks/tests/agefixtures/ed25519_enc.age +5 -0
  209. msks-0.1.0/src/msks/tests/agefixtures/ed25519_enc.pub +1 -0
  210. msks-0.1.0/src/msks/tests/agefixtures/empty.age +5 -0
  211. msks-0.1.0/src/msks/tests/agefixtures/exact64k.age +0 -0
  212. msks-0.1.0/src/msks/tests/agefixtures/multi.age +15 -0
  213. msks-0.1.0/src/msks/tests/agefixtures/plain.txt +1 -0
  214. msks-0.1.0/src/msks/tests/agefixtures/rsa +38 -0
  215. msks-0.1.0/src/msks/tests/agefixtures/rsa.age +15 -0
  216. msks-0.1.0/src/msks/tests/agefixtures/rsa.pub +1 -0
  217. msks-0.1.0/src/msks/tests/conftest.py +113 -0
  218. msks-0.1.0/src/msks/tests/devenv.lock +65 -0
  219. msks-0.1.0/src/msks/tests/fake_ch.py +98 -0
  220. msks-0.1.0/src/msks/tests/guestassets.py +164 -0
  221. msks-0.1.0/src/msks/tests/netstubs.py +59 -0
  222. msks-0.1.0/src/msks/tests/test_agecrypto.py +709 -0
  223. msks-0.1.0/src/msks/tests/test_api.py +4452 -0
  224. msks-0.1.0/src/msks/tests/test_boundary.py +68 -0
  225. msks-0.1.0/src/msks/tests/test_client_cli.py +4941 -0
  226. msks-0.1.0/src/msks/tests/test_client_config.py +1104 -0
  227. msks-0.1.0/src/msks/tests/test_client_console.py +748 -0
  228. msks-0.1.0/src/msks/tests/test_client_forward.py +762 -0
  229. msks-0.1.0/src/msks/tests/test_client_rsync.py +742 -0
  230. msks-0.1.0/src/msks/tests/test_client_ssh.py +2235 -0
  231. msks-0.1.0/src/msks/tests/test_client_tabular.py +112 -0
  232. msks-0.1.0/src/msks/tests/test_client_unlock.py +504 -0
  233. msks-0.1.0/src/msks/tests/test_config.py +927 -0
  234. msks-0.1.0/src/msks/tests/test_conformance.py +909 -0
  235. msks-0.1.0/src/msks/tests/test_consent_coordinator.py +756 -0
  236. msks-0.1.0/src/msks/tests/test_consent_page.py +2680 -0
  237. msks-0.1.0/src/msks/tests/test_consent_specs.py +171 -0
  238. msks-0.1.0/src/msks/tests/test_consent_tui.py +570 -0
  239. msks-0.1.0/src/msks/tests/test_consent_tui_helpers.py +418 -0
  240. msks-0.1.0/src/msks/tests/test_console_api.py +460 -0
  241. msks-0.1.0/src/msks/tests/test_decide_app.py +1046 -0
  242. msks-0.1.0/src/msks/tests/test_deciders.py +30 -0
  243. msks-0.1.0/src/msks/tests/test_dns_gate.py +589 -0
  244. msks-0.1.0/src/msks/tests/test_dnsmsg.py +217 -0
  245. msks-0.1.0/src/msks/tests/test_doctor.py +923 -0
  246. msks-0.1.0/src/msks/tests/test_egress_api.py +1314 -0
  247. msks-0.1.0/src/msks/tests/test_egress_consent_model.py +408 -0
  248. msks-0.1.0/src/msks/tests/test_events_watcher.py +777 -0
  249. msks-0.1.0/src/msks/tests/test_forward_api.py +490 -0
  250. msks-0.1.0/src/msks/tests/test_fuzz_egress_reap.py +508 -0
  251. msks-0.1.0/src/msks/tests/test_guestassets.py +665 -0
  252. msks-0.1.0/src/msks/tests/test_home_volume_api.py +619 -0
  253. msks-0.1.0/src/msks/tests/test_hostsetup.py +1256 -0
  254. msks-0.1.0/src/msks/tests/test_identity.py +748 -0
  255. msks-0.1.0/src/msks/tests/test_imagestore.py +2330 -0
  256. msks-0.1.0/src/msks/tests/test_import_graph.py +53 -0
  257. msks-0.1.0/src/msks/tests/test_interceptor_ca.py +99 -0
  258. msks-0.1.0/src/msks/tests/test_interceptor_egress.py +306 -0
  259. msks-0.1.0/src/msks/tests/test_interceptor_engine.py +808 -0
  260. msks-0.1.0/src/msks/tests/test_interceptor_live.py +588 -0
  261. msks-0.1.0/src/msks/tests/test_interceptor_manager.py +628 -0
  262. msks-0.1.0/src/msks/tests/test_layering.py +256 -0
  263. msks-0.1.0/src/msks/tests/test_llm.py +1146 -0
  264. msks-0.1.0/src/msks/tests/test_local_driver.py +1476 -0
  265. msks-0.1.0/src/msks/tests/test_localsocket.py +724 -0
  266. msks-0.1.0/src/msks/tests/test_main.py +156 -0
  267. msks-0.1.0/src/msks/tests/test_main_tui.py +5079 -0
  268. msks-0.1.0/src/msks/tests/test_model.py +1081 -0
  269. msks-0.1.0/src/msks/tests/test_net_alloc.py +51 -0
  270. msks-0.1.0/src/msks/tests/test_net_conntrack.py +39 -0
  271. msks-0.1.0/src/msks/tests/test_net_dhcp.py +413 -0
  272. msks-0.1.0/src/msks/tests/test_net_dns.py +338 -0
  273. msks-0.1.0/src/msks/tests/test_net_loopio.py +118 -0
  274. msks-0.1.0/src/msks/tests/test_net_manager.py +1978 -0
  275. msks-0.1.0/src/msks/tests/test_net_nfq.py +739 -0
  276. msks-0.1.0/src/msks/tests/test_net_nfqbind.py +459 -0
  277. msks-0.1.0/src/msks/tests/test_net_nft.py +700 -0
  278. msks-0.1.0/src/msks/tests/test_net_taps.py +53 -0
  279. msks-0.1.0/src/msks/tests/test_persist.py +778 -0
  280. msks-0.1.0/src/msks/tests/test_pkg_mirror.py +248 -0
  281. msks-0.1.0/src/msks/tests/test_probe.py +355 -0
  282. msks-0.1.0/src/msks/tests/test_pytest_timeout_config.py +79 -0
  283. msks-0.1.0/src/msks/tests/test_release_scripts.py +191 -0
  284. msks-0.1.0/src/msks/tests/test_reload.py +177 -0
  285. msks-0.1.0/src/msks/tests/test_secretcache.py +212 -0
  286. msks-0.1.0/src/msks/tests/test_secretstore.py +245 -0
  287. msks-0.1.0/src/msks/tests/test_settings.py +441 -0
  288. msks-0.1.0/src/msks/tests/test_smoke/__init__.py +596 -0
  289. msks-0.1.0/src/msks/tests/test_smoke/conftest.py +32 -0
  290. msks-0.1.0/src/msks/tests/test_smoke/core/__init__.py +10 -0
  291. msks-0.1.0/src/msks/tests/test_smoke/core/test_boot_lifecycle.py +164 -0
  292. msks-0.1.0/src/msks/tests/test_smoke/core/test_conformance.py +58 -0
  293. msks-0.1.0/src/msks/tests/test_smoke/core/test_console.py +116 -0
  294. msks-0.1.0/src/msks/tests/test_smoke/core/test_sudo.py +98 -0
  295. msks-0.1.0/src/msks/tests/test_smoke/core/test_toolchain.py +155 -0
  296. msks-0.1.0/src/msks/tests/test_smoke/core/test_user_data.py +199 -0
  297. msks-0.1.0/src/msks/tests/test_smoke/egress/__init__.py +9 -0
  298. msks-0.1.0/src/msks/tests/test_smoke/egress/test_conformance.py +29 -0
  299. msks-0.1.0/src/msks/tests/test_smoke/egress/test_daemon_e2e.py +385 -0
  300. msks-0.1.0/src/msks/tests/test_smoke/egress/test_dev_workspace.py +180 -0
  301. msks-0.1.0/src/msks/tests/test_smoke/egress/test_egress.py +1147 -0
  302. msks-0.1.0/src/msks/tests/test_smoke/egress/test_identity.py +1097 -0
  303. msks-0.1.0/src/msks/tests/test_smoke/egress/test_llm_tap_gate.py +173 -0
  304. msks-0.1.0/src/msks/tests/test_smoke/egress/test_nixos_fold_e2e.py +291 -0
  305. msks-0.1.0/src/msks/tests/test_smoke/egress/test_nixos_user_nix_e2e.py +395 -0
  306. msks-0.1.0/src/msks/tests/test_smoke/egress/test_probe_e2e.py +214 -0
  307. msks-0.1.0/src/msks/tests/test_smoke/egress/test_ssh_forward.py +449 -0
  308. msks-0.1.0/src/msks/tests/test_smoke_harness.py +152 -0
  309. msks-0.1.0/src/msks/tests/test_storage.py +287 -0
  310. msks-0.1.0/src/msks/tests/test_term_popup.py +1005 -0
  311. msks-0.1.0/src/msks/tests/test_tls.py +305 -0
  312. msks-0.1.0/src/msks/tests/test_tui_secrets.py +2004 -0
  313. msks-0.1.0/src/msks/tests/test_wintitle.py +117 -0
  314. msks-0.1.0/src/msks/tests/testkeys.py +67 -0
  315. msks-0.1.0/uv.lock +2701 -0
@@ -0,0 +1,10 @@
1
+ # actionlint config — repo-specific knowledge the linter cannot
2
+ # infer (https://github.com/rhysd/actionlint/blob/main/docs/config.md).
3
+ #
4
+ # Custom self-hosted runner labels: nightly-l3.yml's proving smoke
5
+ # runs on a registered runner labeled `msks-l3` (#135) — the depth
6
+ # of nesting the recursion proves (two levels below the runner)
7
+ # exists only there, not on GitHub's hosted runners.
8
+ self-hosted-runner:
9
+ labels:
10
+ - msks-l3
@@ -0,0 +1,73 @@
1
+ # CI: the authoritative unit-test run (#6, split to tests-only in #17).
2
+ #
3
+ # Everything runs inside the devenv shell built from this repo's
4
+ # devenv.nix/devenv.yaml — the same pinned nixpkgs, the same uv venv,
5
+ # the same cloud-hypervisor as every contributor gets — so a green run
6
+ # here means the dev environment itself is healthy too.
7
+ #
8
+ # Lint/xenon (lint.yml) and the guest-asset build (guest-assets.yml)
9
+ # are now their own workflows: each reports as its own check and none
10
+ # can mask the others, which is what the old `if: !cancelled()`
11
+ # chaining approximated inside a single job.
12
+ #
13
+ # The real-VM smoke tests run in smoke.yml — the hosted runner's
14
+ # /dev/kvm (changelog 2024-04-02) plus the built guest assets
15
+ # (.devenv/state/guest) satisfy the local suite's gate (#64). This
16
+ # workflow stays TEST_*-free by design: coverage-gated units
17
+ # only.
18
+ name: CI
19
+
20
+ # Markdown-only changes cannot move the gates (ruff/xenon read src/,
21
+ # the unit tests exercise code) — so an .md-only push skips both runs
22
+ # instead of burning a runner. guest-assets.yml already path-filters
23
+ # to its own inputs.
24
+ on:
25
+ push:
26
+ branches: [main]
27
+ paths-ignore:
28
+ - "**.md"
29
+ pull_request:
30
+ paths-ignore:
31
+ - "**.md"
32
+
33
+ permissions:
34
+ contents: read
35
+
36
+ concurrency:
37
+ group: ci-${{ github.ref }}
38
+ cancel-in-progress: true
39
+
40
+ jobs:
41
+ unit-tests:
42
+ runs-on: ubuntu-latest
43
+ timeout-minutes: 60
44
+ steps:
45
+ - uses: actions/checkout@v5
46
+
47
+ - uses: cachix/install-nix-action@v31
48
+ with:
49
+ github_access_token: ${{ secrets.GITHUB_TOKEN }}
50
+
51
+ - uses: cachix/cachix-action@v17
52
+ with:
53
+ name: devenv
54
+
55
+ # Pinned release, not the moving default registry: the devenv
56
+ # that evaluates the 2.x tasks syntax and parses -O is the one
57
+ # moving part otherwise — an upstream break would red CI with no
58
+ # repo change.
59
+ - name: Install devenv.sh
60
+ run: nix profile add github:cachix/devenv/v2.3.1
61
+
62
+ # The CI-identical invocation (AGENTS.md): pytest -n auto with
63
+ # coverage flags from pyproject addopts, COVERAGE_CORE=sysmon
64
+ # pinned by conftest, fail_under = 100 branch coverage. The
65
+ # runner's TMPDIR nests deep enough to push AF_UNIX socket paths
66
+ # past the 108-byte limit; pytest's basetemp belongs under /tmp
67
+ # (belt to the fixtures' shallow state dirs).
68
+ - name: Unit tests
69
+ env:
70
+ TMPDIR: /tmp
71
+ run: >-
72
+ devenv --quiet -O dotenv.enable:bool false shell --
73
+ unit-tests
@@ -0,0 +1,107 @@
1
+ # Guest-asset build (#17, split out of ci.yml).
2
+ #
3
+ # The nix-heavy derivation check from #5's ubuntu-latest criterion:
4
+ # a cold checkout must still build the kernel/initrd/rootfs, so the
5
+ # guest-asset expressions cannot silently rot. The
6
+ # artifacts are not consumed on the runner — CH smoke needs KVM —
7
+ # which is exactly why this reports as its own check: slow and
8
+ # independent of both lint and the unit tests.
9
+ #
10
+ # Needs none of the test-run plumbing (no TMPDIR pin, no coverage
11
+ # env): just the devenv shell and the msks-build-guest script.
12
+ #
13
+ # Path-filtered: runs only when the guest inputs change — the nix
14
+ # expressions, the build script, the devenv files that pin nixpkgs
15
+ # and define the task, or this workflow itself. The trade vs. #5's
16
+ # anti-rot criterion is accepted deliberately: unrelated PRs skip
17
+ # this job (and show it as skipped), guest-touching PRs and any
18
+ # devenv pin move still build from cold.
19
+ name: Guest assets
20
+
21
+ on:
22
+ push:
23
+ branches: [main]
24
+ paths:
25
+ - .github/workflows/guest-assets.yml
26
+ - devenv.nix
27
+ - devenv.yaml
28
+ - devenv.lock
29
+ - nix/guest.nix
30
+ - nix/agent-toolchain.nix
31
+ - nix/pi-shrinkwrap-patch.py
32
+ - nix/pi-shrinkwrap-integrity.json
33
+ - nix/guest-debian.nix
34
+ - nix/guest-nixos.nix
35
+ - nix/image-archive.nix
36
+ - nix/guest-pi-extension.ts
37
+ - scripts/build-guest.sh
38
+ pull_request:
39
+ paths:
40
+ - .github/workflows/guest-assets.yml
41
+ - devenv.nix
42
+ - devenv.yaml
43
+ - devenv.lock
44
+ - nix/guest.nix
45
+ - nix/agent-toolchain.nix
46
+ - nix/pi-shrinkwrap-patch.py
47
+ - nix/pi-shrinkwrap-integrity.json
48
+ - nix/guest-debian.nix
49
+ - nix/guest-nixos.nix
50
+ - nix/image-archive.nix
51
+ - nix/guest-pi-extension.ts
52
+ - scripts/build-guest.sh
53
+
54
+ permissions:
55
+ contents: read
56
+
57
+ concurrency:
58
+ group: guest-assets-${{ github.ref }}
59
+ cancel-in-progress: true
60
+
61
+ jobs:
62
+ build-guest:
63
+ runs-on: ubuntu-latest
64
+ timeout-minutes: 60
65
+ steps:
66
+ - uses: actions/checkout@v5
67
+
68
+ - uses: cachix/install-nix-action@v31
69
+ with:
70
+ github_access_token: ${{ secrets.GITHUB_TOKEN }}
71
+
72
+ - uses: cachix/cachix-action@v17
73
+ with:
74
+ name: devenv
75
+
76
+ # Pinned release, not the moving default registry: the devenv
77
+ # that evaluates the 2.x tasks syntax and parses -O is the one
78
+ # moving part otherwise — an upstream break would red CI with no
79
+ # repo change.
80
+ - name: Install devenv.sh
81
+ run: nix profile add github:cachix/devenv/v2.3.1
82
+
83
+ - name: Build guest assets
84
+ run: >-
85
+ devenv --quiet -O dotenv.enable:bool false shell --
86
+ msks-build-guest
87
+
88
+ # The NixOS guest (#250): the second catalog image, built from
89
+ # the same pinned nixpkgs the Debian guest's toolchain uses.
90
+ # Its closure is all cache.nixos.org downloads on a cold
91
+ # runner — the 60-minute ceiling holds, but the step sits
92
+ # after the Debian build so a slow mirror never muddies its
93
+ # signal.
94
+ - name: Build the NixOS guest assets
95
+ run: >-
96
+ devenv --quiet -O dotenv.enable:bool false shell --
97
+ msks-build-guest nixos
98
+
99
+ # The real-artifact pinning test: the ''-escape RepoTag bug
100
+ # shipped precisely because this test skipped everywhere CI
101
+ # ran. The import costs ~8s against each built archive.
102
+ - name: Import the built images (pinning test)
103
+ run: >-
104
+ devenv --quiet -O dotenv.enable:bool false shell --
105
+ python -m pytest src/msks/tests/test_imagestore.py
106
+ -q -p no:cacheprovider --no-cov
107
+ -k real_built
@@ -0,0 +1,80 @@
1
+ # Lint + complexity + format gates (#17, #72).
2
+ #
3
+ # Split out of ci.yml so a lint failure reports as its own check: it
4
+ # can neither mask the unit-test result nor be masked by it, and both
5
+ # run in parallel. The ruff and xenon steps keep their pre-split
6
+ # invocations (targeted failure names); the hook-suite step runs the
7
+ # full pre-commit suite — the same .pre-commit-config.yaml devenv.nix
8
+ # generates and `git commit` enforces — so a push whose commits
9
+ # bypassed the hooks (or landed before a new one) still meets every
10
+ # gate in CI.
11
+ name: Lint
12
+
13
+ # Every file type is gated by some hook (markdownlint and prettier
14
+ # read docs/*.md, nixfmt reads nix/, shfmt/shellcheck read scripts/),
15
+ # so no path filters here: every push runs. ci.yml keeps its .md
16
+ # ignore — the unit tests do not read Markdown.
17
+ on:
18
+ push:
19
+ branches: [main]
20
+ pull_request:
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ concurrency:
26
+ group: lint-${{ github.ref }}
27
+ cancel-in-progress: true
28
+
29
+ jobs:
30
+ lint:
31
+ runs-on: ubuntu-latest
32
+ timeout-minutes: 60
33
+ steps:
34
+ - uses: actions/checkout@v5
35
+
36
+ - uses: cachix/install-nix-action@v31
37
+ with:
38
+ github_access_token: ${{ secrets.GITHUB_TOKEN }}
39
+
40
+ - uses: cachix/cachix-action@v17
41
+ with:
42
+ name: devenv
43
+
44
+ # Pinned release, not the moving default registry: the devenv
45
+ # that evaluates the 2.x tasks syntax and parses -O is the one
46
+ # moving part otherwise — an upstream break would red CI with no
47
+ # repo change.
48
+ - name: Install devenv.sh
49
+ run: nix profile add github:cachix/devenv/v2.3.1
50
+
51
+ - name: Lint (ruff check + format)
52
+ env:
53
+ TMPDIR: /tmp
54
+ run: >-
55
+ devenv --quiet -O dotenv.enable:bool false shell --
56
+ bash -c 'ruff check src/ && ruff format --check src/'
57
+
58
+ - name: Complexity gate (xenon rank A)
59
+ env:
60
+ TMPDIR: /tmp
61
+ run: >-
62
+ devenv --quiet -O dotenv.enable:bool false shell --
63
+ msks-xenon
64
+
65
+ # The full pre-commit suite (#72): deferred-imports, shfmt,
66
+ # shellcheck, markdownlint, actionlint, trufflehog, nixfmt,
67
+ # check-toml, yamllint, prettier, jscpd (ruff and xenon run in
68
+ # their own steps above and again here). devenv:git-hooks:run is
69
+ # pre-commit run --all-files over the generated config — the one
70
+ # definition, shared with the commit-time hook. Write-mode hooks
71
+ # (prettier/nixfmt/shfmt/ruff format) only "fail" in CI when a
72
+ # tracked file is not formatted; the runner tree is disposable.
73
+ # trufflehog scans the file set itself (staged files at commit
74
+ # time, all tracked files here) for verifiable live credentials.
75
+ - name: Pre-commit hook suite
76
+ env:
77
+ TMPDIR: /tmp
78
+ run: >-
79
+ devenv --quiet -O dotenv.enable:bool false shell --
80
+ devenv tasks run devenv:git-hooks:run
@@ -0,0 +1,248 @@
1
+ # Release (#564): the wheel publishes to PyPI, guest images
2
+ # publish to a hash-keyed channel, and the version release carries
3
+ # the small manifest that ties them together.
4
+ #
5
+ # Three jobs, ordered so each has what it needs:
6
+ #
7
+ # 1. wheel — build the wheel, publish to PyPI through the
8
+ # registered trusted publisher (the pending publisher
9
+ # names this file and the `pypi` environment; no
10
+ # token lives in the repo).
11
+ # 2. release — create the version release: the changelog's
12
+ # human-authored section (scripts/release_notes.py
13
+ # extracts it at the tag) prepended to GitHub's
14
+ # auto-generated notes, with the wheel and its
15
+ # checksum attached.
16
+ # 3. images — the hash-keyed channel, one job walking the flavors
17
+ # in order (both legs share one images.json merge, so
18
+ # the merge cannot race itself). Per flavor the nix
19
+ # evaluation of the archive derivation — building
20
+ # nothing — is compared against the last manifest; a
21
+ # changed flavor builds once, compresses with
22
+ # zstd -19 (the debian archive lands ~266 MiB, the
23
+ # nixos one ~578 MiB — both under GitHub's 2 GiB asset
24
+ # ceiling, which the plain nixos tar exceeds), and
25
+ # uploads to an immutable release tagged
26
+ # images/<hash> whose title names the flavor and body
27
+ # links what changed. images.json — ref, hash,
28
+ # description, kernel, size, url, build, built_from
29
+ # for every flavor — attaches to the version release,
30
+ # so `msks image import <url>` pulls straight from
31
+ # the channel with no manual download.
32
+ name: Release
33
+
34
+ on:
35
+ push:
36
+ tags:
37
+ - "v*"
38
+
39
+ permissions:
40
+ contents: read
41
+
42
+ jobs:
43
+ wheel:
44
+ runs-on: ubuntu-latest
45
+ timeout-minutes: 15
46
+ permissions:
47
+ contents: read
48
+ id-token: write
49
+ environment: pypi
50
+ steps:
51
+ - uses: actions/checkout@v5
52
+
53
+ # The changelog gate, before anything publishes: a tag cut
54
+ # without its docs/changes.md section refuses here — a live
55
+ # PyPI package with no GitHub release is the failure this
56
+ # ordering prevents.
57
+ - name: Refuse a tag without its changelog section
58
+ env:
59
+ TAG: ${{ github.ref_name }}
60
+ run: python scripts/release_notes.py "$TAG" > /dev/null
61
+
62
+ - uses: actions/setup-python@v6
63
+ with:
64
+ python-version: "3.14"
65
+
66
+ - name: Build the wheel and sdist
67
+ run: |
68
+ python -m pip install --quiet build
69
+ python -m build
70
+
71
+ - name: Publish to PyPI (trusted publisher)
72
+ uses: pypa/gh-action-pypi-publish@release/v1
73
+
74
+ - name: Stage the wheel for the release job
75
+ uses: actions/upload-artifact@v4
76
+ with:
77
+ name: wheel
78
+ path: dist/
79
+ retention-days: 1
80
+
81
+ release:
82
+ runs-on: ubuntu-latest
83
+ timeout-minutes: 10
84
+ needs: wheel
85
+ permissions:
86
+ contents: write
87
+ steps:
88
+ - uses: actions/checkout@v5
89
+
90
+ - name: Download the wheel
91
+ uses: actions/download-artifact@v4
92
+ with:
93
+ name: wheel
94
+ path: dist/
95
+
96
+ # The human-authored notes come first; GitHub's auto-generated
97
+ # PR list and compare link follow. release_notes.py refuses a
98
+ # tag whose changelog section is missing — the section is the
99
+ # reason the file exists.
100
+ - name: Compose the release notes
101
+ env:
102
+ GH_TOKEN: ${{ github.token }}
103
+ TAG: ${{ github.ref_name }}
104
+ run: |
105
+ python scripts/release_notes.py "$TAG" > notes.md
106
+ gh api \
107
+ -X POST "repos/$GITHUB_REPOSITORY/releases/generate-notes" \
108
+ -f tag_name="$TAG" \
109
+ --jq .body > auto.md || printf '' > auto.md
110
+ cat auto.md >> notes.md
111
+
112
+ - name: Create the release with the wheel
113
+ env:
114
+ GH_TOKEN: ${{ github.token }}
115
+ TAG: ${{ github.ref_name }}
116
+ run: |
117
+ sha256sum dist/* > dist/SHASUMS
118
+ gh release create "$TAG" \
119
+ --title "$TAG" \
120
+ --notes-file notes.md \
121
+ --verify-tag \
122
+ dist/*
123
+
124
+ images:
125
+ runs-on: ubuntu-latest
126
+ timeout-minutes: 90
127
+ needs: release
128
+ permissions:
129
+ contents: write
130
+ env:
131
+ GH_TOKEN: ${{ github.token }}
132
+ TAG: ${{ github.ref_name }}
133
+ SHA: ${{ github.sha }}
134
+ REPO: ${{ github.repository }}
135
+ steps:
136
+ # Full history: the previous-version lookup reads the tag
137
+ # list, and a depth-1 checkout carries only this tag.
138
+ - uses: actions/checkout@v5
139
+ with:
140
+ fetch-depth: 0
141
+
142
+ - uses: cachix/install-nix-action@v31
143
+ with:
144
+ github_access_token: ${{ secrets.GITHUB_TOKEN }}
145
+
146
+ - uses: cachix/cachix-action@v17
147
+ with:
148
+ name: devenv
149
+
150
+ - name: Install devenv.sh
151
+ run: nix profile add github:cachix/devenv/v2.3.1
152
+
153
+ # The manifest being replaced: the previous version
154
+ # release's images.json — the newest tag strictly below
155
+ # this one (a re-tagged release's own manifest wins when it
156
+ # carries one) — so flavors whose derivation did not change
157
+ # carry their rows — and their built_from — forward across
158
+ # releases.
159
+ - name: Read the previous manifest
160
+ run: |
161
+ set -eu
162
+ if gh release download "$TAG" -p images.json -O prev.json; then
163
+ echo "re-tagged release: keeping its manifest"
164
+ elif prev_tag=$(git tag --sort=-v:refname | grep '^v' | \
165
+ grep -vx "$TAG" | while read -r t; do \
166
+ if [ "$t" = "$(printf '%s\n%s\n' "$TAG" "$t" | \
167
+ sort -V | head -1)" ]; then \
168
+ echo "$t"; break; \
169
+ fi; \
170
+ done) && \
171
+ [ -n "$prev_tag" ] && \
172
+ gh release download "$prev_tag" -p images.json -O prev.json
173
+ then
174
+ echo "carrying forward from $prev_tag's manifest"
175
+ else
176
+ printf '{}' > prev.json
177
+ fi
178
+
179
+ # One flavor at a time: each changed flavor publishes its
180
+ # immutable channel slot, and every flavor (changed or not)
181
+ # leaves a row in rows.json for the final merge.
182
+ - name: Publish changed images, collect manifest rows
183
+ run: |
184
+ set -eu
185
+ printf '{}' > rows.json
186
+ for flavor in debian nixos; do
187
+ attr=guest
188
+ dir=.devenv/state/guest
189
+ if [ "$flavor" = nixos ]; then attr=guest-nixos; dir=.devenv/state/guest-nixos; fi
190
+ # The single quotes are the point: the devenv shell's
191
+ # environment expands GUEST_NIXPKGS inside the inner
192
+ # bash, where it lives.
193
+ # shellcheck disable=SC2016
194
+ build=$(devenv --quiet -O dotenv.enable:bool false shell -- \
195
+ bash -c 'nix eval --raw -I nixpkgs="$GUEST_NIXPKGS" \
196
+ -f nix/guest.nix "$1.imageArchive.outPath"' \
197
+ bash "$attr" \
198
+ | xargs basename)
199
+ prev_build=$(jq -r --arg f "$flavor" '.[$f].build // empty' prev.json)
200
+ prev_row=$(jq -c --arg f "$flavor" '.[$f] // empty' prev.json)
201
+ if [ "$build" = "$prev_build" ] && [ -n "$prev_row" ]; then
202
+ echo "$flavor: derivation unchanged ($build) — row carries forward"
203
+ jq --arg f "$flavor" --argjson row "$prev_row" \
204
+ '.[$f] = $row' rows.json > rows.json.new
205
+ mv rows.json.new rows.json
206
+ continue
207
+ fi
208
+ echo "$flavor: derivation changed — building"
209
+ devenv --quiet -O dotenv.enable:bool false shell -- \
210
+ msks-build-guest "$flavor"
211
+ zstd -19 -T0 -q "$dir/$(cat "$dir/image-archive-name")" \
212
+ -o "$flavor.tar.zst"
213
+ hash=$(sha256sum "$flavor.tar.zst" | cut -d' ' -f1)
214
+ short=${hash:0:12}
215
+ tag="images/$short"
216
+ if gh release view "$tag" >/dev/null 2>&1; then
217
+ echo "$flavor: channel slot $tag already published"
218
+ else
219
+ prev_from=$(jq -r --arg f "$flavor" \
220
+ '.[$f].built_from // empty' prev.json)
221
+ gh release create "$tag" \
222
+ --target "$SHA" \
223
+ --title "$flavor image ($short)" \
224
+ --notes "Built from $SHA. What changed: https://github.com/$REPO/compare/${prev_from:-$SHA}...$SHA" \
225
+ "$flavor.tar.zst#$flavor.tar.zst" \
226
+ || gh release upload "$tag" \
227
+ "$flavor.tar.zst#$flavor.tar.zst" --clobber
228
+ fi
229
+ meta=$(devenv --quiet -O dotenv.enable:bool false shell -- \
230
+ python scripts/image_meta.py "$dir")
231
+ jq \
232
+ --arg f "$flavor" \
233
+ --arg hash "$hash" \
234
+ --arg url "https://github.com/$REPO/releases/download/$tag/$flavor.tar.zst" \
235
+ --arg build "$build" \
236
+ --arg sha "$SHA" \
237
+ --argjson size "$(stat -c%s "$flavor.tar.zst")" \
238
+ --argjson meta "$meta" \
239
+ '.[$f] = {ref: $meta.ref, description: $meta.description,
240
+ kernel: $meta.kernel_version, hash: $hash,
241
+ size: $size, url: $url, build: $build,
242
+ built_from: $sha}' \
243
+ rows.json > rows.json.new
244
+ mv rows.json.new rows.json
245
+ done
246
+
247
+ - name: Attach images.json to the version release
248
+ run: gh release upload "$TAG" rows.json#images.json --clobber
@@ -0,0 +1,138 @@
1
+ # The Debian real-VM smoke suite (#64, split from the one-job smoke
2
+ # workflow): boots workspace VMs through cloud-hypervisor against
3
+ # the built Debian guest assets, unprivileged — /dev/kvm but no
4
+ # root, no host network. The egress/root suite and the NixOS image's
5
+ # suite each carry their own workflow now, so a failure names its
6
+ # lane and the lanes run in parallel.
7
+ #
8
+ # The suite never counts toward the coverage gate (the
9
+ # faked-transport unit suites own the 100%); serial (-n stays off)
10
+ # so the real boots do not fight over the runner's 2 vCPUs. TMPDIR
11
+ # is pinned for the AF_UNIX path-depth reason as ci.yml. Verbose (-
12
+ # v): every test's name and verdict streams as it runs, so a
13
+ # hanging boot is visible mid-job, not only in the final summary.
14
+ #
15
+ # Path-filtered to its real inputs: this workflow's own file, the
16
+ # toolchain pins, the Debian guest build and the shared pieces,
17
+ # everything under the server package the boots exercise, and the
18
+ # suite itself.
19
+ name: Smoke (Debian)
20
+
21
+ on:
22
+ push:
23
+ branches: [main]
24
+ paths:
25
+ - .github/workflows/smoke-debian.yml
26
+ - devenv.nix
27
+ - devenv.yaml
28
+ - devenv.lock
29
+ - nix/guest.nix
30
+ - nix/agent-toolchain.nix
31
+ - nix/pi-shrinkwrap-patch.py
32
+ - nix/pi-shrinkwrap-integrity.json
33
+ - nix/guest-debian.nix
34
+ - nix/image-archive.nix
35
+ - nix/guest-pi-extension.ts
36
+ - scripts/build-guest.sh
37
+ - src/msks/msks/**
38
+ - src/msks/tests/conftest.py
39
+ - src/msks/tests/test_smoke/__init__.py
40
+ - src/msks/tests/test_smoke/*.py
41
+ - src/msks/tests/test_smoke/core/**
42
+ pull_request:
43
+ paths:
44
+ - .github/workflows/smoke-debian.yml
45
+ - devenv.nix
46
+ - devenv.yaml
47
+ - devenv.lock
48
+ - nix/guest.nix
49
+ - nix/agent-toolchain.nix
50
+ - nix/pi-shrinkwrap-patch.py
51
+ - nix/pi-shrinkwrap-integrity.json
52
+ - nix/guest-debian.nix
53
+ - nix/image-archive.nix
54
+ - nix/guest-pi-extension.ts
55
+ - scripts/build-guest.sh
56
+ - src/msks/msks/**
57
+ - src/msks/tests/conftest.py
58
+ - src/msks/tests/test_smoke/__init__.py
59
+ - src/msks/tests/test_smoke/*.py
60
+ - src/msks/tests/test_smoke/core/**
61
+
62
+ permissions:
63
+ contents: read
64
+
65
+ concurrency:
66
+ group: smoke-debian-${{ github.ref }}
67
+ cancel-in-progress: true
68
+
69
+ jobs:
70
+ smoke-debian:
71
+ runs-on: ubuntu-latest
72
+ timeout-minutes: 15
73
+ steps:
74
+ - uses: actions/checkout@v5
75
+
76
+ - uses: cachix/install-nix-action@v31
77
+ with:
78
+ github_access_token: ${{ secrets.GITHUB_TOKEN }}
79
+
80
+ - uses: cachix/cachix-action@v17
81
+ with:
82
+ name: devenv
83
+
84
+ # Pinned release, not the moving default registry: the devenv
85
+ # that evaluates the 2.x tasks syntax and parses -O is the one
86
+ # moving part otherwise — an upstream break would red CI with no
87
+ # repo change.
88
+ - name: Install devenv.sh
89
+ run: nix profile add github:cachix/devenv/v2.3.1
90
+
91
+ # The documented KVM permission rule (github.blog 2024-04-02):
92
+ # /dev/kvm exists on the hosted runner image but is restricted;
93
+ # this makes it 0666 for the runner user. kvm_available() then
94
+ # passes and the conftest points the smoke tests at the built
95
+ # guest assets under .devenv/state/guest.
96
+ - name: Enable KVM group perms
97
+ run: |
98
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
99
+ sudo udevadm control --reload-rules
100
+ sudo udevadm trigger --name-match=kvm
101
+
102
+ - name: Build guest assets
103
+ run: >-
104
+ devenv --quiet -O dotenv.enable:bool false shell --
105
+ msks-build-guest
106
+
107
+ # Nested-KVM guests run slower than a dev host's KVM guest
108
+ # (#64): the env ceilings below give the persistence test's four
109
+ # boot cycles room; the closing grep is the guard pattern — the
110
+ # smoke tests skip — not fail — when a prerequisite is missing,
111
+ # and a run that quietly skipped everything would report green
112
+ # while proving nothing.
113
+ - name: Smoke tests
114
+ env:
115
+ TMPDIR: /tmp
116
+ TEST_GUEST_UP_TIMEOUT_S: "180"
117
+ TEST_CONSOLE_TIMEOUT_S: "120"
118
+ TEST_SHUTDOWN_TIMEOUT_S: "180"
119
+ run: |
120
+ set -eo pipefail
121
+ devenv --quiet -O dotenv.enable:bool false shell -- \
122
+ python -m pytest src/msks/tests/test_smoke/core \
123
+ -v -rs -p no:cacheprovider --no-cov 2>&1 \
124
+ | tee /tmp/smoke-debian.log
125
+ ! grep -q 'test_boot_lifecycle.*SKIPPED' /tmp/smoke-debian.log \
126
+ || { echo 'the local boots skipped — assets or /dev/kvm regressed'; exit 1; }
127
+
128
+ # The failing boot's own evidence, kept by the tests' failure
129
+ # path — the runner recycles, so /tmp/msks-smoke-failed/ is the
130
+ # only place the guest's side of the story survives.
131
+ - name: Collect smoke state on failure
132
+ if: failure() || cancelled()
133
+ uses: actions/upload-artifact@v4
134
+ with:
135
+ name: smoke-debian-evidence
136
+ path: /tmp/msks-smoke-failed/
137
+ if-no-files-found: ignore
138
+ retention-days: 7