Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
408 lines (373 loc) · 15.5 KB
/
Copy pathMakefile
File metadata and controls
408 lines (373 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# Coffer — Makefile
# Zero-Docker build workflow. All tools run natively on the host.
#
# Prerequisites:
# Kernel build: gcc make bc bison flex libssl-dev libelf-dev wget cpio kmod
# Rootfs build: erofs-utils lz4
# Rust: cargo >= 1.78
#
# Quickstart:
# make install # Full installation (deps + build + binaries + template)
# make install-deps # Install system dependencies
# make firecracker # Download Firecracker + Jailer binaries
# make kernel # Build guest kernel
# make rootfs # Build minimal rootfs
# make test # Run all tests
KERNEL_VERSION ?= 5.10.225
ARCH ?= x86_64
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
# Installation paths
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
# Coffer data home
COFFER_HOME ?= $(HOME)/.coffer
TEMPLATE_DIR ?= $(COFFER_HOME)/templates
KERNEL_DIR ?= $(COFFER_HOME)/kernel
RUN_DIR ?= $(COFFER_HOME)/run
FIRECRACKER_VERSION ?= 1.7.0
FIRECRACKER_URL ?= https://github.com/firecracker-microvm/firecracker/releases/download/v$(FIRECRACKER_VERSION)/firecracker-v$(FIRECRACKER_VERSION)-$(ARCH).tgz
# Default OCI image for the alpine template.
# Override this if you are in a region with poor Docker Hub connectivity.
# Verified mirrors (as of 2026-05):
# make template DEFAULT_IMAGE=m.daocloud.io/docker.io/library/alpine:latest
# make template DEFAULT_IMAGE=docker.1panel.live/library/alpine:latest
DEFAULT_IMAGE ?= m.daocloud.io/docker.io/library/alpine:latest
# Paths to native build scripts
SCRIPT_DIR ?= $(PWD)/scripts
# Local pre-built artifacts
LOCAL_FIRECRACKER ?= kernel/build/firecracker
LOCAL_JAILER ?= kernel/build/jailer
LOCAL_VMLINUX ?= kernel/build/vmlinux
LOCAL_AGENT ?= target/release/coffer-agent
LOCAL_ROOTFS ?= rootfs-builder/output/rootfs.erofs
.PHONY: all help install uninstall install-deps check-deps \
firecracker kernel rootfs agent build \
install-bin install-data install-template verify-data \
template test test-integration test-local check clean clean-all \
setup-perms fix-perms
# ===================================================================
# Help
# ===================================================================
all: help
help:
@echo "Coffer — MicroVM runtime for AI Agents (zero-Docker)"
@echo ""
@echo "Installation:"
@echo " make install — Full install: deps, build, binaries, template"
@echo " make install-deps — Install system build dependencies"
@echo " make uninstall — Remove installed binary and data"
@echo ""
@echo "Build targets:"
@echo " make build — Build all Rust crates (release)"
@echo " make firecracker — Download Firecracker and Jailer"
@echo " make kernel — Build guest kernel"
@echo " make rootfs — Build minimal rootfs"
@echo " make template — Build default alpine template"
@echo ""
@echo "Mirror override (for poor Docker Hub connectivity):"
@echo " make template DEFAULT_IMAGE=m.daocloud.io/docker.io/library/alpine:latest"
@echo ""
@echo "Development:"
@echo " make check — cargo check"
@echo " make test — Run unit tests"
@echo " make test-local — Run integration tests with local artifacts"
@echo " make clean — Clean build artifacts"
@echo " make clean-all — Clean everything including data"
# ===================================================================
# Full installation
# ===================================================================
install: install-deps build install-bin setup-perms verify-data install-template
@echo ""
@echo "========================================"
@echo "✓ Coffer installed successfully!"
@echo "========================================"
@echo ""
@echo " CLI binary: $(BINDIR)/coffer-cli"
@echo " Data home: $(COFFER_HOME)"
@echo ""
@echo "Quick start:"
@echo " coffer-cli check"
@echo " coffer-cli template list"
@echo " coffer-cli run --template alpine -- echo hello"
@echo ""
@if [ ! -f "$(TEMPLATE_DIR)/alpine/snapshot.state" ]; then \
echo "⚠ Template snapshot was not created (may require root for KVM)."; \
echo " To create it manually, run:"; \
echo " sudo $$(id -un):$$(id -gn) coffer-cli template build --name alpine docker.io/library/alpine:latest"; \
echo " Or simply run 'make template' with appropriate privileges."; \
fi
uninstall:
rm -f $(DESTDIR)$(BINDIR)/coffer-cli
rm -rf $(COFFER_HOME)
@echo "✓ Coffer uninstalled."
# ===================================================================
# Install sub-steps
# ===================================================================
install-bin: build
@# Check write permission before attempting install
@if [ ! -w "$(DESTDIR)$(BINDIR)" ] 2>/dev/null && [ -d "$(DESTDIR)$(BINDIR)" ]; then \
echo ""; \
echo "Error: No write permission to $(DESTDIR)$(BINDIR)."; \
echo ""; \
echo "To install system-wide (recommended):"; \
echo " sudo make install"; \
echo ""; \
echo "To install for current user only:"; \
echo " PREFIX=\$$HOME/.local make install"; \
echo ""; \
exit 1; \
fi
@install -d $(DESTDIR)$(BINDIR) 2>/dev/null || { \
echo ""; \
echo "Error: Cannot create $(DESTDIR)$(BINDIR). Permission denied."; \
echo ""; \
echo "To install system-wide (recommended):"; \
echo " sudo make install"; \
echo ""; \
echo "To install for current user only:"; \
echo " PREFIX=\$$HOME/.local make install"; \
echo ""; \
exit 1; \
}
install -m 755 target/release/coffer-cli $(DESTDIR)$(BINDIR)/coffer-cli
@echo "✓ Installed coffer-cli -> $(DESTDIR)$(BINDIR)/coffer-cli"
setup-perms:
@echo "Setting up runtime permissions..."
@# Ensure coffer-cli has CAP_NET_ADMIN
@if command -v setcap >/dev/null 2>&1 && [ -f $(DESTDIR)$(BINDIR)/coffer-cli ]; then \
if getcap $(DESTDIR)$(BINDIR)/coffer-cli 2>/dev/null | grep -q cap_net_admin; then \
echo "✓ coffer-cli already has CAP_NET_ADMIN"; \
else \
if setcap cap_net_admin+eip $(DESTDIR)$(BINDIR)/coffer-cli 2>/dev/null; then \
echo "✓ Granted CAP_NET_ADMIN to coffer-cli"; \
elif sudo -n setcap cap_net_admin+eip $(DESTDIR)$(BINDIR)/coffer-cli 2>/dev/null; then \
echo "✓ Granted CAP_NET_ADMIN to coffer-cli (via sudo)"; \
else \
echo ""; \
echo "⚠ Failed to set CAP_NET_ADMIN on $(DESTDIR)$(BINDIR)/coffer-cli"; \
echo " Fix it manually with:"; \
echo " sudo setcap cap_net_admin+eip $(DESTDIR)$(BINDIR)/coffer-cli"; \
echo " Or run:"; \
echo " make fix-perms"; \
echo ""; \
fi; \
fi \
elif [ -f $(DESTDIR)$(BINDIR)/coffer-cli ]; then \
echo "⚠ setcap not found. Install libcap2-bin to set file capabilities."; \
fi
@# Ensure /dev/kvm is accessible
@if [ -c /dev/kvm ]; then \
KVM_PERMS=$$(stat -c '%a' /dev/kvm 2>/dev/null || echo 0); \
if [ "$$KVM_PERMS" = "666" ] || groups | grep -qw kvm; then \
echo "✓ /dev/kvm is accessible"; \
else \
if chmod 666 /dev/kvm 2>/dev/null; then \
echo "✓ Set /dev/kvm permissions to 666"; \
elif sudo -n chmod 666 /dev/kvm 2>/dev/null; then \
echo "✓ Set /dev/kvm permissions to 666 (via sudo)"; \
else \
echo "⚠ Cannot modify /dev/kvm (try: sudo chmod 666 /dev/kvm)"; \
fi; \
fi \
else \
echo "⚠ /dev/kvm not found. KVM may not be available."; \
fi
fix-perms:
@echo "Fixing runtime permissions..."
@if [ -f $(DESTDIR)$(BINDIR)/coffer-cli ]; then \
if sudo setcap cap_net_admin+eip $(DESTDIR)$(BINDIR)/coffer-cli; then \
echo "✓ Granted CAP_NET_ADMIN to coffer-cli"; \
else \
echo "⚠ Failed to set CAP_NET_ADMIN (setcap requires root)"; \
fi; \
else \
echo "⚠ coffer-cli not found at $(DESTDIR)$(BINDIR)/coffer-cli"; \
fi
@if [ -c /dev/kvm ]; then \
if sudo chmod 666 /dev/kvm; then \
echo "✓ Set /dev/kvm permissions to 666"; \
else \
echo "⚠ Failed to modify /dev/kvm"; \
fi; \
fi
verify-data:
@echo "Verifying runtime data..."
@install -d $(DESTDIR)$(KERNEL_DIR)
@install -d $(DESTDIR)$(COFFER_HOME)/bin
@install -d $(DESTDIR)$(TEMPLATE_DIR)
@install -d $(DESTDIR)$(RUN_DIR)
@install -d $(DESTDIR)$(RUN_DIR)/vsock
# Firecracker
@if [ ! -f "$(DESTDIR)$(KERNEL_DIR)/firecracker" ]; then \
if [ -f $(LOCAL_FIRECRACKER) ]; then \
install -m 755 $(LOCAL_FIRECRACKER) $(DESTDIR)$(KERNEL_DIR)/firecracker; \
echo "✓ Installed firecracker (local build) -> $(KERNEL_DIR)/firecracker"; \
else \
echo "→ Downloading firecracker..."; \
$(MAKE) firecracker; \
echo "✓ Firecracker downloaded -> $(KERNEL_DIR)/firecracker"; \
fi \
else \
echo "✓ firecracker already present"; \
fi
# Jailer
@if [ ! -f "$(DESTDIR)$(KERNEL_DIR)/jailer" ]; then \
if [ -f $(LOCAL_JAILER) ]; then \
install -m 755 $(LOCAL_JAILER) $(DESTDIR)$(KERNEL_DIR)/jailer; \
echo "✓ Installed jailer (local build) -> $(KERNEL_DIR)/jailer"; \
else \
echo "⚠ jailer not found (optional)"; \
fi \
else \
echo "✓ jailer already present"; \
fi
# Kernel
@if [ ! -f "$(DESTDIR)$(KERNEL_DIR)/vmlinux" ]; then \
if [ -f $(LOCAL_VMLINUX) ]; then \
install -m 644 $(LOCAL_VMLINUX) $(DESTDIR)$(KERNEL_DIR)/vmlinux; \
echo "✓ Installed kernel (local build) -> $(KERNEL_DIR)/vmlinux"; \
else \
echo "→ Building kernel..."; \
$(MAKE) kernel; \
echo "✓ Kernel built -> $(KERNEL_DIR)/vmlinux"; \
fi \
else \
echo "✓ kernel already present"; \
fi
# Agent — always update from local build if available to ensure static-linked binary
@if [ -f $(LOCAL_AGENT) ]; then \
install -m 755 $(LOCAL_AGENT) $(DESTDIR)$(COFFER_HOME)/bin/coffer-agent; \
echo "✓ Installed/updated coffer-agent (built) -> $(COFFER_HOME)/bin/coffer-agent"; \
elif [ -f rootfs-builder/coffer-agent ]; then \
install -m 755 rootfs-builder/coffer-agent $(DESTDIR)$(COFFER_HOME)/bin/coffer-agent; \
echo "✓ Installed coffer-agent (pre-built) -> $(COFFER_HOME)/bin/coffer-agent"; \
elif [ ! -f "$(DESTDIR)$(COFFER_HOME)/bin/coffer-agent" ]; then \
echo "⚠ coffer-agent not found. Run 'make build' first."; \
else \
echo "✓ coffer-agent already present"; \
fi
install-template: verify-data
@echo ""
@echo "Building default alpine template..."
@if ! command -v skopeo >/dev/null 2>&1; then \
echo "⚠ skopeo not found. Skipping template build."; \
echo " Install it first: make install-deps"; \
elif ! command -v umoci >/dev/null 2>&1; then \
echo "⚠ umoci not found. Skipping template build."; \
echo " Install it first: make install-deps"; \
else \
if [ -f "$(BINDIR)/coffer-cli" ]; then \
CLI="$(BINDIR)/coffer-cli"; \
else \
CLI="cargo run --bin coffer-cli --release --"; \
fi; \
COFFER_FIRECRACKER_PATH=$(KERNEL_DIR)/firecracker \
COFFER_KERNEL_PATH=$(KERNEL_DIR)/vmlinux \
COFFER_AGENT_BIN=$(COFFER_HOME)/bin/coffer-agent \
$$CLI template build --name alpine $(DEFAULT_IMAGE) || \
(echo "⚠ Template build failed (may require root for KVM / network setup)." && exit 0); \
fi
# ===================================================================
# Install dependencies (auto-detect distro)
# ===================================================================
install-deps:
$(SCRIPT_DIR)/install-deps.sh
# ===================================================================
# Dependency check
# ===================================================================
check-deps:
@echo "Checking host dependencies..."
@command -v gcc >/dev/null 2>&1 || { echo "MISSING: gcc"; exit 1; }
@command -v make >/dev/null 2>&1 || { echo "MISSING: make"; exit 1; }
@command -v bc >/dev/null 2>&1 || { echo "MISSING: bc"; exit 1; }
@command -v bison >/dev/null 2>&1 || { echo "MISSING: bison"; exit 1; }
@command -v flex >/dev/null 2>&1 || { echo "MISSING: flex"; exit 1; }
@command -v wget >/dev/null 2>&1 || { echo "MISSING: wget"; exit 1; }
@command -v mkfs.erofs >/dev/null 2>&1 || { echo "MISSING: mkfs.erofs (install erofs-utils)"; exit 1; }
@echo "All required tools found."
# ===================================================================
# Firecracker binary (downloaded directly)
# ===================================================================
firecracker:
@if [ -f "$(KERNEL_DIR)/firecracker" ]; then \
echo "✓ Firecracker already exists at $(KERNEL_DIR)/firecracker"; \
else \
echo "Downloading Firecracker v$(FIRECRACKER_VERSION)..."; \
mkdir -p $(KERNEL_DIR); \
cd $(KERNEL_DIR) && curl -fsSL $(FIRECRACKER_URL) | tar -xz; \
cp $(KERNEL_DIR)/release-v$(FIRECRACKER_VERSION)-$(ARCH)/firecracker-v$(FIRECRACKER_VERSION)-$(ARCH) $(KERNEL_DIR)/firecracker; \
cp $(KERNEL_DIR)/release-v$(FIRECRACKER_VERSION)-$(ARCH)/jailer-v$(FIRECRACKER_VERSION)-$(ARCH) $(KERNEL_DIR)/jailer; \
chmod +x $(KERNEL_DIR)/firecracker $(KERNEL_DIR)/jailer; \
rm -rf $(KERNEL_DIR)/release-v$(FIRECRACKER_VERSION)-$(ARCH); \
echo "✓ Firecracker installed to $(KERNEL_DIR)/firecracker"; \
fi
firecracker-force:
@echo "Forcing re-download of Firecracker v$(FIRECRACKER_VERSION)..."
@# Kill any running firecracker processes that may lock the binary
@pgrep -x firecracker >/dev/null 2>&1 && pkill -x firecracker && sleep 1 || true
@rm -f $(KERNEL_DIR)/firecracker $(KERNEL_DIR)/jailer
@$(MAKE) firecracker
# ===================================================================
# Kernel build (native, no Docker)
# ===================================================================
kernel:
@echo "Building kernel $(KERNEL_VERSION) natively..."
@mkdir -p $(KERNEL_DIR)
KERNEL_VERSION=$(KERNEL_VERSION) \
ARCH=$(ARCH) \
JOBS=$(JOBS) \
BUILD_DIR=$(KERNEL_DIR) \
$(SCRIPT_DIR)/build-kernel-native.sh
# ===================================================================
# Rootfs build (native, no Docker)
# ===================================================================
rootfs:
@echo "Building rootfs natively..."
@mkdir -p $(TEMPLATE_DIR)/alpine
BUILD_DIR=$(TEMPLATE_DIR)/alpine \
OUTPUT=$(TEMPLATE_DIR)/alpine/rootfs.erofs \
AGENT_BIN=$(PWD)/rootfs-builder/coffer-agent \
$(SCRIPT_DIR)/build-rootfs-native.sh
# ===================================================================
# Full template (rootfs + snapshot)
# ===================================================================
template: rootfs kernel firecracker build install-bin setup-perms verify-data
@echo "Creating template snapshot..."
@if [ -f "$(BINDIR)/coffer-cli" ]; then \
CLI="$(BINDIR)/coffer-cli"; \
else \
CLI="cargo run --bin coffer-cli --release --"; \
fi; \
COFFER_FIRECRACKER_PATH=$(KERNEL_DIR)/firecracker \
COFFER_KERNEL_PATH=$(KERNEL_DIR)/vmlinux \
COFFER_AGENT_BIN=$(COFFER_HOME)/bin/coffer-agent \
$$CLI template build --name alpine $(DEFAULT_IMAGE)
# ===================================================================
# Rust build & test
# ===================================================================
check:
cargo check --workspace
build:
cargo build --workspace --release
# Rebuild coffer-agent as a fully-static binary so it runs on musl-based guests (Alpine).
cargo rustc --release -p coffer-agent --bin coffer-agent -- -C target-feature=+crt-static
test:
cargo test --workspace
test-integration:
COFFER_TEST_FIRECRACKER_PATH=$(KERNEL_DIR)/firecracker \
COFFER_TEST_KERNEL_PATH=$(KERNEL_DIR)/vmlinux \
COFFER_TEST_ROOTFS_PATH=$(TEMPLATE_DIR)/alpine/rootfs.erofs \
cargo test --test integration -- --ignored
test-local:
COFFER_TEST_FIRECRACKER_PATH=$(PWD)/kernel/build/firecracker \
COFFER_TEST_KERNEL_PATH=$(PWD)/kernel/build/vmlinux \
COFFER_TEST_ROOTFS_PATH=$(PWD)/rootfs-builder/output/rootfs.erofs \
cargo test --test integration -- --ignored
# ===================================================================
# Cleanup
# ===================================================================
clean:
cargo clean
rm -rf $(RUN_DIR)
clean-all: clean
rm -rf $(KERNEL_DIR) $(TEMPLATE_DIR)