Minimal Firecracker control-plane + TLS forwarder in Go.
mergen-fire provides:
mergen: daemon + image lifecycle CLImergen-forwarder: TLS SNI terminating netns-aware TCP proxy (pre-Envoy dataplane bridge)mergen-converter: OCI/Docker registry image -> OCI-aligned MicroVM rootfs convertermergen-init: Go PID1 init binary for converted rootfs
- Requirements
- Quick start
- API behavior notes
- Configuration
- Forwarder Configuration
- Systemd Template and Mergen Lifecycle
- Testing
- Run Firecracker VMs with simple REST endpoints.
- Keep VM processes alive even if manager process crashes (
systemdowns VM services). - Use deterministic filesystem layout for config, runtime, and data.
- Prepare hook points for future integrations (Envoy xDS, Consul, webhooks).
- Lifecycle endpoints:
POST /v1/vmsPOST /v1/vms/:id/startPOST /v1/vms/:id/stopDELETE /v1/vms/:idGET /v1/vms/:id
GET /v1/vms/:id/meta.jsonGET /v1/vms/:id/vm.jsonGET /v1/vms/:id/hooks.jsonGET /v1/vmsGET /v1/images- File store:
vm.json(Firecracker config)meta.json(manager metadata)hooks.json(optional VM hooks)env(systemd env file)
systemdservice model:mergen@<uuid>.service- Basic port publish + sequential IP allocation
- Per-VM HTTP target port metadata (
httpPort) for TLS-terminated:443routing - Per-VM lock file to prevent lifecycle race conditions
- Structured logging with configurable level/format
- Best-effort hooks:
onCreateonDeleteonStartonStop
- Control plane: Go HTTP API server (
cmd/mergen server) - CLI plane:
cmd/mergen(server,config init/login,images ls,images pull/push base) - Forwarding plane (pre-Envoy): TLS SNI proxy (
cmd/mergen-forwarder) - Image conversion plane: Registry-image-to-rootfs converter (
converter/cmd/mergen-converter) - Data plane:
systemd+ Firecracker/Jailer processes - State source: filesystem under
MGR_CONFIG_ROOT,MGR_RUN_ROOT,MGR_DATA_ROOT
Forwarder design details: docs/forwarder-design.md
cmd/mergen: daemon + image lifecycle CLI entrypointcmd/mergend: legacy daemon-only wrapper aroundmergen servercmd/mergen-forwarder: TLS SNI forwarderconverter/cmd/mergen-converter: registry image conversion CLIconverter/internal/converter: native image pull/cache/rootfs/ext4 conversion pipelineconverter/README.md: standalone converter usage, login, push, and pull flowruntime/cmd/mergen-init: in-guest init/PID1 runtimeruntime/cmd/mergen-agent: payload launcher inside guestinternal/api: REST handlersinternal/manager: orchestration/service layerinternal/forwarder: SNI resolver + TLS proxy + netns dialerinternal/store: filesystem persistenceinternal/systemd:systemctlwrapperinternal/firecracker: VM config rendering and socket probeinternal/network: host-port and guest-IP allocationinternal/hooks: hook runnerdeploy/systemd/mergen@.service: systemd unit templatedeploy/systemd/mergen-forwarder.service: forwarder systemd unitcmd/mergen-lifecycle: lifecycle binary used by systemd hooksscripts/gen-wildcard-cert.sh: self-signed wildcard TLS cert generatorruntime/scripts/build-golden-rootfs.sh: Buildroot + BusyBox based golden rootfs (disk0) buildergo.work: multi-module workspace binding root,converter, andruntime
- Linux host with:
systemdfirecrackerjailerip+iptables/nftmkfs.ext4+truncate
- Go 1.24+
Optional (only for legacy helper script scripts/build-rootfs-from-dockerhub.sh):
dockerjq
Note: This repo can be developed on macOS, but actual VM runtime requires a Linux host with
systemdand Firecracker.
Install systemd template + lifecycle binary (Linux host):
sudo install -D -m 0644 deploy/systemd/mergen@.service /etc/systemd/system/mergen@.service
sudo install -D -m 0644 deploy/systemd/mergen-failure@.service /etc/systemd/system/mergen-failure@.service
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -o /tmp/mergen-lifecycle ./cmd/mergen-lifecycle
sudo install -m 0755 /tmp/mergen-lifecycle /usr/local/bin/mergen-lifecycle
sudo systemctl daemon-reloadRun daemon:
go run ./cmd/mergen serverHealth check:
curl -s http://127.0.0.1:8080/healthzInstall latest kernel:
ARCH="$(uname -m)"
release_url="https://github.com/firecracker-microvm/firecracker/releases"
latest_version="$(basename "$(curl -fsSLI -o /dev/null -w '%{url_effective}' ${release_url}/latest)")"
CI_VERSION="${latest_version%.*}"
latest_kernel_key="$(curl "http://spec.ccfc.min.s3.amazonaws.com/?prefix=firecracker-ci/${CI_VERSION}/${ARCH}/vmlinux-&list-type=2" \
| grep -oP "(firecracker-ci/${CI_VERSION}/${ARCH}/vmlinux-[0-9]+\.[0-9]+\.[0-9]{1,3})" \
| sort -V | tail -1)"
sudo install -d -m 0755 /var/lib/mergen/base/current
sudo wget -O /var/lib/mergen/base/current/vmlinux "https://s3.amazonaws.com/spec.ccfc.min/${latest_kernel_key}"Generate wildcard cert into /var/lib/mergen/certs:
sudo install -d -m 0755 /var/lib/mergen/certs
# for *.vm.example.com domain
export FWD_DOMAIN="vm.example.com"
export FWD_DOMAIN_CERT_DIR="/var/lib/mergen/certs"
export FWD_DOMAIN_CERT_DAYS=365 # 1 year
export FWD_DOMAIN_CERT_FILE="${FWD_DOMAIN_CERT_DIR}/wildcard.${FWD_DOMAIN}.crt"
export FWD_DOMAIN_CERT_KEY_FILE="${FWD_DOMAIN_CERT_DIR}/wildcard.${FWD_DOMAIN}.key"
openssl req \
-x509 \
-newkey rsa:2048 \
-sha256 \
-nodes \
-days "${FWD_DOMAIN_CERT_DAYS}" \
-subj "/CN=*.${FWD_DOMAIN}" \
-addext "subjectAltName=DNS:*.${FWD_DOMAIN},DNS:${FWD_DOMAIN}" \
-keyout "${FWD_DOMAIN_CERT_KEY_FILE}" \
-out "${FWD_DOMAIN_CERT_FILE}"Run forwarder:
# set env values before start
export FWD_DOMAIN="vm.example.com"
export FWD_TLS_CERT_FILE="/var/lib/mergen/certs/wildcard.${FWD_DOMAIN}.crt"
export FWD_TLS_KEY_FILE="/var/lib/mergen/certs/wildcard.${FWD_DOMAIN}.key"
export FWD_LOG_LEVEL=debug
go run ./cmd/mergen-forwarderForwarder behavior:
- Listens on HTTPS
:443by default (FWD_HTTPS_ADDR). - Terminates TLS and resolves SNI label to VM metadata.
- Routes to
guestIP:httpPortfrom VMmeta.json. - Returns
502when resolved VM has no validhttpPort.
Build and install guest/base binaries directly into /var/lib/mergen/base/current:
export BASE_DIR="/var/lib/mergen/base/current"
sudo ./scripts/build-sbin-init-from-go.sh \
--goos linux --goarch amd64 \
--install-base \
--base-dir "${BASE_DIR}"This installs binaries under ${BASE_DIR}/bin/:
sbin-initmergen-agent- optional:
mergen-supervisor,mergen-telemetry(built only if correspondingcmd/...has Go files)
Place immutable base assets into the same directory:
export BASE_DIR="/var/lib/mergen/base/current"
sudo install -d -m 0755 "${BASE_DIR}"
sudo install -m 0444 /path/to/vmlinux "${BASE_DIR}/vmlinux"
sudo install -m 0444 /path/to/golden-rootfs.ext4 "${BASE_DIR}/golden-rootfs.ext4"
sudo install -m 0444 /path/to/agent-rootfs.ext4 "${BASE_DIR}/agent-rootfs.ext4"
# optional env disk:
sudo install -m 0444 /path/to/env-rootfs.ext4 "${BASE_DIR}/env-rootfs.ext4"Only binaries and disk/kernel artifacts should be placed in /var/lib/mergen/base/current (do not place README/docs there).
Host-side binaries are separate and should be installed to /usr/local/bin (example set):
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/mergen ./cmd/mergen
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/mergen-lifecycle ./cmd/mergen-lifecycle
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/mergen-forwarder ./cmd/mergen-forwarder
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/mergen-converter ./converter/cmd/mergen-converter
sudo install -m 0755 /tmp/mergen /usr/local/bin/mergen
sudo install -m 0755 /tmp/mergen-lifecycle /usr/local/bin/mergen-lifecycle
sudo install -m 0755 /tmp/mergen-forwarder /usr/local/bin/mergen-forwarder
sudo install -m 0755 /tmp/mergen-converter /usr/local/bin/mergen-converterOptional: build a reusable BusyBox-based golden rootfs (disk0) with Buildroot:
# install build_root dependencies
sudo apt install -y build-essential unzip
# if you are root add FORCE_UNSAFE_CONFIGURE=1 to the command
FORCE_UNSAFE_CONFIGURE=1 ./scripts/build-golden-rootfs.sh
# output:
# ./artifacts/golden-rootfs/golden-rootfs
# ./artifacts/golden-rootfs/golden-rootfs.ext4If you do not want to run Buildroot as root, you can create a temporary passwordless builder user and run the script with sudo -u from project root:
REPO_ROOT="$(pwd)"
sudo useradd -m -s /bin/bash mergen-builder || true
sudo -u mergen-builder -H bash -lc "cd '${REPO_ROOT}' && ./scripts/build-golden-rootfs.sh"
# optional cleanup after build
sudo pkill -u mergen-builder || true
sudo userdel -r mergen-builderRun converter (default mode is payload-only):
export IMAGE="nginx:alpine"
go run ./converter/cmd/mergen-converter \
convert \
-image "$IMAGE"Generate or refresh agent-rootfs.ext4 directly from base binaries (/var/lib/mergen/base/current/bin):
go run ./converter/cmd/mergen-converter \
convert \
--agent-rootfs \
--base-assets-dir /var/lib/mergen/base/currentOptional output path and size:
go run ./converter/cmd/mergen-converter \
convert \
--agent-rootfs \
--base-assets-dir /var/lib/mergen/base/current \
--agent-rootfs-output /var/lib/mergen/base/current/agent-rootfs.ext4 \
--agent-rootfs-size-mib 64--agent-rootfs mode does not pull image and does not build payload; it only builds mountable ext4 agent disk from base bin binaries and marks output as read-only.
Generate env-rootfs.ext4 from runtime metadata (mergen.runtime.json) and optional env vars:
export IMAGE="nginx:alpine"
go run ./converter/cmd/mergen-converter \
convert \
--env-rootfs \
--runtime-json "/var/lib/mergen/images/${IMAGE}/mergen.runtime.json" \
--env-rootfs-output "/var/lib/mergen/images/${IMAGE}/env-rootfs.ext4" \
--env-var "APP_ENV=prod" \
--env-var "LOG_LEVEL=info"--env-rootfs mode does not pull image and does not build payload; it only packages runtime/env metadata into mountable ext4 env disk.
mergen-converter pulls image layers natively with containers/image (go.podman.io/image/v5) and does not execute Docker CLI.
Use -skip-pull to reuse output-dir/image-cache from a previous conversion run.
Default behavior creates only payload artifacts (payload-rootfs/, payload-rootfs.ext4, metadata files).
If you really need old behavior (golden/agent/env bundle per image), use -legacy-full-bundle.
Legacy bundle mode can still use -golden-rootfs-dir and -sbin-init/-sbin-agent.
Injected /sbin/init is expected to be built from runtime/cmd/mergen-init.
Default path is under /var/lib/mergen/images, so run with sufficient permissions (or override with -output-dir).
Default output path follows image reference hierarchy under /var/lib/mergen/images:
nginx:alpine->/var/lib/mergen/images/nginx:alpineghcr.io/org/app:1.2.3->/var/lib/mergen/images/ghcr.io/org/app:1.2.3
Converter can also work like a lightweight registry client for payload disks:
go run ./converter/cmd/mergen-converter init --registry default
go run ./converter/cmd/mergen-converter login --registry default
go run ./converter/cmd/mergen-converter push --registry default --image "$IMAGE"
go run ./converter/cmd/mergen-converter pull --registry default --image "$IMAGE"init stores user S3 / MinIO profile details locally, login stores username/password for that profile, and push / pull transfer only payload-rootfs.ext4.
This keeps mergen-converter usable as a standalone external tool on another machine without the rest of the Mergen host stack.
Converter outputs:
payload-rootfs/extracted image filesystempayload-rootfs.tarpayload-rootfs.ext4(disk2)image-meta.json(entrypoint/cmd/env/startCmd metadata from image)mergen.runtime.json(runtime metadata output for orchestration/debug)suggested-bootargs.txt(init=/sbin/init)suggested-vm-request.json(ready-to-edit payload forPOST /v1/vms, points kernel/rootfs/agent to base dir)
Base assets (kernel, golden rootfs, agent disk, optional env disk) should be managed directly under /var/lib/mergen/base/current.
Delete a converted image rootfs bundle:
export IMAGE="nginx:alpine"
go run ./converter/cmd/mergen-converter \
convert \
-image "$IMAGE" \
-delete-rootfsUse -output-dir if you want to delete a non-default conversion location.
Run converter doctor checks:
go run ./converter/cmd/mergen-converter doctorExample JSON output:
go run ./converter/cmd/mergen-converter doctor --jsondoctor validates base assets under /var/lib/mergen/base/current by default:
vmlinuxgolden-rootfs.ext4agent-rootfs.ext4bin/sbin-initbin/mergen-agent- optional:
env-rootfs.ext4,bin/mergen-supervisor,bin/mergen-telemetry
doctor does not check payload rootfs because payload is image-specific and provided at VM create time.
CLI output uses emojis for readability:
β PASSβ FAILβ οΈ WARN
Copy env.template into your shell or process manager environment and set at least:
MGR_CONFIG_S3_BUCKETMGR_CONFIG_S3_REGIONMGR_CONFIG_S3_ENDPOINTfor MinIO or another S3-compatible endpointMGR_CONFIG_S3_ACCESS_KEY_ID/MGR_CONFIG_S3_SECRET_ACCESS_KEY
Initialize and store the config registry profile used for base image distribution:
go run ./cmd/mergen config init --registry default
go run ./cmd/mergen config login --registry defaultList what exists locally under /var/lib/mergen/base/current and /var/lib/mergen/images:
go run ./cmd/mergen images lsPush shared base artifacts as an admin:
go run ./cmd/mergen images push base \
--registry default \
--platform linux-amd64 \
--flavor buildroot \
--version v0.0.1Pull shared base artifacts onto a new machine:
go run ./cmd/mergen images pull base \
--registry default \
--platform linux-amd64 \
--flavor buildroot \
--version latestRemote config-S3 layout for base artifacts:
<prefix>/mergen/<platform>/<flavor>/<version>/manifest.json
<prefix>/mergen/<platform>/<flavor>/<version>/vmlinux.bin
<prefix>/mergen/<platform>/<flavor>/<version>/<flavor>-disk.ext4
<prefix>/mergen/<platform>/<flavor>/<version>/agent-disk.ext4
Example:
artifacts/mergen/linux-amd64/buildroot/v0.0.1/vmlinux.bin
artifacts/mergen/linux-amd64/buildroot/v0.0.1/buildroot-disk.ext4
artifacts/mergen/linux-amd64/buildroot/v0.0.1/agent-disk.ext4
artifacts/mergen/linux-amd64/buildroot/latest/vmlinux.bin
artifacts/mergen/linux-amd64/buildroot/latest/buildroot-disk.ext4
artifacts/mergen/linux-amd64/buildroot/latest/agent-disk.ext4
mergen images pull base is a read-only S3 fetch for already published base disks. Payload conversion and user payload push/pull stay in converter/README.md under mergen-converter.
Uploads and downloads emit byte progress continuously in the CLI, and the same progress model is reused internally so HTTP/SSE style progress endpoints can be added later without changing the transfer core.
# create vm (payload from converter output, base rootfs/agent from /var/lib/mergen/base/current)
export IMAGE="nginx:alpine"
export PORT=80
export SUBDOMAIN="app1"
export FWD_DOMAIN="vm.example.com"
export ENV_DISK="/var/lib/mergen/images/$IMAGE/env-rootfs.ext4"
export VM_JSON="$(curl -s -X POST http://127.0.0.1:8080/v1/vms \
-H 'content-type: application/json' \
-d '{
"rootfs": "/var/lib/mergen/base/current/golden-rootfs.ext4",
"agentDisk": "/var/lib/mergen/base/current/agent-rootfs.ext4",
"payloadDisk": "/var/lib/mergen/images/$IMAGE/payload-rootfs.ext4",
"envDisk": "/var/lib/mergen/images/$IMAGE/env-rootfs.ext4",
"kernel": "/var/lib/mergen/base/current/vmlinux",
"vcpu": 1,
"memMiB": 512,
"ports": [{"guest": $PORT, "host": 0}],
"httpPort": $PORT,
"tags": {"app": "$SUBDOMAIN"},
"autoStart": false
}')"
echo "$VM_JSON"
export APPID="$(echo "$VM_JSON" | jq -r '.id')"
# start vm service
curl -s -X POST "http://127.0.0.1:8080/v1/vms/${APPID}/start"
# check logs
journalctl -xeu mergen@${APPID}.service
# TEST-1: uuid based
curl -k --resolve "${APPID}.${FWD_DOMAIN}:443:127.0.0.1" "https://${APPID}.${FWD_DOMAIN}/"
# TEST-2: tag based
curl -k --resolve "${SUBDOMAIN}.${FWD_DOMAIN}:443:127.0.0.1" https://${SUBDOMAIN}.${FWD_DOMAIN}/
# stop + delete
curl -s -X POST "http://127.0.0.1:8080/v1/vms/${APPID}/stop"
curl -s -X DELETE "http://127.0.0.1:8080/v1/vms/${APPID}"
# if you want to run vm netns commands
# 1 - show all available ips in netns of vm
sudo ip netns exec mergen-$(cut -d '-' -f 1 <<< $APPID) ip neigh show dev tap-$(cut -d '-' -f 1 <<< $APPID)
# 2 - show ip addr in netns of vm
sudo ip netns exec mergen-$(cut -d '-' -f 1 <<< $APPID) ip addr show dev tap-$(cut -d '-' -f 1 <<< $APPID)startis idempotent: already running VM still returns success.stopis idempotent: already stopped VM still returns success.deletereturns404if VM does not exist.- Dependency issues (for example missing/unsupported
systemd) return503.
Environment variables:
MGR_HTTP_ADDR(default:8080)MGR_CONFIG_ROOT(default/var/lib/mergen/vm.d)MGR_DATA_ROOT(default/var/lib/mergen)MGR_RUN_ROOT(default/run/mergen)MGR_GLOBAL_HOOKS_DIR(default/var/lib/mergen/hooks.d)MGR_UNIT_PREFIX(defaultmergen)MGR_SYSTEMCTL_PATH(defaultsystemctl)MGR_COMMAND_TIMEOUT_SECONDS(default10)MGR_SHUTDOWN_TIMEOUT_SECONDS(default15)MGR_PORT_START(default20000)MGR_PORT_END(default40000)MGR_GUEST_CIDR(default172.30.0.0/24)MGR_BASE_ASSETS_DIR(default/var/lib/mergen/base/current)MGR_IMAGES_ROOT(default/var/lib/mergen/images)MGR_DEFAULT_KERNEL(default<MGR_BASE_ASSETS_DIR>/vmlinux)MGR_DEFAULT_ROOTFS(default<MGR_BASE_ASSETS_DIR>/golden-rootfs.ext4)MGR_DEFAULT_AGENT_DISK(default<MGR_BASE_ASSETS_DIR>/agent-rootfs.ext4)MGR_DEFAULT_ENV_DISK(default empty, optional)MERGEN_CONFIG_FILE(default~/.config/mergen/config.json)MGR_CONFIG_S3_ENDPOINT(optional for AWS, required for MinIO/local S3)MGR_CONFIG_S3_REGION(defaultus-east-1)MGR_CONFIG_S3_BUCKETMGR_CONFIG_S3_PREFIX(defaultartifacts)MGR_CONFIG_S3_USERNAME(used for admin login metadata, not S3 key layout)MGR_CONFIG_S3_PASSWORD(used for local admin login metadata)MGR_CONFIG_S3_ACCESS_KEY_IDMGR_CONFIG_S3_SECRET_ACCESS_KEYMGR_CONFIG_S3_SESSION_TOKEN(optional)MGR_CONFIG_S3_USE_PATH_STYLE(defaultfalse, settruefor MinIO)MGR_BASE_PLATFORM(defaultlinux-<host-arch>)MGR_BASE_FLAVOR(defaultbuildroot)MGR_PROGRESS_EVERY_MILLISECONDS(default250)MGR_LOG_LEVEL(defaultinfo, values:debug|info|warn|error)MGR_LOG_FORMAT(defaultconsole, values:console|json|text)
POST /v1/vms supports:
httpPort(optional): Guest HTTP port for TLS-terminated:443forwarder routing.
Enable verbose debugging:
MGR_LOG_LEVEL=debug MGR_LOG_FORMAT=console go run ./cmd/mergen serverconsole format prints colored output in this order: [LEVEL] TIMESTAMP MESSAGE key=value...
INFOis blueWARNis yellowERRORis redDEBUGis cyan
Forwarder logging uses:
FWD_LOG_LEVEL(defaultdebug, values:debug|info|warn|error)FWD_LOG_FORMAT(defaultconsole, values:console|json|text)
To emit JSON for Elastic:
FWD_LOG_FORMAT=json go run ./cmd/mergen-forwarderEnvironment variables:
FWD_CONFIG_ROOT(default/var/lib/mergen/vm.d)FWD_NETNS_ROOT(default/run/netns)FWD_TLS_CERT_FILE(default/var/lib/mergen/certs/wildcard.localhost.crt)FWD_TLS_KEY_FILE(default/var/lib/mergen/certs/wildcard.localhost.key)FWD_DOMAIN(defaultlocalhost)FWD_HTTPS_ADDR(default:443)FWD_DIAL_TIMEOUT_SECONDS(default5)FWD_RESOLVER_CACHE_TTL_SECONDS(default5)FWD_SHUTDOWN_TIMEOUT_SECONDS(default15)FWD_LOG_LEVEL(defaultdebug)FWD_LOG_FORMAT(defaultconsole)
SNI matching:
<label>.<domain>(for exampleapp1.vm.example.com)
This SNI rule applies to HTTPS listener routing.
- Unit template:
deploy/systemd/mergen@.service - Failure hook template:
deploy/systemd/mergen-failure@.service(OnFailure=) - Lifecycle binary:
cmd/mergen-lifecycle(install as/usr/local/bin/mergen-lifecycle)- Systemd stages:
pre-start,start,post-start,stop,post-stop,on-failure - Extra toolkit commands:
status,restart,pre-delete,delete,post-delete
mergen-lifecycle post-start runs the Firecracker API flow (socket + config + InstanceStart) through github.com/firecracker-microvm/firecracker-go-sdk. mergen-lifecycle stop sends SendCtrlAltDel via SDK as best-effort stop signal. mergen-lifecycle status reads instance info from the Firecracker API socket, and mergen-lifecycle restart sends the same guest restart action. MGN_ENABLE_ENTROPY_DEVICE=1 enables pre-boot entropy device setup (PUT /entropy) before drives/network are attached. Stage extensions run sequentially and wait for each hook to complete before the next hook starts. Lifecycle stage events are append-only written to MGN_DAEMON_LOG_FILE (default /var/lib/mergen/daemon.log).
For per-VM extension hooks, create lifecycle-hooks.json under the VM config dir (/var/lib/mergen/vm.d/<vm-id>/lifecycle-hooks.json) with stage arrays (preStart, postStart, preStop, postStop, preDelete, delete, postDelete). You can also pass command hooks via env (MGN_LIFECYCLE_PRE_START_HOOKS, MGN_LIFECYCLE_POST_STOP_HOOKS, etc.), using ; as separator. {{vm_id}} and ${VM_ID} tokens are supported.
go test ./...
go test ./converter/...
GOOS=linux GOARCH=amd64 go build ./runtime/...
go run ./cmd/mergen --help- Real netns/tap/iptables implementation in lifecycle stage extensions
- Envoy/Consul integration via hooks
- Stronger authn/authz for manager API
This example prints a console log right after the post-start stage completes.
Create cmd/mergen-lifecycle/hooks/poststart/log_ready.go:
package poststart
import (
"context"
"fmt"
lifecyclehooks "github.com/alperreha/mergen-fire/cmd/mergen-lifecycle/hooks"
)
// HandleLogReady runs in post-start stage and prints a human-readable message.
func HandleLogReady(_ context.Context, req lifecyclehooks.Request) error {
fmt.Printf("MERGEN_HOOK_POSTSTART: VM Start Success of vm_id=%s\n", req.VMID)
return nil
}Open cmd/mergen-lifecycle/lifecycle.go and add the new hook to stagePostStart:
manager.Register(string(stagePostStart),
lifecyclehooks.Definition{Name: "template", Strict: true, Handle: hookpoststart.HandleTemplate},
lifecyclehooks.Definition{Name: "log-ready", Strict: true, Handle: hookpoststart.HandleLogReady},
)Hooks run sequentially in the order you register them. The next hook starts only after the previous one finishes.
go build -o /tmp/mergen-lifecycle ./cmd/mergen-lifecycle
sudo install -m 0755 /tmp/mergen-lifecycle /usr/local/bin/mergen-lifecyclesudo systemctl restart mergen@<vm-id>.service
journalctl -u mergen@<vm-id>.service -n 100 --no-pagerExpected line:
MERGEN_HOOK_POSTSTART: VM Start Success of vm_id=<vm-id>