Hard-cut banger away from source-checkout runtime bundles as an implicit source of\nimage and host defaults. Managed images now own their full boot set,\nimage build starts from an existing registered image, and daemon startup\nno longer synthesizes a default image from host paths.\n\nResolve Firecracker from PATH or firecracker_bin, make SSH keys config-owned\nwith an auto-managed XDG default, replace the external name generator and\npackage manifests with Go code, and keep the vsock helper as a companion\nbinary instead of a user-managed runtime asset.\n\nUpdate the manual scripts, web/CLI forms, config surface, and docs around\nthe new build/manual flow and explicit image registration semantics.\n\nValidation: GOCACHE=/tmp/banger-gocache go test ./..., bash -n scripts/*.sh,\nand make build.
571 lines
16 KiB
Bash
Executable file
571 lines
16 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
log() {
|
|
printf '[customize] %s\n' "$*"
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./scripts/customize.sh <base-rootfs> [--out <path>] [--size <size>] [--kernel <path>] [--initrd <path>] [--docker] [--modules <dir>]
|
|
|
|
Creates a copy of rootfs.ext4, optionally resizes it, boots a VM using the
|
|
copy as a writable rootfs, then applies base configuration and packages.
|
|
EOF
|
|
}
|
|
|
|
parse_size() {
|
|
local raw="$1"
|
|
if [[ "$raw" =~ ^([0-9]+)([KMG])?$ ]]; then
|
|
local num="${BASH_REMATCH[1]}"
|
|
local unit="${BASH_REMATCH[2]}"
|
|
case "$unit" in
|
|
K) echo $((num * 1024)) ;;
|
|
M|"") echo $((num * 1024 * 1024)) ;;
|
|
G) echo $((num * 1024 * 1024 * 1024)) ;;
|
|
esac
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
STATE="${BANGER_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/banger/image-build}"
|
|
VM_ROOT="$STATE/vms"
|
|
mkdir -p "$VM_ROOT"
|
|
|
|
BR_DEV="br-fc"
|
|
BR_IP="172.16.0.1"
|
|
CIDR="24"
|
|
DNS_SERVER="1.1.1.1"
|
|
|
|
resolve_banger_bin() {
|
|
if [[ -n "${BANGER_BIN:-}" ]]; then
|
|
printf '%s\n' "$BANGER_BIN"
|
|
return
|
|
fi
|
|
if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then
|
|
printf '%s\n' "$REPO_ROOT/build/bin/banger"
|
|
return
|
|
fi
|
|
if [[ -x "$REPO_ROOT/banger" ]]; then
|
|
printf '%s\n' "$REPO_ROOT/banger"
|
|
return
|
|
fi
|
|
if command -v banger >/dev/null 2>&1; then
|
|
command -v banger
|
|
return
|
|
fi
|
|
log "banger binary not found; install/build banger or set BANGER_BIN"
|
|
exit 1
|
|
}
|
|
|
|
BANGER_BIN="$(resolve_banger_bin)"
|
|
NAT_ACTIVE=0
|
|
FC_BIN="$("$BANGER_BIN" internal firecracker-path)"
|
|
SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)"
|
|
VSOCK_AGENT="$("$BANGER_BIN" internal vsock-agent-path)"
|
|
|
|
banger_nat() {
|
|
local action="$1"
|
|
"$BANGER_BIN" internal nat "$action" --guest-ip "$GUEST_IP" --tap "$TAP_DEV"
|
|
}
|
|
|
|
load_package_preset() {
|
|
local preset="$1"
|
|
local -n out="$2"
|
|
mapfile -t out < <("$BANGER_BIN" internal packages "$preset")
|
|
(( ${#out[@]} > 0 ))
|
|
}
|
|
|
|
write_rootfs_manifest_metadata() {
|
|
local rootfs_path="$1"
|
|
local manifest_hash="$2"
|
|
printf '%s\n' "$manifest_hash" > "${rootfs_path}.packages.sha256"
|
|
}
|
|
|
|
BASE_ROOTFS=""
|
|
OUT_ROOTFS=""
|
|
SIZE_SPEC=""
|
|
INSTALL_DOCKER=0
|
|
KERNEL=""
|
|
INITRD=""
|
|
MISE_VERSION="v2025.12.0"
|
|
MISE_INSTALL_PATH="/usr/local/bin/mise"
|
|
MISE_ACTIVATE_LINE='eval "$(/usr/local/bin/mise activate bash)"'
|
|
TMUX_PLUGIN_DIR="/root/.tmux/plugins"
|
|
TMUX_RESURRECT_DIR="/root/.tmux/resurrect"
|
|
TMUX_TPM_REPO="https://github.com/tmux-plugins/tpm"
|
|
TMUX_RESURRECT_REPO="https://github.com/tmux-plugins/tmux-resurrect"
|
|
TMUX_CONTINUUM_REPO="https://github.com/tmux-plugins/tmux-continuum"
|
|
TMUX_MANAGED_START="# >>> banger tmux plugins >>>"
|
|
TMUX_MANAGED_END="# <<< banger tmux plugins <<<"
|
|
MODULES_DIR=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--out)
|
|
OUT_ROOTFS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--size)
|
|
SIZE_SPEC="${2:-}"
|
|
shift 2
|
|
;;
|
|
--kernel)
|
|
KERNEL="${2:-}"
|
|
shift 2
|
|
;;
|
|
--initrd)
|
|
INITRD="${2:-}"
|
|
shift 2
|
|
;;
|
|
--docker)
|
|
INSTALL_DOCKER=1
|
|
shift
|
|
;;
|
|
--modules)
|
|
MODULES_DIR="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [[ -z "$BASE_ROOTFS" ]]; then
|
|
BASE_ROOTFS="$1"
|
|
shift
|
|
else
|
|
log "unknown option: $1"
|
|
usage
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$BASE_ROOTFS" ]]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$BASE_ROOTFS" ]]; then
|
|
log "base rootfs not found: $BASE_ROOTFS"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$OUT_ROOTFS" ]]; then
|
|
base_dir="$(dirname "$BASE_ROOTFS")"
|
|
base_name="$(basename "$BASE_ROOTFS")"
|
|
OUT_ROOTFS="${base_dir}/docker-${base_name}"
|
|
fi
|
|
if [[ "$OUT_ROOTFS" == *.ext4 ]]; then
|
|
WORK_SEED="${OUT_ROOTFS%.ext4}.work-seed.ext4"
|
|
else
|
|
WORK_SEED="${OUT_ROOTFS}.work-seed"
|
|
fi
|
|
if [[ -z "$KERNEL" ]]; then
|
|
log "kernel path is required; pass --kernel"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$KERNEL" ]]; then
|
|
log "kernel not found: $KERNEL"
|
|
exit 1
|
|
fi
|
|
if [[ -n "$INITRD" && ! -f "$INITRD" ]]; then
|
|
log "initrd not found: $INITRD"
|
|
exit 1
|
|
fi
|
|
if [[ -n "$MODULES_DIR" && ! -d "$MODULES_DIR" ]]; then
|
|
log "modules dir not found: $MODULES_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -e "$OUT_ROOTFS" ]]; then
|
|
log "output rootfs already exists: $OUT_ROOTFS"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v resize2fs >/dev/null 2>&1; then
|
|
log "resize2fs required"
|
|
exit 1
|
|
fi
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
log "jq required"
|
|
exit 1
|
|
fi
|
|
if ! command -v sha256sum >/dev/null 2>&1; then
|
|
log "sha256sum required to record package preset metadata"
|
|
exit 1
|
|
fi
|
|
if [[ ! -x "$VSOCK_AGENT" ]]; then
|
|
log "vsock agent not found or not executable: $VSOCK_AGENT"
|
|
log "run 'make build'"
|
|
exit 1
|
|
fi
|
|
|
|
APT_PACKAGES=()
|
|
if ! load_package_preset debian APT_PACKAGES; then
|
|
log "debian package preset is empty"
|
|
exit 1
|
|
fi
|
|
if ! PACKAGES_HASH="$(printf '%s\n' "${APT_PACKAGES[@]}" | sha256sum | awk '{print $1}')"; then
|
|
log "failed to hash package preset"
|
|
exit 1
|
|
fi
|
|
printf -v APT_PACKAGES_ESCAPED '%q ' "${APT_PACKAGES[@]}"
|
|
|
|
log "copying base rootfs to $OUT_ROOTFS"
|
|
cp --reflink=auto "$BASE_ROOTFS" "$OUT_ROOTFS"
|
|
|
|
if [[ -n "$SIZE_SPEC" ]]; then
|
|
SIZE_BYTES="$(parse_size "$SIZE_SPEC")"
|
|
BASE_BYTES="$(stat -c%s "$BASE_ROOTFS")"
|
|
if [[ -z "$SIZE_BYTES" || "$SIZE_BYTES" -lt "$BASE_BYTES" ]]; then
|
|
log "size must be >= base image size"
|
|
exit 1
|
|
fi
|
|
log "resizing rootfs to $SIZE_SPEC"
|
|
truncate -s "$SIZE_BYTES" "$OUT_ROOTFS"
|
|
e2fsck -p -f "$OUT_ROOTFS" >/dev/null
|
|
resize2fs "$OUT_ROOTFS" >/dev/null
|
|
fi
|
|
|
|
VM_ID="$(head -c 32 /dev/urandom | xxd -p -c 256)"
|
|
VM_TAG="${VM_ID:0:8}"
|
|
VM_NAME="customize-${VM_TAG}"
|
|
VM_DIR="$VM_ROOT/$VM_ID"
|
|
mkdir -p "$VM_DIR"
|
|
|
|
API_SOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/banger/fc-$VM_TAG.sock"
|
|
LOG_FILE="$VM_DIR/firecracker.log"
|
|
TAP_DEV="tap-fc-$VM_TAG"
|
|
|
|
# Allocate guest IP
|
|
NEXT_IP_FILE="$STATE/next_ip"
|
|
NEXT_IP="$(cat "$NEXT_IP_FILE" 2>/dev/null || echo 2)"
|
|
GUEST_IP="172.16.0.$NEXT_IP"
|
|
echo "$((NEXT_IP + 1))" > "$NEXT_IP_FILE"
|
|
|
|
sudo -v
|
|
|
|
cleanup() {
|
|
sudo kill "${FC_PID:-}" 2>/dev/null || true
|
|
if [[ "$NAT_ACTIVE" -eq 1 ]]; then
|
|
banger_nat down >/dev/null 2>&1 || true
|
|
fi
|
|
sudo ip link del "$TAP_DEV" 2>/dev/null || true
|
|
rm -f "$API_SOCK"
|
|
rm -rf "$VM_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
sudo mkdir -p "$(dirname "$API_SOCK")"
|
|
sudo chown "$(id -u):$(id -g)" "$(dirname "$API_SOCK")"
|
|
|
|
# Host bridge
|
|
if ! ip link show "$BR_DEV" >/dev/null 2>&1; then
|
|
log "creating host bridge $BR_DEV ($BR_IP/$CIDR)"
|
|
sudo ip link add name "$BR_DEV" type bridge
|
|
sudo ip addr add "${BR_IP}/${CIDR}" dev "$BR_DEV"
|
|
sudo ip link set "$BR_DEV" up
|
|
else
|
|
sudo ip link set "$BR_DEV" up
|
|
fi
|
|
|
|
log "creating tap device $TAP_DEV"
|
|
TAP_USER="${SUDO_UID:-$(id -u)}"
|
|
TAP_GROUP="${SUDO_GID:-$(id -g)}"
|
|
sudo ip tuntap add dev "$TAP_DEV" mode tap user "$TAP_USER" group "$TAP_GROUP"
|
|
sudo ip link set "$TAP_DEV" master "$BR_DEV"
|
|
sudo ip link set "$TAP_DEV" up
|
|
sudo ip link set "$BR_DEV" up
|
|
|
|
log "starting firecracker process"
|
|
rm -f "$API_SOCK"
|
|
nohup sudo -E "$FC_BIN" --api-sock "$API_SOCK" >"$LOG_FILE" 2>&1 &
|
|
FC_PID="$!"
|
|
|
|
log "waiting for firecracker api socket"
|
|
for _ in $(seq 1 200); do
|
|
[[ -S "$API_SOCK" ]] && break
|
|
sleep 0.02
|
|
done
|
|
[[ -S "$API_SOCK" ]] || { log "firecracker api socket not ready"; exit 1; }
|
|
|
|
log "configuring machine"
|
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/machine-config \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"vcpu_count": 2,
|
|
"mem_size_mib": 1024,
|
|
"smt": false
|
|
}' >/dev/null
|
|
|
|
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0:${VM_NAME}:eth0:off:${DNS_SERVER} hostname=${VM_NAME} systemd.mask=home.mount systemd.mask=var.mount"
|
|
|
|
INITRD_JSON=""
|
|
if [[ -n "$INITRD" ]]; then
|
|
INITRD_JSON=", \"initrd_path\": \"$INITRD\""
|
|
fi
|
|
|
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"kernel_image_path\": \"$KERNEL\",
|
|
\"boot_args\": \"$KCMD\"${INITRD_JSON}
|
|
}" >/dev/null
|
|
|
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/drives/rootfs \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"drive_id\": \"rootfs\",
|
|
\"path_on_host\": \"$OUT_ROOTFS\",
|
|
\"is_root_device\": true,
|
|
\"is_read_only\": false
|
|
}" >/dev/null
|
|
|
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/network-interfaces/eth0 \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"iface_id\": \"eth0\",
|
|
\"host_dev_name\": \"$TAP_DEV\"
|
|
}" >/dev/null
|
|
|
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/actions \
|
|
-H "Content-Type: application/json" \
|
|
-d '{ "action_type": "InstanceStart" }' >/dev/null
|
|
|
|
SUDO_CHILD_PID="$(pgrep -n -f "$API_SOCK" || true)"
|
|
if [[ -n "$SUDO_CHILD_PID" ]]; then
|
|
FC_PID="$SUDO_CHILD_PID"
|
|
fi
|
|
|
|
VM_CONFIG_JSON="$(sudo -E curl --unix-socket "$API_SOCK" -sS http://localhost/vm/config)"
|
|
CREATED_AT="$(date -Iseconds)"
|
|
jq -n \
|
|
--arg id "$VM_ID" \
|
|
--arg name "$VM_NAME" \
|
|
--arg pid "$FC_PID" \
|
|
--arg created_at "$CREATED_AT" \
|
|
--arg guest_ip "$GUEST_IP" \
|
|
--arg tap "$TAP_DEV" \
|
|
--arg api_sock "$API_SOCK" \
|
|
--arg log "$LOG_FILE" \
|
|
--arg rootfs "$OUT_ROOTFS" \
|
|
--arg kernel "$KERNEL" \
|
|
--argjson config "$VM_CONFIG_JSON" \
|
|
'{meta:{id:$id,name:$name,pid:$pid,created_at:$created_at,guest_ip:$guest_ip,tap:$tap,api_sock:$api_sock,log:$log,rootfs:$rootfs,kernel:$kernel},config:$config}' \
|
|
> "$VM_DIR/vm.json"
|
|
|
|
log "enabling NAT for customization"
|
|
banger_nat up >/dev/null
|
|
NAT_ACTIVE=1
|
|
|
|
log "waiting for SSH"
|
|
SSH_READY=0
|
|
for _ in $(seq 1 60); do
|
|
if ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"root@${GUEST_IP}" "true" >/dev/null 2>&1; then
|
|
SSH_READY=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [[ "$SSH_READY" -ne 1 ]]; then
|
|
log "ssh did not become ready on $GUEST_IP"
|
|
exit 1
|
|
fi
|
|
|
|
log "configuring guest"
|
|
log "installing vsock agent"
|
|
scp -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"$VSOCK_AGENT" "root@${GUEST_IP}:/usr/local/bin/banger-vsock-agent" >/dev/null
|
|
|
|
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"root@${GUEST_IP}" bash -lc "set -e
|
|
printf 'nameserver %s\n' \"$DNS_SERVER\" > /etc/resolv.conf
|
|
echo \"$VM_NAME\" > /etc/hostname
|
|
printf '127.0.0.1 localhost\n127.0.1.1 %s\n' \"$VM_NAME\" > /etc/hosts
|
|
touch /etc/fstab
|
|
sed -i '\|^/dev/vdb[[:space:]]\+/home[[:space:]]|d; \|^/dev/vdc[[:space:]]\+/var[[:space:]]|d' /etc/fstab
|
|
if ! grep -q '^tmpfs /run ' /etc/fstab; then
|
|
echo 'tmpfs /run tmpfs defaults,nodev,nosuid,mode=0755 0 0' >> /etc/fstab
|
|
fi
|
|
if ! grep -q '^tmpfs /tmp ' /etc/fstab; then
|
|
echo 'tmpfs /tmp tmpfs defaults,nodev,nosuid,mode=1777 0 0' >> /etc/fstab
|
|
fi
|
|
apt-get update
|
|
DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
|
|
DEBIAN_FRONTEND=noninteractive apt-get -y install ${APT_PACKAGES_ESCAPED}
|
|
curl -fsSL https://mise.run | MISE_INSTALL_PATH=\"$MISE_INSTALL_PATH\" MISE_VERSION=\"$MISE_VERSION\" sh
|
|
\"$MISE_INSTALL_PATH\" use -g github:anomalyco/opencode
|
|
\"$MISE_INSTALL_PATH\" reshim
|
|
if [[ ! -e /root/.local/share/mise/shims/opencode ]]; then
|
|
echo 'opencode shim not found after mise install' >&2
|
|
exit 1
|
|
fi
|
|
ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode
|
|
mkdir -p /etc/profile.d
|
|
cat > /etc/profile.d/mise.sh <<'MISEPROFILE'
|
|
if [ -n \"\${BASH_VERSION:-}\" ] && [ -x \"$MISE_INSTALL_PATH\" ]; then
|
|
eval \"\$($MISE_INSTALL_PATH activate bash)\"
|
|
fi
|
|
MISEPROFILE
|
|
chmod 0644 /etc/profile.d/mise.sh
|
|
touch /etc/bash.bashrc
|
|
if ! grep -Fqx '$MISE_ACTIVATE_LINE' /etc/bash.bashrc; then
|
|
printf '\n%s\n' '$MISE_ACTIVATE_LINE' >> /etc/bash.bashrc
|
|
fi
|
|
if [[ \"$INSTALL_DOCKER\" == \"1\" ]]; then
|
|
DEBIAN_FRONTEND=noninteractive apt-get -y remove containerd || true
|
|
if ! DEBIAN_FRONTEND=noninteractive apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then
|
|
DEBIAN_FRONTEND=noninteractive apt-get -y install docker.io
|
|
fi
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl enable --now docker || true
|
|
fi
|
|
fi
|
|
rm -f /root/get-docker /root/get-docker.sh /tmp/get-docker /tmp/get-docker.sh
|
|
chmod 0755 /usr/local/bin/banger-vsock-agent
|
|
mkdir -p /etc/modules-load.d /etc/systemd/system
|
|
cat > /etc/systemd/system/banger-opencode.service <<'EOF'
|
|
[Unit]
|
|
Description=Banger opencode server
|
|
After=network.target
|
|
RequiresMountsFor=/root
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=HOME=/root
|
|
WorkingDirectory=/root
|
|
ExecStart=/usr/local/bin/opencode serve --hostname 0.0.0.0 --port 4096
|
|
Restart=on-failure
|
|
RestartSec=1
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
chmod 0644 /etc/systemd/system/banger-opencode.service
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl daemon-reload || true
|
|
systemctl enable --now banger-opencode.service || true
|
|
fi
|
|
cat > /etc/modules-load.d/banger-vsock.conf <<'EOF'
|
|
vsock
|
|
vmw_vsock_virtio_transport
|
|
EOF
|
|
chmod 0644 /etc/modules-load.d/banger-vsock.conf
|
|
cat > /etc/systemd/system/banger-vsock-agent.service <<'EOF'
|
|
[Unit]
|
|
Description=Banger vsock agent
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/local/bin/banger-vsock-agent
|
|
Restart=on-failure
|
|
RestartSec=1
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
chmod 0644 /etc/systemd/system/banger-vsock-agent.service
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl daemon-reload || true
|
|
systemctl enable --now banger-vsock-agent.service || true
|
|
fi
|
|
git config --system init.defaultBranch main
|
|
"
|
|
|
|
log "configuring tmux resurrect"
|
|
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"root@${GUEST_IP}" bash -se <<EOF
|
|
set -euo pipefail
|
|
|
|
install_tmux_plugin() {
|
|
local dir="\$1"
|
|
local repo="\$2"
|
|
|
|
if [[ -d "\$dir/.git" ]]; then
|
|
git -C "\$dir" fetch --depth 1 origin
|
|
git -C "\$dir" reset --hard FETCH_HEAD
|
|
else
|
|
rm -rf "\$dir"
|
|
git clone --depth 1 "\$repo" "\$dir"
|
|
fi
|
|
}
|
|
|
|
mkdir -p "$TMUX_PLUGIN_DIR" "$TMUX_RESURRECT_DIR"
|
|
install_tmux_plugin "$TMUX_PLUGIN_DIR/tpm" "$TMUX_TPM_REPO"
|
|
install_tmux_plugin "$TMUX_PLUGIN_DIR/tmux-resurrect" "$TMUX_RESURRECT_REPO"
|
|
install_tmux_plugin "$TMUX_PLUGIN_DIR/tmux-continuum" "$TMUX_CONTINUUM_REPO"
|
|
|
|
TMUX_CONF="/root/.tmux.conf"
|
|
tmp_tmux_conf="\$(mktemp)"
|
|
if [[ -f "\$TMUX_CONF" ]]; then
|
|
awk -v begin="$TMUX_MANAGED_START" -v end="$TMUX_MANAGED_END" '
|
|
\$0 == begin { skip = 1; next }
|
|
\$0 == end { skip = 0; next }
|
|
!skip { print }
|
|
' "\$TMUX_CONF" > "\$tmp_tmux_conf"
|
|
else
|
|
: > "\$tmp_tmux_conf"
|
|
fi
|
|
if [[ -s "\$tmp_tmux_conf" ]]; then
|
|
printf '\n' >> "\$tmp_tmux_conf"
|
|
fi
|
|
cat >> "\$tmp_tmux_conf" <<'TMUXCONF'
|
|
$TMUX_MANAGED_START
|
|
set -g @plugin 'tmux-plugins/tpm'
|
|
set -g @plugin 'tmux-plugins/tmux-resurrect'
|
|
set -g @plugin 'tmux-plugins/tmux-continuum'
|
|
set -g @continuum-save-interval '15'
|
|
set -g @continuum-restore 'off'
|
|
set -g @resurrect-dir '/root/.tmux/resurrect'
|
|
run '~/.tmux/plugins/tpm/tpm'
|
|
$TMUX_MANAGED_END
|
|
TMUXCONF
|
|
mv "\$tmp_tmux_conf" "\$TMUX_CONF"
|
|
chmod 0644 "\$TMUX_CONF"
|
|
EOF
|
|
|
|
if [[ -n "$MODULES_DIR" ]]; then
|
|
MODULES_BASE="$(basename "$MODULES_DIR")"
|
|
log "copying kernel modules ($MODULES_BASE) into guest"
|
|
tar -C "$(dirname "$MODULES_DIR")" -cf - "$MODULES_BASE" | \
|
|
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"root@${GUEST_IP}" bash -lc "set -e
|
|
mkdir -p /lib/modules
|
|
tar -C /lib/modules -xf -
|
|
depmod -a \"$MODULES_BASE\"
|
|
mkdir -p /etc/modules-load.d
|
|
printf 'nf_tables\nnft_chain_nat\nveth\nbr_netfilter\noverlay\n' > /etc/modules-load.d/docker-netfilter.conf
|
|
mkdir -p /etc/sysctl.d
|
|
cat > /etc/sysctl.d/99-docker.conf <<'SYSCTL'
|
|
net.bridge.bridge-nf-call-iptables = 1
|
|
net.bridge.bridge-nf-call-ip6tables = 1
|
|
net.ipv4.ip_forward = 1
|
|
SYSCTL
|
|
sysctl --system >/dev/null 2>&1 || true
|
|
sync
|
|
"
|
|
fi
|
|
|
|
log "shutting down guest"
|
|
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"root@${GUEST_IP}" bash -lc "sync" || true
|
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/actions \
|
|
-H "Content-Type: application/json" \
|
|
-d '{ "action_type": "SendCtrlAltDel" }' >/dev/null || true
|
|
for _ in $(seq 1 200); do
|
|
if ! ps -p "$FC_PID" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 0.05
|
|
done
|
|
write_rootfs_manifest_metadata "$OUT_ROOTFS" "$PACKAGES_HASH"
|
|
log "building work seed $WORK_SEED"
|
|
"$BANGER_BIN" internal work-seed --rootfs "$OUT_ROOTFS" --out "$WORK_SEED"
|
|
log "done"
|