|
| 1 | +# Development Guide |
| 2 | + |
| 3 | +This document covers development setup, configuration, and contributing to Hypeman. |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +**Go 1.25.4+**, **KVM**, **erofs-utils**, **dnsmasq** |
| 8 | + |
| 9 | +```bash |
| 10 | +# Verify prerequisites |
| 11 | +mkfs.erofs --version |
| 12 | +dnsmasq --version |
| 13 | +``` |
| 14 | + |
| 15 | +**Install on Debian/Ubuntu:** |
| 16 | +```bash |
| 17 | +sudo apt-get install erofs-utils dnsmasq |
| 18 | +``` |
| 19 | + |
| 20 | +**KVM Access:** User must be in `kvm` group for VM access: |
| 21 | +```bash |
| 22 | +sudo usermod -aG kvm $USER |
| 23 | +# Log out and back in, or use: newgrp kvm |
| 24 | +``` |
| 25 | + |
| 26 | +**Network Capabilities:** |
| 27 | + |
| 28 | +Before running or testing Hypeman, ensure IPv4 forwarding is enabled: |
| 29 | + |
| 30 | +```bash |
| 31 | +# Enable IPv4 forwarding (temporary - until reboot) |
| 32 | +sudo sysctl -w net.ipv4.ip_forward=1 |
| 33 | + |
| 34 | +# Enable IPv4 forwarding (persistent across reboots) |
| 35 | +echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf |
| 36 | +sudo sysctl -p |
| 37 | +``` |
| 38 | + |
| 39 | +**Why:** Required for routing traffic between VM network and external network. |
| 40 | + |
| 41 | +The hypeman binary needs network administration capabilities to create bridges and TAP devices: |
| 42 | +```bash |
| 43 | +# After building, grant network capabilities |
| 44 | +sudo setcap 'cap_net_admin,cap_net_bind_service=+eip' /path/to/hypeman |
| 45 | + |
| 46 | +# For development builds |
| 47 | +sudo setcap 'cap_net_admin,cap_net_bind_service=+eip' ./bin/hypeman |
| 48 | + |
| 49 | +# Verify capabilities |
| 50 | +getcap ./bin/hypeman |
| 51 | +``` |
| 52 | + |
| 53 | +**Note:** The `i` (inheritable) flag allows child processes spawned by hypeman (like `ip` and `iptables` commands) to inherit capabilities via the ambient capability set. |
| 54 | + |
| 55 | +**Note:** These capabilities must be reapplied after each rebuild. For production deployments, set capabilities on the installed binary. For local testing, this is handled automatically in `make test`. |
| 56 | + |
| 57 | +**File Descriptor Limits:** |
| 58 | + |
| 59 | +Caddy (used for ingress) requires a higher file descriptor limit than the default on some systems. If you see "Too many open files" errors, increase the limit: |
| 60 | + |
| 61 | +```bash |
| 62 | +# Check current limit (also check with: sudo bash -c 'ulimit -n') |
| 63 | +ulimit -n |
| 64 | + |
| 65 | +# Increase temporarily (current session) |
| 66 | +ulimit -n 65536 |
| 67 | + |
| 68 | +# For persistent changes, add to /etc/security/limits.conf: |
| 69 | +* soft nofile 65536 |
| 70 | +* hard nofile 65536 |
| 71 | +root soft nofile 65536 |
| 72 | +root hard nofile 65536 |
| 73 | +``` |
| 74 | + |
| 75 | +## Configuration |
| 76 | + |
| 77 | +### Environment variables |
| 78 | + |
| 79 | +Hypeman can be configured using the following environment variables: |
| 80 | + |
| 81 | +| Variable | Description | Default | |
| 82 | +|----------|-------------|---------| |
| 83 | +| `PORT` | HTTP server port | `8080` | |
| 84 | +| `DATA_DIR` | Directory for storing VM images, volumes, and other data | `/var/lib/hypeman` | |
| 85 | +| `BRIDGE_NAME` | Name of the network bridge for VM networking | `vmbr0` | |
| 86 | +| `SUBNET_CIDR` | CIDR notation for the VM network subnet (gateway derived automatically) | `10.100.0.0/16` | |
| 87 | +| `UPLINK_INTERFACE` | Host network interface to use for VM internet access | _(auto-detect)_ | |
| 88 | +| `JWT_SECRET` | Secret key for JWT authentication (required for production) | _(empty)_ | |
| 89 | +| `DNS_SERVER` | DNS server IP address for VMs | `1.1.1.1` | |
| 90 | +| `MAX_CONCURRENT_BUILDS` | Maximum number of concurrent image builds | `1` | |
| 91 | +| `MAX_OVERLAY_SIZE` | Maximum size for overlay filesystem | `100GB` | |
| 92 | +| `ENV` | Deployment environment (filters telemetry, e.g. your name for dev) | `unset` | |
| 93 | +| `OTEL_ENABLED` | Enable OpenTelemetry traces/metrics | `false` | |
| 94 | +| `OTEL_ENDPOINT` | OTLP gRPC endpoint | `127.0.0.1:4317` | |
| 95 | +| `OTEL_SERVICE_INSTANCE_ID` | Instance ID for telemetry (differentiates multiple servers) | hostname | |
| 96 | +| `LOG_LEVEL` | Default log level (debug, info, warn, error) | `info` | |
| 97 | +| `LOG_LEVEL_<SUBSYSTEM>` | Per-subsystem log level (API, IMAGES, INSTANCES, NETWORK, VOLUMES, VMM, SYSTEM, EXEC, CADDY) | inherits default | |
| 98 | +| `CADDY_LISTEN_ADDRESS` | Address for Caddy ingress listeners | `0.0.0.0` | |
| 99 | +| `CADDY_ADMIN_ADDRESS` | Address for Caddy admin API | `127.0.0.1` | |
| 100 | +| `CADDY_ADMIN_PORT` | Port for Caddy admin API | `2019` | |
| 101 | +| `CADDY_STOP_ON_SHUTDOWN` | Stop Caddy when hypeman shuts down (set to `true` for dev) | `false` | |
| 102 | +| `ACME_EMAIL` | Email for ACME certificate registration (required for TLS ingresses) | _(empty)_ | |
| 103 | +| `ACME_DNS_PROVIDER` | DNS provider for ACME challenges: `cloudflare` | _(empty)_ | |
| 104 | +| `ACME_CA` | ACME CA URL (empty = Let's Encrypt production) | _(empty)_ | |
| 105 | +| `TLS_ALLOWED_DOMAINS` | Comma-separated allowed domains for TLS (e.g., `*.example.com,api.other.com`) | _(empty)_ | |
| 106 | +| `DNS_PROPAGATION_TIMEOUT` | Max time to wait for DNS propagation (e.g., `2m`) | _(empty)_ | |
| 107 | +| `DNS_RESOLVERS` | Comma-separated DNS resolvers for propagation checking | _(empty)_ | |
| 108 | +| `CLOUDFLARE_API_TOKEN` | Cloudflare API token (when using `cloudflare` provider) | _(empty)_ | |
| 109 | + |
| 110 | +**Important: Subnet Configuration** |
| 111 | + |
| 112 | +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. |
| 113 | + |
| 114 | +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`). |
| 115 | + |
| 116 | +**Alternative subnets if needed:** |
| 117 | +- `172.30.0.0/16` - Private range between common Docker (172.17.x.x) and cloud provider (172.31.x.x) ranges |
| 118 | +- `10.200.0.0/16` - Another private range option |
| 119 | + |
| 120 | +**Example:** |
| 121 | +```bash |
| 122 | +# In your .env file |
| 123 | +SUBNET_CIDR=172.30.0.0/16 |
| 124 | +``` |
| 125 | + |
| 126 | +**Finding the uplink interface (`UPLINK_INTERFACE`)** |
| 127 | + |
| 128 | +`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. |
| 129 | + |
| 130 | +**Quick way to discover it:** |
| 131 | +```bash |
| 132 | +# Ask the kernel which interface is used to reach the internet |
| 133 | +ip route get 1.1.1.1 |
| 134 | +``` |
| 135 | +Look for the `dev` field in the output, for example: |
| 136 | +```text |
| 137 | +1.1.1.1 via 192.168.12.1 dev wlp2s0 src 192.168.12.98 |
| 138 | +``` |
| 139 | +In this case, `wlp2s0` is the uplink interface, so you would set: |
| 140 | +```bash |
| 141 | +UPLINK_INTERFACE=wlp2s0 |
| 142 | +``` |
| 143 | + |
| 144 | +You can also inspect all routes: |
| 145 | +```bash |
| 146 | +ip route show |
| 147 | +``` |
| 148 | +Pick the interface used by the default route (usually the line starting with `default`). Avoid using local bridges like `docker0`, `br-...`, `virbr0`, or `vmbr0` as the uplink; those are typically internal virtual networks, not your actual internet-facing interface. |
| 149 | + |
| 150 | +### TLS Ingress (HTTPS) |
| 151 | + |
| 152 | +Hypeman uses Caddy with automatic ACME certificates for TLS termination. Certificates are issued via DNS-01 challenges (Cloudflare). |
| 153 | + |
| 154 | +To enable TLS ingresses: |
| 155 | + |
| 156 | +1. Configure ACME credentials in your `.env`: |
| 157 | +```bash |
| 158 | +# Required for any TLS ingress |
| 159 | + |
| 160 | + |
| 161 | +# For Cloudflare |
| 162 | +ACME_DNS_PROVIDER=cloudflare |
| 163 | +CLOUDFLARE_API_TOKEN=your-api-token |
| 164 | +``` |
| 165 | + |
| 166 | +2. Create an ingress with TLS enabled: |
| 167 | +```bash |
| 168 | +curl -X POST http://localhost:8080/v1/ingresses \ |
| 169 | + -H "Content-Type: application/json" \ |
| 170 | + -d '{ |
| 171 | + "name": "my-https-app", |
| 172 | + "rules": [{ |
| 173 | + "match": {"hostname": "app.example.com", "port": 443}, |
| 174 | + "target": {"instance": "my-instance", "port": 8080}, |
| 175 | + "tls": true, |
| 176 | + "redirect_http": true |
| 177 | + }] |
| 178 | + }' |
| 179 | +``` |
| 180 | + |
| 181 | +Certificates are stored in `$DATA_DIR/caddy/data/` and auto-renewed by Caddy. |
| 182 | + |
| 183 | +### Setup |
| 184 | + |
| 185 | +```bash |
| 186 | +cp .env.example .env |
| 187 | +# Edit .env and set JWT_SECRET and other configuration values |
| 188 | +``` |
| 189 | + |
| 190 | +### Data directory |
| 191 | + |
| 192 | +Hypeman stores data in a configurable directory. Configure permissions for this directory. |
| 193 | + |
| 194 | +```bash |
| 195 | +sudo mkdir /var/lib/hypeman |
| 196 | +sudo chown $USER:$USER /var/lib/hypeman |
| 197 | +``` |
| 198 | + |
| 199 | +### Dockerhub login |
| 200 | + |
| 201 | +Requires Docker Hub authentication to avoid rate limits when running the tests: |
| 202 | +```bash |
| 203 | +docker login |
| 204 | +``` |
| 205 | + |
| 206 | +Docker itself isn't required to be installed. `~/.docker/config.json` is a standard used for handling registry authentication. |
| 207 | + |
| 208 | +## Build |
| 209 | + |
| 210 | +```bash |
| 211 | +make build |
| 212 | +``` |
| 213 | + |
| 214 | +## Running the Server |
| 215 | + |
| 216 | +1. Generate a JWT token for testing (optional): |
| 217 | +```bash |
| 218 | +make gen-jwt |
| 219 | +``` |
| 220 | + |
| 221 | +2. Start the server with hot-reload for development: |
| 222 | +```bash |
| 223 | +make dev |
| 224 | +``` |
| 225 | +The server will start on port 8080 (configurable via `PORT` environment variable). |
| 226 | + |
| 227 | +### Local OpenTelemetry (optional) |
| 228 | + |
| 229 | +To collect traces and metrics locally, run the Grafana LGTM stack (Loki, Grafana, Tempo, Mimir): |
| 230 | + |
| 231 | +```bash |
| 232 | +# Start Grafana LGTM (UI at http://localhost:3000, login: admin/admin) |
| 233 | +# Note, if you are developing on a shared server, you can use the same LGTM stack as your peer(s) |
| 234 | +# You will be able to sort your metrics, traces, and logs using the ENV configuration (see below) |
| 235 | +docker run -d --name lgtm \ |
| 236 | + -p 127.0.0.1:3000:3000 \ |
| 237 | + -p 127.0.0.1:4317:4317 \ |
| 238 | + -p 127.0.0.1:4318:4318 \ |
| 239 | + -p 127.0.0.1:9090:9090 \ |
| 240 | + -p 127.0.0.1:4040:4040 \ |
| 241 | + grafana/otel-lgtm:latest |
| 242 | + |
| 243 | +# If developing on a remote server, forward the port to your local machine: |
| 244 | +# ssh -L 3001:localhost:3000 your-server (then open http://localhost:3001) |
| 245 | + |
| 246 | +# Enable OTel in .env (set ENV to your name to filter your telemetry) |
| 247 | +echo "OTEL_ENABLED=true" >> .env |
| 248 | +echo "ENV=yourname" >> .env |
| 249 | + |
| 250 | +# Restart dev server |
| 251 | +make dev |
| 252 | +``` |
| 253 | + |
| 254 | +Open http://localhost:3000 to view traces (Tempo), metrics (Mimir), and logs (Loki) in Grafana. |
| 255 | + |
| 256 | +**Import the Hypeman dashboard:** |
| 257 | +1. Go to Dashboards → New → Import |
| 258 | +2. Upload `dashboards/hypeman.json` or paste its contents |
| 259 | +3. Select the Prometheus datasource and click Import |
| 260 | + |
| 261 | +Use the Environment/Instance dropdowns to filter by `deployment.environment` or `service.instance.id`. |
| 262 | + |
| 263 | +## Testing |
| 264 | + |
| 265 | +Network tests require elevated permissions to create bridges and TAP devices. |
| 266 | + |
| 267 | +```bash |
| 268 | +make test |
| 269 | +``` |
| 270 | + |
| 271 | +The test command compiles test binaries, grants capabilities via `sudo setcap`, then runs tests as the current user (not root). You may be prompted for your sudo password during the capability grant step. |
| 272 | + |
| 273 | +## Code Generation |
| 274 | + |
| 275 | +After modifying `openapi.yaml`, regenerate the Go code: |
| 276 | + |
| 277 | +```bash |
| 278 | +make oapi-generate |
| 279 | +``` |
| 280 | + |
| 281 | +After modifying dependency injection in `cmd/api/wire.go` or `lib/providers/providers.go`, regenerate wire code: |
| 282 | + |
| 283 | +```bash |
| 284 | +make generate-wire |
| 285 | +``` |
| 286 | + |
| 287 | +Or generate everything at once: |
| 288 | + |
| 289 | +```bash |
| 290 | +make generate-all |
| 291 | +``` |
0 commit comments