#!/usr/bin/env bash # CONSTANTS BASEDIR=$(dirname "$0") MIAOU_IMAGE=images:debian/13 APT_ARCHIVES=/var/cache/apt/archives PROJECT_SANDBOX=sandbox TEMPLATE_SANDBOX=miaou-debian13-template TEMPLATE_REFRESH_DELAY="7 days" CONTAINER= # FUNCTIONS function usage { echo "$(basename "$0") " } function humanize_ago { local seconds=$(($(date +%s) - $(date -d "$1" +%s))) local days=$((seconds / 86400)) local hours=$(((seconds % 86400) / 3600)) local minutes=$(((seconds % 3600) / 60)) if ((days > 365)); then echo "$((days / 365)) year(s) ago" elif ((days > 30)); then echo "$((days / 30)) month(s) ago" elif ((days > 0)); then echo "$days day(s) ago" elif ((hours > 0)); then echo "$hours hour(s) ago" elif ((minutes > 0)); then echo "$minutes minute(s) ago" else echo "$seconds second(s) ago" fi } function parse_options { while [[ $# -gt 0 ]]; do case "$1" in --help | -h) usage && exit 0 ;; *) if [[ -z $CONTAINER ]]; then CONTAINER=$1 else echo >&2 "Unknown option: $1" && usage && exit 2 fi ;; esac shift 1 # Move to the next argument done [[ -n $CONTAINER ]] || (usage && exit 1) } function mount_miaou_bash { printenv MIAOU_BASH_DIR >/dev/null || (echo >&2 "Warn: environment variable MIAOU_BASH_DIR is missing!" && return) local target="$1" local optional_project=${2:-} [[ -n $optional_project ]] && optional_project="--project $optional_project" incus $optional_project config device add $target MIAOU-BASH disk source=$MIAOU_BASH_DIR path=/opt/miaou-bash readonly=true | grep -q 'Device MIAOU-BASH added' } function infocmp_xterm_ghostty { local target="$1" local optional_project=${2:-} [[ -n $optional_project ]] && optional_project="--project $optional_project" echo "export GHOSTTY infocmp from host to container" infocmp -x xterm-ghostty | incus exec $optional_project $target -- tic -x - 2>/dev/null } function build_sandbox_project { if ! incus project show $PROJECT_SANDBOX >/dev/null; then incus project create $PROJECT_SANDBOX incus --project $PROJECT_SANDBOX profile device add default root disk path=/ pool=default incus --project $PROJECT_SANDBOX profile device add default eth0 nic name=eth0 network=incusbr0 fi } function refresh_template { if incus --project "$PROJECT_SANDBOX" info "$TEMPLATE_SANDBOX" | grep '^Status: STOPPED'; then incus --project "$PROJECT_SANDBOX" start "$TEMPLATE_SANDBOX" fi incus --project "$PROJECT_SANDBOX" exec "$TEMPLATE_SANDBOX" -- bash </dev/null EOF incus --project "$PROJECT_SANDBOX" stop "$TEMPLATE_SANDBOX" local image_date=$(incus --project "$PROJECT_SANDBOX" list "$TEMPLATE_SANDBOX" -f compact,noheader -cl) echo "template refreshed at: $image_date" } function build_template_from_scratch { build_sandbox_project echo "building template from scratch..." incus --project "$PROJECT_SANDBOX" launch "$MIAOU_IMAGE" "$TEMPLATE_SANDBOX" incus --project "$PROJECT_SANDBOX" file delete "$TEMPLATE_SANDBOX"/etc/apt/sources.list incus --project "$PROJECT_SANDBOX" file push --uid 0 --gid 0 --mode 644 --create-dirs /etc/apt/sources.list.d/debian.sources "$TEMPLATE_SANDBOX"/etc/apt/sources.list.d/debian.sources cat </dev/null && infocmp_xterm_ghostty "$TEMPLATE_SANDBOX" "$PROJECT_SANDBOX" refresh_template echo "template:"$TEMPLATE_SANDBOX" from project:$PROJECT_SANDBOX built successfully!" } function prepare_template_for_quick_creation { local image_date=$(incus list "$TEMPLATE_SANDBOX" -f compact,noheader -cl --project "$PROJECT_SANDBOX") if [[ -z $image_date ]]; then build_template_from_scratch else local true_date=$(date -d "$image_date" +%s) local expiration_date=$(date -d "$TEMPLATE_REFRESH_DELAY ago" +%s) if ((true_date < expiration_date)); then echo "template exists $(humanize_ago "$image_date"), needs a refresh..." refresh_template else : # echo "template already exists $(humanize_ago "$image_date")!" fi fi } function customize_host { local target domain target="$1" domain=$(incus network get incusbr0 dns.domain) domain=${domain:-incus} printf "127.0.1.1\t$target.$domain $target\n127.0.0.1\tlocalhost\n" | incus file push - $target/etc/hosts echo "$target.$domain" | incus file push - $target/etc/hostname } function assert_not_sandboxing { [[ $(incus project get-current) == "$PROJECT_SANDBOX" ]] && echo ERROR: actual sandboxing project, please switch back to your 'default' project >&2 && exit 30 || true } function create { if incus --project "$PROJECT_SANDBOX" info "$TEMPLATE_SANDBOX" | grep '^Status: RUNNING'; then incus --project "$PROJECT_SANDBOX" stop "$TEMPLATE_SANDBOX" fi incus --project "$PROJECT_SANDBOX" copy --instance-only "$TEMPLATE_SANDBOX" "$CONTAINER" --target-project $(incus project get-current) incus file delete "$CONTAINER/etc/machine-id" incus config unset "$CONTAINER" volatile.eth0.hwaddr incus start "$CONTAINER" customize_host "$CONTAINER" } # MAIN set -Eueo pipefail parse_options "$@" assert_not_sandboxing prepare_template_for_quick_creation create echo Success