Skip to content

Commit 0a8b795

Browse files
authored
feat: migrate config system from dotenv to koanf/YAML (#102)
* feat: migrate config system from dotenv to koanf/YAML Replace godotenv-based .env config loading with koanf and YAML config files across the entire stack: Server (hypeman-api): - Config struct now uses koanf tags for YAML unmarshaling - Loads config.yaml from platform-specific paths with env var overrides - CONFIG_PATH env var for explicit config file location Token tool (hypeman-token): - Reads jwt_secret directly from config.yaml (no more wrapper scripts) - Added -duration flag for configurable token expiry Install script: - Generates config.yaml instead of dotenv-style config - Generates ~/.config/hypeman/cli.yaml with pre-authenticated token - Removed all wrapper scripts, replaced with symlinks on Linux - Updated launchd/systemd service definitions Also adds config.example.yaml, config.darwin.example.yaml, and cli.example.yaml as reference templates. * fix: address bot review feedback on config loading - Pass JWT_SECRET explicitly to hypeman-token in install.sh (fixes Linux installs where config.yaml is root-only) - Return error from config.Load() when explicit CONFIG_PATH fails instead of silently falling back to defaults - Deduplicate config paths: gen-jwt now imports and uses config.GetDefaultConfigPaths() instead of maintaining its own copy * fix: skip empty env vars to preserve config/default values Use env.ProviderWithValue instead of env.Provider so that empty environment variables (e.g. PORT="") don't override valid defaults or YAML config values. This preserves the old getEnv() behavior. * fix: gen-jwt respects CONFIG_PATH, install.sh uses sudo for config read - hypeman-token now checks CONFIG_PATH env var before default paths, matching hypeman-api behavior for custom config locations - install.sh uses $SUDO when reading jwt_secret from existing config file on Linux reinstalls (file is 640 root:root) * fix: robust YAML parsing in install.sh and restore make gen-jwt dev workflow - Make jwt_secret and port grep/sed pipelines handle leading whitespace, single/double quotes, trailing whitespace, and multiple matches - Update make gen-jwt to auto-detect local config.yaml via CONFIG_PATH, restoring the dev workflow that previously relied on godotenv/.env Addresses bugbot review comments on #102. * refactor: use __ delimiter for nested env vars, rename example files - Replace explicit envKeyMap with koanf's __ delimiter for auto-mapping env vars to nested config paths (e.g. CADDY__LISTEN_ADDRESS -> caddy.listen_address) - Rename config.darwin.example.yaml -> config.example.darwin.yaml for consistent naming - Remove .env.example and .env.darwin.example references - Update DEVELOPMENT.md to document __ convention and YAML-first config - Update README.md configuration table BREAKING: Old flat env var names (CADDY_LISTEN_ADDRESS, BRIDGE_NAME, etc.) no longer work. Use double-underscore for nested keys (CADDY__LISTEN_ADDRESS, NETWORK__BRIDGE_NAME) or configure via config.yaml instead. * docs: simplify config docs to reference YAML keys, not env vars README and DEVELOPMENT.md now document configuration using YAML key names (dot notation for nested keys). Env var override convention mentioned once as a footnote rather than being the primary reference. * chore: default build.builder_image to "none" (built on first run) * fix: BSD sed compat for docker_socket injection in install.sh The macOS BSD sed `a\` (append) command was inserting the docker_socket line on the same line as builder_image, producing invalid YAML. Fix by: 1. Including docker_socket in example config templates so the simpler sed s| replacement path is used instead of sed a\. 2. Fixing the fallback sed a\ to use BSD-compatible s| with literal newline. Also updates builder_image default to "none" in example files. * fix: export CLI env vars in e2e test for released CLI compat The released CLI (v0.11.0) doesn't support cli.yaml yet, so the e2e test needs to export HYPEMAN_BASE_URL and HYPEMAN_API_KEY env vars. Once the CLI release with koanf/yaml support ships, cli.yaml will handle this and the env vars become redundant. * Revert "fix: export CLI env vars in e2e test for released CLI compat" This reverts commit a6e82a5. * feat: support CLI_BRANCH to build CLI from source in install.sh Add CLI_BRANCH env var to install.sh that clones and builds the CLI from the specified branch of kernel/hypeman-cli instead of downloading a release binary. Useful for testing unreleased CLI features. The e2e test now passes CLI_BRANCH through to install.sh. Temporarily set to koanf-yaml-config in CI so the e2e test uses the CLI with cli.yaml config file support. * fix: update GPU test files to use nested config struct Missed gpu_module_test.go, gpu_e2e_test.go, and gpu_inference_test.go during the config nesting migration. Uses config.NetworkConfig for BridgeName, SubnetCIDR, and DNSServer fields. * fix: remove fallback config generation from install.sh The inline heredoc configs duplicated defaults defined in the example YAML files. Treat a failed config template download as a hard error instead of silently generating a potentially stale config. * cleanup: remove redundant OS check inside darwin block * fix: hard error if jwt_secret not found in config template Without jwt_secret the server can't authenticate API requests, so silently skipping it leaves the install in a broken state. * fix: remove accidentally committed gen-jwt binary * docs: remove token tool row from config table * refactor: remove dead fallbacks and "none" sentinel for builder_image - Remove unreachable zero-value fallbacks in ProvideBuildManager that duplicated defaults already set by defaultConfig(). - Replace the "none" sentinel for builder_image with empty string. Empty string now canonically means "build from embedded Dockerfile on first run", which is simpler than a magic string. * fix: validate build.timeout and build.max_concurrent_source_builds Reject zero/negative values at startup rather than passing them through to the build manager where they'd cause immediate timeouts or a zero-capacity semaphore that blocks all builds.
1 parent eeb78cf commit 0a8b795

35 files changed

Lines changed: 1228 additions & 697 deletions

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ jobs:
112112
- name: Install dependencies
113113
run: brew list caddy &>/dev/null || brew install caddy
114114
- name: Run E2E install test
115-
run: bash scripts/e2e-install-test.sh
115+
run: CLI_BRANCH=koanf-yaml-config bash scripts/e2e-install-test.sh
116116
- name: Cleanup on failure
117117
if: failure()
118118
run: bash scripts/uninstall.sh || true

DEVELOPMENT.md

Lines changed: 79 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -89,45 +89,46 @@ root hard nofile 65536
8989

9090
## Configuration
9191

92-
### Environment variables
93-
94-
Hypeman can be configured using the following environment variables:
95-
96-
| Variable | Description | Default |
97-
| -------------------------- | -------------------------------------------------------------------------------------------- | ------------------ |
98-
| `PORT` | HTTP server port | `8080` |
99-
| `DATA_DIR` | Directory for storing VM images, volumes, and other data | `/var/lib/hypeman` |
100-
| `BRIDGE_NAME` | Name of the network bridge for VM networking | `vmbr0` |
101-
| `SUBNET_CIDR` | CIDR notation for the VM network subnet (gateway derived automatically) | `10.100.0.0/16` |
102-
| `UPLINK_INTERFACE` | Host network interface to use for VM internet access | _(auto-detect)_ |
103-
| `JWT_SECRET` | Secret key for JWT authentication (required for production) | _(empty)_ |
104-
| `DNS_SERVER` | DNS server IP address for VMs | `1.1.1.1` |
105-
| `MAX_CONCURRENT_BUILDS` | Maximum number of concurrent image builds | `1` |
106-
| `MAX_OVERLAY_SIZE` | Maximum size for overlay filesystem | `100GB` |
107-
| `ENV` | Deployment environment (filters telemetry, e.g. your name for dev) | `unset` |
108-
| `OTEL_ENABLED` | Enable OpenTelemetry traces/metrics | `false` |
109-
| `OTEL_ENDPOINT` | OTLP gRPC endpoint | `127.0.0.1:4317` |
110-
| `OTEL_SERVICE_INSTANCE_ID` | Instance ID for telemetry (differentiates multiple servers) | hostname |
111-
| `LOG_LEVEL` | Default log level (debug, info, warn, error) | `info` |
112-
| `LOG_LEVEL_<SUBSYSTEM>` | Per-subsystem log level (API, IMAGES, INSTANCES, NETWORK, VOLUMES, VMM, SYSTEM, EXEC, CADDY) | inherits default |
113-
| `CADDY_LISTEN_ADDRESS` | Address for Caddy ingress listeners | `0.0.0.0` |
114-
| `CADDY_ADMIN_ADDRESS` | Address for Caddy admin API | `127.0.0.1` |
115-
| `CADDY_ADMIN_PORT` | Port for Caddy admin API | `2019` |
116-
| `CADDY_STOP_ON_SHUTDOWN` | Stop Caddy when hypeman shuts down (set to `true` for dev) | `false` |
117-
| `ACME_EMAIL` | Email for ACME certificate registration (required for TLS ingresses) | _(empty)_ |
118-
| `ACME_DNS_PROVIDER` | DNS provider for ACME challenges: `cloudflare` | _(empty)_ |
119-
| `ACME_CA` | ACME CA URL (empty = Let's Encrypt production) | _(empty)_ |
120-
| `TLS_ALLOWED_DOMAINS` | Comma-separated allowed domains for TLS (e.g., `*.example.com,api.other.com`) | _(empty)_ |
121-
| `DNS_PROPAGATION_TIMEOUT` | Max time to wait for DNS propagation (e.g., `2m`) | _(empty)_ |
122-
| `DNS_RESOLVERS` | Comma-separated DNS resolvers for propagation checking | _(empty)_ |
123-
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token (when using `cloudflare` provider) | _(empty)_ |
124-
| `DOCKER_SOCKET` | Path to Docker socket (for builder image builds) | `/var/run/docker.sock` |
92+
Hypeman reads configuration from a YAML config file. See `config.example.yaml` (Linux) and `config.example.darwin.yaml` (macOS) for all available settings with comments.
93+
94+
The config file is searched in these locations (first match wins):
95+
- Path specified by `CONFIG_PATH` environment variable
96+
- `/etc/hypeman/config.yaml` (Linux)
97+
- `~/.config/hypeman/config.yaml` (all platforms)
98+
99+
Common settings:
100+
101+
| Key | Description | Default |
102+
|-----|-------------|---------|
103+
| `port` | HTTP server port | `8080` |
104+
| `data_dir` | Data directory for VM images, volumes, etc. | `/var/lib/hypeman` |
105+
| `jwt_secret` | Secret key for JWT authentication (required) | _(empty)_ |
106+
| `env` | Deployment environment (filters telemetry) | `unset` |
107+
| `network.bridge_name` | Network bridge for VM networking | `vmbr0` |
108+
| `network.subnet_cidr` | CIDR for the VM network subnet | `10.100.0.0/16` |
109+
| `network.uplink_interface` | Host interface for VM internet access | _(auto-detect)_ |
110+
| `network.dns_server` | DNS server for VMs | `1.1.1.1` |
111+
| `caddy.listen_address` | Address for Caddy ingress listeners | `0.0.0.0` |
112+
| `caddy.admin_address` | Address for Caddy admin API | `127.0.0.1` |
113+
| `caddy.admin_port` | Port for Caddy admin API | `2019` |
114+
| `caddy.stop_on_shutdown` | Stop Caddy when hypeman shuts down | `false` |
115+
| `logging.level` | Log level (debug, info, warn, error) | `info` |
116+
| `otel.enabled` | Enable OpenTelemetry traces/metrics | `false` |
117+
| `otel.endpoint` | OTLP gRPC endpoint | `127.0.0.1:4317` |
118+
| `limits.max_concurrent_builds` | Max concurrent image builds | `1` |
119+
| `limits.max_overlay_size` | Max overlay filesystem size | `100GB` |
120+
| `acme.email` | Email for ACME certificate registration | _(empty)_ |
121+
| `acme.dns_provider` | DNS provider for ACME challenges | _(empty)_ |
122+
| `acme.cloudflare_api_token` | Cloudflare API token | _(empty)_ |
123+
| `build.docker_socket` | Path to Docker socket | `/var/run/docker.sock` |
124+
125+
Environment variables can also override any config key using `__` as the nesting separator (e.g. `CADDY__LISTEN_ADDRESS` overrides `caddy.listen_address`).
125126

126127
**Important: Subnet Configuration**
127128

128129
The default subnet `10.100.0.0/16` is chosen to avoid common conflicts. Hypeman will detect conflicts with existing routes on startup and fail with guidance.
129130

130-
If you need a different subnet, set `SUBNET_CIDR` in your environment. The gateway is automatically derived as the first IP in the subnet (e.g., `10.100.0.0/16``10.100.0.1`).
131+
If you need a different subnet, set `network.subnet_cidr` in your config file. The gateway is automatically derived as the first IP in the subnet (e.g., `10.100.0.0/16``10.100.0.1`).
131132

132133
**Alternative subnets if needed:**
133134

@@ -136,14 +137,15 @@ If you need a different subnet, set `SUBNET_CIDR` in your environment. The gatew
136137

137138
**Example:**
138139

139-
```bash
140-
# In your .env file
141-
SUBNET_CIDR=172.30.0.0/16
140+
```yaml
141+
# In your config.yaml
142+
network:
143+
subnet_cidr: 172.30.0.0/16
142144
```
143145
144-
**Finding the uplink interface (`UPLINK_INTERFACE`)**
146+
**Finding the uplink interface (`network.uplink_interface`)**
145147

146-
`UPLINK_INTERFACE` tells Hypeman which host interface to use for routing VM traffic to the outside world (for iptables MASQUERADE rules). On many hosts this is `eth0`, but laptops and more complex setups often use WiFi or other names.
148+
`network.uplink_interface` tells Hypeman which host interface to use for routing VM traffic to the outside world (for iptables MASQUERADE rules). On many hosts this is `eth0`, but laptops and more complex setups often use Wi-Fi or other names.
147149

148150
**Quick way to discover it:**
149151

@@ -158,10 +160,11 @@ Look for the `dev` field in the output, for example:
158160
1.1.1.1 via 192.168.12.1 dev wlp2s0 src 192.168.12.98
159161
```
160162

161-
In this case, `wlp2s0` is the uplink interface, so you would set:
163+
In this case, `wlp2s0` is the uplink interface, so you would set in your config file:
162164

163-
```bash
164-
UPLINK_INTERFACE=wlp2s0
165+
```yaml
166+
network:
167+
uplink_interface: wlp2s0
165168
```
166169

167170
You can also inspect all routes:
@@ -178,15 +181,13 @@ Hypeman uses Caddy with automatic ACME certificates for TLS termination. Certifi
178181

179182
To enable TLS ingresses:
180183

181-
1. Configure ACME credentials in your `.env`:
184+
1. Configure ACME credentials in your `config.yaml`:
182185

183-
```bash
184-
# Required for any TLS ingress
185-
ACME_EMAIL=admin@example.com
186-
187-
# For Cloudflare
188-
ACME_DNS_PROVIDER=cloudflare
189-
CLOUDFLARE_API_TOKEN=your-api-token
186+
```yaml
187+
acme:
188+
email: admin@example.com
189+
dns_provider: cloudflare
190+
cloudflare_api_token: your-api-token
190191
```
191192

192193
2. Create an ingress with TLS enabled:
@@ -205,13 +206,13 @@ curl -X POST http://localhost:8080/v1/ingresses \
205206
}'
206207
```
207208

208-
Certificates are stored in `$DATA_DIR/caddy/data/` and auto-renewed by Caddy.
209+
Certificates are stored in `<data_dir>/caddy/data/` and auto-renewed by Caddy.
209210

210211
### Setup
211212

212213
```bash
213-
cp .env.example .env
214-
# Edit .env and set JWT_SECRET and other configuration values
214+
cp config.example.yaml ~/.config/hypeman/config.yaml
215+
# Edit config.yaml and set jwt_secret and other configuration values
215216
```
216217

217218
### Data directory
@@ -253,15 +254,16 @@ make gen-jwt
253254
make dev
254255
```
255256

256-
The server will start on port 8080 (configurable via `PORT` environment variable).
257+
The server will start on port 8080 (configurable via `port` in config.yaml).
257258

258259
### Setting Up the Builder Image (for Dockerfile builds)
259260

260-
The builder image is required for `hypeman build` to work. When `BUILDER_IMAGE` is unset or empty, the server will automatically build and push the builder image on startup using Docker. This is the easiest way to get started — just ensure Docker is available and run `make dev`. If a build is requested while the builder image is still being prepared, the server returns a clear error asking you to retry shortly.
261+
The builder image is required for `hypeman build` to work. When `build.builder_image` is unset or empty, the server will automatically build and push the builder image on startup using Docker. This is the easiest way to get started — just ensure Docker is available and run `make dev`. If a build is requested while the builder image is still being prepared, the server returns a clear error asking you to retry shortly.
261262

262-
On macOS with Colima, set the Docker socket path:
263-
```bash
264-
DOCKER_SOCKET=$HOME/.colima/default/docker.sock
263+
On macOS with Colima, set the Docker socket path in your config file:
264+
```yaml
265+
build:
266+
docker_socket: ~/.colima/default/docker.sock
265267
```
266268

267269
### Local OpenTelemetry (optional)
@@ -287,9 +289,11 @@ docker run -d --name lgtm \
287289
# If developing on a remote server, forward the port to your local machine (or YOLO):
288290
# ssh -L 3001:localhost:3000 your-server (then open http://localhost:3001)
289291
290-
# Enable OTel in .env (set ENV to your name to filter your telemetry)
291-
echo "OTEL_ENABLED=true" >> .env
292-
echo "ENV=yourname" >> .env
292+
# Enable OTel in config.yaml (set env to your name to filter your telemetry)
293+
# Add to your config.yaml:
294+
# otel:
295+
# enabled: true
296+
# env: yourname
293297
294298
# Restart dev server
295299
make dev
@@ -359,8 +363,9 @@ export PATH="/opt/homebrew/opt/e2fsprogs/bin:/opt/homebrew/opt/e2fsprogs/sbin:$P
359363
# Add to ~/.zshrc for persistence
360364
361365
# 3. Configure environment
362-
cp .env.darwin.example .env
363-
# Edit .env as needed (defaults work for local development)
366+
mkdir -p ~/.config/hypeman
367+
cp config.example.darwin.yaml ~/.config/hypeman/config.yaml
368+
# Edit config.yaml as needed (defaults work for local development)
364369
365370
# 4. Create data directory
366371
mkdir -p ~/Library/Application\ Support/hypeman
@@ -387,16 +392,16 @@ The `make dev` command automatically detects macOS and:
387392

388393
### macOS-Specific Configuration
389394

390-
The following environment variables work differently on macOS (see `.env.darwin.example`):
395+
The following config keys work differently on macOS (see `config.example.darwin.yaml`):
391396

392-
| Variable | Linux | macOS |
397+
| Config key | Linux | macOS |
393398
|----------|-------|-------|
394-
| `DEFAULT_HYPERVISOR` | `cloud-hypervisor` | `vz` |
395-
| `DATA_DIR` | `/var/lib/hypeman` | `~/Library/Application Support/hypeman` |
396-
| `INTERNAL_DNS_PORT` | `5353` | `5354` (5353 is used by mDNSResponder) |
397-
| `BRIDGE_NAME` | Used | Ignored (NAT) |
398-
| `SUBNET_CIDR` | Used | Ignored (NAT) |
399-
| `UPLINK_INTERFACE` | Used | Ignored (NAT) |
399+
| `hypervisor.default` | `cloud-hypervisor` | `vz` |
400+
| `data_dir` | `/var/lib/hypeman` | `~/Library/Application Support/hypeman` |
401+
| `caddy.internal_dns_port` | `5353` | `5354` (5353 is used by mDNSResponder) |
402+
| `network.bridge_name` | Used | Ignored (NAT) |
403+
| `network.subnet_cidr` | Used | Ignored (NAT) |
404+
| `network.uplink_interface` | Used | Ignored (NAT) |
400405
| Network rate limiting | Supported | Not supported |
401406
| GPU passthrough | Supported (VFIO) | Not supported |
402407

@@ -463,8 +468,8 @@ brew install caddy
463468

464469
**"address already in use" on port 5353**
465470
- Port 5353 is used by mDNSResponder (Bonjour) on macOS
466-
- Use port 5354 instead: `INTERNAL_DNS_PORT=5354` in `.env`
467-
- The `.env.darwin.example` already has this configured correctly
471+
- Use port 5354 instead: set `caddy.internal_dns_port: 5354` in `config.yaml`
472+
- The `config.example.darwin.yaml` already has this configured correctly
468473

469474
**"Virtualization.framework is not available"**
470475
- Ensure you're on macOS 11.0+
@@ -475,7 +480,7 @@ brew install caddy
475480
- Check: `sw_vers` and `uname -m` (should be arm64)
476481

477482
**VM fails to start**
478-
- Check serial log: `$DATA_DIR/instances/<id>/serial.log`
483+
- Check serial log: `<data_dir>/instances/<id>/serial.log`
479484
- Ensure kernel and initrd paths are correct in config
480485

481486
**IOMMU/VFIO warnings at startup**

Makefile

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ $(BIN_DIR):
1111
OAPI_CODEGEN ?= $(BIN_DIR)/oapi-codegen
1212
AIR ?= $(BIN_DIR)/air
1313
WIRE ?= $(BIN_DIR)/wire
14-
GODOTENV ?= $(BIN_DIR)/godotenv
1514
XCADDY ?= $(BIN_DIR)/xcaddy
1615

1716
# Install oapi-codegen
@@ -26,15 +25,11 @@ $(AIR): | $(BIN_DIR)
2625
$(WIRE): | $(BIN_DIR)
2726
GOBIN=$(BIN_DIR) go install github.com/google/wire/cmd/wire@latest
2827

29-
# Install godotenv for loading .env files
30-
$(GODOTENV): | $(BIN_DIR)
31-
GOBIN=$(BIN_DIR) go install github.com/joho/godotenv/cmd/godotenv@latest
32-
3328
# Install xcaddy for building Caddy with plugins
3429
$(XCADDY): | $(BIN_DIR)
3530
GOBIN=$(BIN_DIR) go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
3631

37-
install-tools: $(OAPI_CODEGEN) $(AIR) $(WIRE) $(GODOTENV) $(XCADDY)
32+
install-tools: $(OAPI_CODEGEN) $(AIR) $(WIRE) $(XCADDY)
3833

3934
# Download Cloud Hypervisor binaries
4035
download-ch-binaries:
@@ -261,8 +256,9 @@ test-darwin: build-embedded sign-vz-shim
261256

262257
# Generate JWT token for testing
263258
# Usage: make gen-jwt [USER_ID=test-user]
264-
gen-jwt: $(GODOTENV)
265-
@$(GODOTENV) -f .env go run ./cmd/gen-jwt -user-id $${USER_ID:-test-user}
259+
# Checks CONFIG_PATH, then local config.yaml, then default config paths
260+
gen-jwt:
261+
@CONFIG_PATH=$${CONFIG_PATH:-$$([ -f config.yaml ] && echo config.yaml)} go run ./cmd/gen-jwt -user-id $${USER_ID:-test-user}
266262

267263
# Build the generic builder image for builds
268264
build-builder:

README.md

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,59 @@ Install Hypeman (Linux and macOS supported):
3535
curl -fsSL https://get.hypeman.sh | bash
3636
```
3737

38-
This installs both the Hypeman server and CLI. The installer handles all dependencies and configuration automatically.
38+
This installs the Hypeman server, CLI, and token tool. The installer:
39+
- Generates a YAML config file with a random JWT secret
40+
- Starts the server as a system service (launchd on macOS, systemd on Linux)
41+
- Creates a CLI config file (`~/.config/hypeman/cli.yaml`) with a pre-authenticated token
3942

40-
## CLI Installation
43+
No environment variables needed -- just run `hypeman` commands immediately after install.
4144

42-
To use Hypeman via the CLI on a separate machine:
45+
## Remote CLI Access
4346

44-
**Homebrew:**
47+
To use the Hypeman CLI from a **different machine** than the server:
48+
49+
**Homebrew (macOS):**
4550
```bash
4651
brew install kernel/tap/hypeman
4752
```
4853

54+
**Linux:**
55+
```bash
56+
curl -fsSL https://get.hypeman.sh/cli | bash
57+
```
58+
4959
**Go:**
5060
```bash
5161
go install 'github.com/kernel/hypeman-cli/cmd/hypeman@latest'
5262
```
5363

54-
**Configure CLI access:**
64+
Then create a CLI config file at `~/.config/hypeman/cli.yaml`:
65+
66+
```yaml
67+
base_url: http://<server-host>:8080
68+
api_key: "<token>"
69+
```
70+
71+
To generate a token, run `hypeman-token` on the server:
5572

5673
```bash
57-
export HYPEMAN_API_KEY="<token>"
58-
export HYPEMAN_BASE_URL="http://<host>:8080"
74+
hypeman-token -user-id "my-user" -duration 8760h
5975
```
6076

77+
Environment variables (`HYPEMAN_BASE_URL`, `HYPEMAN_API_KEY`) and CLI flags (`--base-url`) also work and take precedence over the config file.
78+
79+
## Configuration
80+
81+
Hypeman is configured via YAML config files.
82+
83+
| Component | Config File |
84+
|-----------|-------------|
85+
| Server | `/etc/hypeman/config.yaml` (Linux) or `~/.config/hypeman/config.yaml` (macOS) |
86+
| CLI | `~/.config/hypeman/cli.yaml` |
87+
88+
89+
See [`config.example.yaml`](config.example.yaml) (Linux) and [`config.example.darwin.yaml`](config.example.darwin.yaml) (macOS) for all available server options.
90+
6191
## Usage
6292

6393
```bash

cli.example.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# =============================================================================
2+
# Hypeman CLI Configuration
3+
# =============================================================================
4+
# Place this file at ~/.config/hypeman/cli.yaml
5+
#
6+
# The install script automatically generates this file with a valid token
7+
# for local access. For remote CLI access, create this file manually.
8+
#
9+
# Configuration precedence (highest to lowest):
10+
# 1. CLI flags (e.g., --base-url)
11+
# 2. Environment variables (HYPEMAN_BASE_URL, HYPEMAN_API_KEY)
12+
# 3. This YAML config file
13+
# =============================================================================
14+
15+
# Hypeman API URL
16+
base_url: http://localhost:8080
17+
18+
# API authentication token (generated by hypeman-token)
19+
api_key: ""

cmd/api/api/api_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ import (
2525
// newTestService creates an ApiService for testing with automatic cleanup
2626
func newTestService(t *testing.T) *ApiService {
2727
cfg := &config.Config{
28-
DataDir: t.TempDir(),
29-
BridgeName: "vmbr0",
30-
SubnetCIDR: "10.100.0.0/16",
31-
DNSServer: "1.1.1.1",
28+
DataDir: t.TempDir(),
29+
Network: config.NetworkConfig{
30+
BridgeName: "vmbr0",
31+
SubnetCIDR: "10.100.0.0/16",
32+
DNSServer: "1.1.1.1",
33+
},
3234
}
3335

3436
p := paths.New(cfg.DataDir)

0 commit comments

Comments
 (0)