Skip to content

Commit e7c40c9

Browse files
miguelgilaclaude
andauthored
feat: cross-OS config file and docs cleanup (#22)
* docs: remove completed plan files These plans have been fully implemented and are no longer needed: - PLAN-quickstart-playground.md (playground script shipped) - VERSIONING_PLAN.md (replaced by RELEASING.md) - INTEGRATION_TEST_PLAN.md (all 14 tests implemented) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add cross-OS config file support (/etc/reaper/reaper.conf) Replace the Debian-specific /etc/default/containerd env-file approach with a Reaper-owned config file that works on any Linux distribution. Both binaries now load /etc/reaper/reaper.conf at startup (KEY=VALUE format). Environment variables override file values for backward compatibility. The Ansible playbook writes config to the new location and cleans up the legacy systemd drop-in if present. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add config file integration tests and update docs Add Rust integration tests (tests/integration_config.rs) verifying config file loading, env var override precedence, and missing file handling. Add K8s integration test verifying /etc/reaper/reaper.conf exists on nodes with correct content (29/29 pass). Update README, deploy/kubernetes/README, and example 08 to reference the config file as the standard configuration method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add .omc/ to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3b4b5ff commit e7c40c9

File tree

17 files changed

+624
-434
lines changed

17 files changed

+624
-434
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ target-linux/
88
.vscode/
99
.ignored/
1010
.claude/
11+
.omc/
1112
cobertura.xml
1213
.ignore/
1314

CLAUDE.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ Host Root (/) ─── read-only lower layer
8484
- `/tmp` is NOT bind-mounted (protected by overlay)
8585

8686
**Configuration:**
87+
- Config file: `/etc/reaper/reaper.conf` (cross-distro, `KEY=VALUE` format)
88+
- Config load order: config file defaults → env vars override
89+
- `REAPER_CONFIG`: Override config file path (default: `/etc/reaper/reaper.conf`)
8790
- `REAPER_OVERLAY_BASE`: Default `/run/reaper/overlay`
8891
- `REAPER_DNS_MODE`: DNS resolution mode — `host` (default) uses node's resolv.conf, `kubernetes`/`k8s` writes kubelet-prepared resolv.conf (pointing to CoreDNS) into the overlay
8992
- Overlay is mandatory on Linux (no fail-open)
@@ -109,9 +112,11 @@ Kubernetes volumes (ConfigMap, Secret, hostPath, emptyDir, etc.) are supported v
109112

110113
```
111114
reaper/
112-
├── src/bin/
113-
│ ├── containerd-shim-reaper-v2/
114-
│ │ └── main.rs # Shim implementation (ttrpc server, Task trait)
115+
├── src/
116+
│ ├── config.rs # Shared config file loader (/etc/reaper/reaper.conf)
117+
│ └── bin/
118+
│ ├── containerd-shim-reaper-v2/
119+
│ │ └── main.rs # Shim implementation (ttrpc server, Task trait)
115120
│ └── reaper-runtime/
116121
│ ├── main.rs # OCI runtime CLI (fork-first architecture)
117122
│ ├── state.rs # State persistence (/run/reaper/<id>/)

README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -300,26 +300,30 @@ cargo test
300300

301301
## Configuration
302302

303-
### Overlay Filesystem
304-
305-
All Reaper workloads share a single overlay filesystem:
303+
Reaper reads configuration from `/etc/reaper/reaper.conf` on each node. The Ansible installer creates this file automatically. Environment variables of the same name override file values.
306304

307-
```bash
308-
# Custom overlay location (default: /run/reaper/overlay)
309-
export REAPER_OVERLAY_BASE=/custom/path
305+
```ini
306+
# /etc/reaper/reaper.conf
307+
REAPER_DNS_MODE=kubernetes
308+
REAPER_RUNTIME_LOG=/run/reaper/runtime.log
309+
REAPER_OVERLAY_BASE=/run/reaper/overlay
310310
```
311311

312-
The host root is the read-only lower layer; writes go to a shared upper layer. This means workloads can share files with each other while protecting the host from modifications.
312+
See [deploy/kubernetes/README.md](deploy/kubernetes/README.md#configuration) for the full list of settings.
313+
314+
### Overlay Filesystem
315+
316+
All Reaper workloads share a single overlay filesystem. The host root is the read-only lower layer; writes go to a shared upper layer. This means workloads can share files with each other while protecting the host from modifications.
313317

314318
For details, see [docs/OVERLAY_DESIGN.md](docs/OVERLAY_DESIGN.md).
315319

316320
### Runtime Logging
317321

318-
Enable runtime logging for debugging:
322+
Enable runtime logging for debugging (in `/etc/reaper/reaper.conf` or via env vars):
319323

320324
```bash
321-
export REAPER_SHIM_LOG=/var/log/reaper-shim.log
322-
export REAPER_RUNTIME_LOG=/var/log/reaper-runtime.log
325+
REAPER_SHIM_LOG=/var/log/reaper-shim.log
326+
REAPER_RUNTIME_LOG=/var/log/reaper-runtime.log
323327
```
324328

325329
## Known Issues

deploy/ansible/install-reaper.yml

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -144,31 +144,34 @@
144144
executable: /bin/bash
145145
when: reaper_configured.rc != 0
146146

147-
- name: Set Reaper runtime log environment for containerd
148-
lineinfile:
149-
path: /etc/default/containerd
150-
line: "REAPER_RUNTIME_LOG=/run/reaper/runtime.log"
151-
create: yes
152-
mode: '0644'
147+
- name: Create Reaper configuration directory
148+
file:
149+
path: /etc/reaper
150+
state: directory
151+
mode: '0755'
152+
153+
- name: Write Reaper configuration file
154+
copy:
155+
dest: /etc/reaper/reaper.conf
156+
content: |
157+
# Reaper runtime configuration
158+
# Environment variables override values in this file.
159+
# See: https://github.com/miguelgila/reaper
153160
154-
- name: Set Reaper DNS mode for containerd
155-
lineinfile:
156-
path: /etc/default/containerd
157-
line: "REAPER_DNS_MODE={{ reaper_dns_mode | default('kubernetes') }}"
158-
regexp: "^REAPER_DNS_MODE="
159-
create: yes
161+
REAPER_DNS_MODE={{ reaper_dns_mode | default('kubernetes') }}
162+
REAPER_RUNTIME_LOG=/run/reaper/runtime.log
160163
mode: '0644'
161164

162-
- name: Ensure containerd service picks up environment file
163-
shell: |
164-
mkdir -p /etc/systemd/system/containerd.service.d
165-
cat > /etc/systemd/system/containerd.service.d/reaper-env.conf << 'EOF'
166-
[Service]
167-
EnvironmentFile=-/etc/default/containerd
168-
EOF
169-
systemctl daemon-reload
170-
args:
171-
executable: /bin/bash
165+
- name: Remove legacy environment file drop-in (if present)
166+
file:
167+
path: /etc/systemd/system/containerd.service.d/reaper-env.conf
168+
state: absent
169+
register: legacy_dropin_removed
170+
171+
- name: Reload systemd if legacy drop-in was removed
172+
systemd:
173+
daemon_reload: yes
174+
when: legacy_dropin_removed.changed
172175

173176
- name: Validate containerd configuration
174177
command: containerd config dump

deploy/kubernetes/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,37 @@ kubectl logs reaper-example
139139

140140
Expected output: `Hello from Reaper runtime!`
141141

142+
## Configuration
143+
144+
Reaper reads configuration from `/etc/reaper/reaper.conf` on each node. The Ansible installer creates this file automatically. Environment variables of the same name override file values.
145+
146+
```ini
147+
# /etc/reaper/reaper.conf — written by Ansible installer
148+
REAPER_DNS_MODE=kubernetes
149+
REAPER_RUNTIME_LOG=/run/reaper/runtime.log
150+
```
151+
152+
Common settings:
153+
154+
| Variable | Default | Description |
155+
|----------|---------|-------------|
156+
| `REAPER_DNS_MODE` | `host` | DNS mode: `host` (node resolv.conf) or `kubernetes` (CoreDNS) |
157+
| `REAPER_RUNTIME_LOG` | (none) | Runtime log file path |
158+
| `REAPER_SHIM_LOG` | (none) | Shim log file path |
159+
| `REAPER_OVERLAY_BASE` | `/run/reaper/overlay` | Overlay filesystem base directory |
160+
| `REAPER_FILTER_ENABLED` | `true` | Enable sensitive host file filtering |
161+
162+
For manual installations, create the file:
163+
164+
```bash
165+
sudo mkdir -p /etc/reaper
166+
sudo tee /etc/reaper/reaper.conf << 'EOF'
167+
REAPER_DNS_MODE=kubernetes
168+
REAPER_RUNTIME_LOG=/run/reaper/runtime.log
169+
EOF
170+
sudo systemctl restart containerd
171+
```
172+
142173
## Integration Testing
143174

144175
See [TESTING.md](../../docs/TESTING.md) for:

docs/INTEGRATION_TEST_PLAN.md

Lines changed: 0 additions & 61 deletions
This file was deleted.

docs/PLAN-config-file.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Plan: Cross-OS Configuration File
2+
3+
**Goal**: Replace the Debian-specific `/etc/default/containerd` env-file approach with a Reaper-owned config file at `/etc/reaper/reaper.conf` that works on any Linux distribution.
4+
5+
---
6+
7+
## Problem
8+
9+
Configuration is currently injected via environment variables into containerd's process using:
10+
1. `/etc/default/containerd` — a Debian/Ubuntu convention
11+
2. A systemd drop-in (`EnvironmentFile=-/etc/default/containerd`)
12+
13+
This doesn't work on RHEL/Fedora/SUSE/Arch/Alpine where `/etc/default/` doesn't exist or isn't the convention. The systemd drop-in is also unnecessary coupling — Reaper can read its own config.
14+
15+
## Current REAPER_* Environment Variables
16+
17+
Both binaries read configuration purely from `std::env::var()`:
18+
19+
| Variable | Binary | Default | Purpose |
20+
|----------|--------|---------|---------|
21+
| `REAPER_RUNTIME_ROOT` | both | `/run/reaper` | State directory root |
22+
| `REAPER_RUNTIME_PATH` | shim | auto-detect | Path to runtime binary |
23+
| `REAPER_RUNTIME_LOG` | runtime | (none) | Runtime log file path |
24+
| `REAPER_SHIM_LOG` | shim | (none) | Shim log file path |
25+
| `REAPER_OVERLAY_BASE` | runtime | `/run/reaper/overlay` | Overlay base directory |
26+
| `REAPER_OVERLAY_NS` | runtime | `/run/reaper/shared-mnt-ns` | Namespace bind-mount path |
27+
| `REAPER_OVERLAY_LOCK` | runtime | `/run/reaper/overlay.lock` | Overlay lock file |
28+
| `REAPER_DNS_MODE` | runtime | `host` | DNS mode: host, kubernetes, k8s |
29+
| `REAPER_FILTER_ENABLED` | runtime | `true` | Enable sensitive file filtering |
30+
| `REAPER_FILTER_MODE` | runtime | `append` | Filter mode: append or replace |
31+
| `REAPER_FILTER_PATHS` | runtime | (none) | Colon-separated extra filter paths |
32+
| `REAPER_FILTER_ALLOWLIST` | runtime | (none) | Colon-separated allowlist paths |
33+
| `REAPER_FILTER_DIR` | runtime | `/run/reaper/overlay-filters` | Placeholder file directory |
34+
35+
## Solution
36+
37+
### Config file: `/etc/reaper/reaper.conf`
38+
39+
Simple `KEY=VALUE` format (same as existing env vars):
40+
41+
```
42+
# Reaper runtime configuration
43+
# Lines starting with # are comments. Blank lines are ignored.
44+
# Values here are overridden by environment variables of the same name.
45+
46+
REAPER_DNS_MODE=kubernetes
47+
REAPER_RUNTIME_LOG=/run/reaper/runtime.log
48+
# REAPER_OVERLAY_BASE=/run/reaper/overlay
49+
# REAPER_FILTER_ENABLED=true
50+
```
51+
52+
### Load order (last wins)
53+
54+
1. Built-in defaults (in Rust code, unchanged)
55+
2. Config file values (set as env vars if not already set)
56+
3. Environment variables (override everything)
57+
58+
This means env vars always win — backward compatible with anyone already using systemd env injection.
59+
60+
### Config file search
61+
62+
1. `REAPER_CONFIG` env var (explicit override)
63+
2. `/etc/reaper/reaper.conf` (standard location)
64+
3. No file found → silently continue with env vars / defaults
65+
66+
---
67+
68+
## Implementation
69+
70+
### Phase 1: Shared config loader (`src/config.rs`)
71+
72+
New file shared by both binaries via `#[path]` attribute (no lib crate needed).
73+
74+
```rust
75+
// ~40 lines: read file, parse KEY=VALUE, set env if not already set
76+
pub fn load_config() {
77+
let path = std::env::var("REAPER_CONFIG")
78+
.unwrap_or_else(|_| "/etc/reaper/reaper.conf".to_string());
79+
// read file, skip comments/blanks, split on first '=', set_var if not present
80+
}
81+
```
82+
83+
Key design:
84+
- Uses `std::env::set_var` only if the key is NOT already set (env wins)
85+
- No dependencies — just `std::fs` and `std::env`
86+
- Silently skips if file doesn't exist (not an error)
87+
- Logs a warning for malformed lines (if tracing is initialized — but config loads before tracing, so just silently skip malformed lines)
88+
89+
### Phase 2: Wire into both binaries
90+
91+
**`src/bin/reaper-runtime/main.rs`**: Call `config::load_config()` as the very first line of `main()`, before tracing setup (since `REAPER_RUNTIME_LOG` comes from config).
92+
93+
**`src/bin/containerd-shim-reaper-v2/main.rs`**: Call `config::load_config()` as the very first line of `main()`, before version check and tracing setup.
94+
95+
### Phase 3: Update Ansible playbook
96+
97+
Replace the three tasks (write to `/etc/default/containerd`, write systemd drop-in, daemon-reload) with:
98+
99+
1. Create `/etc/reaper/` directory
100+
2. Write `/etc/reaper/reaper.conf` with template
101+
3. Remove the systemd `EnvironmentFile` drop-in (no longer needed)
102+
103+
### Phase 4: Update documentation
104+
105+
- Update `CLAUDE.md` to mention `/etc/reaper/reaper.conf`
106+
- Update `deploy/kubernetes/README.md` if it references the old approach
107+
108+
---
109+
110+
## File Changes
111+
112+
| File | Action | Description |
113+
|------|--------|-------------|
114+
| `src/config.rs` | **Create** | Shared config file loader (~40 lines) |
115+
| `src/bin/reaper-runtime/main.rs` | **Edit** | Add `mod config; config::load_config();` at top of main() |
116+
| `src/bin/containerd-shim-reaper-v2/main.rs` | **Edit** | Add `mod config; config::load_config();` at top of main() |
117+
| `deploy/ansible/install-reaper.yml` | **Edit** | Write `/etc/reaper/reaper.conf` instead of `/etc/default/containerd` |
118+
| `CLAUDE.md` | **Edit** | Document config file location |
119+
120+
---
121+
122+
## What We're NOT Doing
123+
124+
- **TOML/YAML config**: Adds a parsing dependency for flat key-value data
125+
- **containerd runtime options**: Requires custom protobuf, containerd-specific
126+
- **Distro detection**: Fragile, same result as just owning our config path
127+
- **Removing env var support**: Env vars still override config file (backward compat)

0 commit comments

Comments
 (0)