Make the local-only void-exp rootfs useful as a dev VM baseline by baking Docker and Compose into the XBPS package set instead of leaving container setup to manual follow-up. Enable the docker runit service during image assembly, add a small boot preflight that loads the needed netfilter/overlay modules and applies the Docker sysctl file before dockerd starts, and keep the Void cleanup path removing caches, docs, and stale get-docker artifacts. Refresh the README and repo guidance to describe Docker as part of the current Void image contract and to remind users that they need to rebuild and recreate Void VMs to pick it up. Verified with bash -n make-rootfs-void.sh and git diff --check for the touched files. I did not run a live make rootfs-void or boot a fresh Void VM in this pass.
503 lines
14 KiB
Bash
Executable file
503 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
log() {
|
|
printf '[make-rootfs-void] %s\n' "$*"
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./make-rootfs-void.sh [--out <path>] [--size <size>] [--mirror <url>] [--arch <arch>] [--packages <path>]
|
|
|
|
Build an experimental Void Linux rootfs image plus a matching /root work-seed.
|
|
|
|
Defaults:
|
|
--out ./runtime/rootfs-void.ext4
|
|
--size 2G
|
|
--mirror https://repo-default.voidlinux.org
|
|
--arch x86_64
|
|
--packages ./packages.void
|
|
|
|
This path is experimental and local-only. It reuses the current runtime bundle
|
|
kernel/initrd/modules and does not change the default Debian image flow.
|
|
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) printf '%s\n' $((num * 1024)) ;;
|
|
M|"") printf '%s\n' $((num * 1024 * 1024)) ;;
|
|
G) printf '%s\n' $((num * 1024 * 1024 * 1024)) ;;
|
|
esac
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
require_command() {
|
|
local name="$1"
|
|
command -v "$name" >/dev/null 2>&1 || {
|
|
log "required command not found: $name"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
resolve_banger_bin() {
|
|
if [[ -n "${BANGER_BIN:-}" ]]; then
|
|
printf '%s\n' "$BANGER_BIN"
|
|
return
|
|
fi
|
|
if [[ -x "$SCRIPT_DIR/banger" ]]; then
|
|
printf '%s\n' "$SCRIPT_DIR/banger"
|
|
return
|
|
fi
|
|
if command -v banger >/dev/null 2>&1; then
|
|
command -v banger
|
|
return
|
|
fi
|
|
log "banger binary not found; build it first with 'make build' or set BANGER_BIN"
|
|
exit 1
|
|
}
|
|
|
|
normalize_mirror() {
|
|
local mirror="${1%/}"
|
|
mirror="${mirror%/current}"
|
|
mirror="${mirror%/static}"
|
|
printf '%s\n' "$mirror"
|
|
}
|
|
|
|
bundle_path() {
|
|
local key="$1"
|
|
local fallback="$2"
|
|
local rel=""
|
|
|
|
if [[ -f "$BUNDLE_METADATA" ]] && command -v jq >/dev/null 2>&1; then
|
|
rel="$(jq -r --arg key "$key" '.[$key] // empty' "$BUNDLE_METADATA" 2>/dev/null || true)"
|
|
fi
|
|
if [[ -n "$rel" && "$rel" != "null" ]]; then
|
|
printf '%s\n' "$RUNTIME_DIR/$rel"
|
|
return
|
|
fi
|
|
printf '%s\n' "$fallback"
|
|
}
|
|
|
|
find_static_binary() {
|
|
local name="$1"
|
|
find "$STATIC_DIR" -type f \( -name "$name" -o -name "$name.static" \) -perm -u+x | sort | head -n 1
|
|
}
|
|
|
|
find_static_keys_dir() {
|
|
find "$STATIC_DIR" -type d -path '*/var/db/xbps/keys' | sort | head -n 1
|
|
}
|
|
|
|
ensure_sshd_include() {
|
|
local cfg="$ROOT_MOUNT/etc/ssh/sshd_config"
|
|
local tmp_cfg="$TMP_DIR/sshd_config"
|
|
local include_line="Include /etc/ssh/sshd_config.d/*.conf"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/ssh/sshd_config.d"
|
|
if sudo test -f "$cfg"; then
|
|
sudo cat "$cfg" > "$tmp_cfg"
|
|
else
|
|
: > "$tmp_cfg"
|
|
fi
|
|
|
|
if ! grep -Eq '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config\.d/\*\.conf([[:space:]]|$)' "$tmp_cfg"; then
|
|
{
|
|
printf '%s\n' "$include_line"
|
|
cat "$tmp_cfg"
|
|
} > "${tmp_cfg}.new"
|
|
mv "${tmp_cfg}.new" "$tmp_cfg"
|
|
sudo install -m 0644 "$tmp_cfg" "$cfg"
|
|
fi
|
|
}
|
|
|
|
install_vsock_service() {
|
|
local service_dir="$ROOT_MOUNT/etc/sv/banger-vsock-agent"
|
|
local run_path="$service_dir/run"
|
|
local finish_path="$service_dir/finish"
|
|
|
|
sudo mkdir -p "$service_dir"
|
|
cat <<'EOF' | sudo tee "$run_path" >/dev/null
|
|
#!/bin/sh
|
|
modprobe vsock 2>/dev/null || true
|
|
modprobe vmw_vsock_virtio_transport 2>/dev/null || true
|
|
exec /usr/local/bin/banger-vsock-agent
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$finish_path" >/dev/null
|
|
#!/bin/sh
|
|
exit 0
|
|
EOF
|
|
sudo chmod 0755 "$run_path" "$finish_path"
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/banger-vsock-agent "$ROOT_MOUNT/etc/runit/runsvdir/default/banger-vsock-agent"
|
|
}
|
|
|
|
configure_docker_bootstrap() {
|
|
local modules_conf="$ROOT_MOUNT/etc/modules-load.d/docker-netfilter.conf"
|
|
local sysctl_conf="$ROOT_MOUNT/etc/sysctl.d/99-docker.conf"
|
|
local service_dir="$ROOT_MOUNT/etc/sv/docker"
|
|
local run_path="$service_dir/run"
|
|
local orig_run_path="$service_dir/run.orig"
|
|
local preflight_path="$ROOT_MOUNT/usr/local/bin/banger-docker-preflight"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/modules-load.d" "$ROOT_MOUNT/etc/sysctl.d" "$ROOT_MOUNT/usr/local/bin"
|
|
cat <<'EOF' | sudo tee "$modules_conf" >/dev/null
|
|
nf_tables
|
|
nft_chain_nat
|
|
veth
|
|
br_netfilter
|
|
overlay
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$sysctl_conf" >/dev/null
|
|
net.bridge.bridge-nf-call-iptables = 1
|
|
net.bridge.bridge-nf-call-ip6tables = 1
|
|
net.ipv4.ip_forward = 1
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$preflight_path" >/dev/null
|
|
#!/bin/sh
|
|
for module in nf_tables nft_chain_nat veth br_netfilter overlay; do
|
|
modprobe "$module" 2>/dev/null || true
|
|
done
|
|
if command -v sysctl >/dev/null 2>&1; then
|
|
sysctl --load /etc/sysctl.d/99-docker.conf >/dev/null 2>&1 || true
|
|
fi
|
|
EOF
|
|
|
|
if [[ ! -f "$run_path" ]]; then
|
|
log "Void rootfs is missing /etc/sv/docker/run after docker install"
|
|
exit 1
|
|
fi
|
|
sudo install -m 0755 "$run_path" "$orig_run_path"
|
|
cat <<'EOF' | sudo tee "$run_path" >/dev/null
|
|
#!/bin/sh
|
|
set -e
|
|
/usr/local/bin/banger-docker-preflight
|
|
exec /etc/sv/docker/run.orig
|
|
EOF
|
|
sudo chmod 0644 "$modules_conf" "$sysctl_conf"
|
|
sudo chmod 0755 "$preflight_path" "$run_path" "$orig_run_path"
|
|
}
|
|
|
|
enable_sshd_service() {
|
|
if [[ ! -d "$ROOT_MOUNT/etc/sv/sshd" ]]; then
|
|
log "Void rootfs is missing /etc/sv/sshd after openssh install"
|
|
exit 1
|
|
fi
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/sshd "$ROOT_MOUNT/etc/runit/runsvdir/default/sshd"
|
|
}
|
|
|
|
enable_docker_service() {
|
|
if [[ ! -d "$ROOT_MOUNT/etc/sv/docker" ]]; then
|
|
log "Void rootfs is missing /etc/sv/docker after docker install"
|
|
exit 1
|
|
fi
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/docker "$ROOT_MOUNT/etc/runit/runsvdir/default/docker"
|
|
}
|
|
|
|
normalize_root_shell() {
|
|
local passwd="$ROOT_MOUNT/etc/passwd"
|
|
local shells="$ROOT_MOUNT/etc/shells"
|
|
local wanted_shell="/bin/bash"
|
|
local tmp_passwd="$TMP_DIR/passwd"
|
|
local root_shell=""
|
|
|
|
if [[ ! -x "$ROOT_MOUNT$wanted_shell" ]]; then
|
|
log "required root shell is missing from the Void image: $wanted_shell"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$shells" ]]; then
|
|
log "Void image is missing /etc/shells"
|
|
exit 1
|
|
fi
|
|
if ! sudo grep -Fxq "$wanted_shell" "$shells"; then
|
|
log "Void image does not allow $wanted_shell in /etc/shells"
|
|
exit 1
|
|
fi
|
|
|
|
sudo cat "$passwd" > "$tmp_passwd"
|
|
awk -F: -v OFS=: -v shell="$wanted_shell" '
|
|
$1 == "root" {
|
|
$7 = shell
|
|
found = 1
|
|
}
|
|
{ print }
|
|
END {
|
|
if (!found) {
|
|
exit 1
|
|
}
|
|
}
|
|
' "$tmp_passwd" > "${tmp_passwd}.new" || {
|
|
log "failed to rewrite root shell in /etc/passwd"
|
|
exit 1
|
|
}
|
|
mv "${tmp_passwd}.new" "$tmp_passwd"
|
|
sudo install -m 0644 "$tmp_passwd" "$passwd"
|
|
|
|
root_shell="$(sudo awk -F: '$1 == "root" { print $7 }' "$passwd")"
|
|
if [[ "$root_shell" != "$wanted_shell" ]]; then
|
|
log "root shell normalization failed: expected $wanted_shell, got ${root_shell:-<empty>}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
configure_root_bash_prompt() {
|
|
local bashrc="$ROOT_MOUNT/root/.bashrc"
|
|
local bash_profile="$ROOT_MOUNT/root/.bash_profile"
|
|
local profile_prompt="$ROOT_MOUNT/etc/profile.d/banger-bash-prompt.sh"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/root" "$ROOT_MOUNT/etc/profile.d"
|
|
cat <<'EOF' | sudo tee "$bashrc" >/dev/null
|
|
# banger: default interactive prompt for experimental Void guests
|
|
case "$-" in
|
|
*i*) ;;
|
|
*) return ;;
|
|
esac
|
|
|
|
PS1='\u@\h:\w\$ '
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$bash_profile" >/dev/null
|
|
if [ -f ~/.bashrc ]; then
|
|
. ~/.bashrc
|
|
fi
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$profile_prompt" >/dev/null
|
|
case "$-" in
|
|
*i*) ;;
|
|
*) return 0 2>/dev/null || exit 0 ;;
|
|
esac
|
|
|
|
if [ -n "${BASH_VERSION:-}" ]; then
|
|
PS1='\u@\h:\w\$ '
|
|
fi
|
|
EOF
|
|
sudo chmod 0644 "$bashrc" "$bash_profile" "$profile_prompt"
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "${ROOT_MOUNT:-}" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT"; then
|
|
sudo umount "$ROOT_MOUNT" || true
|
|
fi
|
|
if [[ "${BUILD_DONE:-0}" != "1" ]]; then
|
|
rm -f "${OUT_ROOTFS:-}" "${WORK_SEED:-}" "${OUT_ROOTFS:-}.packages.sha256"
|
|
fi
|
|
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
|
|
rm -rf "$TMP_DIR"
|
|
fi
|
|
}
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PACKAGES_FILE="$SCRIPT_DIR/packages.void"
|
|
export BANGER_APT_PACKAGES_FILE="$PACKAGES_FILE"
|
|
source "$SCRIPT_DIR/packages.sh"
|
|
|
|
DEFAULT_RUNTIME_DIR="$SCRIPT_DIR"
|
|
if [[ -d "$SCRIPT_DIR/runtime" ]]; then
|
|
DEFAULT_RUNTIME_DIR="$SCRIPT_DIR/runtime"
|
|
fi
|
|
RUNTIME_DIR="${BANGER_RUNTIME_DIR:-$DEFAULT_RUNTIME_DIR}"
|
|
if [[ ! -d "$RUNTIME_DIR" ]]; then
|
|
log "runtime bundle not found: $RUNTIME_DIR"
|
|
log "run 'make runtime-bundle' or set BANGER_RUNTIME_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
BUNDLE_METADATA="$RUNTIME_DIR/bundle.json"
|
|
OUT_ROOTFS="$RUNTIME_DIR/rootfs-void.ext4"
|
|
SIZE_SPEC="2G"
|
|
MIRROR="https://repo-default.voidlinux.org"
|
|
ARCH="x86_64"
|
|
MODULES_DIR="$(bundle_path default_modules_dir "$RUNTIME_DIR/wtf/root/lib/modules/6.8.0-94-generic")"
|
|
VSOCK_AGENT="$(bundle_path vsock_agent_path "$RUNTIME_DIR/banger-vsock-agent")"
|
|
if [[ "$VSOCK_AGENT" == "$RUNTIME_DIR/banger-vsock-agent" && ! -x "$VSOCK_AGENT" ]]; then
|
|
VSOCK_AGENT="$(bundle_path vsock_ping_helper_path "$RUNTIME_DIR/banger-vsock-pingd")"
|
|
fi
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--out)
|
|
OUT_ROOTFS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--size)
|
|
SIZE_SPEC="${2:-}"
|
|
shift 2
|
|
;;
|
|
--mirror)
|
|
MIRROR="${2:-}"
|
|
shift 2
|
|
;;
|
|
--arch)
|
|
ARCH="${2:-}"
|
|
shift 2
|
|
;;
|
|
--packages)
|
|
PACKAGES_FILE="${2:-}"
|
|
export BANGER_APT_PACKAGES_FILE="$PACKAGES_FILE"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
log "unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
MIRROR="$(normalize_mirror "$MIRROR")"
|
|
REPO_URL="$MIRROR/current"
|
|
STATIC_ARCHIVE_URL="$MIRROR/static/xbps-static-latest.x86_64-musl.tar.xz"
|
|
|
|
if [[ "$ARCH" != "x86_64" ]]; then
|
|
log "unsupported arch: $ARCH"
|
|
log "this experimental builder currently supports only x86_64-glibc"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$PACKAGES_FILE" ]]; then
|
|
log "package manifest not found: $PACKAGES_FILE"
|
|
exit 1
|
|
fi
|
|
if [[ ! -d "$MODULES_DIR" ]]; then
|
|
log "modules dir not found: $MODULES_DIR"
|
|
exit 1
|
|
fi
|
|
if [[ ! -x "$VSOCK_AGENT" ]]; then
|
|
log "vsock agent not found or not executable: $VSOCK_AGENT"
|
|
log "run 'make build' or refresh the runtime bundle"
|
|
exit 1
|
|
fi
|
|
if [[ -e "$OUT_ROOTFS" ]]; then
|
|
log "output rootfs already exists: $OUT_ROOTFS"
|
|
exit 1
|
|
fi
|
|
|
|
require_command curl
|
|
require_command tar
|
|
require_command sudo
|
|
require_command mkfs.ext4
|
|
require_command mount
|
|
require_command umount
|
|
require_command install
|
|
require_command find
|
|
require_command awk
|
|
require_command sed
|
|
require_command sha256sum
|
|
require_command truncate
|
|
require_command mountpoint
|
|
|
|
VOID_PACKAGES=()
|
|
if ! banger_packages_read_array VOID_PACKAGES "$PACKAGES_FILE"; then
|
|
log "package manifest is empty: $PACKAGES_FILE"
|
|
exit 1
|
|
fi
|
|
if ! PACKAGES_HASH="$(banger_packages_manifest_hash "$PACKAGES_FILE")"; then
|
|
log "failed to hash package manifest: $PACKAGES_FILE"
|
|
exit 1
|
|
fi
|
|
if ! SIZE_BYTES="$(parse_size "$SIZE_SPEC")"; then
|
|
log "invalid size: $SIZE_SPEC"
|
|
exit 1
|
|
fi
|
|
|
|
BANGER_BIN="$(resolve_banger_bin)"
|
|
if [[ "$OUT_ROOTFS" == *.ext4 ]]; then
|
|
WORK_SEED="${OUT_ROOTFS%.ext4}.work-seed.ext4"
|
|
else
|
|
WORK_SEED="${OUT_ROOTFS}.work-seed"
|
|
fi
|
|
|
|
TMP_DIR="$(mktemp -d -t banger-void-rootfs-XXXXXX)"
|
|
STATIC_DIR="$TMP_DIR/static"
|
|
ROOT_MOUNT="$TMP_DIR/rootfs"
|
|
STATIC_ARCHIVE="$TMP_DIR/xbps-static.tar.xz"
|
|
BUILD_DONE=0
|
|
trap cleanup EXIT
|
|
|
|
mkdir -p "$STATIC_DIR" "$ROOT_MOUNT"
|
|
|
|
log "downloading static XBPS from $STATIC_ARCHIVE_URL"
|
|
curl -fsSL "$STATIC_ARCHIVE_URL" -o "$STATIC_ARCHIVE"
|
|
tar -xf "$STATIC_ARCHIVE" -C "$STATIC_DIR"
|
|
|
|
XBPS_INSTALL="$(find_static_binary xbps-install)"
|
|
XBPS_QUERY="$(find_static_binary xbps-query)"
|
|
STATIC_KEYS_DIR="$(find_static_keys_dir)"
|
|
|
|
if [[ -z "$XBPS_INSTALL" || ! -x "$XBPS_INSTALL" ]]; then
|
|
log "failed to locate xbps-install in the static archive"
|
|
exit 1
|
|
fi
|
|
if [[ -z "$STATIC_KEYS_DIR" || ! -d "$STATIC_KEYS_DIR" ]]; then
|
|
log "failed to locate Void repository keys in the static archive"
|
|
exit 1
|
|
fi
|
|
|
|
log "creating $OUT_ROOTFS ($SIZE_SPEC)"
|
|
truncate -s "$SIZE_BYTES" "$OUT_ROOTFS"
|
|
mkfs.ext4 -F -m 0 -L banger-void-root "$OUT_ROOTFS" >/dev/null
|
|
sudo mount -o loop "$OUT_ROOTFS" "$ROOT_MOUNT"
|
|
sudo mkdir -p "$ROOT_MOUNT/var/db/xbps/keys"
|
|
sudo cp -a "$STATIC_KEYS_DIR/." "$ROOT_MOUNT/var/db/xbps/keys/"
|
|
|
|
log "installing Void packages into the rootfs"
|
|
sudo env XBPS_ARCH="$ARCH" "$XBPS_INSTALL" -S -y -r "$ROOT_MOUNT" -R "$REPO_URL" "${VOID_PACKAGES[@]}"
|
|
|
|
if [[ -n "$XBPS_QUERY" && -x "$XBPS_QUERY" ]]; then
|
|
log "installed package set:"
|
|
sudo env XBPS_ARCH="$ARCH" "$XBPS_QUERY" -r "$ROOT_MOUNT" -l | awk '/^ii/ {print " " $2}' || true
|
|
fi
|
|
|
|
log "copying bundled kernel modules into the guest"
|
|
sudo mkdir -p "$ROOT_MOUNT/lib/modules"
|
|
sudo cp -a "$MODULES_DIR" "$ROOT_MOUNT/lib/modules/"
|
|
|
|
log "installing the guest-side vsock agent"
|
|
sudo mkdir -p "$ROOT_MOUNT/usr/local/bin"
|
|
sudo install -m 0755 "$VSOCK_AGENT" "$ROOT_MOUNT/usr/local/bin/banger-vsock-agent"
|
|
|
|
log "preparing SSH and runit services"
|
|
ensure_sshd_include
|
|
enable_sshd_service
|
|
install_vsock_service
|
|
configure_docker_bootstrap
|
|
enable_docker_service
|
|
normalize_root_shell
|
|
configure_root_bash_prompt
|
|
sudo mkdir -p "$ROOT_MOUNT/root/.ssh"
|
|
sudo touch "$ROOT_MOUNT/etc/fstab" "$ROOT_MOUNT/etc/hostname"
|
|
sudo chroot "$ROOT_MOUNT" /usr/bin/ssh-keygen -A
|
|
|
|
log "removing bulky caches, docs, and stale installer artifacts from the experimental image"
|
|
sudo rm -rf \
|
|
"$ROOT_MOUNT/var/cache/xbps" \
|
|
"$ROOT_MOUNT/usr/share/doc" \
|
|
"$ROOT_MOUNT/usr/share/info" \
|
|
"$ROOT_MOUNT/usr/share/man"
|
|
sudo rm -f \
|
|
"$ROOT_MOUNT/root/get-docker" \
|
|
"$ROOT_MOUNT/root/get-docker.sh" \
|
|
"$ROOT_MOUNT/tmp/get-docker" \
|
|
"$ROOT_MOUNT/tmp/get-docker.sh"
|
|
|
|
sudo umount "$ROOT_MOUNT"
|
|
|
|
banger_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"
|
|
|
|
BUILD_DONE=1
|
|
log "built experimental Void rootfs: $OUT_ROOTFS"
|
|
log "built experimental Void work-seed: $WORK_SEED"
|
|
log "use examples/void-exp.config.toml as the local config override template"
|