empeira 0.1.0.pre.alpha.1

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 (274) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +150 -0
  4. data/bin/empeira +18 -0
  5. data/config/defaults.yaml +229 -0
  6. data/docs/addons.md +331 -0
  7. data/docs/architecture.md +277 -0
  8. data/docs/configuration.md +1204 -0
  9. data/docs/development.md +472 -0
  10. data/docs/hiera-and-eyaml.md +361 -0
  11. data/docs/installation.md +280 -0
  12. data/docs/networking.md +362 -0
  13. data/docs/nodes.md +642 -0
  14. data/docs/proxy.md +136 -0
  15. data/docs/troubleshooting.md +361 -0
  16. data/lib/empeira/addons/environment.rb +82 -0
  17. data/lib/empeira/addons/error.rb +14 -0
  18. data/lib/empeira/addons/installation.rb +119 -0
  19. data/lib/empeira/addons/manifest.rb +112 -0
  20. data/lib/empeira/addons/service.rb +50 -0
  21. data/lib/empeira/addons/storage.rb +94 -0
  22. data/lib/empeira/agent/acquisition.rb +120 -0
  23. data/lib/empeira/agent/artifact.rb +41 -0
  24. data/lib/empeira/agent/authentication.rb +122 -0
  25. data/lib/empeira/agent/cache.rb +122 -0
  26. data/lib/empeira/agent/cache_storage.rb +82 -0
  27. data/lib/empeira/agent/download.rb +84 -0
  28. data/lib/empeira/agent/native_commands.rb +84 -0
  29. data/lib/empeira/agent/release_sources.rb +64 -0
  30. data/lib/empeira/agent/target.rb +66 -0
  31. data/lib/empeira/agent/versions.rb +29 -0
  32. data/lib/empeira/application.rb +86 -0
  33. data/lib/empeira/build.json +5 -0
  34. data/lib/empeira/build_info.rb +46 -0
  35. data/lib/empeira/cli/addons.rb +45 -0
  36. data/lib/empeira/cli/base.rb +73 -0
  37. data/lib/empeira/cli/completion.rb +42 -0
  38. data/lib/empeira/cli/config.rb +22 -0
  39. data/lib/empeira/cli/main.rb +114 -0
  40. data/lib/empeira/cli/node.rb +87 -0
  41. data/lib/empeira/cli/progress.rb +169 -0
  42. data/lib/empeira/cli/proxy.rb +36 -0
  43. data/lib/empeira/cli/update.rb +23 -0
  44. data/lib/empeira/completion/addons.rb +93 -0
  45. data/lib/empeira/completion/bash.rb +62 -0
  46. data/lib/empeira/completion/catalog.rb +170 -0
  47. data/lib/empeira/configuration/additional_services.rb +74 -0
  48. data/lib/empeira/configuration/addons.rb +85 -0
  49. data/lib/empeira/configuration/agent_schema.rb +163 -0
  50. data/lib/empeira/configuration/agent_values.rb +54 -0
  51. data/lib/empeira/configuration/bootstrap_guests.rb +42 -0
  52. data/lib/empeira/configuration/command_mocks.rb +65 -0
  53. data/lib/empeira/configuration/display.rb +20 -0
  54. data/lib/empeira/configuration/dns_rewrites.rb +58 -0
  55. data/lib/empeira/configuration/document.rb +67 -0
  56. data/lib/empeira/configuration/eyaml.rb +46 -0
  57. data/lib/empeira/configuration/hiera_modulepath.rb +40 -0
  58. data/lib/empeira/configuration/hiera_mount_schema.rb +62 -0
  59. data/lib/empeira/configuration/hiera_mounts.rb +83 -0
  60. data/lib/empeira/configuration/image_schema.rb +68 -0
  61. data/lib/empeira/configuration/loader.rb +70 -0
  62. data/lib/empeira/configuration/merge.rb +48 -0
  63. data/lib/empeira/configuration/network_redirects.rb +73 -0
  64. data/lib/empeira/configuration/node_images.rb +64 -0
  65. data/lib/empeira/configuration/schema.rb +215 -0
  66. data/lib/empeira/configuration/server_mounts.rb +106 -0
  67. data/lib/empeira/configuration/service_configuration.rb +40 -0
  68. data/lib/empeira/configuration/ssh_preferences.rb +83 -0
  69. data/lib/empeira/configuration/user.rb +46 -0
  70. data/lib/empeira/configuration/vm_interfaces.rb +147 -0
  71. data/lib/empeira/control_plane/additional_configurations.rb +106 -0
  72. data/lib/empeira/control_plane/browser.rb +73 -0
  73. data/lib/empeira/control_plane/controller.rb +763 -0
  74. data/lib/empeira/control_plane/discovery.rb +69 -0
  75. data/lib/empeira/control_plane/environment.rb +101 -0
  76. data/lib/empeira/control_plane/files.rb +263 -0
  77. data/lib/empeira/control_plane/health.rb +104 -0
  78. data/lib/empeira/control_plane/plan.rb +356 -0
  79. data/lib/empeira/control_plane/storage.rb +48 -0
  80. data/lib/empeira/errors.rb +9 -0
  81. data/lib/empeira/execution/console.rb +45 -0
  82. data/lib/empeira/execution/diagnostics.rb +210 -0
  83. data/lib/empeira/execution/launch_error.rb +14 -0
  84. data/lib/empeira/execution/result.rb +11 -0
  85. data/lib/empeira/execution/runner.rb +235 -0
  86. data/lib/empeira/execution/terminal.rb +35 -0
  87. data/lib/empeira/hostname_pattern.rb +14 -0
  88. data/lib/empeira/images/configuration.rb +44 -0
  89. data/lib/empeira/images/identity.rb +30 -0
  90. data/lib/empeira/images/recipe.rb +22 -0
  91. data/lib/empeira/images/reference.rb +43 -0
  92. data/lib/empeira/images/source.rb +19 -0
  93. data/lib/empeira/immutable.rb +19 -0
  94. data/lib/empeira/infrastructure/cleanup.rb +328 -0
  95. data/lib/empeira/infrastructure/definition.rb +59 -0
  96. data/lib/empeira/infrastructure/owned_files.rb +63 -0
  97. data/lib/empeira/infrastructure/recovery_inventory.rb +53 -0
  98. data/lib/empeira/infrastructure/service.rb +237 -0
  99. data/lib/empeira/infrastructure/status.rb +73 -0
  100. data/lib/empeira/infrastructure/store.rb +274 -0
  101. data/lib/empeira/modules/events.rb +67 -0
  102. data/lib/empeira/modules/git_safety.rb +60 -0
  103. data/lib/empeira/modules/host_git.rb +91 -0
  104. data/lib/empeira/modules/image.rb +27 -0
  105. data/lib/empeira/modules/installer.rb +67 -0
  106. data/lib/empeira/modules/request.rb +68 -0
  107. data/lib/empeira/modules/state.rb +87 -0
  108. data/lib/empeira/modules/storage.rb +35 -0
  109. data/lib/empeira/network/bootstrap_proxy.rb +157 -0
  110. data/lib/empeira/network/definition.rb +61 -0
  111. data/lib/empeira/network/direct_egress.rb +133 -0
  112. data/lib/empeira/network/egress.rb +27 -0
  113. data/lib/empeira/network/gateway.rb +79 -0
  114. data/lib/empeira/network/host_policy.rb +53 -0
  115. data/lib/empeira/network/naming.rb +28 -0
  116. data/lib/empeira/network/peer/adapter_image.rb +32 -0
  117. data/lib/empeira/network/peer/allocation.rb +45 -0
  118. data/lib/empeira/network/peer/attachment.rb +27 -0
  119. data/lib/empeira/network/peer/backend.rb +108 -0
  120. data/lib/empeira/network/peer/channel.rb +133 -0
  121. data/lib/empeira/network/peer/channel_server.rb +71 -0
  122. data/lib/empeira/network/peer/docker_adapter.rb +165 -0
  123. data/lib/empeira/network/peer/helper_security.rb +40 -0
  124. data/lib/empeira/network/peer/layout.rb +55 -0
  125. data/lib/empeira/network/peer/linux_podman.rb +114 -0
  126. data/lib/empeira/network/peer/podman_machine.rb +112 -0
  127. data/lib/empeira/network/policy.rb +39 -0
  128. data/lib/empeira/network/proxy_bindings.rb +52 -0
  129. data/lib/empeira/network/proxy_policy.rb +64 -0
  130. data/lib/empeira/network/redirects.rb +89 -0
  131. data/lib/empeira/network/resource.rb +50 -0
  132. data/lib/empeira/node/agent_installer.rb +31 -0
  133. data/lib/empeira/node/agent_package.rb +125 -0
  134. data/lib/empeira/node/agent_repository.rb +160 -0
  135. data/lib/empeira/node/apt_configuration.rb +85 -0
  136. data/lib/empeira/node/bootstrap.rb +17 -0
  137. data/lib/empeira/node/certificates.rb +98 -0
  138. data/lib/empeira/node/command_mock.rb +38 -0
  139. data/lib/empeira/node/command_mocks.rb +114 -0
  140. data/lib/empeira/node/container.rb +134 -0
  141. data/lib/empeira/node/container_agent_bootstrap.rb +43 -0
  142. data/lib/empeira/node/container_bootstrap.rb +52 -0
  143. data/lib/empeira/node/container_guest.rb +42 -0
  144. data/lib/empeira/node/container_lifecycle.rb +145 -0
  145. data/lib/empeira/node/container_preparation.rb +144 -0
  146. data/lib/empeira/node/container_state.rb +129 -0
  147. data/lib/empeira/node/definition.rb +20 -0
  148. data/lib/empeira/node/diagnostic_command.rb +19 -0
  149. data/lib/empeira/node/dnf_agent_repository.rb +202 -0
  150. data/lib/empeira/node/external_fact.rb +21 -0
  151. data/lib/empeira/node/image.rb +38 -0
  152. data/lib/empeira/node/interactive_tools.rb +33 -0
  153. data/lib/empeira/node/interface.rb +18 -0
  154. data/lib/empeira/node/inventory.rb +143 -0
  155. data/lib/empeira/node/package_bootstrap.rb +129 -0
  156. data/lib/empeira/node/package_proxy.rb +102 -0
  157. data/lib/empeira/node/package_sources.rb +19 -0
  158. data/lib/empeira/node/puppet_command.rb +12 -0
  159. data/lib/empeira/node/rpm_configuration.rb +62 -0
  160. data/lib/empeira/node/run_request.rb +52 -0
  161. data/lib/empeira/node/runtime_proxy.rb +86 -0
  162. data/lib/empeira/node/service.rb +72 -0
  163. data/lib/empeira/node/ssh_client.rb +25 -0
  164. data/lib/empeira/node/ssh_credentials.rb +78 -0
  165. data/lib/empeira/node/ssh_endpoint.rb +58 -0
  166. data/lib/empeira/node/ssh_identity.rb +37 -0
  167. data/lib/empeira/node/user_ssh.rb +60 -0
  168. data/lib/empeira/node/vm.rb +89 -0
  169. data/lib/empeira/node/vm_bootstrap.rb +53 -0
  170. data/lib/empeira/node/vm_lifecycle.rb +212 -0
  171. data/lib/empeira/node/vm_preflight.rb +37 -0
  172. data/lib/empeira/node/vm_preparation.rb +161 -0
  173. data/lib/empeira/node/vm_state.rb +161 -0
  174. data/lib/empeira/platform/facts.rb +66 -0
  175. data/lib/empeira/platform/kvm.rb +91 -0
  176. data/lib/empeira/platform/locations.rb +51 -0
  177. data/lib/empeira/platform/resolvers.rb +85 -0
  178. data/lib/empeira/platform/routes.rb +50 -0
  179. data/lib/empeira/progress.rb +70 -0
  180. data/lib/empeira/project_root.rb +47 -0
  181. data/lib/empeira/providers/lifecycle.rb +96 -0
  182. data/lib/empeira/providers/registry.rb +21 -0
  183. data/lib/empeira/providers/resource.rb +32 -0
  184. data/lib/empeira/providers.rb +55 -0
  185. data/lib/empeira/runtime/agent_helper.rb +34 -0
  186. data/lib/empeira/runtime/container.rb +184 -0
  187. data/lib/empeira/runtime/diagnostic_execution.rb +52 -0
  188. data/lib/empeira/runtime/docker.rb +87 -0
  189. data/lib/empeira/runtime/image_metadata.rb +83 -0
  190. data/lib/empeira/runtime/interface.rb +8 -0
  191. data/lib/empeira/runtime/mount_probe.rb +57 -0
  192. data/lib/empeira/runtime/podman.rb +122 -0
  193. data/lib/empeira/runtime/recipe_build.rb +103 -0
  194. data/lib/empeira/runtime/registry_diagnostics.rb +23 -0
  195. data/lib/empeira/runtime/service_arguments.rb +55 -0
  196. data/lib/empeira/runtime/service_images.rb +118 -0
  197. data/lib/empeira/runtime/service_inspection.rb +159 -0
  198. data/lib/empeira/runtime/services.rb +120 -0
  199. data/lib/empeira/runtime/update_helper.rb +97 -0
  200. data/lib/empeira/runtime/workspace_network.rb +69 -0
  201. data/lib/empeira/server/environment_configuration.rb +53 -0
  202. data/lib/empeira/server/http.rb +38 -0
  203. data/lib/empeira/server/puppetdb_runtime.rb +74 -0
  204. data/lib/empeira/server/relay_certificate.rb +76 -0
  205. data/lib/empeira/server/runtime.rb +90 -0
  206. data/lib/empeira/services/definition.rb +42 -0
  207. data/lib/empeira/updates/images.rb +104 -0
  208. data/lib/empeira/updates/modules.rb +45 -0
  209. data/lib/empeira/updates/service.rb +45 -0
  210. data/lib/empeira/version_requirement.rb +23 -0
  211. data/lib/empeira/vm/agent.rb +66 -0
  212. data/lib/empeira/vm/bootstrap_requirements.rb +69 -0
  213. data/lib/empeira/vm/certificate_runtime.rb +30 -0
  214. data/lib/empeira/vm/cloud_init.rb +171 -0
  215. data/lib/empeira/vm/disk.rb +136 -0
  216. data/lib/empeira/vm/guest.rb +123 -0
  217. data/lib/empeira/vm/guest_channel.rb +110 -0
  218. data/lib/empeira/vm/guest_network.rb +94 -0
  219. data/lib/empeira/vm/guest_output.rb +52 -0
  220. data/lib/empeira/vm/guest_shell.rb +79 -0
  221. data/lib/empeira/vm/guest_upload.rb +43 -0
  222. data/lib/empeira/vm/image_cache.rb +77 -0
  223. data/lib/empeira/vm/image_source.rb +91 -0
  224. data/lib/empeira/vm/interface.rb +8 -0
  225. data/lib/empeira/vm/interface_ownership.rb +124 -0
  226. data/lib/empeira/vm/interface_routes.rb +101 -0
  227. data/lib/empeira/vm/interfaces.rb +240 -0
  228. data/lib/empeira/vm/management.rb +73 -0
  229. data/lib/empeira/vm/prerequisites.rb +42 -0
  230. data/lib/empeira/vm/qemu.rb +83 -0
  231. data/lib/empeira/vm/qemu_runtime.rb +404 -0
  232. data/lib/empeira/vm/removal.rb +30 -0
  233. data/lib/empeira/vm/root_disk.rb +92 -0
  234. data/lib/empeira/vm/session_lock.rb +56 -0
  235. data/lib/empeira/vm/system_ssh.rb +29 -0
  236. data/lib/empeira/workspace.rb +42 -0
  237. data/lib/empeira.rb +209 -0
  238. data/resources/browser/relay.rb +22 -0
  239. data/resources/gateway/Containerfile +8 -0
  240. data/resources/gateway/bridge.rb +66 -0
  241. data/resources/gateway/configure.rb +323 -0
  242. data/resources/modules/Containerfile +15 -0
  243. data/resources/modules/Gemfile +5 -0
  244. data/resources/modules/Gemfile.lock +105 -0
  245. data/resources/modules/worker.rb +161 -0
  246. data/resources/network/adapter/Containerfile +13 -0
  247. data/resources/network/adapter/bridge_linux.go +51 -0
  248. data/resources/network/adapter/framing.go +45 -0
  249. data/resources/network/adapter/framing_test.go +53 -0
  250. data/resources/network/adapter/go.mod +3 -0
  251. data/resources/network/adapter/main_linux.go +273 -0
  252. data/resources/network/channel.rb +18 -0
  253. data/resources/network/ssh_connect.rb +32 -0
  254. data/resources/nodes/apt_sources.rb +66 -0
  255. data/resources/nodes/debian/Containerfile +10 -0
  256. data/resources/nodes/diagnostic_exec.rb +109 -0
  257. data/resources/nodes/interactive_tools.rb +139 -0
  258. data/resources/nodes/managed_file.rb +91 -0
  259. data/resources/nodes/management/guest_agent.py +415 -0
  260. data/resources/nodes/management/setup.sh +20 -0
  261. data/resources/nodes/management/shell_rc.sh +12 -0
  262. data/resources/nodes/management/system_account.sh +24 -0
  263. data/resources/nodes/management/system_home.sh +15 -0
  264. data/resources/nodes/rpm/Containerfile +10 -0
  265. data/resources/nodes/runtime_proxy.rb +244 -0
  266. data/resources/nodes/ssh/proxy.rb +16 -0
  267. data/resources/nodes/ssh/sshd_config +15 -0
  268. data/resources/nodes/ssh/start +6 -0
  269. data/resources/proxy/Containerfile +10 -0
  270. data/resources/relay/Containerfile +5 -0
  271. data/resources/relay/puppetdb.rb +81 -0
  272. data/resources/relay/server_environment.rb +10 -0
  273. data/resources/relay/server_start.sh +41 -0
  274. metadata +326 -0
data/docs/nodes.md ADDED
@@ -0,0 +1,642 @@
1
+ # Nodes
2
+
3
+ Create nodes imperatively after `empeira up`; named nodes do not belong in
4
+ `.empeira.yaml`. `node run HOSTNAME` defaults to `container`; select `--provider vm` for a VM.
5
+ Existing-node commands use the provider recorded in workspace state.
6
+
7
+ ```console
8
+ empeira node run host1 --provider container --os ubuntu --version 24.04
9
+ empeira node run vm1 --provider vm --memory 2048 --cpus 2
10
+ empeira node list
11
+ empeira node puppet host1
12
+ empeira node logs host1
13
+ empeira node exec host1 -- id
14
+ empeira node shell host1
15
+ empeira node console vm1
16
+ empeira node ssh host1
17
+ empeira node stop host1
18
+ empeira node start host1
19
+ empeira node destroy host1
20
+ ```
21
+
22
+ The default node is Ubuntu 24.04, 1024 MiB, and two CPUs. External traffic requires
23
+ an explicit TCP egress rule or an allowlisted proxy policy. If the workspace proxy
24
+ is enabled, the node automatically gets
25
+ controlled HTTP/HTTPS access to destinations allowed by its proxy policy.
26
+ Hostname/certname is unique across both providers within a workspace. Names are
27
+ normalized to lowercase; service names are reserved. Another checkout may reuse
28
+ the same hostname. Before the first Puppet run, Empeira installs
29
+ `/etc/puppetlabs/facter/facts.d/empeira.yaml` with exactly one structured fact:
30
+
31
+ ```yaml
32
+ empeira:
33
+ provider: container
34
+ hierarchy: empeira
35
+ ```
36
+
37
+ VM nodes use `provider: vm`. Both providers verify this readable external fact before
38
+ enrollment; Facter's normal `facts.virtual` remains unchanged. The fixed
39
+ `hierarchy: empeira` selector enables [shared Hiera overrides](hiera-and-eyaml.md).
40
+ Existing provisioned nodes receive the updated fact on `up`, `node start` or
41
+ `node puppet`; unchanged fact revisions require no extra guest probe.
42
+
43
+ New nodes use their full inventory name as the kernel hostname, including multiple
44
+ domain components, with a maximum of 64 bytes. Containers already supply that name
45
+ to the runtime; new VMs persist it through cloud-init (`prefer_fqdn_over_hostname`),
46
+ `/etc/hostname` and `/etc/hosts`. A guest cloud-init fragment preserves the initialized
47
+ hostname on later boots, including after temporary seed configuration is removed.
48
+ `hostname` and `hostname -f` return the FQDN, while
49
+ Facter keeps its native short `networking.hostname`, domain and full
50
+ `networking.fqdn`. Puppet certname remains the inventory name. Only guest files are
51
+ configured; the physical host is never changed.
52
+
53
+ Existing VMs retain their hostname configuration: Puppet may already manage those
54
+ files, so `up` and `node start` do not overwrite them. Recreate an old VM to apply
55
+ the new FQDN initialization. Stop/start preserves the new configuration unless
56
+ Puppet or the guest administrator deliberately changes it.
57
+
58
+ Creation reserves identity atomically, including failed attempts.
59
+ `stop` preserves the disk and identity, `start` resumes them, and `destroy` permanently
60
+ removes the owned resource before releasing the name. Its public CA key fingerprint
61
+ is retained for verified cleanup before the next enrollment under that name.
62
+ Puppet exit codes 0 and 2 succeed. A failed catalog retains diagnostic state.
63
+
64
+ Puppet output uses ANSI colors when both stdout and stderr are terminals. Redirecting
65
+ either stream, setting `CI`, using `TERM=dumb`, or setting `NO_COLOR` disables Puppet
66
+ colors. Container and VM runs use the same policy. The progress display is suspended
67
+ while Puppet streams its output and resumes on a fresh line afterward.
68
+
69
+ `node list` reports observed runtime state and the last Puppet result without
70
+ waiting for guest readiness. `node puppet` verifies node ownership, completed
71
+ bootstrap and running state, then
72
+ streams the agent immediately. Container access also verifies workspace network
73
+ ownership and isolation, permitted attachments and SSH ports. It performs no
74
+ server-readiness check, infrastructure
75
+ reconciliation, guest configuration, command-mock update or VM disk inspection.
76
+ If the server is unavailable, the agent reports its native error and Empeira saves
77
+ the exit code. Apply configuration changes with `up` or resume with `node start`.
78
+ `node logs` streams runtime output for containers and the serial log for VMs.
79
+ Node deletion uses host/runtime mechanisms and requires no running guest or CA.
80
+ The retired public certificate fingerprint remains in workspace inventory; creating
81
+ the same hostname later cleans only its matching CA identity before enrollment.
82
+ Whole-workspace `destroy` removes the CA together with nodes.
83
+
84
+ ## Environment cache and live code
85
+
86
+ Managed servers enforce native `[server] environment_timeout = 0` before startup
87
+ and verify the effective setting during `up`, independently of image environment
88
+ variable support. Existing workspaces adopt it on
89
+ their next `up`; run that once after updating from a version that enabled caching.
90
+ Each catalog request reloads live control code, modules and mounted Hiera data.
91
+ Empeira does not hash these trees,
92
+ persist content checkpoints or call the environment-cache admin API. Code edits
93
+ need neither `up` nor a server restart, including for agents invoked manually.
94
+ Empeira does not enable `use_cached_catalog`.
95
+
96
+ A repository's own `environment.conf` can override the server timeout; use zero
97
+ for immediate edits. Avoid concurrent edits during a catalog run. Existing files
98
+ and directories in projected environments stay live through read-only links.
99
+ New siblings under ancestors projected for nested Hiera mounts require `up` to
100
+ create their links. `node start` resumes the node without invoking Puppet; Puppet
101
+ services enabled by a catalog may run independently on startup.
102
+
103
+ ## Distribution package sources
104
+
105
+ The reviewed Ubuntu container recipes retain the upstream distribution sources.
106
+ Ubuntu 24.04 uses `/etc/apt/sources.list.d/ubuntu.sources` (deb822); older images may
107
+ use `/etc/apt/sources.list`. VM Cloud-Init leaves the base image's APT configuration
108
+ untouched. It does not generate archive, security, ports, or internal repository
109
+ entries.
110
+
111
+ For either agent installation method, Empeira handles `bootstrap.packages` against
112
+ the base image's sources first. A disposable host-side helper resolves the selected
113
+ agent source using the target OS and architecture. The agent artifact is acquired
114
+ and normally reused from the shared user cache, then uploaded through the existing
115
+ container or VM transport and installed locally with APT/DNF. Agent repositories
116
+ and repository credentials never need to be installed on the node. Valid cached
117
+ artifacts are reused offline without HTTP authentication or login, including private
118
+ sources. Missing dependencies may still use the node's unchanged base repositories
119
+ through the short-lived bootstrap proxy, so installation can still need network access.
120
+
121
+ Empeira archives `/etc/apt` for Ubuntu or `/etc/yum.repos.d` and `/etc/dnf` for RPM
122
+ guests. Uploaded packages, temporary signing keys and proxy configuration are
123
+ removed, and the archive is restored and verified before bootstrap access is
124
+ removed, certificate enrollment completes and the first catalog runs. A failure
125
+ retains the incomplete node for diagnostics and prevents Puppet. Already
126
+ provisioned nodes are not bootstrapped again by `up` or `node start`. See
127
+ [agent sources and caching](configuration.md#agent-sources-and-shared-package-cache)
128
+ for generic sources, version resolution, optional verification and HTTP login.
129
+
130
+ Bootstrap command failures include the operation, native stdout/stderr, exit code
131
+ and timeout status with secrets redacted. Internal VM SSH confirms the guest exit
132
+ status separately from its transport status. Once the APT archive is restored and
133
+ compared successfully, a clearly transient SSH failure deleting that backup is
134
+ retried once, with a warning. Only the idempotent removal is repeated; packages and
135
+ restoration are not replayed. Native errors, missing completion status, command
136
+ timeouts and a second transport failure stop provisioning. Incomplete nodes are not automatically
137
+ resumed across commands; inspect the retained backup, then correct and recreate.
138
+
139
+ After restoration and before the first catalog, both node providers perform the same
140
+ read-only check for active Ubuntu/Debian distribution APT sources. Legacy `.list`
141
+ and deb822 `.sources` files are accepted. A missing/disabled source fails with an
142
+ actionable error and retains the node for inspection. No source is rewritten on
143
+ `up` or `node start`: repository ownership after bootstrap belongs to the tested
144
+ Puppet code. If a catalog purges unmanaged sources without declaring replacements,
145
+ the control repository must fix that classification. Check both source locations;
146
+ an empty `sources.list.d` alone does not prove that APT has no sources.
147
+
148
+ Source configuration does not grant egress. Managed VM agent installation uses the
149
+ temporary authenticated bootstrap proxy with reviewed base-distribution destinations.
150
+ Agent acquisition uses host/helper access, outside the isolated node network. After
151
+ bootstrap, normal access depends on the workspace proxy. Interactive `apt-get update`
152
+ or package resources need an enabled proxy and an explicit destination policy as described in
153
+ [networking](networking.md). Empeira never grants unrestricted Internet access to
154
+ repair package sources.
155
+
156
+ Normal APT/DNF proxy settings persist across restart and work for Puppet package
157
+ resources. Managed Puppet runs receive the current proxy environment directly,
158
+ without relying on login initialization. Login shells also load the owned
159
+ `/etc/profile.d/90-empeira-proxy.sh`. This file clears stale container environment
160
+ variables when proxy access is disabled; repository files are never replaced.
161
+
162
+ ## Shell and SSH
163
+
164
+ After agent installation, Empeira detects the installed `puppet` and `facter`
165
+ directory and exposes it through `/etc/profile.d/90-empeira-tools.sh`. An owned
166
+ block in the existing global Bash initializer covers interactive non-login Bash.
167
+ Login SSH, serial-console logins and container shells share this configuration.
168
+ It preserves existing PATH entries, adds the directory once, and leaves
169
+ non-interactive PATH and personal startup files unchanged. Existing nodes receive
170
+ the setting on `up` and `node start`. Custom personal
171
+ startup files can still deliberately override their environment; they should
172
+ retain the distribution's global initialization. Modified owned fragments or
173
+ unsafe global paths stop reconciliation with a diagnosis.
174
+
175
+ | Provider | `node exec` | `node shell` | `node console` | `node ssh` |
176
+ | --- | --- | --- | --- | --- |
177
+ | Container | Native runtime execution as UID 0 | Native interactive root Bash | Not available | Real OpenSSH to the guest daemon |
178
+ | VM | Private VirtIO execution as UID 0 | Private VirtIO root PTY, Bash or POSIX sh | Direct QEMU serial console with guest login | Real OpenSSH to the guest daemon |
179
+
180
+ Direct access is useful for repair even when SSH, PAM, login profiles or network
181
+ configuration is broken. SSH exercises the login path that Puppet actually configures.
182
+ None of these commands starts a stopped node implicitly. Use `node start` first.
183
+ Both retain normal interactive output without a progress overlay.
184
+
185
+ For container nodes, the default SSH username is the local execution user's account
186
+ name (the Linux account on WSL2). Default keys, `ssh-agent` and normal OpenSSH
187
+ configuration remain available. Configure that user, its authorized keys and daemon
188
+ through Puppet or guest configuration.
189
+
190
+ For VM nodes, the default is the existing Cloud-Init account `empeira` and its
191
+ managed private key. Password and keyboard-interactive authentication are disabled;
192
+ the client selects the supplied identity without offering unrelated agent keys.
193
+ `--user`, `--identity` and `--port` independently override these defaults. The guest
194
+ system SSH port defaults to 22. Missing or unsafe
195
+ managed keys fail before connection, without generating a replacement. This access
196
+ also works for incomplete nodes once the VM and SSH daemon are running.
197
+ An authentication failure remains an SSH failure, with the client's output and
198
+ exit status. It never creates a login user, injects keys, or falls back to exec.
199
+
200
+ Optional personal defaults and hostname rules in `~/.empeira.yaml` apply to this
201
+ interactive command. Each field uses CLI override, last matching rule, global
202
+ preference, then provider default, in that order. Rules match full hostnames with
203
+ case-insensitive `*`/`?` globs; omitted fields preserve their existing value.
204
+ See [personal SSH configuration](configuration.md#optional-user-preferences) for
205
+ the schema and identity-file checks. VM management uses its private VirtIO channel
206
+ for bootstrap, enrollment and Puppet independently of these preferences.
207
+ SSH preferences do not affect `exec`, `shell` or `console`.
208
+
209
+ ```console
210
+ empeira node ssh host1
211
+ empeira node ssh host1 --user deploy
212
+ empeira node ssh host1 --identity ~/.ssh/test_ed25519
213
+ empeira node ssh host1 --user deploy --identity ~/.ssh/test_ed25519
214
+ empeira node ssh host1 --port 2222
215
+ ```
216
+
217
+ The three SSH overrides are independent. The logical hostname can select a `Host`
218
+ section in SSH configuration. Empeira fixes the host, port and transport to the owned
219
+ node, disables connection sharing and forwarding, and uses a private per-node
220
+ `known_hosts` file with `accept-new` checking. User settings cannot redirect the
221
+ connection through another proxy or jump host. Container logins retain the developer's
222
+ normal SSH agent behavior. Host-key changes fail until investigated; disposable host entries
223
+ are removed with the node and do not pollute `~/.ssh/known_hosts`.
224
+
225
+ Podman publishes SSH on a verified dynamic loopback port. Docker's internal bridge
226
+ does not activate published ports, so OpenSSH uses a runtime exec byte tunnel to
227
+ port 22 inside the ownership-checked container. This still carries real SSH traffic.
228
+ An explicit container guest port uses the same ownership-checked runtime tunnel,
229
+ without another host publication. VM SSH connects directly to the system daemon
230
+ on the owned peer IP and selected guest port (22 by default). Native rootless
231
+ Podman uses a capless peer client alongside its existing TAP; Docker uses its
232
+ existing adapter as an internal IP peer, and Podman machine uses its capless
233
+ staging adapter. No host route is added. No management command, authentication or
234
+ forwarding is needed.
235
+ Unavailable system SSH reports the native OpenSSH error without repair or fallback.
236
+
237
+ New VM records have `management_layout: 1`. Cloud-Init starts and enables
238
+ `empeira-management.service` before project scripts and package bootstrap.
239
+ The selected cloud image must already provide Python 3 (`/usr/bin/python3` or Rocky Linux's
240
+ `/usr/libexec/platform-python`); the adapter uses
241
+ only its standard library, so management needs no package download before it can
242
+ install the agent. The service runs with numeric UID/GID 0 and opens the dedicated
243
+ `org.empeira.management.0` VirtIO-Serial port. It has no SSH, PAM, sudo, password,
244
+ NSS login, authorized-key or network-port dependency. Puppet can set root shadow
245
+ to `!`, replace sudoers, remove regular users and groups, or restart/disable system
246
+ SSH without losing management. No root-password repair occurs after a catalog.
247
+
248
+ Before commands, the owned QEMU monitor verifies workspace, hostname and instance.
249
+ The host management socket lives in a private 0700 directory, is mode 0600, and
250
+ must match its recorded device/inode. The protocol also verifies the instance and
251
+ operation identity. Management has no host-published port, network NIC or SSH key.
252
+ Commands run in the normal guest filesystem with owned process groups. stdout and
253
+ stderr stream without truncation. Confirmed native exits (including 4, 6 and 255)
254
+ are distinct from transport failure; timeout and interruption terminate the owned
255
+ group. Guest commands do not inherit the service's `RUNTIME_DIRECTORY`, so
256
+ OpenVox retains its normal agent runtime directory. A failed catalog retains the
257
+ provisioned VM and can be retried.
258
+
259
+ Uploads stage in root-owned 0700 `/run/empeira-management`, verify SHA-256 and
260
+ atomically install the requested contents and mode. Staging is removed on success,
261
+ failure or disconnect. Management remains available across stop/start; the new
262
+ socket identity is recorded for each launch. It does not continuously poll idle
263
+ VMs or reconcile the whole guest before each Puppet run.
264
+
265
+ Cloud-Init creates the locked Linux system account `empeira` once for the default
266
+ interactive `node ssh` login, with `/bin/bash`, a dynamically allocated system UID
267
+ and private `/var/lib/empeira` home. Its public key goes into that home's
268
+ `.ssh/authorized_keys`. No sudo grant is added. Puppet owns subsequent account,
269
+ key, home and system-SSHD changes. Authentication failures never cause repairs.
270
+
271
+ If management is unavailable, Empeira preserves the overlay and reports unknown
272
+ guest completion without replaying installation. Use `node console` for the private
273
+ serial console and `node logs` for the serial log. In the console inspect
274
+ `journalctl -u empeira-management.service`, `cloud-init status --long`, the
275
+ VirtIO port, Python 3 and the unit. Console passwords apply only during initial
276
+ seed setup and remain under Puppet's control afterward. Removing Python, the
277
+ management unit, VirtIO support or the root filesystem can still interrupt this
278
+ privileged guest facility; there is no authentication fallback or automatic deletion.
279
+
280
+ Earlier management layouts fail before normal operation. Use the current
281
+ `node destroy` to remove the positively identified VM, then recreate it. Destroy
282
+ does not require a compatible guest management layout or working guest. `management_layout` and socket identity are internal inventory metadata,
283
+ not YAML options.
284
+
285
+ Packaged container images install and start OpenSSH, generating host keys at first
286
+ startup. They enable public-key login with `.ssh/authorized_keys`; password and
287
+ keyboard-interactive login are initially disabled. They do not provision developer
288
+ accounts or authorized keys. Puppet may manage the daemon and authentication policy.
289
+ Custom node images must provide the corresponding startup and package-manager contract.
290
+
291
+ Empeira uses the private privileged VirtIO channel internally to install the VM agent,
292
+ enroll certificates and run Puppet. User-facing SSH opens an ordinary interactive
293
+ login without the internal provisioning command or automatic sudo.
294
+
295
+ VM exec, shell, SSH and serial sessions release the workspace mutation lock after ownership and
296
+ running-state checks. Parallel sessions and read-only status remain available. A
297
+ shared instance guard prevents `node stop`, `node destroy` or restart from replacing
298
+ the VM while a session is attached; detach first and retry the conflicting operation.
299
+ Other nodes and workspace commands can proceed. Instance lock files stay stable;
300
+ their open descriptors are released on detach, disconnect, errors and signals.
301
+
302
+ ## Root diagnostic commands and interactive shell
303
+
304
+ ```console
305
+ empeira node exec host1 -- id
306
+ empeira node exec host1 --timeout 120 -- journalctl -u ssh.service -n 100 --no-pager
307
+ empeira node exec host1 -- sh -c 'echo hello && id'
308
+ empeira node shell host1
309
+ ```
310
+
311
+ Everything after `--` is a literal guest argument vector. Shell operators are
312
+ interpreted only when you explicitly run a shell such as `sh -c`. stdout and stderr
313
+ stream separately without truncation or saved transcripts. The CLI returns the
314
+ confirmed guest exit status, including 255. Missing executables return 127. Transport
315
+ or unconfirmed cancellation failures report an error and retain the node; they are
316
+ never treated as confirmed guest exits.
317
+
318
+ Exec has no default timeout. `--timeout` accepts positive seconds up to 86400;
319
+ timeout returns 124 after controlled guest process-group cleanup. Ctrl+C cancels the
320
+ owned session and its process groups. Internal bootstrap and Puppet deadlines keep
321
+ their existing values. Container exec uses the agent's embedded Ruby interpreter
322
+ as a guest-only cancellation supervisor; a container without that interpreter cannot
323
+ confirm this diagnostic protocol and reports a transport failure. In process-init
324
+ containers SSHD is PID 1; stopping that daemon stops the whole container. Native
325
+ exec and shell require a running container. Podman systemd-init containers retain
326
+ root runtime access when the independent SSH service is stopped.
327
+
328
+ VM shell allocates a controlling PTY under numeric UID 0 and invokes Bash with `-i`,
329
+ or the existing POSIX `sh` if Bash is absent. It bypasses login authentication without
330
+ changing shadow, PAM, sudoers or SSHD. A Puppet-managed root shadow value `!` and a
331
+ stopped system SSH daemon do not prevent exec or shell. Both local input and output
332
+ must be terminals; otherwise shell fails clearly before opening a guest session.
333
+ Initial rows/columns and subsequent SIGWINCH resizes reach the guest PTY. ANSI,
334
+ completion, Ctrl+C/D/Z, foreground jobs and full-screen terminal applications use
335
+ normal guest terminal semantics. Shell output uses the PTY's merged stream.
336
+
337
+ Host raw mode exists only for the session and is restored after exit, errors and
338
+ handled termination signals. EOF or transport disconnect closes the PTY and kills
339
+ owned guest session groups. Failed cancellation is reported explicitly. Eight private
340
+ VirtIO slots permit independent concurrent sessions with pinned socket identities
341
+ and nonblocking slot locks; an exhausted pool fails clearly. Existing VMs require
342
+ explicit recreation to receive management layout 2. No network ports or SSH transport
343
+ are added, and no shell input is written to Empeira logs. Diagnostic shells disable
344
+ the default persistent history file; VM Bash startup preserves interactive configuration
345
+ and then makes `HISTFILE=/dev/null` read-only.
346
+
347
+ ## Recovery destroy
348
+
349
+ `destroy` and `node destroy` ignore release requirements, current project configuration,
350
+ normal-operation inventory schemas and guest management versions. They use only
351
+ safely interpretable stored ownership plus observed native IDs/labels or the owned
352
+ QEMU monitor identity. Normal operations retain their compatibility gates. Destroy
353
+ never invokes guest commands, starts infrastructure, or requires a Puppetserver.
354
+
355
+ With a missing marker but an existing Git root, invoke destroy from that root.
356
+ If the repository itself has disappeared, supply its former absolute canonical Git
357
+ root path. Invoke these commands from any accessible working directory:
358
+
359
+ ```console
360
+ empeira node destroy host1 --project-path /former/control-repo
361
+ empeira destroy --project-path /former/control-repo
362
+ ```
363
+
364
+ Empeira derives the existing workspace identity from that path and the same user
365
+ home as before. The repository, marker and current configuration need not exist.
366
+ Use the same platform state location as the original workspace. Selection never
367
+ scans/adopts arbitrary containers by name, and native ownership checks still apply.
368
+
369
+ Already absent resources succeed. A deletion failure retains its node/resource
370
+ record and ownership evidence while other positively identified resources continue
371
+ to be removed. Remaining resources are listed and incomplete cleanup returns nonzero.
372
+ Foreign IDs/labels, unsafe paths, symlinks, unverifiable live QEMU processes and active
373
+ VM session guards block their corresponding deletions. Unknown resource groups remain
374
+ recorded for investigation. Node destroy preserves other nodes and shared services,
375
+ networks and volumes. Workspace destroy removes owned CA/database volumes and generated
376
+ files, preserving the stable mutation-lock inode, control code, modules and shared caches.
377
+
378
+ ## VM console recovery
379
+
380
+ Each VM has a private Unix-domain serial socket created by its QEMU process.
381
+ `node console vm1` attaches to that existing endpoint without starting another QEMU.
382
+ Press Enter to obtain a prompt. **Ctrl-] detaches** and leaves the VM running.
383
+ The connection prints the detach instruction. With `vm.console.root_password: null`,
384
+ it also suggests `empeira node ssh vm1`; it does not assign a replacement password.
385
+ Local terminal mode is restored after detach, EOF or errors. Serial access carries
386
+ terminal bytes rather than SSH window-size negotiation; use `stty rows N cols N`
387
+ in the guest when a full-screen program needs different dimensions.
388
+
389
+ The initial recovery login is `root` with password `empeira`:
390
+
391
+ ```yaml
392
+ vm:
393
+ console:
394
+ root_password: empeira
395
+ ```
396
+
397
+ Set a different string for another initial password, or `null` to leave root password
398
+ configuration to the image/Puppet. The value applies once during cloud-init, only
399
+ to console recovery. It does not enable SSH password authentication. Puppet may
400
+ subsequently change or lock root, and Empeira does not restore this password on
401
+ `start`, `up`, or later Puppet runs. Keep real overrides outside version control.
402
+
403
+ Configuration display redacts the password. It is absent from node inventory,
404
+ image metadata and infrastructure fingerprints. Seed-generation files are private
405
+ and removed after ISO creation. After initial cloud-init, Empeira rewrites the
406
+ host seed without the password and removes guest cloud-init credential payloads.
407
+ The console socket is private to the local user, has a short macOS-compatible path,
408
+ and is separated by workspace and node identity. Node destruction removes it.
409
+
410
+ ## VM images and bootstrap
411
+
412
+ Install the [VM prerequisites](installation.md#vm-prerequisites) before creating a
413
+ VM. Capability detection selects KVM or HVF; software emulation is never automatic.
414
+ Preflight checks commands, acceleration, image metadata, scripts, package destinations
415
+ and control-plane readiness before downloads or node reservation.
416
+
417
+ Ubuntu 22.04/24.04 cloud images and Rocky 8/9 GenericCloud images come directly from
418
+ [Ubuntu](https://cloud-images.ubuntu.com/releases/) and
419
+ [Rocky Linux](https://dl.rockylinux.org/pub/rocky/). Availability depends on architecture.
420
+ SHA-256 manifests are fetched over HTTPS and complete images verified before caching.
421
+ The immutable cache identity includes distribution, version, architecture, source,
422
+ revision and checksum. Each VM has a QCOW2 overlay pinned to its original base.
423
+ Destroying a node removes its overlay and seed, preserving the shared base cache.
424
+
425
+ `vm.disk` sets the virtual capacity of new VM overlays in GiB (default `30`, maximum
426
+ `2048`). A capacity smaller than the base image is rejected. The overlay is thin:
427
+ the host stores changed blocks, rather than reserving the entire virtual capacity.
428
+ Sizing is verified before the overlay is published and before QEMU starts.
429
+ Cloud-init grows the actual root partition and filesystem while preserving boot/EFI
430
+ partitions. Empeira verifies disk, partition and filesystem capacity after cloud-init
431
+ and before managed package installation; failed growth retains an incomplete VM for
432
+ diagnosis and prevents enrollment and Puppet. Check `df -h /` through `node ssh`.
433
+ The checks support both integer and decimal-string byte counts from supported
434
+ guest `lsblk` versions, with identical partition and filesystem growth requirements.
435
+ Changing `vm.disk` affects only subsequently created VMs; `up` and `node start`
436
+ preserve existing disks. Container nodes do not use this setting.
437
+
438
+ Cloud-init prepares identity, VirtIO management, the regular SSH login, DNS adapter, console recovery
439
+ and optional project scripts. Managed installation then installs the selected public
440
+ Puppet/OpenVox agent through separate bootstrap proxy access. Normal proxy rules are
441
+ not needed for standard installation. Bootstrap access ends
442
+ when installation completes or fails. See [proxy policy](proxy.md).
443
+
444
+ Incomplete bootstrap retains the VM for `node logs`, `node shell`, inspection and
445
+ explicit `node destroy`. An incomplete VM must be destroyed and recreated before
446
+ it can be started as a ready node. Preflight failures retain no new node.
447
+
448
+ ## Additional VM interface lifecycle
449
+
450
+ Configure [additional VM interfaces](configuration.md#additional-vm-interfaces)
451
+ when a production manifest expects a named interface in ordinary networking facts.
452
+ After agent installation and bootstrap cleanup, Empeira creates and verifies the
453
+ matched dummy/VLAN devices before enrollment and the first catalog. Subsequent
454
+ `up` and `node start` reconcile devices and verify Facter; `node puppet` runs the
455
+ agent against the already configured guest.
456
+ Container provisioning and facts are unaffected.
457
+
458
+ `vm.disk` and `vm.interfaces` are independent settings and can be used together.
459
+ On a new VM, root disk growth is verified before package bootstrap, agent installation
460
+ and interface reconciliation. Failed growth retains an incomplete VM without creating
461
+ the additional interfaces or enrolling it. Updating interface rules on an existing VM
462
+ preserves its original disk capacity, even when `vm.disk` has changed.
463
+
464
+ `node start` restores ephemeral interfaces after a VM stop/start, and reconciles an
465
+ already running VM. `empeira up` applies edits to running, provisioned VMs without
466
+ recreating them or invoking Puppet. Stopped VMs take the current rules on their next
467
+ start; incomplete nodes remain blocked. Changing patterns, IPs, prefixes or VLAN
468
+ IDs updates only owned devices. A changed definition recreates that device, while
469
+ unchanged devices keep their Linux identity. Removed rules remove owned devices;
470
+ the parent is removed only after the last VLAN child. No guest network-manager
471
+ configuration or Puppet service configuration is installed. Direct/manual Puppet
472
+ and independently enabled Puppet services follow the existing access contract:
473
+ after a guest reboot, use `node start` or `up` before running them. Empeira guarantees
474
+ restoration before its own managed catalog requests.
475
+
476
+ Ownership combines the workspace and node identities, a random instance token in
477
+ the existing locked inventory, and a definition fingerprint. A derived locally
478
+ administered unicast MAC is assigned in the same netlink request as device creation;
479
+ Linux does not apply the alias at that stage. Empeira then sets and verifies the
480
+ full ownership alias. A missing alias is recoverable only when the recorded intent
481
+ and atomic MAC witness match; a conflicting alias or MAC is refused. Before
482
+ changing or removing a link, Empeira checks that marker, its type, VLAN ID, parent,
483
+ addresses and dependencies. Foreign devices, foreign child links, unexpected
484
+ addresses, renamed links and links attached to another network block reconciliation.
485
+ Names alone confer no ownership. Keep Puppet/network managers from managing these
486
+ test devices concurrently. The shared lab remains a cooperative environment.
487
+
488
+ Each new definition and the accepted previous definition are saved before mutation.
489
+ An SSH timeout or lost completion leaves this intent available for the next `up`,
490
+ `node start` or `node puppet`, which inspects the guest before continuing. Completed
491
+ deletions are observed before ownership is released. A failure during initial
492
+ provisioning retains an incomplete VM and prevents the first catalog; inspect it
493
+ through SSH/console, then destroy/recreate. A later reconcile failure blocks the
494
+ requested operation and keeps the provisioned VM available for repair and retry.
495
+
496
+ To inspect regular facts and Linux VLAN metadata:
497
+
498
+ ```console
499
+ empeira node ssh web-one.example.test
500
+ sudo /opt/puppetlabs/bin/facter networking --json
501
+ ip -j -d link show ens192
502
+ ip -j address show ens192
503
+ ```
504
+
505
+ Empeira verifies `networking.interfaces.DEVICE.bindings[0].address` and `netmask`
506
+ against the configured IPv4 CIDR. The ordinary Puppet expression works unchanged:
507
+
508
+ ```puppet
509
+ $node_src_ip = $facts['networking']['interfaces'][$vlan]['bindings'][0]['address']
510
+ ```
511
+
512
+ The [Facter networking contract](https://help.puppet.com/core/current/Content/PuppetCore/Markdown/core_facts.htm)
513
+ documents IPv4 `bindings`; a VLAN-ID fact is not assumed. VLAN ID and 802.1Q protocol
514
+ are checked separately in `ip -j -d link` output. The managed parent can also appear
515
+ in Facter, without an IPv4 binding. Linux dummy devices can have operational state
516
+ `UNKNOWN` while administratively `UP`; Empeira accepts `UP`/`UNKNOWN`, rejects `DOWN`,
517
+ and checks the exact address and device type.
518
+
519
+ ## Node images and fidelity
520
+
521
+ | OS catalog key | Versions | Build source |
522
+ | --- | --- | --- |
523
+ | `ubuntu` | `22.04`, `24.04` | Docker Official Ubuntu images |
524
+ | `rocky` | `8`, `9` | Rocky Linux project images |
525
+ | `almalinux` | `8`, `9` | Docker Official AlmaLinux images |
526
+ | `oraclelinux` | `8` | Docker Official Oracle Linux image |
527
+
528
+ `images.nodes.OS.VERSION` defines the base repository/tag, packaged build recipe and
529
+ allowed `architectures` (`amd64`, `arm64`). The engine architecture is selected
530
+ and the resulting image architecture checked; emulation is never automatic. A
531
+ full `reference` overrides building and must provide the documented shell,
532
+ init mode and networking tools. Agent installation is controlled independently
533
+ by `agent.install` after the node starts.
534
+
535
+ The project-owned [node recipes](../resources/nodes) add SSH and diagnostic
536
+ tools but contain no agent package, source credential or Empeira PATH override.
537
+ The default bootstrap installs the agent version in [built-in defaults](../config/defaults.yaml) through a reviewed,
538
+ SHA-256-pinned release package and native signed APT/DNF repository. Custom
539
+ repository sources or direct verified `.deb`/`.rpm` packages can be configured
540
+ under `agent.install`; the selected server and PuppetDB images do not change.
541
+ Source configuration is restored and bootstrap access removed before certificate
542
+ enrollment and the first Puppet run. Container images remain in the selected
543
+ runtime cache; VM base images use the separate checksum-addressed cache. No
544
+ agent packages or images are bundled in Empeira releases.
545
+
546
+ Runtime tests report the selected OS, init mode and architecture. Systemd and HVF
547
+ need their own capable hosts; amd64 tests do not establish arm64 execution.
548
+
549
+ The Ubuntu base `docker.io/library/ubuntu` is the [official image maintained by
550
+ Canonical](https://hub.docker.com/_/ubuntu); `library` is Docker Hub's official-image
551
+ namespace. The minimal upstream image is extended with `sudo`, `locate` and
552
+ `updatedb` for catalogs that expect these commands before package management runs.
553
+ Ubuntu uses `plocate`; the RPM recipe uses `mlocate`. Puppet remains responsible for
554
+ application packages and sudo policy. No additional sudo grants are installed.
555
+ Run `updatedb` after filesystem changes before relying on `locate` results; process
556
+ mode does not run the distribution's timer or cron daemon. Existing nodes require
557
+ explicit destroy/recreate to receive changed recipe contents. VM images retain
558
+ their upstream package selection.
559
+
560
+ These tools come from the distribution's signed repositories, with package license
561
+ notices retained: [sudo](https://github.com/sudo-project/sudo/blob/main/LICENSE.md)
562
+ uses permissive licenses; [plocate](https://plocate.sesse.net/) and
563
+ [mlocate](https://git.almalinux.org/rpms/mlocate) use GPLv2 licenses. They run as
564
+ separate distribution programs and are not linked into Empeira's AGPL code.
565
+ Review distribution security updates when rebuilding. Cached images do not acquire
566
+ package updates automatically.
567
+
568
+ Default `process` mode uses the runtime's small init and a persistent sleeping process.
569
+ The images contain systemd, but it is not PID 1 in that mode. Explicit
570
+ `node_defaults.init: systemd` requires Podman with cgroup v2. Unsupported runtimes fail
571
+ instead of enabling privileged mode or mounting host cgroups. Systemd mode requires a separate cgroup-v2 integration host. Container catalogs requiring unavailable
572
+ kernel, boot or device functionality fail naturally. VM nodes provide higher
573
+ fidelity when host acceleration and the required QEMU tools are available. The
574
+ provider fact must not be used to hide container limitations.
575
+
576
+ Distribution contents retain their package licenses. Sources include
577
+ [Ubuntu](https://hub.docker.com/_/ubuntu),
578
+ [Rocky Linux](https://github.com/rocky-linux/sig-cloud-instance-images),
579
+ [AlmaLinux](https://github.com/AlmaLinux/docker-images), and
580
+ [Oracle Linux](https://github.com/oracle/container-images).
581
+ The public [OpenVox agent](https://github.com/OpenVoxProject/openvox) and optional
582
+ [Puppet agent](https://github.com/puppetlabs/puppet-agent) use open-source components,
583
+ including Apache-2.0 Puppet/OpenVox code. No RHEL or Puppet Enterprise artifact is used.
584
+
585
+ A configured image change marks existing nodes stale; it never replaces their writable
586
+ state automatically. Destroy/recreate them explicitly. Changing or replacing CoreDNS
587
+ with retained nodes is refused because their resolver bindings must remain valid.
588
+
589
+ ## Configuration and stale resources
590
+
591
+ Workspace `mocks.commands` provides generic executable command stubs for Puppet
592
+ tests on both node providers. See [command mock configuration](configuration.md#command-mocks-on-test-nodes)
593
+ for echo and script-forwarding examples and mandatory exit-code selection.
594
+
595
+ Mocks are applied during initial node provisioning and during `node start`,
596
+ including an already running node. `empeira up` reconciles mocks on running nodes
597
+ without restarting them or running Puppet. Stopped nodes reconcile on their next
598
+ start. A configured mock replaces an existing file or symlink at its target and
599
+ is restored if another program overwrites it. Removed entries delete only
600
+ unchanged, ownership-verified stubs. Destroying a node removes its mocks with
601
+ the owned container or VM disk.
602
+
603
+ Managed container nodes must have the agent Ruby at
604
+ `/opt/puppetlabs/puppet/bin/ruby` after agent bootstrap and before mocks can
605
+ be reconciled. If this interpreter is missing, inspect the retained node and
606
+ its image. Both providers record `provisioned: false` until package cleanup,
607
+ bootstrap access removal, final network/configuration and certificate enrollment
608
+ have succeeded. Failed nodes remain `incomplete` and accessible through shell/logs,
609
+ but `node start`, `node puppet` and mock reconciliation are blocked. After diagnosis,
610
+ destroy and recreate the node. A failed first catalog can be retried once provisioning
611
+ is complete. `up` does not install the agent into an existing node.
612
+
613
+ Unsupported inventory schemas fail closed without automatic migration.
614
+
615
+ Existing-node commands still validate the full project configuration. Invalid
616
+ provisioning settings can block shell, SSH, logs or cleanup until corrected.
617
+ Ownership, inventory and version checks remain active on access operations.
618
+ A stale image does not cause automatic replacement. Preserve required data and
619
+ explicitly destroy/recreate a node when changing its image definition.
620
+
621
+ OpenSSH packages come from the selected distribution repositories. Their
622
+ [upstream license notices](https://github.com/openssh/openssh-portable/blob/master/LICENCE)
623
+ remain in the installed packages; the permissive OpenSSH licenses are compatible
624
+ with project-owned AGPL code. Consult [OpenSSH security advisories](https://www.openssh.com/security.html)
625
+ and distribution updates when refreshing images. `update images` checks native base-image manifest metadata and skips unchanged
626
+ recipes/resources/bases. Required builds use `--pull` and retain native cached
627
+ package-install layers; unchanged inputs do not reinstall packages. Updated bases or recipes cause dependent layers
628
+ to rebuild. The Ruby dependency audit does not audit OS packages.
629
+
630
+ A stopped VM resumes through visible retained-storage, network, QEMU-start and SSH
631
+ readiness milestones. It keeps its existing base/overlay and does not rerun Puppet
632
+ automatically. Container/VM creation and destroy retain their named workflow stages;
633
+ only module and image updates use homogeneous completed/total counts.
634
+
635
+ ## Peer network lifetime
636
+
637
+ Container and VM nodes are direct IPv4 peers. Use their logical DNS names from
638
+ other nodes, additional services and Chromium; arbitrary TCP/UDP requires no port
639
+ publication. Stop/start preserves the instance's IP, MAC and DNS record. Destroy
640
+ releases the lease and removes the record and owned transport resources. Recreating
641
+ a name does not promise the previous IP or MAC. VM status includes network health:
642
+ `running`, `reserved` for a stopped instance, or `degraded` when its adapter is missing.