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
@@ -0,0 +1,1204 @@
1
+ # Configuration
2
+
3
+ Empeira runs `git rev-parse --show-toplevel` through the central execution runner
4
+ from the current directory, canonicalizes the returned root (including macOS
5
+ symlink paths), and requires `<git-root>/.empeira.yaml`. The marker explicitly opts
6
+ that Git repository into Empeira. Invocation from any subdirectory works; there is
7
+ no `--project` override or Puppet-repository heuristic. Missing Git/markers produce
8
+ actionable errors including the resolved root and `touch` command.
9
+
10
+ An empty file or `{}` uses defaults. Nonempty configuration must be one YAML mapping.
11
+ Duplicate keys, multiple documents, aliases and object tags are rejected. Unreadable
12
+ files and broken marker symlinks fail. Help/version/self-update are project-independent.
13
+
14
+ Precedence, from lowest to highest:
15
+
16
+ 1. [Built-in defaults](../config/defaults.yaml).
17
+ 2. The selected control repository's `.empeira.yaml`.
18
+ 3. Optional host preferences in `~/.empeira.yaml`.
19
+ 4. Explicitly permitted CLI overrides.
20
+
21
+ Mappings deep-merge; scalars and arrays replace lower-precedence values. Explicit
22
+ `null` is allowed only where the schema permits it. Every source is validated
23
+ before merging, so a later override cannot hide an invalid project setting.
24
+ Cross-field validation runs against the effective configuration.
25
+
26
+ ## Optional user preferences
27
+
28
+ `~/.empeira.yaml` holds local workstation preferences and is not committed to the
29
+ control repository. A missing or empty file is normal and produces no warning.
30
+ It may contain exactly these settings, independently or together:
31
+
32
+ ```yaml
33
+ runtime:
34
+ container_engine: docker
35
+ images:
36
+ registry: registry.example.net
37
+ ssh:
38
+ user: empeira
39
+ identity: ~/.ssh/id_ed25519
40
+ port: 22
41
+ rules:
42
+ - hosts: ["web-*", "app-?.example.net"]
43
+ user: root
44
+ identity: ~/.ssh/id_ed25519_lab
45
+ port: 2222
46
+ - hosts: ["db-*"]
47
+ user: admin
48
+ ```
49
+
50
+ User preferences override the same project keys; an explicit `--container-engine`
51
+ overrides the user preference. All other project mappings remain intact. For example,
52
+ a project selecting Podman and `images.registry: null` uses Docker and the cache above
53
+ on this workstation without changing the repository. Explicit user `registry: null`
54
+ restores Docker Hub resolution. `config show` displays the effective non-sensitive values.
55
+ There is no separate source-reporting option.
56
+
57
+ `ssh` is personal configuration for interactive `empeira node ssh` only; it is
58
+ rejected in project YAML. Global `user`, `identity` and `port` are independently optional,
59
+ as is the `rules` array. Each rule requires a nonempty `hosts` array and at least
60
+ one of `user`, `identity` or `port`. Ports must be integers from 1 to 65535.
61
+ Unknown keys and passwords are rejected. Patterns match
62
+ the entire hostname, ignore case and support only `*` and `?`, with the same matching
63
+ semantics as proxy hostname rules. Any pattern can select a rule. All matching
64
+ rules apply in list order; the last rule defining a field wins for that field.
65
+ An omitted field preserves the previous value.
66
+
67
+ Each field resolves independently: explicit `--user`/`--identity`/`--port`, then its last
68
+ matching rule, then its global value, then the provider default. Without a configured
69
+ value, VMs use `empeira` and their existing managed key; containers use the local
70
+ username and normal OpenSSH identities/agent/config. Both providers default to system
71
+ SSH guest port 22; this is independent of any private host transport port. Identity paths in this YAML
72
+ must be absolute or start with `~/`, expanded through the platform home. Relative
73
+ CLI identity paths retain their existing meaning relative to the invocation directory.
74
+ Before connecting, the selected external key must be a readable regular file with
75
+ no symlink and no group/other permission bits (`chmod 600` or `400`). Key contents
76
+ are never read, copied or included in diagnostics by preference resolution.
77
+
78
+ Personal SSH preferences are kept outside the effective infrastructure configuration,
79
+ `config show`, inventory and fingerprints. They do not alter internal VM SSH,
80
+ bootstrap, agent configuration, Puppet, shell/console, start/run or control-plane
81
+ behavior. File checks apply only when an identity is selected for `node ssh`.
82
+ New VMs use the locked system account `empeira` with home `/var/lib/empeira` only
83
+ for the default regular system SSH login, without sudo grants. Its UID is allocated
84
+ by the guest. Internal management uses root, an independent key and a dedicated
85
+ public-key-only daemon with PAM disabled; personal SSH settings never affect it.
86
+ There is no configurable management user, UID or compatibility option. Older VM
87
+ layouts require explicit destruction with the previous revision and recreation.
88
+
89
+ This file is deliberately not a second project definition. Any other key, including
90
+ `server`, `agent`, `network`, `containers`, `browser`, `images.server`, `images.nodes`, or
91
+ credential fields, fails with the user-file path and offending YAML path. Duplicate
92
+ keys, aliases, object tags, multiple YAML documents and control-character keys are
93
+ rejected just as for project YAML. Invalid lower-precedence values cannot be hidden
94
+ by a later override. A user file does not replace the required project opt-in marker.
95
+ Home resolution uses Ruby/platform facilities on Linux, macOS and WSL2.
96
+
97
+ ## Project configuration
98
+
99
+ Example `.empeira.yaml`:
100
+
101
+ ```yaml
102
+ version: 1
103
+ requirements:
104
+ empeira: null # Set a release requirement when using a released Empeira build.
105
+ runtime:
106
+ container_engine: podman
107
+ server:
108
+ environment: production
109
+ network:
110
+ egress: []
111
+ proxy:
112
+ enabled: false
113
+ node_defaults:
114
+ os: ubuntu
115
+ version: "24.04"
116
+ memory: 2048
117
+ cpus: 2
118
+ puppetdb:
119
+ enabled: true
120
+ bootstrap:
121
+ enabled: false
122
+ scripts: []
123
+ packages:
124
+ install: {default: [], debian: [], redhat: []}
125
+ remove: {default: [], debian: [], redhat: []}
126
+ ```
127
+
128
+ `version: 1` is the configuration schema version, unrelated to the application
129
+ release version. A partial project mapping may omit it and inherit the default.
130
+ `node_defaults.version` is an OS version string and must be quoted if numeric.
131
+
132
+ | Path | Type and default |
133
+ | --- | --- |
134
+ | `version` | Integer, exactly `1` |
135
+ | `requirements.empeira` | RubyGems requirement string, or `null` (default) |
136
+ | `runtime.container_engine` | `podman` (default) or `docker` |
137
+ | `server.runtime` | OpenVox startup defaults; optional startup, configuration path and environment-key overrides for compatible custom images |
138
+ | `puppetdb.runtime` | OpenVoxDB process defaults; optional entrypoint, user, Java arguments, paths and environment key overrides for compatible custom images |
139
+ | `images.server` | OpenVox Server OCI image by default; repository/tag, digest or exact reference |
140
+ | `images.puppetdb` | OpenVoxDB OCI image by default; independent of the server image |
141
+ | `images.postgres` | PostgreSQL OCI image, independently replaceable |
142
+ | `agent.package`, `agent.version` | Defined in [built-in defaults](../config/defaults.yaml); independent of server images |
143
+ | `agent.install.method` | `repository` (default) or `package` |
144
+ | `agent.install.repositories.KEY` | Pinned OpenVox release packages and reviewed destinations by default |
145
+ | `agent.install.apt.KEY`, `agent.install.dnf.KEY` | Native sources with a generic `default` and partial OS overrides |
146
+ | `agent.install.packages.KEY.ARCH` | Direct HTTPS `.deb`/`.rpm` URL, optional SHA-256 and signature policy |
147
+ | `agent.cache.enabled` | Shared host package cache, enabled by default |
148
+ | `server.environment` | Puppet environment name, `production` |
149
+ | `server.memory`, `server.cpus` | Positive integers, `1536` MiB and `2` CPUs |
150
+ | `server.timeout` | Positive readiness timeout in seconds per component, `240` |
151
+ | `server.mounts` | Additional server-only host bind mounts, `[]`; `source`, `target`, optional boolean `readonly` (default `true`) |
152
+ | `puppetdb.memory` | Positive integer MiB, `768` |
153
+ | `network.egress` | Direct TCP proxy-bypass host/port entries, `[]`; a project array replaces the default array |
154
+ | `network.redirects` | Exact IPv4/protocol/port endpoints redirected to enabled internal services, `[]` |
155
+ | `proxy.enabled` | Boolean, `false`; creates the normal HTTP/HTTPS policy proxy |
156
+ | `proxy.global` | DNS destination array, `[]` |
157
+ | `proxy.rules` | Additive hostname-glob rules with `hosts` and `allow` arrays, `[]` |
158
+ | `vm.disk` | New VM virtual disk capacity in GiB, integer `1`–`2048`, default `30`; must accommodate the base image |
159
+ | `vm.interfaces` | VM-only hostname rules defining dummy/VLAN devices with explicit IPv4 CIDR host addresses, `[]` |
160
+ | `vm.console.root_password` | Initial console password, `empeira`; a string overrides it and `null` disables it |
161
+ | `dns.upstream` | `mode: host` and empty `servers` by default; explicit mode requires resolver IPs |
162
+ | `dns.additional_resolver` | Optional DNS name or IP address queried before the existing upstream for non-Empeira names; NXDOMAIN/NODATA fall through |
163
+ | `dns.rewrites` | Exact external hostnames mapped to enabled `<service>.empeira.internal` targets; `[]` by default |
164
+ | `hiera.mounts` | Puppet-aware module/environment mounts, `[]`; each source is optional unless `required: true` |
165
+ | `node_defaults.os` | Configured node OS family, default `ubuntu` |
166
+ | `node_defaults.version` | OS version string, default `"24.04"` |
167
+ | `node_defaults.init` | `process` by default; `systemd` requires supported Podman/cgroup v2 |
168
+ | `node_defaults.memory` | Positive integer MiB, `1024` |
169
+ | `node_defaults.cpus` | Positive integer, `2` |
170
+ | `mocks.commands` | Named command stubs for test nodes, `{}`; each requires `path`, `mock_to` and `exit_code` |
171
+ | `puppetdb.enabled` | Boolean, `true` |
172
+ | `eyaml.enabled` | Boolean, `false`; requires both key paths when enabled |
173
+ | `eyaml.private_key`, `eyaml.public_key` | Local PEM file paths, `null` by default |
174
+ | `bootstrap.guests` | Reviewed agent-presence metadata and bootstrap-proxy destination domains; Rocky and AlmaLinux supply stable package base URLs |
175
+ | `bootstrap.packages.install` | Global one-time package lists for `default`, `debian` and `redhat` |
176
+ | `bootstrap.packages.remove` | Global one-time removal lists for `default`, `debian` and `redhat` |
177
+ | `images.direct_egress` | Recipe or explicit image reference implementing the workspace gateway contract |
178
+ | `images.r10k.build` | Disposable r10k helper recipe, `modules/Containerfile` |
179
+ | `modules.path` | Actual directory containing modules directly; `modules` by default, relative to the canonical repository root or absolute |
180
+ | `addons` | Mapping of standalone addon names to `git`, `ref`, and optional `config` mapping; empty by default. The addon receives its own settings as `EMPEIRA_ADDON_CONFIG_*`. See [addons](addons.md). |
181
+ | `bootstrap.enabled` | Boolean, `false` |
182
+ | `bootstrap.scripts` | Project-local UTF-8 script paths, `[]`; run during VM cloud-init only when enabled |
183
+ | `images.registry` | Optional default registry host; `null` resolves hostless repositories through `docker.io` |
184
+ | `browser.image` | Configurable repository/tag, `docker.io/linuxserver/chromium:latest` |
185
+ | `browser.start_url` | Initial Chromium page, `about:blank` |
186
+ | `containers.additional` | Array of infrastructure service definitions, `[]` |
187
+
188
+ Unknown keys, unused namespaces, named nodes, and `node_defaults.provider` are
189
+ rejected. `puppetdb.enabled: false` omits PostgreSQL/PuppetDB and disables server
190
+ storeconfigs/report integration, preserving any previously created database volumes.
191
+
192
+ ### DNS rewrites for internal services
193
+
194
+ Use an additional infrastructure service as a local API Compatibility Layer:
195
+
196
+ ```yaml
197
+ dns:
198
+ rewrites:
199
+ - from: ipam.example.net
200
+ to: api-layer.empeira.internal
201
+ - from: inventory.example.net
202
+ to: api-layer.empeira.internal
203
+ containers:
204
+ additional:
205
+ - name: api-layer
206
+ image:
207
+ repository: registry.example.net/lab/api-layer
208
+ tag: "1"
209
+ ```
210
+
211
+ Each entry requires exactly `from` and `to`. Hostnames are case-insensitive; a
212
+ trailing root dot is accepted. Duplicate sources, wildcards, regular expressions,
213
+ URLs and IP addresses are rejected. Sources must be outside `empeira.internal`,
214
+ which prevents self references and cycles. Targets must name an enabled built-in
215
+ or additional service, using exactly `<service>.empeira.internal`; node names and
216
+ nested names are not service targets. `up` reports missing or disabled targets
217
+ before acquiring images or changing infrastructure.
218
+
219
+ Run `empeira up` after adding, changing or removing a rewrite. CoreDNS reloads its
220
+ configuration while preserving its container and existing container/VM nodes.
221
+ Service-discovery updates supply the current target IPv4 address; no IP belongs in
222
+ the rewrite configuration. Repeated unchanged `up` calls do not reload the policy.
223
+ See [DNS rewrites](networking.md#exact-dns-rewrites) for DNS response semantics.
224
+
225
+ A DNS rewrite changes name resolution only. Applications still send the original
226
+ HTTP Host/TLS SNI name and use their original protocol and port. The service must
227
+ implement the desired API behavior and, for HTTPS, present a certificate valid for
228
+ the original hostname that the client trusts. Empeira supplies no TLS termination,
229
+ certificate management, HTTP transformation, port mapping or proxy-policy changes.
230
+
231
+ ### Additional server bind mounts
232
+
233
+ Projects can expose local artifacts to the Puppet/OpenVox configuration server:
234
+
235
+ ```yaml
236
+ server:
237
+ mounts:
238
+ - source: ../artifacts
239
+ target: /srv/puppet-artifacts
240
+ - source: /srv/example-ca
241
+ target: /etc/example-ca
242
+ readonly: true
243
+ ```
244
+
245
+ Relative sources resolve against the canonical Git control-repository root, even
246
+ when invoking Empeira from a subdirectory. Absolute sources outside the repository
247
+ are allowed. Sources must exist and resolve through `realpath` to regular files or
248
+ directories; broken source symlinks, control characters and commas are rejected.
249
+ Targets must be absolute, are normalized, and cannot contain `..` segments or
250
+ control characters/commas. `/` itself and system paths `/dev`, `/proc` and `/sys`
251
+ cannot be mounted. Source data trees must not contain sockets, devices or other
252
+ special files. Validation does not follow nested directory symlinks.
253
+
254
+ Mounts are read-only unless explicitly configured with `readonly: false`. Writable
255
+ mounts allow the server to change host data, subject to host filesystem permissions.
256
+ The runtime must be able to see the canonical host path, including when its engine
257
+ runs in a VM. Empeira checks mounted visibility using the existing mount probe.
258
+ This interface accepts no named volumes, tmpfs, device/runtime-socket mounts,
259
+ propagation flags, SELinux options or arbitrary Docker/Podman mount strings.
260
+
261
+ The mounts belong exclusively to `server.empeira.internal`. For example, a manifest
262
+ can use `file('/srv/puppet-artifacts/banner.txt')` during catalog compilation to
263
+ supply a Puppet `file` resource's content, if that is what the control code expects.
264
+ Empeira provides the bind mount without adding Puppet-specific file handling.
265
+ It does **not** expose the host path to agent nodes: a server-local path is not
266
+ automatically an agent-local `source` path. Container/VM nodes, PuppetDB, PostgreSQL,
267
+ DNS, proxy, browser, additional services and the r10k helper receive no such mounts.
268
+
269
+ Duplicate targets and ancestor/descendant overlaps between project mounts are
270
+ rejected. The same collision check covers all actual Empeira-managed server mount
271
+ targets, including CA, SSL, server data, the environment/control repository,
272
+ modules, Hiera, EYAML and PuppetDB startup/configuration files when enabled.
273
+
274
+ Mounted directory content stays live: editing files requires no server recreation.
275
+ Contents are not hashed. Changing the canonical source, normalized target or
276
+ `readonly` changes the server service fingerprint; the next `empeira up` uses normal
277
+ selective reconciliation to recreate the server. There is no hot-mount operation.
278
+ For an individually mounted file, edit it in place: editors that replace its inode
279
+ may require recreating the server. Mount its containing directory for atomic file
280
+ replacement to remain visible.
281
+
282
+ ### Command mocks on test nodes
283
+
284
+ Use `mocks.commands` for Puppet tests that need external commands to exist and
285
+ respond successfully or with a controlled failure. Each mapping key is the logical
286
+ command name; any number of commands can be configured. Both container and VM nodes
287
+ use this workspace configuration.
288
+
289
+ ```yaml
290
+ mocks:
291
+ commands:
292
+ testcmd:
293
+ path: /usr/local/bin/testcmd
294
+ mock_to: echo
295
+ exit_code: 0
296
+ ```
297
+
298
+ Empeira creates an executable stub at `path`. Running
299
+ `/usr/local/bin/testcmd run example` prints `testcmd run example` and exits with zero.
300
+ The echo target prints the logical name followed by every argument in order,
301
+ without interpreting options, shell expressions or backslash escapes.
302
+
303
+ To forward to a test script already present on the node:
304
+
305
+ ```yaml
306
+ mocks:
307
+ commands:
308
+ foo:
309
+ path: /usr/local/bin/foo
310
+ mock_to: /opt/test/mocks/foo.sh
311
+ exit_code: passthrough
312
+ ```
313
+
314
+ The target receives the original argument vector unchanged, including empty
315
+ arguments and arguments containing spaces. Its stdout and stderr remain visible.
316
+ Empeira does not install the target script.
317
+
318
+ All three fields are required in the effective configuration:
319
+
320
+ | Field | Meaning |
321
+ | --- | --- |
322
+ | `path` | Safe absolute file destination on the node |
323
+ | `mock_to` | `echo` or a safe absolute executable path on the node |
324
+ | `exit_code` | Integer `0`–`255`, or the string `passthrough` |
325
+
326
+ **There is no default for `exit_code`.** A numeric value always overrides the
327
+ target's status, including failure to execute the target. `passthrough` returns
328
+ the target's actual exit status. Command names contain letters, digits, `_`, `.`
329
+ and `-`, beginning with a letter, digit or `_`.
330
+
331
+ The normal YAML parser, duplicate-key checks, schema and deep merge apply.
332
+ Named command mappings merge by key, and fields merge within each command.
333
+ Remove a command from the effective configuration to remove its managed stub;
334
+ with the built-in empty mapping, deleting its project entry is sufficient.
335
+ `null` is not a removal directive. Missing fields, unsupported keys, invalid
336
+ statuses, relative/traversing paths and overlapping managed destinations fail
337
+ validation.
338
+
339
+ Mocks reconcile during initial node provisioning, on `node start`, and on `up`
340
+ for running nodes. Repeated `node puppet` runs use the existing guest stubs.
341
+ Stopped nodes retain their files until the next start. Unchanged configuration
342
+ and files preserve file identity and timestamps. Changing a path removes the old
343
+ owned stub; changing `mock_to` or `exit_code` replaces only the affected stub.
344
+ Node/workspace destruction removes the node's storage and its mocks together.
345
+
346
+ While configured, the mock takes precedence at its exact `path`. Empeira
347
+ atomically replaces an existing regular file or symlink there and restores the
348
+ mock during the next reconciliation if another program overwrites it. The
349
+ original file is not retained. Parent directories must be real directories;
350
+ a directory at the target path is rejected. Removing a mock deletes only a
351
+ file that still matches its recorded content and ownership. Empeira does not
352
+ change packages, package databases, `PATH`, aliases or bind mounts, and the
353
+ mechanism does not simulate a cluster.
354
+
355
+ ### Server images and runtime contract
356
+
357
+ An empty `.empeira.yaml` selects the OpenVox Server, OpenVoxDB and PostgreSQL
358
+ images from [the defaults](../config/defaults.yaml). Their OCI references are
359
+ independent. A project can replace any one without changing the agent:
360
+
361
+ ```yaml
362
+ images:
363
+ server:
364
+ reference: registry.example.org/custom/server:1
365
+ puppetdb:
366
+ reference: registry.example.org/custom/puppetdb:1
367
+ postgres:
368
+ reference: registry.example.org/custom/postgres:1
369
+ ```
370
+
371
+ Empeira does not identify or classify an image from its name. Docker or Podman
372
+ owns image authentication. The user supplies and licenses custom images and
373
+ must ensure that they implement the Puppet-compatible CA, server, catalog and
374
+ PuppetDB interfaces expected by Empeira. Empeira does not provide a complete
375
+ Puppet Enterprise simulation or bundle proprietary software.
376
+
377
+ `server.runtime` contains the internally managed OpenVox startup contract.
378
+ For another image, override only the fields that differ. `startup.entrypoint`
379
+ and `startup.arguments` select its normal foreground command, including when
380
+ PuppetDB and EYAML are disabled;
381
+ `startup.eyaml_keys` is `direct` or `staged`; `paths.puppetdb_config`
382
+ identifies the standard PuppetDB configuration file; `environment_keys` maps
383
+ semantic settings to environment variable names. For example:
384
+
385
+ ```yaml
386
+ server:
387
+ runtime:
388
+ startup:
389
+ entrypoint: /opt/custom/start
390
+ arguments: [foreground]
391
+ paths:
392
+ puppetdb_config: /etc/puppetlabs/puppet/puppetdb.conf
393
+ environment_keys:
394
+ server_hostname: CUSTOM_SERVER_HOSTNAME
395
+ images:
396
+ server:
397
+ reference: registry.example.org/custom/server:1
398
+ ```
399
+
400
+ The mapping deep-merges with OpenVox defaults. An alternative image using
401
+ other variable names must override each affected key. Empeira mounts standard
402
+ Puppet configuration, including `puppetdb.conf`, and supplies the configured
403
+ runtime values without inferring them from the image name. Missing or unsafe
404
+ runtime fields fail configuration validation. Managed servers use
405
+ `environment_timeout = 0` in the native `[server]` section. The startup wrapper
406
+ uses `puppet config set` before the configured entrypoint, independently of image
407
+ environment-variable mappings. Startup and `up` read Puppet's native server
408
+ settings without selecting an environment override, against the
409
+ confdir containing `server.runtime.paths.puppetdb_config`; custom Puppetserver
410
+ `jruby-puppet.master-conf-dir` must refer to that same directory. Puppetserver
411
+ HOCON master directories take precedence over Puppet CLI defaults. A changed
412
+ effective timeout updates only that key and restarts the same owned server; an
413
+ entrypoint that overwrites it fails verification. CA, SSL and database storage
414
+ are preserved. The next catalog request reloads live code without
415
+ an Empeira content scan or admin API invalidation. An explicit `environment.conf`
416
+ can override this server setting; keep its timeout at zero for immediate edits.
417
+ Such overrides are preserved and do not prevent server startup or cause repeated
418
+ restarts. `puppet config print environment_timeout --section server` includes the
419
+ selected environment's override, so it can report `unlimited` while the native
420
+ server setting is zero. Inspect both configuration layers when diagnosing caching.
421
+
422
+ `puppetdb.runtime` holds the independent OpenVoxDB process contract. Custom
423
+ images may override its executable, numeric user, Java and main arguments,
424
+ `paths.jar`, `paths.config`, `paths.bootstrap_config`, and `paths.data`.
425
+ `java_arguments` is the sole JVM argument source. Empeira mounts the generated database files into
426
+ `paths.config` and retains persistent data at `paths.data`. For example:
427
+
428
+ ```yaml
429
+ puppetdb:
430
+ runtime:
431
+ entrypoint: /opt/custom/java
432
+ user: '1000'
433
+ paths:
434
+ jar: /opt/custom/puppetdb.jar
435
+ config: /opt/custom/conf
436
+ bootstrap_config: /opt/custom/bootstrap.cfg
437
+ data: /opt/custom/data
438
+ java_arguments: [-Xms128m, -Xmx512m]
439
+ ```
440
+
441
+ The custom image must accept the configured JVM and PuppetDB CLI arguments.
442
+
443
+ `--container-engine podman|docker` may override `runtime.container_engine` on
444
+ configuration, infrastructure, and node commands. `node run` additionally accepts `--os`,
445
+ `--version`, `--memory`, and `--cpus`. Omitted flags preserve project settings.
446
+
447
+ ```console
448
+ cd ../control-repo
449
+ empeira config show --container-engine docker
450
+ empeira config validate
451
+ ```
452
+
453
+ Validation failures exit with status 1 and identify the complete path, for example
454
+ `node_defaults.memory must be a positive integer`. Configuration display applies
455
+ recursive redaction to sensitive key names. `vm.console.root_password` and sensitive EYAML paths are redacted.
456
+ Never commit real passwords or private keys into a control repository.
457
+
458
+ ### Project Empeira requirements
459
+
460
+ `requirements.empeira` accepts a RubyGems requirement such as `">= 0.4.0, < 1.0"`.
461
+ The default is `null`, meaning no application version restriction.
462
+ Invalid expressions fail validation at `requirements.empeira`.
463
+
464
+ Before `up`, `down` or `destroy` takes a lock or mutates infrastructure/state, the central
465
+ build abstraction verifies this requirement. Incompatible releases fail with the
466
+ required and installed versions and the `empeira self-update` command. Self-update
467
+ itself is not implemented yet, so install a compatible build through your existing
468
+ installation method. Infrastructure operations never update Empeira automatically.
469
+ `--version`, `config show`, `config validate`, and `status` remain usable on mismatch.
470
+ If project or user configuration cannot be loaded, `destroy` uses built-in defaults
471
+ and the validated workspace inventory for recovery. An unreadable requirement cannot
472
+ be enforced in that case. Other infrastructure commands continue to reject invalid
473
+ configuration.
474
+
475
+ A `development` build has no verifiable release version. It can operate when
476
+ `requirements.empeira` is absent or null. An explicit requirement fails closed as
477
+ unverifiable. Use a compatible release build for a version-constrained project,
478
+ or a dedicated local development control repository without a release requirement.
479
+ The numeric gem packaging fallback is never used to bypass this check.
480
+
481
+ ## Policy and mounts
482
+
483
+ ```yaml
484
+ network:
485
+ egress:
486
+ - host: api.example.com
487
+ ports: [443]
488
+ proxy:
489
+ enabled: true
490
+ global: [forgeapi.puppet.com]
491
+ rules:
492
+ - hosts: ["*-web-*"]
493
+ allow: [github.com, "*.github.com"]
494
+ hiera:
495
+ mounts:
496
+ - source: ../hieradata
497
+ type: module
498
+ name: hieradata
499
+ vm:
500
+ disk: 30
501
+ console:
502
+ root_password: null
503
+ ```
504
+
505
+ When `proxy.enabled` is true, every node may use its matching destination policy.
506
+ After bootstrap cleanup, Empeira reconciles normal proxy settings on running,
507
+ provisioned nodes during `up` and `node start`. Repeated Puppet runs use the
508
+ existing guest configuration. No
509
+ node recreation or restart is needed to apply allowlist changes. Disabling the
510
+ proxy removes owned package-manager settings. The owned login environment clears
511
+ stale creation-time container variables; it never retains bootstrap credentials.
512
+ See [proxy policy](proxy.md),
513
+ [Hiera and EYAML](hiera-and-eyaml.md), [DNS](networking.md), and [console recovery](nodes.md#vm-console-recovery).
514
+ Mounts are optional unless `required: true`. The console password is used only on
515
+ initial VM creation. Changing it does not reset an existing node.
516
+ `vm.disk` applies only when creating a VM. Existing disks keep their capacity across
517
+ `up` and `node start`; changing the setting does not resize or migrate them.
518
+
519
+ VM management uses private VirtIO-Serial with numeric root execution; it does not
520
+ use personal SSH settings, root login or sudo. `node ssh` reaches the regular
521
+ system SSH daemon through the owned peer attachment, with default guest port 22.
522
+ The selected cloud image must include Python 3 before package bootstrap. There is
523
+ no transport selector or additional management YAML configuration. See
524
+ [VM access and recovery](nodes.md#shell-and-ssh).
525
+
526
+ ## Images and bootstrap
527
+
528
+ `images.nodes` selects container recipes and base images. VM image sources currently
529
+ resolve reviewed Ubuntu/Rocky upstream URLs in the image-source adapter; they are
530
+ not configurable through an `images.vm` namespace. A custom adapter can be injected
531
+ at the provider boundary. `bootstrap.guests` records whether an agent is preinstalled
532
+ and the reviewed destination domains that the temporary bootstrap proxy may reach.
533
+ Rocky metadata also supplies the stable `baseurl` needed to avoid arbitrary mirror
534
+ selection. Ubuntu and Debian package source URLs come exclusively from the base
535
+ image. For the reviewed Ubuntu images, amd64 permits archive/security domains and arm64
536
+ permits the ports domain. Do not mark stock cloud images as agent-preinstalled.
537
+ See [node images](nodes.md#node-images-and-fidelity) for fidelity and licensing.
538
+
539
+ ### Agent sources and shared package cache
540
+
541
+ With an empty project marker, the pinned OpenVox release packages, agent version,
542
+ server and database images remain the defaults. Release packages configure the
543
+ signed source inside a disposable host-side helper, never on the test node.
544
+ The helper uses the selected runtime's default bridge and target OS image; APT/DNF
545
+ need not be installed on the host. No agent is installed on the host or helper.
546
+
547
+ A generic source can serve all supported releases of its package-manager family:
548
+
549
+ ```yaml
550
+ agent:
551
+ package: puppet-agent
552
+ version: "8.20.0"
553
+ cache:
554
+ enabled: true
555
+ install:
556
+ method: repository
557
+ apt:
558
+ default:
559
+ url: https://packages.example.org/apt
560
+ component: main
561
+ ubuntu24.04:
562
+ suite: noble-special
563
+ dnf:
564
+ default:
565
+ url: https://packages.example.org/yum/$releasever/$basearch
566
+ ```
567
+
568
+ Configure only the manager you use. A project `apt.default` or `dnf.default`
569
+ supersedes the built-in release sources for that family. OS-specific entries
570
+ merge over the generic default; existing complete source definitions and `suffix`
571
+ overrides remain supported. APT suites default to `jammy` for Ubuntu 22.04 and
572
+ `noble` for Ubuntu 24.04. RPM keys remain `el8`/`el9` for Rocky, AlmaLinux and
573
+ Oracle Linux's existing supported releases. Architecture comes from the target
574
+ node: `amd64`/`arm64` for DEB and `x86_64`/`aarch64` for RPM. Only `$suite`,
575
+ `$releasever` and `$basearch` URL placeholders are expanded; arbitrary environment
576
+ interpolation is rejected.
577
+
578
+ Native source metadata resolves the exact software version to a complete native
579
+ version. No newest-version fallback is used, and ambiguous releases require an
580
+ explicit native version or `suffix`. Resolution excludes unrelated repositories.
581
+ The downloaded package's name, version and architecture must match the target.
582
+
583
+ Direct HTTPS artifacts use the same acquisition and installation path:
584
+
585
+ ```yaml
586
+ agent:
587
+ package: puppet-agent
588
+ version: "8.20.0"
589
+ install:
590
+ method: package
591
+ packages:
592
+ ubuntu24.04:
593
+ amd64:
594
+ url: https://packages.example.org/puppet-agent.deb
595
+ # Optional; if present, the pin is mandatory on downloads and cache hits.
596
+ sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
597
+ ```
598
+
599
+ Direct files remain explicit by distribution and architecture; Empeira does not
600
+ invent download URLs. A software version is accepted when package metadata
601
+ matches it exactly; a complete native version pins the release as well.
602
+
603
+ `sha256` is optional for custom release/key artifacts and direct packages.
604
+ Built-in OpenVox release checksums remain pinned. A configured pin is always
605
+ verified. Changing a source or key URL clears its inherited checksum unless
606
+ the override supplies a new pin. Signed APT metadata supplies package checksums;
607
+ RPM signatures are checked natively. Local DEBs have no repository signature chain: HTTPS and an
608
+ optional independent checksum pin are their verification mechanisms.
609
+ The cache's calculated checksum detects corruption, not publisher provenance.
610
+
611
+ Native signature checks stay enabled. Existing trusted keys may be used; an
612
+ optional `key: {url: https://..., sha256: ...}` supplies an additional public key.
613
+ An APT source can also retain its existing `release` artifact. Set
614
+ `verify_signatures: false` on an APT/DNF source or direct package only when you
615
+ intend to use an unsigned source. The exception is scoped to that source or local
616
+ RPM; base-source signature checks and HTTPS verification remain active. Temporarily
617
+ imported RPM keys are removed after installation without removing preexisting keys.
618
+
619
+ Packages are shared across workspaces under the current user's cache:
620
+
621
+ | Host | Agent cache |
622
+ | --- | --- |
623
+ | Linux / WSL2 | `$XDG_CACHE_HOME/empeira/agents` or `~/.cache/empeira/agents` |
624
+ | macOS | `~/Library/Caches/empeira/agents` |
625
+
626
+ Only packages and necessary nonsecret metadata, including public signing keys,
627
+ are retained. Identity includes package name, full native version, OS family,
628
+ distribution/release, target architecture, format and source/verification policy.
629
+ Private directories, per-request filesystem locks, temporary downloads and atomic
630
+ publication protect concurrent callers. Hits validate local metadata, package hashes
631
+ and any configured package SHA-256 pin. A valid hit is fully usable offline, including
632
+ packages acquired from private APT/DNF repositories or direct authenticated HTTPS
633
+ downloads: no HTTP request, credential validation or login is needed for reuse.
634
+ Damaged owned entries are reacquired. Foreign, symlinked or insecure entries fail
635
+ closed. `agent.cache.enabled: false` bypasses the persistent cache entirely, uses
636
+ temporary host storage with the same acquisition path, removes it after use and
637
+ leaves existing cache entries intact. There is no cache-cleanup command.
638
+
639
+ HTTP authentication occurs only when acquisition is necessary, such as a cache miss,
640
+ a damaged artifact or disabled caching. It uses `EMPEIRA_AGENT_REPO_USERNAME` and
641
+ `EMPEIRA_AGENT_REPO_PASSWORD` when both are set. Invalid explicit credentials fail
642
+ without an interactive fallback. Otherwise an HTTP 401 can prompt in a TTY:
643
+ confirm login, enter the username and enter the password without echo. Progress
644
+ is suspended during the prompt. There is one retry; cancellation or another
645
+ failure stops provisioning. Without a TTY, the error names the two environment
646
+ variables for CI. HTTP 403 and proxy HTTP 407 do not trigger repository login.
647
+ ENV credentials take precedence. A rejected interactive login is identified as
648
+ such and includes the last HTTP diagnosis. During acquisition, the login is reused
649
+ for related metadata, signing-key and package requests to the same HTTPS origin
650
+ (case-insensitive hostname and matching port). Credentials are not forwarded to
651
+ other origins; a 401 there names the failing URL rather than asking for the source's
652
+ login again.
653
+
654
+ Credentials stay in memory or temporary 0600 helper files, never in YAML, cache
655
+ keys, cache metadata, workspace inventory or process arguments. Host downloads
656
+ do not follow redirects, and credentials are scoped to the source HTTPS origin.
657
+ Registry authentication continues to use native `docker login` / `podman login`.
658
+
659
+ After configured bootstrap packages are handled using the guest's base sources,
660
+ the host artifact is uploaded through existing container/VM file transport and
661
+ installed locally by APT/DNF. The agent repository is not added to the node.
662
+ Dependencies can still require the unchanged guest base repositories and existing
663
+ bootstrap-proxy grants: this is an agent-artifact cache, not an offline package
664
+ mirror. No additional node egress is granted. Dependency failure blocks the first
665
+ catalog. Uploaded artifacts, temporary keys and proxy files are removed, and the
666
+ original package-manager configuration is restored and verified before bootstrap
667
+ access is removed, enrollment completes and Puppet runs. Unverifiable cleanup
668
+ leaves the node incomplete for diagnostics.
669
+
670
+ APT loads the private proxy file explicitly after the guest's normal configuration
671
+ so existing proxy fragments cannot override the controlled bootstrap endpoint.
672
+ Credentials are redacted before diagnostic truncation, including native errors
673
+ that print a raw credential rather than a URL.
674
+
675
+ `bootstrap.enabled: true` runs `bootstrap.scripts` once during VM cloud-init.
676
+ Scripts must be project-local UTF-8 files below 1 MB, including after symlink
677
+ resolution. This does not control the built-in agent installation, which works
678
+ without project bootstrap scripts or normal Internet access. Remote project
679
+ scripts are not implemented. Keep secrets out of bootstrap scripts.
680
+
681
+ `bootstrap.packages` is a separate, provider-independent package bootstrap for a
682
+ new node instance. Empeira concatenates `default` with the detected distribution
683
+ family for each action. Ubuntu and Debian select `debian`; RHEL, Rocky Linux,
684
+ AlmaLinux and Oracle Linux select `redhat`. Package names remain literal YAML values;
685
+ Empeira has no package-name translation table. The native manager removes packages
686
+ first, then refreshes package metadata when installation is needed, then installs.
687
+ APT is used for the Debian family and DNF for the Red Hat family.
688
+
689
+ ```yaml
690
+ bootstrap:
691
+ packages:
692
+ install:
693
+ default: [git, curl]
694
+ debian: [libxml2-dev]
695
+ redhat: [libxml2-devel]
696
+ remove:
697
+ default: [telnet]
698
+ ```
699
+
700
+ The operation uses the temporary authenticated bootstrap proxy and reviewed active
701
+ agent and distribution package destinations. For Ubuntu and Debian VMs, Empeira
702
+ archives the complete `/etc/apt` tree before package and agent bootstrap, then
703
+ restores that tree and verifies the
704
+ restored archive before removing bootstrap access. The read-only active-source check
705
+ then runs against the restored configuration before certificate enrollment and the
706
+ first Puppet catalog. Empty lists cause no package-manager call.
707
+ A failed remove, metadata refresh or install retains the node and prevents Puppet.
708
+ Empeira also verifies that its required agent executable and Ruby remain available
709
+ after these package operations. Choose the agent through `agent.install`; do not install a replacement agent through
710
+ `bootstrap.packages`.
711
+ Stop/start, later Puppet runs and repeated `up` do not repeat it. Destroying and
712
+ creating a new node instance runs it again. This is initial preparation, not Puppet
713
+ desired state; Puppet owns package state after the first catalog.
714
+
715
+ ## Direct egress
716
+
717
+ `network.egress` contains destination TCP grants, independent of HTTP/HTTPS proxy
718
+ policy. Entries have exactly one `host` or IPv4 `ip` and a nonempty unique `ports`
719
+ array (1–65535). `up` resolves all A records and replaces the complete policy;
720
+ resolution/apply failure blocks egress. IPv6 and external UDP grants are unsupported.
721
+
722
+ ```yaml
723
+ network:
724
+ egress:
725
+ - host: repository.example.net
726
+ ports: [443]
727
+ - ip: 203.0.113.10
728
+ ports: [443, 8443]
729
+ ```
730
+
731
+ Named direct destinations enter generated `NO_PROXY`; applications choose DIRECT
732
+ or proxy themselves. See [networking](networking.md#one-gateway-one-direct-egress-policy)
733
+ for routing, revocation and privilege boundaries.
734
+
735
+ ## Additional VM interfaces
736
+
737
+ `vm.interfaces` supplies real Linux devices for ordinary Facter and Puppet network
738
+ lookups. Container nodes ignore these rules. Arrays replace earlier arrays through
739
+ the normal configuration merge; there is no automatic address assignment. Configure
740
+ `vm.disk` independently in the same mapping; growth is verified before package
741
+ bootstrap and interface creation on new VMs.
742
+
743
+ ```yaml
744
+ vm:
745
+ disk: 32
746
+ interfaces:
747
+ - hosts: ["web-*.example.test", "app-*"]
748
+ devices:
749
+ ens192:
750
+ network: 192.0.2.10/32
751
+ vlan_id: 123
752
+ ens224:
753
+ network: 198.51.100.10/24
754
+ vlan_id: 456
755
+ dummy0:
756
+ network: 203.0.113.10/32
757
+ - hosts: ["db-*"]
758
+ devices:
759
+ ens192:
760
+ network: 192.0.2.20/32
761
+ ```
762
+
763
+ Patterns use the same `*`/`?` glob matcher as `proxy.rules`, against the full stored
764
+ node hostname, case-insensitively. Patterns within a rule are alternatives. All
765
+ matching rules contribute devices; identical definitions merge once. A conflicting
766
+ definition names the hostname, device and both configuration paths. No match creates
767
+ no devices. A short pattern does not implicitly match a fully qualified hostname.
768
+
769
+ Each device requires `network`: a canonical IPv4 **host** address with a CIDR
770
+ prefix from 0 through 32, subject to the unicast/routing checks below. `vlan_id` is
771
+ optional, and must be an integer from 1 through 4094. Without it, Empeira creates a
772
+ Linux dummy device. With it, Empeira creates an 802.1Q VLAN over its private dummy
773
+ parent `empeira-vlan`. Each VLAN ID may occur only once on this shared parent.
774
+ The parent has no IP addresses or external attachment. These are real VLAN devices,
775
+ but they do not simulate a connected production VLAN segment or transmit packets
776
+ to other nodes. No QEMU NIC, DHCP, bridge, forwarding, gateway grant or proxy rule
777
+ is added.
778
+
779
+ Device names use 1–15 ASCII letters, digits, underscores, dots or hyphens, beginning
780
+ with a letter or digit. `lo`, `eth0`, `peer` and `empeira-vlan`
781
+ are reserved. Unknown keys, IPv6, duplicate addresses/VLAN IDs and overlapping
782
+ device networks are rejected. Network/broadcast addresses within prefixes below
783
+ /31 are rejected, as are subnets overlapping unspecified, loopback, link-local,
784
+ shared-address, protocol-assignment, deprecated relay, multicast or reserved ranges.
785
+ Private and RFC documentation addresses are permitted. `/31` endpoints and `/32`
786
+ hosts are permitted; separate `prefix`/`netmask` keys are unsupported.
787
+
788
+ Prefer `/32` for Facter tests. Other prefixes create connected routes, so Empeira
789
+ checks all guest IPv4 routing tables and existing interface addresses first. The
790
+ workspace subnet and `network.redirects` source addresses
791
+ are protected even if their routes are absent. Overlap is refused, including a /32
792
+ inside an existing connected network. Automatically generated routes of owned test
793
+ devices are recognized during reconciliation; manually added routes on those devices
794
+ block mutation. Default routes are compared after reconciliation and never edited.
795
+
796
+ The guest requires JSON-capable `iproute2`, `modprobe` when modules are not already
797
+ loaded, and kernel `dummy`/`8021q` support. Empeira loads an absent dummy module with
798
+ `numdummies=0` to avoid creating an unowned default `dummy0`. An already existing
799
+ device, including a kernel-created `dummy0`, is a collision and is never adopted.
800
+ Failures preserve the VM and report native guest diagnostics.
801
+
802
+ See [VM interface lifecycle](nodes.md#additional-vm-interface-lifecycle) for updates,
803
+ ownership, recovery and the regular Facter structure.
804
+
805
+ ## Transparent TCP redirects
806
+
807
+ Use `network.redirects` to exercise production Puppet/Hiera endpoints against a
808
+ local service while preserving the original IP and port. For example, a PowerDNS
809
+ API compatibility service can receive these two endpoints:
810
+
811
+ ```yaml
812
+ network:
813
+ redirects:
814
+ - from:
815
+ ip: 192.0.2.8
816
+ port: 8080
817
+ to:
818
+ service: api-compat
819
+ port: 8081
820
+ - from:
821
+ ip: 192.0.2.9
822
+ port: 8080
823
+ to:
824
+ service: api-compat
825
+ port: 8081
826
+ ```
827
+
828
+ Define `api-compat` under `containers.additional` using your test service image.
829
+ The example IPs are documentation addresses; substitute the endpoints used by your
830
+ Puppet code. `from` accepts `ip`, `port` and optional `protocol` (`tcp` by default).
831
+ `to` accepts only `service` and `port`. Both ports are integers from 1 through 65535.
832
+ Sources must be exact
833
+ routable unicast IPv4 addresses outside the allocated workspace subnet. CIDRs,
834
+ wildcards, duplicate IP/protocol/port tuples, IPv6 and external target addresses are
835
+ rejected. Omitting `protocol` and specifying `tcp` describe the same source.
836
+ Service names are lowercase internal names, without `.empeira.internal`.
837
+ Enabled control-plane application services and additional services are supported;
838
+ nodes, the gateway, the browser UI relay and temporary bootstrap helpers are excluded.
839
+ `coredns` is an alias for the built-in `dns` service and is reserved against additional
840
+ service names.
841
+
842
+ Run `empeira up` after edits. It resolves owned service addresses, replaces gateway
843
+ policy and preserves existing nodes and the workspace network. Unknown/disabled
844
+ targets fail validation. Temporarily absent targets stay blocked until the owned
845
+ service is reconciled; unreachable target ports fail without contacting the external
846
+ endpoint, even when that endpoint also has an explicit egress grant.
847
+
848
+ Direct connections work from container/VM nodes, the Puppet server (including
849
+ catalog compilation) and internal services. HTTP methods, paths, headers and bodies
850
+ are unchanged. No DNS rewrite, egress grant, proxy exception or TLS termination is
851
+ created. Explicit proxy users connect to the proxy first and remain subject to its
852
+ allowlist; use the application's direct-connection settings when testing redirects,
853
+ for example `curl --noproxy '*' http://192.0.2.8:8080/test`.
854
+ See [redirect routing](networking.md#transparent-tcp-redirects) for connection
855
+ revocation and runtime boundaries.
856
+
857
+ ### Virtual DNS endpoint
858
+
859
+ UDP is supported specifically for DNS: source and target port 53, targeting the
860
+ built-in `coredns` (or `dns`) service. Configure both transports for large responses
861
+ and TCP fallback:
862
+
863
+ ```yaml
864
+ network:
865
+ redirects:
866
+ - from: {ip: 198.18.0.53, port: 53, protocol: udp}
867
+ to: {service: coredns, port: 53}
868
+ - from: {ip: 198.18.0.53, port: 53, protocol: tcp}
869
+ to: {service: coredns, port: 53}
870
+ ```
871
+
872
+ Use `198.18.0.53` in ordinary Puppet/Hiera DNS test data. It is a virtual destination,
873
+ never assigned to an interface. CoreDNS keeps its dynamic workspace address and
874
+ forwards external names to the configured `dns.upstream`; internal names remain
875
+ authoritative. Redirects do not edit `/etc/resolv.conf`, create another DNS service
876
+ or grant external UDP access. NXDOMAIN and TCP fallback retain normal DNS semantics.
877
+ The benchmark range `198.18.0.0/15` is accepted only for validated redirect sources,
878
+ never direct-egress grants. Before mutation, `up` rejects source collisions with
879
+ host/VPN routes, engine networks and the workspace subnet. A default route alone
880
+ is not a collision. Legacy TCP source destinations retain their existing semantics.
881
+
882
+ ## Control-plane images and provenance
883
+
884
+ [Built-in YAML defaults](../config/defaults.yaml) are the sole authoritative source
885
+ for default image repositories, software tags, immutable digests, agent versions
886
+ and release-package checksums. The server, database and PostgreSQL defaults use OCI
887
+ index digests covering both `linux/amd64` and `linux/arm64`. The runtime executes
888
+ `repository@sha256:...`; the accompanying tag describes the selected software.
889
+ Project configuration may independently replace each image. For example:
890
+
891
+ ```yaml
892
+ images:
893
+ server:
894
+ reference: registry.example.org/custom/server:1
895
+ puppetdb:
896
+ repository: registry.example.org/custom/puppetdb
897
+ tag: "1"
898
+ ```
899
+
900
+ Server, PuppetDB and PostgreSQL images are independent. Image descriptors accept
901
+ `repository` plus `tag`, an optional `digest: sha256:...`, or a complete
902
+ `reference`. An explicit repository or tag clears an inherited default digest
903
+ unless the override also supplies a digest. A full reference replaces inherited
904
+ descriptor fields; combining it with repository, tag, digest or build in the same
905
+ configuration source is rejected. An explicitly supplied digest determines the
906
+ revision even if a descriptive tag is also supplied. Custom images need not be
907
+ digest-pinned. Validation is local and does not contact registries.
908
+ Compatibility of project-selected software is the project's responsibility.
909
+
910
+ OCI digests identify exact manifest/index bytes; descriptive tags identify software
911
+ releases. Default pin changes require verification of official registry indexes and
912
+ both platform manifests. Digest integrity does not establish publisher signatures.
913
+ Empeira does not promise verified signatures or attestations for its selected images.
914
+
915
+ The proxy is built locally from [resources/proxy/Containerfile](../resources/proxy/Containerfile).
916
+ Its reviewed base, Squid package version and Ubuntu snapshot are recorded in that recipe.
917
+ A signed current-archive package install bootstraps public CA trust, so the entire
918
+ build dependency closure is not byte-for-byte pinned. Recipe contents determine the
919
+ local image tag and verification label. Builds send only the generated Containerfile,
920
+ never control code or credentials. Changed recipes create new local image identities.
921
+ Existing tagged images are reused without automatic refresh. Package/image acquisition
922
+ uses engine networking during the build; running nodes remain isolated.
923
+
924
+ | Component | Selected version | Authoritative source / license |
925
+ | --- | --- | --- |
926
+ | OpenVox server | Default software tag and index digest | [OpenVoxProject](https://github.com/OpenVoxProject/container-openvoxserver), AGPL-3.0 container recipes |
927
+ | OpenVoxDB | Default software tag and index digest | [OpenVoxProject](https://github.com/OpenVoxProject/container-openvoxdb), AGPL-3.0 container recipes |
928
+ | PostgreSQL | Independent default software tag and index digest | [Docker Official Image](https://github.com/docker-library/postgres), MIT recipes and [PostgreSQL license](https://www.postgresql.org/about/licence/) |
929
+ | CoreDNS | Default software tag | [CoreDNS](https://github.com/coredns/coredns), Apache-2.0 |
930
+ | Squid | Reviewed package and snapshot in the local recipe | [Ubuntu snapshot service](https://snapshot.ubuntu.com/), Squid GPL-2.0-or-later and distribution package licenses |
931
+
932
+ Third-party image
933
+ contents retain their own licenses and notices. Empeira references and pulls these
934
+ public images and locally builds the proxy, but does not bundle or redistribute image contents. No Puppet Enterprise
935
+ artifact is used. Selecting a version is not a claim that an image has
936
+ no vulnerabilities; image refreshes need renewed upstream/security review.
937
+ Review [CoreDNS advisories](https://github.com/coredns/coredns/security/advisories)
938
+ and distribution security updates when selecting new artifacts. Locally built
939
+ helpers preserve rootless-compatible UID mappings and upstream notices.
940
+
941
+ The default control plane has six containers, or seven with the proxy, plus the
942
+ runtime's peer-network adapter. Requested limits
943
+ are 1536 MiB for the server (768 MiB JVM heap), 768 MiB for PuppetDB (256 MiB heap),
944
+ 256 MiB for PostgreSQL, 64 MiB for DNS, 96 MiB for the database relay, 64 MiB
945
+ for the gateway and 128 MiB for Squid. The control-plane memory ceiling is about
946
+ 2.7 GiB without the proxy, excluding the peer adapter and nodes. The server gets two CPUs and each
947
+ other service one. Actual idle use is lower and varies by provider and platform.
948
+ `up` automatically pulls missing remote images and builds missing recipe images.
949
+ Existing images are reused without any remote freshness check. Use `update images`
950
+ to explicitly check remote metadata and retrieve changed artifacts. A pinned image
951
+ is checked and acquired by its exact digest; updates never follow its descriptive
952
+ tag to a different build. Unchanged pins cause no pull. Changing an image reference
953
+ or digest changes its managed component fingerprint for selective reconciliation
954
+ on the next `up`. PostgreSQL version changes remain explicit; no database migration
955
+ is performed. No service is exposed on a host
956
+ port by default.
957
+
958
+ ## Default image registry
959
+
960
+ ```yaml
961
+ images:
962
+ registry: null # explicit docker.io for hostless repositories
963
+ ```
964
+
965
+ To use a Docker Hub cache, set `images.registry: registry.example.net`
966
+ (or `registry.example.net:5000`). This optional value is a registry host,
967
+ with no scheme, path or credentials. Use an unambiguous host containing a dot,
968
+ `localhost`, or a host with a port. Repository paths remain unchanged:
969
+
970
+ | Repository | With `images.registry: registry.example.net` |
971
+ | --- | --- |
972
+ | `library/postgres` | `registry.example.net/library/postgres` |
973
+ | `linuxserver/chromium` | `registry.example.net/linuxserver/chromium` |
974
+ | `registry.example.org/team/server` | `registry.example.org/team/server` |
975
+ | `docker.io/library/postgres` | `docker.io/library/postgres` |
976
+
977
+ Without an override, hostless repositories explicitly resolve through `docker.io`;
978
+ Empeira never relies on runtime short-name search rules. A first path segment
979
+ containing `.` or `:`, or equal to `localhost`, is an explicit registry and stays
980
+ unchanged. Exact `reference` overrides also stay unchanged, including digest pins.
981
+ Tags and digest fields still work normally.
982
+
983
+ Docker Hub defaults for PostgreSQL, CoreDNS, Ubuntu, RockyLinux, AlmaLinux,
984
+ OracleLinux, the Go builder and Chromium are hostless. Explicit OpenVox, OpenVoxDB,
985
+ OpenVox View GHCR repositories remain direct. Reviewed Ruby/Ubuntu
986
+ build bases use the same resolver; resolved bases enter the recipe hash, so a
987
+ registry change gives those local images a new identity. Build dependencies are
988
+ explicit reserved `EMPEIRA_BASE_*` arguments, not arbitrary Dockerfile rewriting.
989
+
990
+ Docker/Podman supply their normal login, credential-helper and auth-file behavior
991
+ for metadata, pulls and builds. No registry credentials are stored in Empeira YAML
992
+ or state. A cache must expose the same repository paths and configured versions.
993
+ Registry authentication belongs to the selected container runtime, not to Empeira
994
+ configuration. Authenticate directly with the runtime you selected:
995
+
996
+ ```bash
997
+ docker login registry.example.net
998
+ # Or, when using Podman:
999
+ podman login registry.example.net
1000
+ ```
1001
+
1002
+ Use `docker login docker.io` or `podman login docker.io` for Docker Hub, and the
1003
+ actual explicit host for GHCR or another registry. Native credential helpers and
1004
+ auth files remain owned by Docker/Podman. Empeira does not prompt for passwords,
1005
+ copy auth files, persist tokens, or provide a `registry login` command. Never put
1006
+ registry usernames, passwords, tokens, auth-file paths or credential structures in
1007
+ either Empeira YAML file. Probable authentication/authorization failures include
1008
+ the appropriate native login command; missing tags, network and TLS errors have
1009
+ separate diagnostics and do not receive a blanket login suggestion.
1010
+
1011
+ ## Browser and additional infrastructure services
1012
+
1013
+ ```yaml
1014
+ browser:
1015
+ start_url: "about:blank"
1016
+ image:
1017
+ repository: linuxserver/chromium
1018
+ tag: "latest"
1019
+ containers:
1020
+ additional:
1021
+ - name: custom-service
1022
+ image:
1023
+ repository: registry.example.org/team/web-helper
1024
+ tag: "1.2.3"
1025
+ environment:
1026
+ KEY: "value"
1027
+ command: ["serve", "--port", "8080"]
1028
+ ```
1029
+
1030
+ The helper image above is a schema illustration; substitute your actual image/tag.
1031
+ Both browser image fields may be overridden independently, including internal/private
1032
+ registries. Authenticate using the selected runtime's normal registry tooling.
1033
+ Browser-compatible images must provide the same HTTPS desktop UI on container port
1034
+ 3001, support `/config` as a disposable profile and operate without direct Internet.
1035
+ No custom browser image is built by Empeira. Run `empeira up` before
1036
+ `empeira browser`. The browser command validates the existing workspace and CoreDNS,
1037
+ starts or reconciles only the disposable browser and its UI relay, and prints
1038
+ `https://127.0.0.1:<dynamic-port>/`. It never starts or reconciles the configuration
1039
+ server, PuppetDB, CoreDNS, gateways, additional services, or nodes. A missing or
1040
+ stopped required workspace service produces an error that asks you to run
1041
+ `empeira up`. The platform currently has no host-browser opener. The UI may take a
1042
+ few seconds to initialize. Its default certificate is self-signed: accept it only
1043
+ for the printed local endpoint. Browse `http://custom-service.empeira.internal:8080`
1044
+ or node hostnames inside Chromium. No startup URL CLI argument or browser Internet
1045
+ access is currently provided.
1046
+
1047
+ `browser.start_url` selects only the page initially opened in Chromium; it defaults
1048
+ to `about:blank`. Supply a nonempty string without control characters. Empeira passes
1049
+ the value unchanged as one `CHROME_CLI` environment value, without shell evaluation
1050
+ or a network/HTTP readiness check. The destination must be reachable from the
1051
+ isolated browser network. If the application is not ready when Chromium starts,
1052
+ reload the page after it becomes available. Changing the value recreates an existing
1053
+ browser on the next `empeira browser` or `up` reconciliation, using the new start page
1054
+ and a fresh disposable profile.
1055
+
1056
+ Additional services automatically join `up`, `status`, `down`, and `destroy`.
1057
+ Each has ownership labels, an inventory ID, a component fingerprint and the DNS name
1058
+ `<name>.empeira.internal` (plus short name). Configuration changes recreate only the
1059
+ affected service; removing a definition removes its owned container on the next `up`.
1060
+ `down`/`destroy` also remove services no longer present in configuration. Writable
1061
+ container layers are disposable; no helper data persistence is promised.
1062
+
1063
+ The only fields are `name`, `image.repository`, `image.tag`, optional `environment`
1064
+ (string values), optional `command` (nonempty literal argument array), and optional
1065
+ `configuration` (a generated YAML file, described below). Names are
1066
+ unique lowercase DNS labels, up to 63 characters, and cannot collide with built-in
1067
+ services, gateway identities or nodes. There are no host ports, volumes, privileged
1068
+ options, devices, capabilities, arbitrary runtime arguments, dependency graphs,
1069
+ restart policies or healthchecks. Status reports container state, not application
1070
+ readiness. This is deliberately **not a Compose replacement**. Environment changes
1071
+ are fingerprinted; inventory stores their hash rather than plaintext values. Runtime
1072
+ administrators can still inspect container environments. Keep real secrets out of Git.
1073
+
1074
+ ### Generated service configuration
1075
+
1076
+ An additional service can receive a configuration file directly from `.empeira.yaml`:
1077
+
1078
+ ```yaml
1079
+ containers:
1080
+ additional:
1081
+ - name: example-api
1082
+ image:
1083
+ repository: example/api
1084
+ tag: "1.0"
1085
+ configuration:
1086
+ target: /etc/example/config.yaml
1087
+ content:
1088
+ version: 1
1089
+ listen:
1090
+ port: 8080
1091
+ ```
1092
+
1093
+ This synthetic image is a schema example. Empeira serializes the parsed `content`
1094
+ as YAML, generates a separate file for each service in the managed workspace, and
1095
+ bind-mounts it **read-only** at `target`. No separate file in the control repository
1096
+ is required. Maps, arrays, strings, integers, floats, booleans and `null` are supported,
1097
+ including nested structures. Strings remain literal strings: Empeira adds no template,
1098
+ environment-variable or other interpolation. YAML tags, anchors, aliases and Ruby
1099
+ objects are rejected.
1100
+
1101
+ `target` must be an absolute file path, with nonempty path components containing only
1102
+ letters, digits, `_`, `-` and `.`; `/`, trailing slashes, `.`/`..` components and unsafe
1103
+ mount syntax are rejected. It must not overlap another managed mount, including the
1104
+ runtime's `/etc/hosts`, `/etc/hostname` and `/etc/resolv.conf` files.
1105
+
1106
+ Changing `content` or `target` recreates the affected service on the next `up`.
1107
+ Repeated `up` with unchanged configuration preserves the container and generated file.
1108
+ `down` retains generated files; `destroy` removes them. Removing `configuration` or
1109
+ the service removes its unused file after successful reconciliation. Host files are
1110
+ readable by the container's service user but kept in a private workspace directory.
1111
+ This is ordinary configuration storage, with no special handling for secrets; users
1112
+ remain responsible for sensitive content. Omitting `configuration` preserves the
1113
+ existing additional-service behavior.
1114
+
1115
+ ### OpenVox View example
1116
+
1117
+ OpenVox View is the preferred example web viewer for OpenVoxDB/PuppetDB. It is an
1118
+ ordinary `containers.additional` service, with no special Empeira orchestration.
1119
+ Use this configuration to connect it to the internal HTTP API:
1120
+
1121
+ ```yaml
1122
+ browser:
1123
+ start_url: "http://openvoxview.empeira.internal:5000"
1124
+
1125
+ containers:
1126
+ additional:
1127
+ - name: openvoxview
1128
+ image:
1129
+ repository: ghcr.io/voxpupuli/openvoxview
1130
+ tag: "v1.8.0"
1131
+ environment:
1132
+ LISTEN: "0.0.0.0"
1133
+ PORT: "5000"
1134
+ PUPPETDB_HOST: "puppetdb.empeira.internal"
1135
+ PUPPETDB_PORT: "8080"
1136
+ PUPPETDB_TLS: "false"
1137
+ ```
1138
+
1139
+ The official image starts `/openvoxview` itself; no `command` is needed.
1140
+ `LISTEN=0.0.0.0` is required because the application defaults to `localhost`.
1141
+ Run `empeira up`, then `empeira browser`. Inside Chromium,
1142
+ open `http://openvoxview.empeira.internal:5000`. No host port is published.
1143
+
1144
+ OpenVox View uses Empeira's internal PuppetDB HTTP relay at
1145
+ `puppetdb.empeira.internal:8080`, with `PUPPETDB_TLS=false` and no TLS client
1146
+ certificates. This API is intentionally unauthenticated inside the isolated network;
1147
+ every node or service with access can use it. This is a disposable local test
1148
+ configuration, not production security.
1149
+
1150
+ The optional Puppet CA interface is not enabled. It requires client key/certificate
1151
+ material with `pp_cli_auth` and a CA certificate. The generic additional-service
1152
+ schema intentionally offers no mounts for these files; the PuppetDB viewer needs
1153
+ none of them. Leave `PUPPETCA_HOST` unset.
1154
+ See the [upstream project](https://github.com/voxpupuli/openvoxview) and
1155
+ [configuration reference](https://github.com/voxpupuli/openvoxview/blob/main/CONFIGURATION.md).
1156
+
1157
+ ### Browser and example image provenance
1158
+
1159
+ - [LinuxServer Chromium](https://docs.linuxserver.io/images/docker-chromium/) is
1160
+ referenced as `linuxserver/chromium:latest`, resolved to Docker Hub by default.
1161
+ [Upstream packaging](https://github.com/linuxserver/docker-chromium) is GPL-3.0;
1162
+ Chromium is BSD-style with separately licensed components. Debian, Selkies and
1163
+ other bundled components retain their notices. Empeira does not redistribute
1164
+ image contents. The mutable `latest` tag is deliberate: `up` reuses the local
1165
+ image, while `update images` checks the selected platform manifest for changes.
1166
+ - The upstream UI is **Selkies HTTPS on 3001**, with HTTP 3000 intended for reverse
1167
+ proxy use. Empeira publishes only HTTPS through a fixed TLS byte relay using the
1168
+ dedicated recipe-hashed Empeira relay image, with no custom browser image. Shared Docker/Podman
1169
+ adapters supply the same arguments; no privileged or unconfined mode is added.
1170
+ See [networking](networking.md) and [development](development.md) for smoke commands
1171
+ and actual platform validation scope.
1172
+ - [Vox Pupuli OpenVox View](https://github.com/voxpupuli/openvoxview) uses
1173
+ [Apache-2.0](https://github.com/voxpupuli/openvoxview/blob/main/LICENSE), compatible
1174
+ with Empeira's AGPL-3.0-only license. The example references the official
1175
+ `ghcr.io/voxpupuli/openvoxview:v1.8.0` release image. The explicit GHCR repository is unaffected by `images.registry`.
1176
+ A release tag is not a digest lock. The upstream
1177
+ [container entrypoint](https://github.com/voxpupuli/openvoxview/blob/main/Containerfile.release)
1178
+ starts `/openvoxview` on port 5000. No upstream source, binary or image is bundled
1179
+ or redistributed by Empeira.
1180
+
1181
+ ### Dedicated relay utility image
1182
+
1183
+ `images.relay.build: relay/Containerfile` selects the packaged recipe. The local
1184
+ image is `localhost/empeira-relay:<SHA256-of-recipe>` and is verified by its recipe
1185
+ label. Builds use an isolated temporary context containing only the recipe, never
1186
+ control code or credentials. `images.relay.reference` may select an independently
1187
+ prepared equivalent Ruby image. Browser relay scripts remain reviewed read-only
1188
+ packaged mounts and their contents participate in service fingerprints.
1189
+
1190
+ The recipe uses the [Docker Official Ruby image](https://github.com/docker-library/ruby)
1191
+ (MIT recipes), [Ruby](https://www.ruby-lang.org/en/about/license/) (Ruby/BSD terms)
1192
+ and Alpine packages with their own notices. It adds no packages and runs as UID/GID
1193
+ 65534. It is built locally, not published or included in the gem as a binary.
1194
+ A version tag is not an immutable digest; arm64 execution needs separate validation.
1195
+
1196
+ ## Shared peer network internals
1197
+
1198
+ The selected container runtime and CLI host choose the peer backend automatically.
1199
+ There are no user settings for subnet, VM IP/MAC, TAP, bridge, socket or adapter.
1200
+ Application configuration uses logical names; additional services need no `ports`
1201
+ or `expose` schema. `images.network_adapter_builder` selects the reviewed Go builder
1202
+ image used by the existing recipe-hash image cache; Go is not a host prerequisite.
1203
+ Node IP/MAC leases survive stop/start and are released on destroy. See
1204
+ [networking](networking.md) for address pools, isolation and platform validation.