empeira 0.1.0.pre.alpha.1 → 0.1.0.pre.alpha.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de7c894839d24da7b03726dc8daae007f1c3e97ecbca32330ae451b1265409c0
4
- data.tar.gz: '008e104e79374009b3811f365df8e301cc9a78157af5f5b6d841d32c2367b54d'
3
+ metadata.gz: 4e4e7094658b9e3d8f4ee0edbafa3e4e45be28921900cce8eb4af7d66096cee6
4
+ data.tar.gz: 7f062dc35a9cef14ad52f55362beba6c4a5a0ab1b57225ce27a191123dc01fc9
5
5
  SHA512:
6
- metadata.gz: 8d6a4db00084f8e013f208e655313edbaf032b320507840220a416e8eb98644ad2d6d9a74d5db21dbc9298e2d7aa5243d78d805e0da1c0dfc043501e2dfb8630
7
- data.tar.gz: d452d8412d487fc9800fcc549b8be52f34068093f71fdca0b70778dadf941ce0c674cd87974dbe2b00bd0aea869d04e855aef8d03eecb3a0e79d624394f311a7
6
+ metadata.gz: 8410139aeffa1ccaab90f3ddac57a809a86420e385f1354f801f8181c936ecf5653762cc4dbade0178d6df99b2dad1cc8b68cb8d537013132ec09e2ed65aefa1
7
+ data.tar.gz: 52ba2b37546bc90263d9834193b5080440e2a66425c9f9403b19800e791bf5f26b895d4c4e1af4bc218951b3a0e433437c07c550f493f4577e4e8dfd156a06bb
data/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
  [![Build & Tests](https://img.shields.io/github/actions/workflow/status/c8m6/empeira/ci.yml?branch=main&label=Build%20%26%20Tests)](https://github.com/c8m6/empeira/actions/workflows/ci.yml)
6
6
  [![OpenVox E2E](https://img.shields.io/github/actions/workflow/status/c8m6/empeira/openvox.yml?branch=main&label=OpenVox%20E2E)](https://github.com/c8m6/empeira/actions/workflows/openvox.yml)
7
7
  [![Release](https://img.shields.io/github/v/release/c8m6/empeira?include_prereleases&sort=date&label=Release)](https://github.com/c8m6/empeira/releases)
8
- [![Ruby: 3.4 / 4.0](https://img.shields.io/badge/Ruby-3.4%20%7C%204.0-CC342D)](docs/installation.md)
9
- [![License: AGPL-3.0-only](https://img.shields.io/badge/License-AGPL--3.0--only-blue)](LICENSE)
8
+ [![Ruby: 3.1–4.0](https://img.shields.io/badge/Ruby-3.1%20%7C%203.2%20%7C%203.3%20%7C%203.4%20%7C%204.0-CC342D)](docs/installation.md)
9
+ [![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue)](LICENSE)
10
10
 
11
11
  **ἐμπειρία (empeiría)** — experience gained through practice. The name reflects
12
12
  learning about infrastructure by experimenting, testing and seeing what happens.
@@ -46,35 +46,37 @@ signed repositories or verified package artifacts are selected separately.
46
46
  - An internal Chromium desktop and small additional infrastructure services.
47
47
  - Explicit Puppetfile module and image updates.
48
48
 
49
- Hosts: Linux, macOS and Windows through WSL2/Linux. Ruby 3.4 is the minimum;
50
- Ruby 3.4 and 4.0 are supported; CI runs deterministic tests with both versions.
49
+ Hosts: Linux, macOS and Windows through WSL2/Linux. Ruby 3.1 is the minimum; Ruby 3.1, 3.2, 3.3, 3.4 and 4.0 are supported;
50
+ CI runs deterministic tests and official addon/Core acceptance on all five versions
51
+ on Linux and macOS, plus the existing Linux Docker/OpenVox smoke on every Ruby.
51
52
  Podman with Netavark is the default; Docker requires a Linux engine 28+ with
52
53
  isolated bridge support. VMs require usable KVM or HVF. Platform setup and
53
54
  validation limits are in [installation](docs/installation.md).
54
55
 
55
56
  ## Installation
56
57
 
57
- Until a release is available, install from source with Ruby, Bundler and Git:
58
+ The [Empeira gem on RubyGems.org](https://rubygems.org/gems/empeira) is the
59
+ recommended installation. With Ruby 3.1, 3.2, 3.3, 3.4 or 4.0 already installed:
58
60
 
59
61
  ```bash
60
- git clone https://github.com/c8m6/empeira.git
61
- cd empeira
62
- bundle install
63
- export EMPEIRA_SOURCE="$PWD"
64
- empeira() { BUNDLE_GEMFILE="$EMPEIRA_SOURCE/Gemfile" bundle exec ruby "$EMPEIRA_SOURCE/bin/empeira" "$@"; }
62
+ gem install --user-install empeira --pre
63
+ export PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
65
64
  empeira --version
66
65
  ```
67
66
 
68
- The function lets you invoke the source CLI from your control repository. See
69
- [installation](docs/installation.md) for runtime prerequisites, release-gem
70
- installation and Bash completion.
67
+ `--pre` enables prerelease installation, including the first alpha
68
+ [`v0.1.0-alpha.1`](https://github.com/c8m6/empeira/releases/tag/v0.1.0-alpha.1).
69
+ No source checkout or Bundler is needed for a gem installation. Install Git,
70
+ a supported container runtime and any optional VM tools separately; see
71
+ [installation](docs/installation.md) for host prerequisites, exact-version
72
+ installation and Bash completion. Developers can also
73
+ [run from source](docs/installation.md#source-installation-and-project-opt-in).
71
74
 
72
75
  ## Quickstart
73
76
 
74
77
  Use a dedicated Git control repository and an empty configuration marker:
75
78
 
76
79
  ```bash
77
- cd ..
78
80
  mkdir control-repo
79
81
  cd control-repo
80
82
  git init
@@ -126,8 +128,10 @@ See [diagnostic access and recovery](docs/nodes.md#root-diagnostic-commands-and-
126
128
 
127
129
  ## Alpha status
128
130
 
129
- empeira is being prepared for its first public alpha. Interfaces and inventory
130
- schemas may change; unsupported inventory fails closed without automatic migration.
131
+ The first public alpha, [`v0.1.0-alpha.1`](https://github.com/c8m6/empeira/releases/tag/v0.1.0-alpha.1),
132
+ is available on [RubyGems.org](https://rubygems.org/gems/empeira). Interfaces
133
+ and inventory schemas may change; unsupported inventory fails closed without
134
+ automatic migration.
131
135
  Use synthetic control code and disposable credentials. The shared network is a
132
136
  cooperative lab, and its internal PuppetDB API is unauthenticated.
133
137
 
@@ -135,7 +139,7 @@ Container process mode does not provide a full booted OS. Systemd requires Podma
135
139
  and cgroup v2. VM and shared-network support require actual host capabilities;
136
140
  macOS/HVF and arm64 runtime validation are manual gates. Native Windows, automatic
137
141
  self-update, aggregate `update all` and standalone platform binaries are unavailable.
138
- The initial release distribution is a Ruby gem.
142
+ Distribution is through RubyGems.org and the matching GitHub Release gem asset.
139
143
 
140
144
  ## Community and license
141
145
 
@@ -145,6 +149,6 @@ and [pull requests](https://github.com/c8m6/empeira/pulls) are welcome. Follow t
145
149
  [security policy](SECURITY.md).
146
150
 
147
151
  Project-owned code and documentation are licensed under
148
- [AGPL-3.0-only](LICENSE). Third-party packages and images retain their own licenses
152
+ [AGPL-3.0-or-later](LICENSE). Third-party packages and images retain their own licenses
149
153
  and notices and are obtained from upstream; empeira does not bundle them or
150
154
  redistribute proprietary Puppet Enterprise software.
data/docs/addons.md CHANGED
@@ -329,3 +329,15 @@ is incomplete, stop users of that source and remove only its affected source
329
329
  namespace before synchronizing again; never delete a live lock as recovery.
330
330
  For missing command completion, inspect `addons info NAME` and source the current
331
331
  Bash completion again after an Empeira upgrade.
332
+
333
+ ## Ruby support and addon development
334
+
335
+ Core and the official Convert, Pre-Commit and Sync addons support Ruby 3.1, 3.2,
336
+ 3.3, 3.4 and 4.0 on Linux, macOS and WSL2. Ruby 3.1 is the minimum. Keep API 1
337
+ entrypoints runnable directly, preserve argv, terminal streams, environment and
338
+ exit status, and explicitly require libraries that newer Rubies no longer ship.
339
+ Do not install gems implicitly. Convert's XML support needs user-installed REXML.
340
+ Test complete standalone behavior and actual execution through Core on all five
341
+ Rubies; syntax checks alone are insufficient. See the [development gate](development.md#ruby-compatibility)
342
+ for the required Linux/macOS matrix, pinned source checkouts and local commands.
343
+ Real container/VM and WSL2 capabilities require their own integration gates.
data/docs/architecture.md CHANGED
@@ -184,9 +184,12 @@ there are no implicit database-major migrations or automatic node replacements.
184
184
 
185
185
  Managed servers disable environment caching through native Puppet configuration
186
186
  (`environment_timeout = 0` in `[server]`), applied by the startup wrapper before
187
- the configured image entrypoint. `up` checks the actual native setting and restarts
188
- only a server whose setting changed; it preserves every other configuration key
189
- and retained storage. The configured Puppet confdir must agree with Puppetserver
187
+ the configured image entrypoint. An independent `environment.conf` copy in the
188
+ managed environment enforces zero even when the control repository specifies
189
+ `unlimited`; the original remains unchanged and read-only. `up` refreshes the copy,
190
+ checks both native and selected-environment settings, and restarts only a server
191
+ whose native setting changed. It preserves every other configuration key and
192
+ retained storage. The configured Puppet confdir must agree with Puppetserver
190
193
  HOCON `master-conf-dir`. Catalog
191
194
  requests reload live code without repository hashes or environment-cache checkpoints.
192
195
  See [nodes](nodes.md#environment-cache-and-live-code).
@@ -404,20 +404,25 @@ runtime values without inferring them from the image name. Missing or unsafe
404
404
  runtime fields fail configuration validation. Managed servers use
405
405
  `environment_timeout = 0` in the native `[server]` section. The startup wrapper
406
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
407
+ environment-variable mappings. Empeira also prepares an independent copy of the
408
+ project's `environment.conf` in its managed environment directory, replacing or
409
+ adding only `environment_timeout = 0`. Other settings, including `modulepath` and
410
+ `config_version`, are preserved. The original file and the control repository
411
+ remain read-only in the server. The copy is refreshed during `up`, including when
412
+ the project has no `environment.conf` or Hiera mounts.
413
+
414
+ `up` verifies both the native server setting and the effective timeout for
415
+ `server.environment`, against the confdir containing
416
+ `server.runtime.paths.puppetdb_config`; custom Puppetserver
410
417
  `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
418
+ HOCON master directories take precedence over Puppet CLI defaults. Native setting
419
+ drift updates only that key and restarts the same owned server; an
413
420
  entrypoint that overwrites it fails verification. CA, SSL and database storage
414
421
  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.
422
+ an Empeira content scan or admin API invalidation. A project timeout of `unlimited`
423
+ therefore stays unchanged in the checkout while the managed server uses zero.
424
+ To verify it, run `puppet config print environment_timeout --section server
425
+ --environment ENVIRONMENT` inside the server with the configured environment name.
421
426
 
422
427
  `puppetdb.runtime` holds the independent OpenVoxDB process contract. Custom
423
428
  images may override its executable, numeric user, Java and main arguments,
data/docs/development.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  ## Environment and quality checks
4
4
 
5
- Install Git, Ruby 3.4, Bundler and the platform prerequisites in
6
- [installation](installation.md). The gem requires Ruby 3.4+; Ruby 3.2 and 3.3 are
7
- unsupported. Normal Ubuntu CI runs deterministic RSpec on Ruby 3.4 and 4.0, with
8
- RuboCop and both dependency audits only on Ruby 3.4. Release builds use Ruby 3.4.
5
+ Install Git, Ruby 3.1+, Bundler and the platform prerequisites in
6
+ [installation](installation.md). The gem requires Ruby 3.1+; normal Ubuntu and macOS CI
7
+ runs deterministic RSpec on Ruby 3.1, 3.2, 3.3, 3.4 and 4.0, with
8
+ RuboCop and both dependency audits only on Ubuntu with Ruby 3.4. Release builds use Ruby 3.4.
9
9
  Use synthetic control repositories and disposable credentials.
10
10
 
11
11
  ```console
@@ -20,7 +20,7 @@ bundle exec bundler-audit check --gemfile-lock resources/modules/Gemfile.lock
20
20
  local child processes and loopback fixtures. Explicit integration gates are pending
21
21
  unless enabled; a pending test is not a successful runtime check. Security auditing
22
22
  covers known Ruby advisories in both resolutions, not all container OS packages.
23
- To reproduce either CI test job, activate the corresponding Ruby, install the locked
23
+ To reproduce a CI test job, activate the corresponding Ruby, install the locked
24
24
  bundle and run `bundle exec rspec --exclude-pattern 'spec/integration/**/*_spec.rb'`.
25
25
  Runtime and VM compatibility remain separate manual gates.
26
26
 
@@ -93,18 +93,73 @@ For workflow changes, parse `.github/workflows/*.yml` and run `actionlint` when
93
93
  available. Review action pins, triggers and permissions as well as YAML syntax.
94
94
  See [contributing](../CONTRIBUTING.md) for review, commit and label conventions.
95
95
 
96
- ### Ruby 4.0 compatibility
96
+ ### Ruby compatibility
97
97
 
98
- Ruby 3.4 and 4.0 are supported. The deterministic suite passed locally on Linux
99
- x86_64 with Ruby 3.4.7 and 4.0.7. Ruby 4.0 validation used an official Ruby container;
100
- it does not establish macOS, container-engine or accelerated VM integration results.
98
+ Ruby 3.1 is the minimum. Ruby 3.1, 3.2, 3.3, 3.4 and 4.0 run the complete
99
+ deterministic suite on Ubuntu 24.04 and native macOS 15 in regular CI. Every
100
+ combination is required, including Ruby 4.0. The suite covers the CLI, Bash
101
+ completion, providers, networking, bootstrap, updates, addon contracts and gem
102
+ build/install/start. The existing Docker/OpenVox node smoke also runs on all five
103
+ Rubies on Ubuntu, with at most two concurrent jobs. Broader manual runtime
104
+ integrations and release builds remain on Ruby 3.4;
105
+ a unit test does not establish Docker, rootless Podman, KVM/HVF or WSL2 results.
101
106
 
102
- Source development uses Bundler 4.0.22 from `Gemfile.lock`, matching the existing
103
- module-helper tooling. With Ruby 4.0.7, the previous Bundler 2.4.19 redefined
104
- `Gem::Platform` constants and emitted warnings in child processes. This failed 14
105
- strict stderr, streaming, Bash completion and Puppetfile installer tests. Updating
106
- Bundler preserves those output assertions; no application output filtering or
107
- warning suppression is used. The dependency gem versions are unchanged.
107
+ Both committed lockfiles use Bundler 2.6.9, compatible with Ruby 3.1. Ruby 4.0
108
+ requires the explicit Bundler 4.0.22 selection to avoid RubyGems platform-constant
109
+ warnings. Frozen installation keeps the same dependencies and lockfiles on every
110
+ Ruby. To reproduce Ruby 4 CI without editing either lockfile:
111
+
112
+ ```sh
113
+ gem install bundler -v 4.0.22 --no-document
114
+ export BUNDLER_VERSION=4.0.22 BUNDLE_FROZEN=true
115
+ bundle install
116
+ bundle exec rspec --exclude-pattern 'spec/integration/**/*_spec.rb'
117
+ ```
118
+
119
+ For Ruby 3.x, use `BUNDLER_VERSION=2.6.9` instead. The module-helper image uses
120
+ Bundler 2.6.9 on its separately pinned Ruby base. r10k's transitive dependencies
121
+ `fast_gettext` and `multi_json` remain on 3.1.x and 1.18.0 respectively because
122
+ newer versions require Ruby 3.2. Other locked gem versions are retained. Ruby 3.1's
123
+ internal Data compatibility implementation preserves positional and keyword
124
+ constructors (including `[]` and custom initializers), frozen copies and empty
125
+ record value semantics. Newer Rubies retain their native Data implementation. Both
126
+ locks contain registry SHA-256 checksums generated by Bundler, not hand-entered:
127
+
128
+ ```sh
129
+ bundle _2.6.9_ lock --add-checksums
130
+ BUNDLE_GEMFILE=resources/modules/Gemfile bundle _2.6.9_ lock --add-checksums
131
+ git diff -- Gemfile.lock resources/modules/Gemfile.lock
132
+ ```
133
+
134
+ The real official addon acceptance gate is also required in every Linux/macOS
135
+ matrix job. CI checks out reviewed addon commits, then installs them through
136
+ Core's actual local Git acquisition, cache, manifest, configuration and execution
137
+ paths. Synthetic control/Hiera repositories, a real PTY and a local rsync stub
138
+ cover revision updates, failed-update retention, argv, inherited environment,
139
+ configuration exports, native errors and signal-derived status. No real data or
140
+ external synchronization endpoints are used. With the three addon checkouts
141
+ under a common directory, run:
142
+
143
+ ```sh
144
+ EMPEIRA_OFFICIAL_ADDON_ROOT=/path/to/checkouts \
145
+ bundle exec rspec spec/acceptance/official_addons.rb
146
+ ```
147
+
148
+ Each checkout must be named `empeira-addon-convert`, `empeira-addon-pre-commit`
149
+ or `empeira-addon-sync`. Set `EMPEIRA_OFFICIAL_ADDONS=sync` (or a comma-separated
150
+ subset) to select checkouts; absent sources fail the gate. Addon CI uses the
151
+ same gate against a pinned Core revision. Update the reviewed commit pins in
152
+ CI when changing either side of API 1. Standalone addon unit tests remain in
153
+ each addon's matrix. macOS installs GNU coreutils and GNU tar for fixtures that
154
+ execute Linux guest package-manager commands against synthetic roots; these are
155
+ test dependencies, not macOS product prerequisites. Fixtures canonicalize temporary
156
+ paths, keep Unix socket names short and declare synthetic CPU architectures.
157
+ The single Python Linux-guest adapter probe requires Linux `epoll` and `waitid`;
158
+ it is explicitly unavailable on Darwin and remains required on every Ubuntu
159
+ Ruby job. All other deterministic examples and official addon acceptance run
160
+ natively on macOS. Pre-commit's real container integration remains required on
161
+ Linux; hosted macOS has no configured Docker/Podman engine. No native Windows
162
+ jobs are used, and WSL2 runtime validation remains a separate real-host gate.
108
163
 
109
164
  The test checkout must be writable: the QEMU monitor-directory cleanup fixture
110
165
  uses the checkout as a synthetic home. A read-only container bind mount caused one
@@ -198,6 +253,7 @@ The APT authentication regression uses a local HTTPS repository with synthetic B
198
253
  credentials and an ephemeral GPG signing key. It checks the helper's effective
199
254
  `auth.conf.d` configuration, private file mode and exact credential bytes, then
200
255
  verifies metadata returns 401 before one interactive login and 200 after the retry.
256
+ The fixture omits the 401 reason phrase to exercise APT's bare status/IP diagnostic.
201
257
  The selected package download must reuse that login and pass its SHA-256 check.
202
258
  It requires host `gpg`/`gpgconf` and a locally reachable container engine; helper and
203
259
  host TLS verification remain enabled. Run it explicitly for either engine:
@@ -390,8 +446,35 @@ Title and release-note edits do not repair incorrect immutable assets.
390
446
 
391
447
  Release notes use merged PR labels from `.github/release.yml`, independently of
392
448
  Conventional Commit types. See [label conventions](../CONTRIBUTING.md#release-note-labels).
393
- The first distribution is a GitHub-attached Ruby gem; RubyGems.org publication,
394
- standalone Linux/macOS binaries and automatic self-update are not implemented.
449
+ The first alpha is distributed as a verified GitHub Release gem and published
450
+ unchanged to [RubyGems.org](https://rubygems.org/gems/empeira) via OIDC Trusted
451
+ Publishing. Standalone Linux/macOS binaries and automatic self-update are not
452
+ implemented.
453
+
454
+ ### RubyGems.org distribution (manual)
455
+
456
+ After a verified GitHub Release exists, use
457
+ `.github/workflows/publish-rubygems.yml` via **Actions → Publish existing release to RubyGems.org → Run workflow**.
458
+ Run it from `main`, entering the existing release tag (for example `v0.1.0-alpha.1`).
459
+ The default `dry_run: true` checks the existing GitHub release, tag ancestry,
460
+ uploaded gem SHA-256 and embedded build metadata; it never creates a tag,
461
+ rebuilds a gem or contacts RubyGems with publishing credentials.
462
+
463
+ For a new gem that has not yet been published, the owner must first configure a
464
+ [Pending Trusted Publisher](https://rubygems.org/profile/oidc/pending_trusted_publishers)
465
+ on RubyGems.org. Empeira's publisher is already configured for gem `empeira`,
466
+ repository owner `c8m6`, repository `empeira`, workflow filename
467
+ `publish-rubygems.yml` and no GitHub Environment. After a successful dry-run,
468
+ run the same workflow with `dry_run: false`. Only its upload step receives
469
+ short-lived RubyGems credentials via GitHub OIDC; no persistent API key is required.
470
+
471
+ Publishing uploads the exact previously verified GitHub Release asset, not a
472
+ second build, and does not modify GitHub Releases or source. It fails closed
473
+ if the asset differs, the tag is not on `main`, or RubyGems rejects the upload.
474
+ RubyGems does not allow uploading the same gem name/version twice; inspect
475
+ the existing published version before rerunning a failed push. After success,
476
+ install the alpha with `gem install empeira --pre`. GitHub Releases remain
477
+ the authoritative archive of release artifacts.
395
478
 
396
479
  ## Dependency and image provenance
397
480
 
@@ -429,9 +512,10 @@ Run `EMPEIRA_INTEGRATION=server-environment bundle exec rspec spec/integration/s
429
512
  to check native timeout 0, live edits, CA retention, PuppetDB and idempotent up
430
513
  under Docker/rootless Podman with the default server and a custom entrypoint
431
514
  that ignores timeout environment mapping.
432
- The same gate verifies startup and idempotent `up` with an explicit
433
- `environment.conf` timeout override, preserving the file and distinguishing its
434
- effective value from the native server setting.
515
+ The same gate verifies that an explicit `environment.conf` timeout of `unlimited`
516
+ is zero in the managed copy while the original stays unchanged. It edits a Ruby
517
+ Puppet function between catalogs and checks the changed result without `up` or a
518
+ restart, followed by an idempotent `up`.
435
519
 
436
520
  Run the isolated diagnostics/recovery gate with each native runtime:
437
521
 
@@ -470,3 +554,8 @@ The DNS gate checks UDP/TCP, internal/external names, NXDOMAIN, large-answer fal
470
554
  unavailable/recreated CoreDNS, a second workspace with a different subnet and
471
555
  CoreDNS address, rule removal and idempotent reconciliation. Node identity also
472
556
  checks both virtual DNS transports before and after node stop/start.
557
+
558
+ The VirtIO upload regression deliberately delays staged-file unlinking. Successful
559
+ completion must follow both atomic installation and temporary-file cleanup; a
560
+ cleanup failure cannot acknowledge success. The unchanged binary-upload/empty-stage
561
+ assertions cover this ordering on Linux, independently from the real KVM gate.
@@ -105,7 +105,8 @@ An optional mount is not permission to ignore unsafe configuration.
105
105
  Puppet's [default modulepath](https://help.puppet.com/core/current/Content/PuppetCore/config_file_environment.htm)
106
106
  includes the environment's `modules` directory. Empeira checks explicit
107
107
  `environment.conf` modulepaths and reports a known exclusion before startup. It
108
- never rewrites that file. Expressions containing unresolved Puppet variables cannot
108
+ never rewrites the original file. Its managed copy changes only
109
+ `environment_timeout` to zero. Expressions containing unresolved Puppet variables cannot
109
110
  be verified completely from the CLI host; ensure the effective server modulepath
110
111
  includes the mount destination. Environment mounts can be referenced by ordinary
111
112
  relative paths in `hiera.yaml`, such as `data/external` or `external` when the datadir
@@ -117,7 +118,8 @@ an image rebuild, `up`, or a service restart.
117
118
  Empeira neither copies their data nor writes probe files into them. It uses a managed
118
119
  environment directory containing links to the read-only control repository so nested
119
120
  mount targets can exist without modifying control code. Existing control files and
120
- linked directories remain live. `up` refreshes links for new control entries under
121
+ linked directories remain live; `environment.conf` is an independent copy refreshed
122
+ by `up`. `up` also refreshes links for new top-level control entries and entries under
121
123
  projected ancestor directories. EYAML keys remain configured separately in `eyaml`,
122
124
  and decryption runs on the server.
123
125
 
data/docs/installation.md CHANGED
@@ -1,36 +1,63 @@
1
1
  # Installation
2
2
 
3
3
  Empeira runs natively on Linux and macOS. Windows uses **WSL2 → Linux → Empeira**;
4
- native Windows Ruby is unsupported. The gemspec requires **Ruby >= 3.4**; the
5
- supported Ruby versions are **3.4 and 4.0**. Normal CI tests both on Ubuntu;
6
- release gems are built with Ruby 3.4. Use source installation until a release gem
7
- is available. No RubyGems.org publication is assumed.
4
+ native Windows Ruby is unsupported. The gemspec requires **Ruby >= 3.1**; supported Ruby versions are **3.1, 3.2,
5
+ 3.3, 3.4 and 4.0**. Normal CI tests all five on Ubuntu and native macOS;
6
+ release gems are built with Ruby 3.4. The first public alpha is
7
+ [`v0.1.0-alpha.1`](https://github.com/c8m6/empeira/releases/tag/v0.1.0-alpha.1)
8
+ and is available on [RubyGems.org](https://rubygems.org/gems/empeira).
9
+
10
+ ## Install from RubyGems.org (recommended)
11
+
12
+ After installing Ruby 3.1+ and the appropriate host tools/container runtime
13
+ described below, install Empeira from RubyGems.org:
14
+
15
+ ```bash
16
+ gem install --user-install empeira --pre
17
+ export PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
18
+ empeira --version
19
+ empeira help
20
+ ```
21
+
22
+ The `--pre` flag is needed while Empeira is available as a prerelease. For an
23
+ exact installation of the first alpha, use:
24
+
25
+ ```bash
26
+ gem install --user-install empeira --version 0.1.0.pre.alpha.1
27
+ ```
28
+
29
+ A gem installation needs neither Bundler nor a Git checkout of Empeira. It
30
+ installs the `thor` runtime dependency automatically; it does **not** install
31
+ Git, Podman/Docker, QEMU, or Puppet/OpenVox node images. Do not use `sudo gem install` for a normal user installation. Keep the gem user's `bin` directory
32
+ in your shell `PATH` to retain the command across new terminal sessions.
33
+
34
+ If you previously used the source-installation `empeira()` shell function,
35
+ remove that function from your shell configuration and start a new shell before
36
+ testing the installed executable. The source function otherwise takes precedence.
8
37
 
9
38
  ## Linux and WSL2
10
39
 
11
- These commands target **Ubuntu 24.04 LTS** and **Debian 13 trixie**. Their default
12
- Ruby packages are below Empeira's minimum; Ruby 3.2 and 3.3 are unsupported.
13
- Install Ruby 3.4+ with your established Ruby version manager before using Empeira.
14
- Ubuntu 22.04 and Debian 12 also need a newer Ruby. Installing the distribution's
15
- default `ruby` package does not satisfy the gemspec on these hosts.
40
+ These commands target **Ubuntu 24.04 LTS** and **Debian 13 trixie**. Use Ruby 3.1+ from the distribution or your established Ruby version manager.
41
+ Check `ruby --version` before installing the gem. The distribution's default
42
+ `ruby` package is suitable only when it meets the minimum; otherwise activate
43
+ a compatible Ruby before proceeding.
16
44
 
17
- Core/source development dependencies and SSH access:
45
+ Host utilities and SSH access:
18
46
 
19
47
  ```bash
20
48
  sudo apt-get update
21
- sudo apt-get install -y git build-essential openssh-client iproute2
22
- # Activate Ruby 3.4+ using your Ruby version manager before continuing.
23
- gem install bundler
49
+ sudo apt-get install -y git openssh-client iproute2
50
+ # Activate Ruby 3.1+ when the distribution's Ruby is too old.
24
51
  ruby --version
25
52
  gem --version
26
- bundle --version
27
53
  git --version
28
54
  ssh -V
29
55
  ```
30
56
 
31
- The selected Ruby installation must supply `gem` and matching development headers;
32
- `gem install bundler` supplies `bundle`. `build-essential` builds native development
33
- gems; additional Ruby build prerequisites depend on your version manager.
57
+ The selected Ruby installation must supply `gem`. Bundler, a source checkout and
58
+ a C/C++ build toolchain are not required to install the published Empeira gem.
59
+ They may be needed for source development or for setting up your Ruby environment;
60
+ see [source installation](#source-installation-and-project-opt-in).
34
61
  `openssh-client` supplies `ssh` and `ssh-keygen` for regular node SSH. VM management
35
62
  uses private VirtIO-Serial and Python 3 already present in the selected cloud image.
36
63
  `iproute2` supplies host route inspection for automatic workspace subnet allocation. Coreutils supplies
@@ -131,11 +158,8 @@ Install Homebrew using its [official instructions](https://brew.sh/), then core:
131
158
  ```bash
132
159
  brew install git ruby@3.4 openssh
133
160
  export PATH="$(brew --prefix ruby@3.4)/bin:$PATH"
134
- gem install --user-install bundler
135
- export PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
136
161
  ruby --version
137
162
  gem --version
138
- bundle --version
139
163
  git --version
140
164
  ssh -V
141
165
  ```
@@ -189,7 +213,13 @@ not establish those results. See the [VM validation gates](development.md#vm-and
189
213
 
190
214
  ## Source installation and project opt-in
191
215
 
216
+ For development or unreleased changes, run Empeira directly from a Git checkout.
217
+ This alternative requires Bundler and the development dependencies (including a
218
+ build toolchain where native gems need it). Set up Bundler first, then:
219
+
192
220
  ```bash
221
+ gem install --user-install bundler
222
+ export PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
193
223
  git clone https://github.com/c8m6/empeira.git
194
224
  cd empeira
195
225
  bundle config set --local path vendor/bundle
@@ -213,21 +243,22 @@ invocation and paths with spaces work. No Puppet-file heuristic or `--project`
213
243
  selector exists. The source shell function preserves the current directory.
214
244
  Version/help/self-update work outside Git (self-update currently reports unavailable).
215
245
 
216
- ## Release gem installation
246
+ ## Install the GitHub Release gem (alternative)
217
247
 
218
- Download the `.gem` asset from a [GitHub release](https://github.com/c8m6/empeira/releases)
219
- when one is available, then install it with your existing Ruby environment:
248
+ The [GitHub Releases](https://github.com/c8m6/empeira/releases) page also
249
+ provides the exact `.gem` asset built and verified by the release pipeline.
250
+ For example, after downloading the first alpha:
220
251
 
221
252
  ```bash
222
- gem install --user-install ./empeira-VERSION.gem
253
+ gem install --user-install ./empeira-0.1.0.pre.alpha.1.gem
223
254
  export PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
224
255
  empeira --version
225
256
  ```
226
257
 
227
- Replace `VERSION` with the downloaded asset's filename. RubyGems installs the Thor
228
- runtime dependency; no third-party agents, images or binaries are bundled. The
229
- release version is embedded in BuildInfo. Standalone binaries and automatic
230
- self-update are unavailable. Do not use `sudo gem install` for normal operation.
258
+ Both distribution paths use the same verified gem for the corresponding release.
259
+ The release version is embedded in BuildInfo. No third-party agents, images or
260
+ binaries are bundled. Standalone platform binaries and automatic self-update
261
+ remain unavailable.
231
262
 
232
263
  For other Linux distributions, install equivalent Ruby/Git/runtime packages through
233
264
  your established host setup. Guest OS support does not imply a tested host recipe.
data/docs/nodes.md CHANGED
@@ -87,18 +87,22 @@ Managed servers enforce native `[server] environment_timeout = 0` before startup
87
87
  and verify the effective setting during `up`, independently of image environment
88
88
  variable support. Existing workspaces adopt it on
89
89
  their next `up`; run that once after updating from a version that enabled caching.
90
+ Adopting the managed environment copy changes the server mount and recreates its
91
+ container once, retaining CA, SSL and database storage.
90
92
  Each catalog request reloads live control code, modules and mounted Hiera data.
91
93
  Empeira does not hash these trees,
92
94
  persist content checkpoints or call the environment-cache admin API. Code edits
93
95
  need neither `up` nor a server restart, including for agents invoked manually.
94
96
  Empeira does not enable `use_cached_catalog`.
95
97
 
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.
98
+ Empeira copies `environment.conf` into its managed environment and sets its timeout
99
+ to zero there, preserving all other settings and the original file. Run `up` to
100
+ refresh this copy after editing other environment settings. Avoid concurrent edits
101
+ during a catalog run. Existing files and directories stay live through read-only
102
+ links. New top-level entries and new siblings under ancestors projected for nested
103
+ Hiera mounts require `up` to create their links. `node start` resumes the node
104
+ without invoking Puppet; Puppet services enabled by a catalog may run independently
105
+ on startup.
102
106
 
103
107
  ## Distribution package sources
104
108
 
@@ -33,6 +33,14 @@ HTTP 403 does not trigger login, and HTTP 407 identifies proxy authentication. R
33
33
  authentication continues to use native `docker login` / `podman login` and the reported
34
34
  registry host; Empeira does not log into registries automatically.
35
35
 
36
+ APT can report an HTTP status without a reason phrase, for example
37
+ `401 [IP: 192.0.2.1 443]`. This still triggers the single repository login attempt
38
+ when no ENV credentials are set and both standard input and standard error are
39
+ connected to a TTY. The corresponding `403` and `407` forms retain their access-denied
40
+ and proxy-authentication diagnoses without prompting. A node retained as `incomplete`
41
+ after an acquisition failure must be destroyed with `empeira node destroy HOSTNAME`
42
+ and recreated with the original `empeira node run` arguments after resolving the cause.
43
+
36
44
  APT helper credentials use literal `login` and `password` tokens in a temporary
37
45
  0600 file under `/etc/apt/auth.conf.d`. APT treats surrounding quotes and added
38
46
  backslashes as credential bytes, so Empeira does not add them. A successful login
@@ -140,11 +148,14 @@ problem. A successful probe with different directory listings indicates a
140
148
  different engine view of the source. Empeira does not weaken mount checks or
141
149
  change host permissions automatically.
142
150
 
143
- An explicit `environment_timeout` in the project's `environment.conf` overrides
144
- the native server setting. Empeira preserves it. Bare `puppet config print`
145
- includes that override; server startup and reconciliation verify only the native
146
- server value. Set the environment override to zero or remove it when immediate
147
- code reloads are required. No explicit cache invalidation is performed.
151
+ Empeira enforces `environment_timeout = 0` in both the native server setting and
152
+ its independent copy of `environment.conf`. The project's original file remains
153
+ unchanged, even when it specifies `unlimited`. Run `up` after updating Empeira or
154
+ editing other settings in `environment.conf` to refresh the managed copy. Inside
155
+ the server, `puppet config print environment_timeout --section server --environment
156
+ ENVIRONMENT` must report `0` for the configured environment. A different value
157
+ fails startup verification; check the custom server's confdir and environmentpath.
158
+ No explicit cache invalidation is performed.
148
159
 
149
160
  `config show` includes `effective_hiera_mounts`. A skipped optional source is
150
161
  reported during `up`; mount it locally or correct the source path, then run `up`
@@ -39,11 +39,11 @@ module Empeira
39
39
  diagnostic = Execution::Diagnostics.native(result.with(stderr: text, stdout: ''),
40
40
  operation: "Agent #{operation}", tool: arguments.first)
41
41
  case text
42
- when /407\s+Proxy Authentication Required|(?:HTTP|Status code|HTTP code)\s*:?\s*407\b/i
42
+ when /407\s+Proxy Authentication Required|(?:HTTP|Status code|HTTP code)\s*:?\s*407\b|\b407[ \t]+\[IP:/i
43
43
  raise Error, "Host helper proxy authentication failed\n#{diagnostic}", cause: nil
44
- when /401\s+Unauthorized|(?:HTTP|Status code|HTTP code)\s*:?\s*401\b/i
44
+ when /401\s+Unauthorized|(?:HTTP|Status code|HTTP code)\s*:?\s*401\b|\b401[ \t]+\[IP:/i
45
45
  raise Agent::AuthenticationRequired, diagnostic, cause: nil
46
- when /403\s+Forbidden|(?:HTTP|Status code|HTTP code)\s*:?\s*403\b/i
46
+ when /403\s+Forbidden|(?:HTTP|Status code|HTTP code)\s*:?\s*403\b|\b403[ \t]+\[IP:/i
47
47
  raise Error, "Agent repository access denied\n#{diagnostic}", cause: nil
48
48
  end
49
49
  raise Error, "Agent #{operation} failed\n#{diagnostic}", cause: nil
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0-alpha.1",
3
- "revision": "7cd2d71ea0b7cfa7008f8faf18e975499c5af9c0",
4
- "build_time": "2026-10-14T09:19:05Z"
2
+ "version": "0.1.0-alpha.4",
3
+ "revision": "b073c6a15db14c4dace4a732703240f4289cbb48",
4
+ "build_time": "2026-10-21T11:41:02Z"
5
5
  }
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby 3.2 introduced immutable Data value objects. Keep Ruby 3.1 support
4
+ # without replacing the native Data implementation on newer Rubies.
5
+ module Empeira
6
+ module Compatibility
7
+ module DataFactory
8
+ module_function
9
+
10
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength -- Ruby 3.1 must emulate Data initializer semantics.
11
+ def define(*members, &definition)
12
+ # The explicit nil class name also permits zero members and preserves
13
+ # Struct's value equality, hashing and pattern matching for empty records.
14
+ base_class = Struct.new(nil, *members, keyword_init: true)
15
+ base_class.class_eval do
16
+ def initialize(*positional, **keywords)
17
+ fields = self.class.members
18
+ raise ArgumentError, 'too many positional arguments' if positional.size > fields.size
19
+
20
+ supplied = fields.take(positional.size).zip(positional).to_h.merge(keywords)
21
+ missing = fields - supplied.keys
22
+ unexpected = supplied.keys - fields
23
+ raise ArgumentError, "missing keywords: #{missing.join(', ')}" unless missing.empty?
24
+ raise ArgumentError, "unknown keywords: #{unexpected.join(', ')}" unless unexpected.empty?
25
+
26
+ super(**supplied)
27
+ freeze
28
+ end
29
+
30
+ def with(**changes)
31
+ self.class.new(**to_h, **changes)
32
+ end
33
+
34
+ def initialize_copy(other)
35
+ super
36
+ freeze
37
+ end
38
+ end
39
+ value_class = Class.new(base_class)
40
+ define_constructors(value_class)
41
+ value_class.include(self)
42
+ value_class.class_eval(&definition) if definition
43
+ value_class
44
+ end
45
+
46
+ # rubocop:disable-next Metrics/AbcSize -- Normalize native Data constructors before custom initialization.
47
+ def define_constructors(value_class)
48
+ value_class.define_singleton_method(:new) do |*positional, **keywords|
49
+ raise ArgumentError, 'cannot mix positional and keyword arguments' unless positional.empty? || keywords.empty?
50
+ raise ArgumentError, 'too many positional arguments' if positional.size > members.size
51
+
52
+ keywords = members.take(positional.size).zip(positional).to_h unless positional.empty?
53
+ super(**keywords).freeze
54
+ end
55
+ value_class.define_singleton_method(:[]) { |*positional, **keywords| new(*positional, **keywords) }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ Data = Empeira::Compatibility::DataFactory unless defined?(Data)
@@ -350,10 +350,11 @@ module Empeira
350
350
 
351
351
  def reconcile_server_environment
352
352
  environment = Server::EnvironmentConfiguration.new(runtime: @runtime, plan: @plan)
353
- return unless environment.reconcile(@observed.fetch('server'))
353
+ if environment.reconcile(@observed.fetch('server'))
354
+ @changed = true
355
+ @health.wait('server', @observed)
356
+ end
354
357
 
355
- @changed = true
356
- @health.wait('server', @observed)
357
358
  environment.verify!(@observed.fetch('server'))
358
359
  end
359
360
 
@@ -9,7 +9,7 @@ module Empeira
9
9
  def initialize(context:, hiera:)
10
10
  @project = context.project.path
11
11
  @root = root(context)
12
- @targets = mount_targets(hiera)
12
+ @targets = ['environment.conf', *mount_targets(hiera)]
13
13
  @modules = Modules::Storage.new(context: context).root if @project.join('Puppetfile').file?
14
14
  @targets |= ['modules'] if @modules
15
15
  @base = context.locations.workspace(context.workspace).join('environments')
@@ -17,14 +17,11 @@ module Empeira
17
17
  end
18
18
 
19
19
  def mounts
20
- return [bind(@project, @root)] if @targets.empty?
21
-
22
20
  [bind(@project, CONTROL), bind(@directory, @root),
23
21
  *(@modules ? [bind(@modules, "#{@root}/modules")] : [])]
24
22
  end
25
23
 
26
24
  def prepare
27
- return if @targets.empty?
28
25
  raise Error, 'Managed environment directory must not be a symlink' if @directory.symlink?
29
26
 
30
27
  FileUtils.mkdir_p(@directory, mode: 0o755)
@@ -81,13 +78,36 @@ module Empeira
81
78
  end
82
79
 
83
80
  def create_entry(relative, destination)
84
- if @targets.any? { |target| target == relative || target.start_with?("#{relative}/") }
81
+ if relative == 'environment.conf'
82
+ prepare_configuration(destination)
83
+ elsif @targets.any? { |target| target == relative || target.start_with?("#{relative}/") }
85
84
  create_target(relative, destination)
86
85
  else
87
86
  create_link(relative, destination)
88
87
  end
89
88
  end
90
89
 
90
+ def prepare_configuration(destination)
91
+ content = environment_configuration
92
+ return if !destination.symlink? && destination.file? && destination.binread == content
93
+
94
+ Tempfile.create('environment-', @directory) do |file|
95
+ file.chmod(0o644)
96
+ file.write(content)
97
+ file.flush
98
+ File.rename(file.path, destination)
99
+ end
100
+ end
101
+
102
+ def environment_configuration
103
+ source = @project.join('environment.conf')
104
+ content = source.exist? ? source.binread : ''
105
+ content = content.gsub(/^[ \t]*environment_timeout[ \t]*=[^\r\n]*/, 'environment_timeout = 0')
106
+ content += "\n" unless content.empty? || content.end_with?("\n")
107
+ content += "environment_timeout = 0\n" unless content.match?(/^environment_timeout = 0\r?$/)
108
+ content
109
+ end
110
+
91
111
  def create_target(relative, destination)
92
112
  raise Error, 'Managed environment destination is unsafe' if destination.symlink?
93
113
 
@@ -6,7 +6,7 @@ module Empeira
6
6
  class Backend
7
7
  attr_reader :context, :runtime, :runner, :store
8
8
 
9
- def self.build(context:, **)
9
+ def self.build(context:, **options)
10
10
  klass = if context.container_engine == 'docker'
11
11
  DockerAdapter
12
12
  elsif context.platform.os == :macos
@@ -14,7 +14,7 @@ module Empeira
14
14
  else
15
15
  LinuxPodman
16
16
  end
17
- klass.new(context: context, **)
17
+ klass.new(context: context, **options)
18
18
  end
19
19
 
20
20
  def initialize(context:, runtime:, runner:, store:)
@@ -92,8 +92,8 @@ module Empeira
92
92
  raise Providers::OwnershipError, 'VM TAP already exists; inspect its owning process'
93
93
  end
94
94
 
95
- def namespace(*, **)
96
- command('podman', '--remote=false', 'unshare', '--rootless-netns', *, **)
95
+ def namespace(*arguments, **options)
96
+ command('podman', '--remote=false', 'unshare', '--rootless-netns', *arguments, **options)
97
97
  end
98
98
 
99
99
  def attached?(record, links)
@@ -98,13 +98,13 @@ module Empeira
98
98
  raise Error, 'Podman Machine cannot verify the peer helper at its host path; configure its shared mount'
99
99
  end
100
100
 
101
- def namespace(*)
102
- executable, *arguments = namespace_command(@machine, *)
103
- command(executable, *arguments)
101
+ def namespace(*arguments)
102
+ executable, *command_arguments = namespace_command(@machine, *arguments)
103
+ command(executable, *command_arguments)
104
104
  end
105
105
 
106
- def namespace_command(machine, *)
107
- ['podman', 'machine', 'ssh', machine, Shellwords.join(['podman', 'unshare', '--rootless-netns', *])]
106
+ def namespace_command(machine, *arguments)
107
+ ['podman', 'machine', 'ssh', machine, Shellwords.join(['podman', 'unshare', '--rootless-netns', *arguments])]
108
108
  end
109
109
  end
110
110
  end
@@ -5,10 +5,10 @@ module Empeira
5
5
  class Definition < Services::Definition
6
6
  attr_reader :hostname
7
7
 
8
- def initialize(hostname:, workspace:, **)
8
+ def initialize(hostname:, workspace:, **options)
9
9
  @hostname = hostname
10
10
  super(key: "node-#{Digest::SHA256.hexdigest(hostname)[0, 24]}", workspace: workspace,
11
- hostname: hostname, **)
11
+ hostname: hostname, **options)
12
12
  end
13
13
 
14
14
  def ownership_labels
@@ -24,10 +24,10 @@ module Empeira
24
24
  end
25
25
 
26
26
  def verify!(resource)
27
- return if current(resource) == '0'
27
+ return if current(resource) == '0' && effective(resource) == '0'
28
28
 
29
29
  raise Error, 'Puppetserver environment_timeout is not 0 after startup; the custom entrypoint or ' \
30
- 'server confdir overrides the managed setting; inspect the server runtime contract'
30
+ 'server confdir/environment overrides the managed setting; inspect the server runtime contract'
31
31
  end
32
32
 
33
33
  private
@@ -36,6 +36,12 @@ module Empeira
36
36
  execute(resource, [ControlPlane::Health::RUBY, READER, '--confdir', confdir]).stdout.strip
37
37
  end
38
38
 
39
+ def effective(resource)
40
+ arguments = [PUPPET, 'config', 'print', 'environment_timeout', '--section', 'server',
41
+ '--confdir', confdir, '--environment', @plan.config.dig('server', 'environment')]
42
+ execute(resource, arguments).stdout.strip
43
+ end
44
+
39
45
  def execute(resource, arguments)
40
46
  result = @runtime.service_exec(resource, arguments)
41
47
  return result if result.success?
@@ -73,9 +73,9 @@ module Empeira
73
73
  { label: 'VM network helper', image: adapter.image, recipe: adapter.recipe, files: adapter.files }]
74
74
  end
75
75
 
76
- def utility_artifact(key, label, **)
76
+ def utility_artifact(key, label, **options)
77
77
  Empeira::Images::Configuration.artifact(
78
- @config.dig('images', key), registry: @config.dig('images', 'registry'), **
78
+ @config.dig('images', key), registry: @config.dig('images', 'registry'), **options
79
79
  ).merge(label: label)
80
80
  end
81
81
 
data/lib/empeira.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'empeira/compatibility/data'
3
4
  require_relative 'empeira/errors'
4
5
  require_relative 'empeira/progress'
5
6
  require_relative 'empeira/build_info'
@@ -5,7 +5,7 @@ RUN apk add --no-cache git ca-certificates
5
5
  WORKDIR /opt/empeira-modules
6
6
  COPY Gemfile Gemfile.lock ./
7
7
  RUN apk add --no-cache --virtual .build-deps build-base libffi-dev \
8
- && gem install bundler -v 4.0.22 --no-document \
8
+ && gem install bundler -v 2.6.9 --no-document \
9
9
  && bundle config set deployment true && bundle install --jobs 2 \
10
10
  && apk del .build-deps
11
11
  COPY worker.rb ./
@@ -3,3 +3,9 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'r10k', '5.0.3'
6
+
7
+ # r10k's newer fast_gettext 4.x dependency requires Ruby >= 3.2.
8
+ # Keep the module helper usable with the oldest supported Ruby (3.1).
9
+ gem 'fast_gettext', '~> 3.1.0'
10
+ # Newer multi_json requires Ruby >= 3.2.
11
+ gem 'multi_json', '1.18.0'
@@ -13,9 +13,8 @@ GEM
13
13
  faraday (>= 1, < 3)
14
14
  faraday-net_http (3.4.4)
15
15
  net-http (~> 0.5)
16
- fast_gettext (4.1.1)
16
+ fast_gettext (3.1.0)
17
17
  prime
18
- racc
19
18
  fiddle (1.1.8)
20
19
  forwardable (1.4.0)
21
20
  gettext (3.5.2)
@@ -36,7 +35,7 @@ GEM
36
35
  log4r (1.1.10)
37
36
  logger (1.7.0)
38
37
  minitar (1.1.0)
39
- multi_json (1.21.2)
38
+ multi_json (1.18.0)
40
39
  net-http (0.9.1)
41
40
  uri (>= 0.11.1)
42
41
  prime (0.1.4)
@@ -68,18 +67,19 @@ PLATFORMS
68
67
  x86_64-linux
69
68
 
70
69
  DEPENDENCIES
70
+ fast_gettext (~> 3.1.0)
71
+ multi_json (= 1.18.0)
71
72
  r10k (= 5.0.3)
72
73
 
73
74
  CHECKSUMS
74
75
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
75
- bundler (4.0.22) sha256=d8d5ec84c8555e0af71db63ed7aee4d1a8fb839ec46d84212d61979242a5d75a
76
76
  colored2 (4.0.3) sha256=63e1038183976287efc43034f5cca17fb180b4deef207da8ba78d051cbce2b37
77
77
  cri (2.15.12) sha256=8abfe924ef53e772a8e4ee907e791d3bfcfca78bc62a5859e3b9899ba29956e5
78
78
  erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
79
79
  faraday (2.14.4) sha256=9bb4408c44621b0dbaaaa39e5dbfc4dbe58cc6751b6eb94d16ae9e1f2f2fab6e
80
80
  faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad
81
81
  faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
82
- fast_gettext (4.1.1) sha256=e8c532f6945b0dc31efb2e91366bd56630bc3ae6a05a4126937124e40366efd8
82
+ fast_gettext (3.1.0) sha256=9c4c84c33a875cc9edc062179229f9d373179a624d3c961eed747cddbab72ac5
83
83
  fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
84
84
  forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838
85
85
  gettext (3.5.2) sha256=ada02c59aa7e9f56bd2522faedaed16421dd2f3ddb5fe28628c0be5abcbf3c74
@@ -90,7 +90,7 @@ CHECKSUMS
90
90
  log4r (1.1.10) sha256=9b452928c964b7c54c09aeb25ff045b5a722b387b16c9ce37cb1baec00062966
91
91
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
92
92
  minitar (1.1.0) sha256=38db0cfb6f3801017946cdcd8dc53f2cf3fd41ff752892312bf9a1639c9ea23e
93
- multi_json (1.21.2) sha256=245531eaf54bdba57aba515ff08b4503505c5e09911daa89c5a561e2f85bea8e
93
+ multi_json (1.18.0) sha256=23f70cf73e2895a33e6d10f0ec955b28ef5627ca4bbad0f292e7d16f4675596f
94
94
  net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
95
95
  prime (0.1.4) sha256=4d755ebf7c2994a6f3a3fee0d072063be3fff2d4042ebff6cd5eebd4747a225e
96
96
  puppet_forge (6.2.0) sha256=9cde4841a7a6950afeb0c4fec02449931179863918c0a6e6909cb2a6c6998a0c
@@ -102,4 +102,4 @@ CHECKSUMS
102
102
  uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
103
103
 
104
104
  BUNDLED WITH
105
- 4.0.22
105
+ 2.6.9
@@ -316,6 +316,7 @@ def handle(message):
316
316
  active['file'].close()
317
317
  active = None
318
318
  pending = None
319
+ status = 0
319
320
  try:
320
321
  with tempfile.NamedTemporaryFile(dir=os.path.dirname(destination), delete=False) as target:
321
322
  pending = target.name
@@ -326,14 +327,16 @@ def handle(message):
326
327
  os.fsync(target.fileno())
327
328
  os.replace(pending, destination)
328
329
  pending = None
329
- send({'type': 'exit', 'status': 0, 'timed_out': False})
330
330
  except OSError as error:
331
331
  send({'type': 'stderr', 'data': base64.b64encode((os.strerror(error.errno) + '\n').encode()).decode()})
332
- send({'type': 'exit', 'status': 1, 'timed_out': False})
332
+ status = 1
333
333
  finally:
334
334
  os.unlink(staged)
335
335
  if pending:
336
336
  os.unlink(pending)
337
+ # Completion includes staging cleanup; an unlink failure must never
338
+ # acknowledge success to a controller that is already enrolling/Puppet.
339
+ send({'type': 'exit', 'status': status, 'timed_out': False})
337
340
  else:
338
341
  raise ValueError('Unexpected protocol operation')
339
342
 
@@ -1,10 +1,7 @@
1
1
  ARG EMPEIRA_BASE_UBUNTU=library/ubuntu@sha256:da6fc2be547864451aa253836dd926da33623312df4a9a243e35dc877c378a78
2
2
  FROM ${EMPEIRA_BASE_UBUNTU}
3
3
  ENV DEBIAN_FRONTEND=noninteractive
4
- RUN ["apt-get", "-o", "APT::Update::Error-Mode=any", "update"]
5
- RUN ["apt-get", "install", "--yes", "--no-install-recommends", "ca-certificates"]
6
- RUN ["sed", "-i", "/Types: deb/a Snapshot: yes", "/etc/apt/sources.list.d/ubuntu.sources"]
7
- RUN ["apt-get", "--snapshot", "20261001T000000Z", "-o", "APT::Update::Error-Mode=any", "update"]
8
- RUN ["apt-get", "--snapshot", "20261001T000000Z", "install", "--yes", "--no-install-recommends", "squid=7.2-2ubuntu2.2"]
9
- RUN ["apt-get", "clean"]
4
+ RUN apt-get -o APT::Update::Error-Mode=any update \
5
+ && apt-get install --yes --no-install-recommends ca-certificates squid \
6
+ && rm -rf /var/lib/apt/lists/*
10
7
  USER proxy
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: empeira
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.alpha.1
4
+ version: 0.1.0.pre.alpha.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Meißner (c8m6)
@@ -72,6 +72,7 @@ files:
72
72
  - lib/empeira/cli/progress.rb
73
73
  - lib/empeira/cli/proxy.rb
74
74
  - lib/empeira/cli/update.rb
75
+ - lib/empeira/compatibility/data.rb
75
76
  - lib/empeira/completion/addons.rb
76
77
  - lib/empeira/completion/bash.rb
77
78
  - lib/empeira/completion/catalog.rb
@@ -303,7 +304,7 @@ files:
303
304
  - resources/relay/server_start.sh
304
305
  homepage: https://github.com/c8m6/empeira
305
306
  licenses:
306
- - AGPL-3.0-only
307
+ - AGPL-3.0-or-later
307
308
  metadata:
308
309
  rubygems_mfa_required: 'true'
309
310
  rdoc_options: []
@@ -313,7 +314,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
313
314
  requirements:
314
315
  - - ">="
315
316
  - !ruby/object:Gem::Version
316
- version: '3.4'
317
+ version: '3.1'
317
318
  required_rubygems_version: !ruby/object:Gem::Requirement
318
319
  requirements:
319
320
  - - ">="