#!/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 create { incus --project "$PROJECT_SANDBOX" copy "$TEMPLATE_SANDBOX" "$CONTAINER" --target-project $(incus project get-current) incus file delete "$CONTAINER/etc/machine-id" incus start "$CONTAINER" customize_host "$CONTAINER" } function mount_miaou_bash { [[ -v MIAOU_BASH_DIR ]] && echo >&2 "Warn: variable MIAOU_BASH_DIR 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' incus $optional_project config set $target environment.PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/miaou-bash/tools } function build_sandbox_project { incus project show $PROJECT_SANDBOX > /dev/null || incus project create $PROJECT_SANDBOX } function refresh_template { incus --project "$PROJECT_SANDBOX" start "$TEMPLATE_SANDBOX" incus --project "$PROJECT_SANDBOX" exec "$TEMPLATE_SANDBOX" -- bash << EOF apt-get update apt-get dist-upgrade -y apt-get install -y curl /opt/miaou-bash/install.sh 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 image from scratch..." 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 << EOF | incus --project "$PROJECT_SANDBOX" file push --uid 0 --gid 0 --mode 644 --create-dirs - "$TEMPLATE_SANDBOX"/etc/systemd/resolved.conf.d/10-disable-ipv4-listener.conf [Resolve] LLMNR=no DNSStubListener=no EOF mount_miaou_bash "$TEMPLATE_SANDBOX" "$PROJECT_SANDBOX" incus --project "$PROJECT_SANDBOX" create "$MIAOU_IMAGE" "$TEMPLATE_SANDBOX" refresh_template echo "image:"$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="$1" local domain=$(incus network get incusbr0 dns.domain) domain=${domain:-incus} cat << EOF | incus file push --uid 0 --gid 0 --mode 644 --create-dirs - $target/etc/hosts 127.0.1.1 $target.$domain $target 127.0.0.1 localhost EOF } 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 } # MAIN set -Eue parse_options "$@" assert_not_sandboxing prepare_template_for_quick_creation create echo Success