aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xborg-internet.sh22
-rwxr-xr-xcapture-clip49
-rwxr-xr-xcitekey-menu28
-rwxr-xr-xdmenu-edit-config52
-rwxr-xr-xdmenu-memo46
-rwxr-xr-xdmenu-scripts38
-rwxr-xr-xdmenu-vimwiki-open75
-rwxr-xr-xfakeid244
l---------faker1
-rwxr-xr-xkvm-lab-create148
-rwxr-xr-xkvm-lab-create.bak160
-rwxr-xr-xkvm-lab-destroy65
-rwxr-xr-xkvm-lab-destroy.bak52
-rwxr-xr-xofficium4
14 files changed, 868 insertions, 116 deletions
diff --git a/borg-internet.sh b/borg-internet.sh
new file mode 100755
index 0000000..84247d1
--- /dev/null
+++ b/borg-internet.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -euo pipefail
+
+REPO="borg@sanctum-wg:/tank/backups/borg"
+NAME="home-$(date +%F_%H-%M)"
+SRC="/home/lukasz"
+
+export BORG_RSH="ssh -p 57385 -i $HOME/.ssh/sacrum.key -o IdentitiesOnly=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3"
+
+LOGDIR="$HOME/.local/state/borg"
+mkdir -p "$LOGDIR"
+LOGFILE="$LOGDIR/backup-$(date +%F).log"
+
+ARGS=(--compression lz4 --exclude-from "$HOME/.config/borg/excludes")
+if [ -t 1 ]; then
+ ARGS+=(--progress --list --stats)
+else
+ ARGS+=(--stats)
+fi
+
+borg create "${ARGS[@]}" "$REPO"::"$NAME" "$SRC" 2>&1 | tee -a "$LOGFILE"
+
diff --git a/capture-clip b/capture-clip
new file mode 100755
index 0000000..3bf17f1
--- /dev/null
+++ b/capture-clip
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+OUTFILE="${OUTFILE:-$HOME/.clips}"
+
+notify() {
+ # $1 = urgency (low|normal|critical)
+ # $2 = title
+ # $3 = body
+ if command -v notify-send >/dev/null 2>&1; then
+ notify-send -u "$1" "$2" "$3"
+ fi
+}
+
+die() {
+ notify critical "capture-clip" "$1"
+ printf -- "ERROR: %s\n" "$1" >&2
+ exit 1
+}
+
+get_clipboard() {
+ if command -v xclip >/dev/null 2>&1; then
+ xclip -o -selection clipboard 2>/dev/null || xclip -o -selection primary
+ return 0
+ fi
+ if command -v xsel >/dev/null 2>&1; then
+ xsel --clipboard --output 2>/dev/null || xsel --primary --output
+ return 0
+ fi
+ die "need xclip or xsel"
+}
+
+clip="$(get_clipboard)"
+
+[[ -n "$clip" ]] || die "clipboard is empty"
+
+ts="$(date '+%y:%m:%d %H:%M:%S')"
+
+{
+ printf -- "# caputred %s\n" "$ts"
+ printf -- "%s\n" "$clip"
+ printf -- "---\n"
+} >> "$OUTFILE"
+
+lines="$(printf "%s" "$clip" | wc -l | tr -d ' ')"
+chars="$(printf "%s" "$clip" | wc -c | tr -d ' ')"
+
+notify normal "capture-clip" "Saved to ~/.clips ($lines lines, $chars chars)"
+
diff --git a/citekey-menu b/citekey-menu
new file mode 100755
index 0000000..3b94bbe
--- /dev/null
+++ b/citekey-menu
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+BIB="${BIB:-/home/lukasz/thesis_main/05_thesis/bibliography.bib}"
+DMENU="${DMENU:-dmenu -i -l 20 -p Citekey}"
+CLIP="${CLIP:-xclip -selection clipboard}"
+
+if [[ ! -r "$BIB" ]]; then
+ printf "ERROR: cannot read %s\n" "$BIB" >&2
+ exit 1
+fi
+
+# Build menu lines: "type<TAB>citekey"
+choices="$(
+ rg -n '^\@(\w+)\{([^,]+),' "$BIB" \
+ | sed -E 's/^[0-9]+:\@(\w+)\{([^,]+),/\1\t\2/' \
+ | sort -u
+)"
+
+picked="$(printf "%s\n" "$choices" | eval "$DMENU" || true)"
+[[ -z "$picked" ]] && exit 0
+
+key="$(printf "%s" "$picked" | awk -F'\t' '{print $2}')"
+[[ -z "$key" ]] && exit 0
+
+printf "[@%s]" "$key" | eval "$CLIP"
+printf "Copied: [@%s]\n" "$key" >&2
+
diff --git a/dmenu-edit-config b/dmenu-edit-config
new file mode 100755
index 0000000..47705e1
--- /dev/null
+++ b/dmenu-edit-config
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Optional dmenu theming
+# shellcheck disable=SC1090
+[[ -r "${HOME}/.local/bin/dmenu-theming" ]] && . "${HOME}/.local/bin/dmenu-theming"
+
+DMENU="${DMENU:-dmenu}"
+PROMPT="${PROMPT:-Config: }"
+EDITOR_CMD="${EDITOR_CMD:-vim}"
+TERM_CMD="${TERM_CMD:-st -e}" # change to "alacritty -e" / "kitty -e" / "wezterm start --" if needed
+INDEX="${INDEX:-${HOME}/.config/dmenu/configs.tsv}"
+
+[[ -r "$INDEX" ]] || { printf "ERROR: cannot read %s\n" "$INDEX" >&2; exit 1; }
+
+# Build menu: show label, keep path as hidden second field
+menu="$(
+ awk -F'\t' 'NF>=2 {print $1 "\t" $2}' "$INDEX"
+)"
+
+picked="$(
+ printf "%s\n" "$menu" \
+ | ${DMENU} -i -l 20 -p "$PROMPT" ${lines:-} ${colors:-} ${font:-} \
+ || true
+)"
+[[ -n "$picked" ]] || exit 0
+
+path="$(printf "%s" "$picked" | awk -F'\t' '{print $2}')"
+[[ -n "$path" ]] || exit 0
+
+# Expand ~ manually (awk won’t expand it)
+path="${path/#\~/$HOME}"
+
+# If path is relative, treat it as relative to $HOME
+case "$path" in
+ /*) : ;;
+ *) path="${HOME}/${path}" ;;
+esac
+
+# Decide whether we need sudo (simple heuristic: /etc, /usr, /var, /run)
+needs_sudo=0
+case "$path" in
+ /etc/*|/usr/*|/var/*|/run/*) needs_sudo=1 ;;
+esac
+
+# Open in terminal editor (no eval)
+if [[ $needs_sudo -eq 1 ]]; then
+ exec ${TERM_CMD} sudo -E "$EDITOR_CMD" "$path"
+else
+ exec ${TERM_CMD} "$EDITOR_CMD" "$path"
+fi
+
diff --git a/dmenu-memo b/dmenu-memo
new file mode 100755
index 0000000..597f191
--- /dev/null
+++ b/dmenu-memo
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+OUTFILE="${OUTFILE:-$HOME/.notes}"
+DMENU="${DMENU:-dmenu}"
+PROMPT="${PROMPT:-Memo: }"
+
+# Optional dmenu theming
+# shellcheck disable=SC1090
+[[ -r "${HOME}/.local/bin/dmenu-theming" ]] && . "${HOME}/.local/bin/dmenu-theming"
+
+notify() {
+ # $1: urgency (low|normal|critical), $2: message
+ if command -v notify-send >/dev/null 2>&1; then
+ notify-send -u "$1" "dmenu-memo" "$2"
+ fi
+}
+
+die() {
+ notify critical "$1"
+ exit 1
+}
+
+# dmenu returns non-zero on ESC; treat as cancel
+note="$(
+ printf "" \
+ | ${DMENU} -p "$PROMPT" ${lines:-} ${colors:-} ${font:-} \
+ || true
+)"
+
+# Cancel / empty input: notify low, then exit quietly
+if [[ -z "${note}" ]]; then
+ notify low "Cancelled (empty memo)"
+ exit 0
+fi
+
+ts="$(date '+%y:%m:%d %H:%M:%S')"
+
+{
+ printf -- "# %s\n" "$ts"
+ printf -- "%s\n" "$note"
+ printf -- "---\n"
+} >> "$OUTFILE" || die "Failed to write to $OUTFILE"
+
+notify normal "Saved to ~/.notes"
+
diff --git a/dmenu-scripts b/dmenu-scripts
new file mode 100755
index 0000000..6d9c9db
--- /dev/null
+++ b/dmenu-scripts
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+script_path="${HOME}/.local/bin"
+
+# Optional dmenu theming
+# shellcheck disable=SC1090
+[[ -r "${HOME}/.local/bin/dmenu-theming" ]] && . "${HOME}/.local/bin/dmenu-theming"
+
+DMENU_CMD="${DMENU_CMD:-dmenu}"
+PROMPT="${PROMPT:-Run: }"
+
+# List only executable non-binary files in ~/.local/bin
+choices="$(
+ find "${script_path}" -maxdepth 1 -type f -executable -print0 \
+ | xargs -0 -I{} sh -c 'grep -Iq . "$1" && printf "%s\n" "$(basename "$1")"' _ {} \
+ | sort -u
+)"
+
+picked="$(
+ printf "%s\n" "$choices" \
+ | ${DMENU_CMD} -i -l 20 -p "${PROMPT}" ${lines:-} ${colors:-} ${font:-} \
+ || true
+)"
+
+[[ -n "${picked}" ]] || exit 0
+
+# Safety: only allow basenames (no slashes)
+case "$picked" in
+ */*|"") exit 0 ;;
+esac
+
+target="${script_path}/${picked}"
+
+[[ -x "$target" ]] || { printf "ERROR: not executable: %s\n" "$target" >&2; exit 1; }
+
+exec "$target"
+
diff --git a/dmenu-vimwiki-open b/dmenu-vimwiki-open
new file mode 100755
index 0000000..e8c4891
--- /dev/null
+++ b/dmenu-vimwiki-open
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Optional dmenu theming
+# shellcheck disable=SC1090
+[[ -r "${HOME}/.local/bin/dmenu-theming" ]] && . "${HOME}/.local/bin/dmenu-theming"
+
+DMENU="${DMENU:-dmenu}"
+PROMPT="${PROMPT:-VimWiki: }"
+EDITOR_CMD="${EDITOR_CMD:-vim}"
+TERM_CMD="${TERM_CMD:-st -e}"
+
+# Set this to your VimWiki root directory
+VIMWIKI_ROOT="${VIMWIKI_ROOT:-${HOME}/vimwiki}" # <-- CHANGE if needed
+
+CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/dmenu"
+CACHE_FILE="${CACHE_DIR}/vimwiki_files.txt"
+CACHE_TTL_SECONDS="${CACHE_TTL_SECONDS:-30}" # how long to trust the cache
+
+mkdir -p "$CACHE_DIR"
+
+now_epoch() { date +%s; }
+
+cache_is_fresh() {
+ [[ -r "$CACHE_FILE" ]] || return 1
+ local mtime
+ mtime="$(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0)"
+ local age=$(( $(now_epoch) - mtime ))
+ [[ "$age" -le "$CACHE_TTL_SECONDS" ]]
+}
+
+build_cache() {
+ if command -v fd >/dev/null 2>&1; then
+ # Relative paths, ignore common junk
+ (cd "$VIMWIKI_ROOT" && fd -t f -H \
+ -E .git -E .cache -E node_modules -E .venv -E __pycache__ \
+ -E attachments -E media \
+ .) > "$CACHE_FILE"
+ else
+ (cd "$VIMWIKI_ROOT" && find . -type f \
+ ! -path './.git/*' \
+ ! -path './.cache/*' \
+ ! -path './node_modules/*' \
+ ! -path './.venv/*' \
+ ! -path './__pycache__/*' \
+ ! -path './attachments/*' \
+ ! -path './media/*' \
+ -print | sed 's|^\./||') > "$CACHE_FILE"
+ fi
+
+ # Sort for stable menu
+ sort -u -o "$CACHE_FILE" "$CACHE_FILE"
+}
+
+[[ -d "$VIMWIKI_ROOT" ]] || { printf "ERROR: VimWiki root not found: %s\n" "$VIMWIKI_ROOT" >&2; exit 1; }
+
+cache_is_fresh || build_cache
+
+picked="$(
+ cat "$CACHE_FILE" \
+ | ${DMENU} -i -l 20 -p "$PROMPT" ${lines:-} ${colors:-} ${font:-} \
+ || true
+)"
+[[ -n "$picked" ]] || exit 0
+
+# Safety: no parent traversal
+case "$picked" in
+ *'..'* ) exit 0 ;;
+esac
+
+target="${VIMWIKI_ROOT}/${picked}"
+[[ -e "$target" ]] || { printf "ERROR: missing: %s\n" "$target" >&2; exit 1; }
+
+exec ${TERM_CMD} "$EDITOR_CMD" "$target"
+
diff --git a/fakeid b/fakeid
new file mode 100755
index 0000000..01256bb
--- /dev/null
+++ b/fakeid
@@ -0,0 +1,244 @@
+#!/usr/bin/env sh
+set -eu
+
+usage() {
+ cat <<'EOF'
+Usage:
+ fakeid <cc> [count]
+
+Examples:
+ fakeid pl
+ fakeid de 5
+ fakeid fr 2
+
+Supported cc -> Faker locale mapping:
+ pl -> pl_PL
+ de -> de_DE
+ fr -> fr_FR
+ en -> en_US
+ it -> it_IT
+ es -> es_ES
+ nl -> nl_NL
+ pt -> pt_PT
+ cz -> cs_CZ
+ sk -> sk_SK
+ se -> sv_SE
+ no -> nb_NO
+ dk -> da_DK
+EOF
+}
+
+cc="${1:-}"
+count="${2:-1}"
+
+[ -n "$cc" ] || { usage; exit 2; }
+
+case "$cc" in
+ pl) locale="pl_PL" ;;
+ de) locale="de_DE" ;;
+ fr) locale="fr_FR" ;;
+ en) locale="en_US" ;;
+ it) locale="it_IT" ;;
+ es) locale="es_ES" ;;
+ nl) locale="nl_NL" ;;
+ pt) locale="pt_PT" ;;
+ cz) locale="cs_CZ" ;;
+ sk) locale="sk_SK" ;;
+ se) locale="sv_SE" ;;
+ no) locale="nb_NO" ;;
+ dk) locale="da_DK" ;;
+ *)
+ echo "Unsupported country code: $cc" >&2
+ usage >&2
+ exit 2
+ ;;
+esac
+
+case "$count" in
+ ''|*[!0-9]*)
+ echo "Count must be a positive integer." >&2
+ exit 2
+ ;;
+esac
+[ "$count" -ge 1 ] || { echo "Count must be >= 1." >&2; exit 2; }
+
+have_iconv=0
+command -v iconv >/dev/null 2>&1 && have_iconv=1
+
+# Convert to an ASCII-ish gmail-safe slug.
+# - transliterate diacritics where possible
+# - lowercase
+# - allow only [a-z0-9.] (dots used as separators)
+# - collapse multiple dots
+# - trim leading/trailing dots
+slugify() {
+ s="$1"
+ if [ "$have_iconv" -eq 1 ]; then
+ s="$(printf "%s" "$s" | iconv -f UTF-8 -t ASCII//TRANSLIT 2>/dev/null || printf "%s" "$s")"
+ fi
+
+ # lowercase
+ s="$(printf "%s" "$s" | tr '[:upper:]' '[:lower:]')"
+
+ # normalize common separators to dot (no tr ranges involved)
+ s="$(printf "%s" "$s" | sed "s/[[:space:]_'’\"-]\+/\./g")"
+
+ # keep only a-z0-9 and dots
+ s="$(printf "%s" "$s" | tr -cd 'a-z0-9.')"
+
+ # collapse repeated dots + trim leading/trailing dots
+ s="$(printf "%s" "$s" | sed 's/\.\{2,\}/./g; s/^\.*//; s/\.*$//')"
+
+ printf "%s" "$s"
+}
+
+# Reasonable gmail local-part length (keep it human-like).
+# Gmail allows longer, but typical addresses are short.
+clip_len() {
+ s="$1"
+ max="${2:-24}"
+ # POSIX-ish: use cut on bytes (fine for ASCII output)
+ printf "%s" "$s" | cut -c1-"$max"
+}
+
+rand_int() {
+ # faker random_int sometimes expects args; simplest:
+ faker -l "$locale" random_int
+}
+
+# Weighted pattern selection per country.
+# Return an integer "pattern id" used by gmail_localpart().
+pick_pattern() {
+ r="$(rand_int)"
+ # reduce r to 0..99
+ r=$((r % 100))
+
+ case "$cc" in
+ # Poland: firstname.lastname and firstlast are common; some initials.
+ pl)
+ if [ "$r" -lt 55 ]; then echo 1
+ elif [ "$r" -lt 80 ]; then echo 2
+ elif [ "$r" -lt 92 ]; then echo 3
+ else echo 4
+ fi
+ ;;
+ # Germany: firstname.lastname strong; initials less frequent.
+ de)
+ if [ "$r" -lt 65 ]; then echo 1
+ elif [ "$r" -lt 85 ]; then echo 2
+ elif [ "$r" -lt 95 ]; then echo 3
+ else echo 4
+ fi
+ ;;
+ # France: firstname.lastname and first.last; sometimes first initial + last.
+ fr)
+ if [ "$r" -lt 60 ]; then echo 1
+ elif [ "$r" -lt 82 ]; then echo 2
+ elif [ "$r" -lt 94 ]; then echo 3
+ else echo 4
+ fi
+ ;;
+ # US: firstlast + numbers more common.
+ en)
+ if [ "$r" -lt 35 ]; then echo 2
+ elif [ "$r" -lt 70 ]; then echo 1
+ elif [ "$r" -lt 90 ]; then echo 3
+ else echo 4
+ fi
+ ;;
+ # Default: balanced
+ *)
+ if [ "$r" -lt 55 ]; then echo 1
+ elif [ "$r" -lt 80 ]; then echo 2
+ elif [ "$r" -lt 92 ]; then echo 3
+ else echo 4
+ fi
+ ;;
+ esac
+}
+
+# Build localpart from first/last with realistic patterns:
+# 1: firstname.lastname
+# 2: firstlast
+# 3: f.lastname
+# 4: firstname.l
+gmail_localpart() {
+ first="$1"
+ last="$2"
+
+ f="$(slugify "$first")"
+ l="$(slugify "$last")"
+
+ # Fallback if Faker returns odd strings
+ [ -n "$f" ] || f="user"
+ [ -n "$l" ] || l="name"
+
+ pat="$(pick_pattern)"
+
+ case "$pat" in
+ 1) base="${f}.${l}" ;;
+ 2) base="${f}${l}" ;;
+ 3) base="$(printf "%s" "$f" | cut -c1).${l}" ;;
+ 4) base="${f}.$(printf "%s" "$l" | cut -c1)" ;;
+ *) base="${f}.${l}" ;;
+ esac
+
+ base="$(slugify "$base")"
+ base="$(clip_len "$base" 24)"
+
+ # Add numeric suffix sometimes (more common in en, but possible everywhere).
+ rr="$(rand_int)"; rr=$((rr % 100))
+ add_num=0
+ case "$cc" in
+ en) [ "$rr" -lt 45 ] && add_num=1 ;;
+ *) [ "$rr" -lt 25 ] && add_num=1 ;;
+ esac
+
+ if [ "$add_num" -eq 1 ]; then
+ # Use a 2–3 digit suffix; avoid looking too synthetic.
+ n="$(rand_int)"
+ n=$(( (n % 900) + 10 )) # 10..909
+ base="$(clip_len "$base" 21)${n}"
+ base="$(slugify "$base")"
+ fi
+
+ # Final safety: ensure non-empty
+ [ -n "$base" ] || base="user$(( ( $(rand_int) % 9000 ) + 1000 ))"
+
+ printf "%s" "$base"
+}
+
+gmail_email() {
+ first="$(faker -l "$locale" first_name)"
+ last="$(faker -l "$locale" last_name)"
+
+ lp="$(gmail_localpart "$first" "$last")"
+
+ # De-dup within this run: if collision, append digits.
+ # (We store as newline-delimited list in $seen)
+ if printf "%s\n" "${seen:-}" | grep -qx "$lp" 2>/dev/null; then
+ k="$(rand_int)"; k=$(( (k % 900) + 10 ))
+ lp="$(clip_len "$lp" 21)${k}"
+ lp="$(slugify "$lp")"
+ fi
+ seen="${seen:-}
+$lp"
+
+ printf "%s@gmail.com\n" "$lp"
+}
+
+i=1
+seen=""
+while [ "$i" -le "$count" ]; do
+ faker -l "$locale" name
+ faker -l "$locale" address
+ faker -l "$locale" phone_number
+ gmail_email
+
+ if [ "$i" -lt "$count" ]; then
+ printf "\n"
+ fi
+
+ i=$((i + 1))
+done
+
diff --git a/faker b/faker
new file mode 120000
index 0000000..2949d5d
--- /dev/null
+++ b/faker
@@ -0,0 +1 @@
+/home/lukasz/.local/share/pipx/venvs/faker/bin/faker \ No newline at end of file
diff --git a/kvm-lab-create b/kvm-lab-create
index 43d2926..e574a4c 100755
--- a/kvm-lab-create
+++ b/kvm-lab-create
@@ -11,14 +11,20 @@ TEMPLATE_DOMAIN="debian13-template"
TEMPLATE_BASE="$BASEDIR/debian13-template.qcow2"
usage() {
- echo "Usage: kvm-lab-create <lab-name> [--base template|work01|/abs/path.qcow2] [--start]"
- echo "Examples:"
- echo " kvm-lab-create lab10 --base template --start"
- echo " kvm-lab-create lab11 --base work01 --start"
- echo " kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start"
+ cat <<'EOF'
+Usage: kvm-lab-create <lab-name> [--base template|<prefix>|<domain>|<filename-in-base>|/abs/path.qcow2] [--start]
+
+Base resolution order:
+ 1) template -> /var/lib/libvirt/images/base/debian13-template.qcow2
+ 2) /abs/path.qcow2 -> that file
+ 3) <filename in /var/lib/libvirt/images/base> -> /var/lib/libvirt/images/base/<filename>
+ 4) <domain name> -> vda disk path of that domain (virsh domblklist)
+ 5) <prefix> -> newest /var/lib/libvirt/images/base/<prefix>-base-*.qcow2
+EOF
}
-# --- Parse args ---
+die() { echo "ERROR: $*" >&2; exit 1; }
+
NAME="${1:-}"
shift || true
@@ -27,97 +33,76 @@ START="no"
while [[ $# -gt 0 ]]; do
case "$1" in
- --base)
- BASESEL="${2:-}"
- shift 2
- ;;
- --start)
- START="yes"
- shift
- ;;
- -h|--help)
- usage
- exit 0
- ;;
- *)
- echo "ERROR: Unknown argument: $1"
- usage
- exit 1
- ;;
+ --base) BASESEL="${2:-}"; shift 2 ;;
+ --start) START="yes"; shift ;;
+ -h|--help) usage; exit 0 ;;
+ *) die "Unknown argument: $1" ;;
esac
done
-if [[ -z "$NAME" ]]; then
- usage
- exit 1
-fi
+[[ -n "$NAME" ]] || { usage; exit 1; }
DISK="${LABDIR}/${NAME}.qcow2"
XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")"
-cleanup() { rm -f "$XML_TMP"; }
+
+defined="no"
+cleanup() {
+ rm -f "$XML_TMP"
+ if [[ "$defined" != "yes" && -e "$DISK" ]]; then
+ echo "WARN: Removing orphan disk: $DISK"
+ sudo rm -f "$DISK" || true
+ fi
+}
trap cleanup EXIT
-# --- Resolve base selection ---
resolve_base() {
local sel="$1"
+
if [[ "$sel" == "template" ]]; then
- echo "$TEMPLATE_BASE"
- return 0
+ echo "$TEMPLATE_BASE"; return 0
fi
- if [[ "$sel" == "work01" ]]; then
- # Pick newest promoted work01 base (must exist first)
- local latest
- latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)"
- if [[ -z "$latest" ]]; then
- echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)."
- echo "Action: run your promote script to create one."
- return 1
- fi
- echo "$latest"
- return 0
+ if [[ "$sel" == /* ]]; then
+ echo "$sel"; return 0
fi
- # Absolute path base
- if [[ "$sel" == /* ]]; then
- echo "$sel"
- return 0
+ if [[ -f "$BASEDIR/$sel" ]]; then
+ echo "$BASEDIR/$sel"; return 0
fi
- echo "ERROR: Invalid --base '$sel' (use template|work01|/abs/path.qcow2)"
- return 1
-}
+ if virsh -c "$URI" dominfo "$sel" &>/dev/null; then
+ local src
+ src="$(virsh -c "$URI" domblklist "$sel" --details \
+ | awk '$2=="disk" && $3=="vda" {print $4; exit}')"
+ [[ -n "$src" ]] || die "Domain '$sel' found but could not resolve vda disk path."
+ echo "$src"; return 0
+ fi
-BASE="$(resolve_base "$BASESEL")"
+ local latest
+ latest="$(ls -1t "$BASEDIR"/"${sel}"-base-*.qcow2 2>/dev/null | head -n 1 || true)"
+ [[ -n "$latest" ]] || die "Invalid --base '$sel' (no matching domain/file/promoted base in $BASEDIR)."
+ echo "$latest"
+}
-# --- Preconditions (read-only checks) ---
-if [[ ! -d "$LABDIR" ]]; then
- echo "ERROR: Lab directory missing: $LABDIR"
- exit 1
+# Preconditions
+[[ -d "$LABDIR" ]] || die "Lab directory missing: $LABDIR"
+[[ -d "$BASEDIR" ]] || die "Base directory missing: $BASEDIR"
+if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then
+ die "Template domain not found in $URI: $TEMPLATE_DOMAIN"
fi
-if ! sudo test -r "$BASE"; then
- echo "ERROR: Base image not readable (via sudo): $BASE"
- exit 1
-fi
+BASE="$(resolve_base "$BASESEL")"
+echo "Base selection: $BASESEL -> $BASE"
-if [[ -e "$DISK" ]]; then
- echo "ERROR: Disk already exists: $DISK"
- exit 1
-fi
+[[ -f "$BASE" ]] || die "Base image not found: $BASE"
+sudo test -r "$BASE" || die "Base image not readable (via sudo): $BASE"
+[[ ! -e "$DISK" ]] || die "Disk already exists: $DISK"
if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then
- echo "ERROR: Domain already exists: $NAME"
- exit 1
+ die "Domain already exists: $NAME"
fi
-if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then
- echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN"
- exit 1
-fi
-
-# --- Create overlay disk ---
-echo "Using base: $BASE"
+# Create overlay
echo "Creating overlay disk: $DISK"
sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null
sudo chown libvirt-qemu:libvirt "$DISK"
@@ -126,28 +111,37 @@ sudo chmod 0660 "$DISK"
echo "Validating backing file..."
sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true
-# --- Clone domain XML ---
+# Clone template XML and adjust
echo "Cloning domain XML from: $TEMPLATE_DOMAIN"
virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP"
-# Set name, disk path, and force NAT-only network "default"
+# Update: name, disk source, network default
+# - replace <name>...</name> once
+# - replace template disk path with overlay path (global)
+# - force network='default'
sed -i \
-e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \
-e "s|${TEMPLATE_BASE}|${DISK}|g" \
-e "s|<source network='[^']*'|<source network='default'|g" \
"$XML_TMP"
-# Remove UUID to avoid collision with template
+# Remove UUID and MAC addresses to avoid collisions
sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP"
+sed -i -E "s/<mac address='[^']*'\/>//g" "$XML_TMP"
-# Remove any host filesystem passthrough (if present)
+# Remove filesystem passthrough (if any)
perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP"
-# --- Define domain ---
+# Validate substitutions happened
+grep -q "<name>${NAME}</name>" "$XML_TMP" || die "XML name substitution failed"
+grep -qF "$DISK" "$XML_TMP" || die "XML disk substitution failed"
+
+# Define domain
echo "Defining domain: $NAME"
virsh -c "$URI" define "$XML_TMP" >/dev/null
+defined="yes"
-# --- Optional start ---
+# Optional start
if [[ "$START" == "yes" ]]; then
echo "Starting domain: $NAME"
virsh -c "$URI" start "$NAME" >/dev/null
diff --git a/kvm-lab-create.bak b/kvm-lab-create.bak
new file mode 100755
index 0000000..43d2926
--- /dev/null
+++ b/kvm-lab-create.bak
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+URI="qemu:///system"
+
+IMGROOT="/var/lib/libvirt/images"
+BASEDIR="$IMGROOT/base"
+LABDIR="$IMGROOT/lab"
+
+TEMPLATE_DOMAIN="debian13-template"
+TEMPLATE_BASE="$BASEDIR/debian13-template.qcow2"
+
+usage() {
+ echo "Usage: kvm-lab-create <lab-name> [--base template|work01|/abs/path.qcow2] [--start]"
+ echo "Examples:"
+ echo " kvm-lab-create lab10 --base template --start"
+ echo " kvm-lab-create lab11 --base work01 --start"
+ echo " kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start"
+}
+
+# --- Parse args ---
+NAME="${1:-}"
+shift || true
+
+BASESEL="template"
+START="no"
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --base)
+ BASESEL="${2:-}"
+ shift 2
+ ;;
+ --start)
+ START="yes"
+ shift
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "ERROR: Unknown argument: $1"
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+if [[ -z "$NAME" ]]; then
+ usage
+ exit 1
+fi
+
+DISK="${LABDIR}/${NAME}.qcow2"
+XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")"
+cleanup() { rm -f "$XML_TMP"; }
+trap cleanup EXIT
+
+# --- Resolve base selection ---
+resolve_base() {
+ local sel="$1"
+ if [[ "$sel" == "template" ]]; then
+ echo "$TEMPLATE_BASE"
+ return 0
+ fi
+
+ if [[ "$sel" == "work01" ]]; then
+ # Pick newest promoted work01 base (must exist first)
+ local latest
+ latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)"
+ if [[ -z "$latest" ]]; then
+ echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)."
+ echo "Action: run your promote script to create one."
+ return 1
+ fi
+ echo "$latest"
+ return 0
+ fi
+
+ # Absolute path base
+ if [[ "$sel" == /* ]]; then
+ echo "$sel"
+ return 0
+ fi
+
+ echo "ERROR: Invalid --base '$sel' (use template|work01|/abs/path.qcow2)"
+ return 1
+}
+
+BASE="$(resolve_base "$BASESEL")"
+
+# --- Preconditions (read-only checks) ---
+if [[ ! -d "$LABDIR" ]]; then
+ echo "ERROR: Lab directory missing: $LABDIR"
+ exit 1
+fi
+
+if ! sudo test -r "$BASE"; then
+ echo "ERROR: Base image not readable (via sudo): $BASE"
+ exit 1
+fi
+
+if [[ -e "$DISK" ]]; then
+ echo "ERROR: Disk already exists: $DISK"
+ exit 1
+fi
+
+if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then
+ echo "ERROR: Domain already exists: $NAME"
+ exit 1
+fi
+
+if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then
+ echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN"
+ exit 1
+fi
+
+# --- Create overlay disk ---
+echo "Using base: $BASE"
+echo "Creating overlay disk: $DISK"
+sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null
+sudo chown libvirt-qemu:libvirt "$DISK"
+sudo chmod 0660 "$DISK"
+
+echo "Validating backing file..."
+sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true
+
+# --- Clone domain XML ---
+echo "Cloning domain XML from: $TEMPLATE_DOMAIN"
+virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP"
+
+# Set name, disk path, and force NAT-only network "default"
+sed -i \
+ -e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \
+ -e "s|${TEMPLATE_BASE}|${DISK}|g" \
+ -e "s|<source network='[^']*'|<source network='default'|g" \
+ "$XML_TMP"
+
+# Remove UUID to avoid collision with template
+sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP"
+
+# Remove any host filesystem passthrough (if present)
+perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP"
+
+# --- Define domain ---
+echo "Defining domain: $NAME"
+virsh -c "$URI" define "$XML_TMP" >/dev/null
+
+# --- Optional start ---
+if [[ "$START" == "yes" ]]; then
+ echo "Starting domain: $NAME"
+ virsh -c "$URI" start "$NAME" >/dev/null
+fi
+
+echo "OK: Lab '$NAME' created (disk: $DISK)."
+if [[ "$START" != "yes" ]]; then
+ echo "To start: virsh -c $URI start $NAME"
+fi
+
diff --git a/kvm-lab-destroy b/kvm-lab-destroy
index 3548ef9..c608831 100755
--- a/kvm-lab-destroy
+++ b/kvm-lab-destroy
@@ -2,51 +2,42 @@
set -euo pipefail
URI="qemu:///system"
-LABDIR="/var/lib/libvirt/images/lab"
+IMGROOT="/var/lib/libvirt/images"
+LABDIR="$IMGROOT/lab"
-NAME="${1:-}"
-
-if [[ -z "$NAME" ]]; then
+usage() {
echo "Usage: kvm-lab-destroy <lab-name>"
- exit 1
-fi
+}
-if [[ "$NAME" == "work01" || "$NAME" == "debian13-template" ]]; then
- echo "Refusing to destroy protected VM: $NAME"
- exit 1
-fi
-
-if ! virsh -c "$URI" dominfo "$NAME" &>/dev/null; then
- echo "Domain not found: $NAME"
- exit 1
-fi
-
-DISK="$(virsh -c "$URI" domblklist "$NAME" --details | awk '$3=="vda"{print $4}')"
+NAME="${1:-}"
+[[ -n "$NAME" ]] || { usage; exit 1; }
-if [[ -z "$DISK" || "$DISK" != "$LABDIR/"* ]]; then
- echo "Refusing to destroy: disk not under $LABDIR"
- echo "Detected disk: $DISK"
- exit 1
-fi
+DISK="${LABDIR}/${NAME}.qcow2"
-STATE="$(virsh -c "$URI" domstate "$NAME")"
+# Domain cleanup (best-effort)
+if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then
+ state="$(virsh -c "$URI" domstate "$NAME" 2>/dev/null || true)"
+ if [[ "$state" == *running* ]]; then
+ virsh -c "$URI" shutdown "$NAME" >/dev/null 2>&1 || true
+ sleep 1
+ virsh -c "$URI" destroy "$NAME" >/dev/null 2>&1 || true
+ else
+ virsh -c "$URI" destroy "$NAME" >/dev/null 2>&1 || true
+ fi
-if [[ "$STATE" == "running" ]]; then
- echo "Shutting down $NAME..."
- virsh -c "$URI" shutdown "$NAME"
- sleep 3
+ virsh -c "$URI" undefine "$NAME" --nvram >/dev/null 2>&1 || \
+ virsh -c "$URI" undefine "$NAME" >/dev/null 2>&1 || true
+else
+ echo "WARN: Domain not found: $NAME"
fi
-if [[ "$(virsh -c "$URI" domstate "$NAME")" == "running" ]]; then
- echo "Force stopping $NAME..."
- virsh -c "$URI" destroy "$NAME"
+# Disk cleanup
+if [[ -e "$DISK" ]]; then
+ echo "Removing disk: $DISK"
+ sudo rm -f "$DISK"
+else
+ echo "WARN: Disk not found: $DISK"
fi
-echo "Undefining $NAME..."
-virsh -c "$URI" undefine "$NAME"
-
-echo "Deleting disk: $DISK"
-sudo rm -v -- "$DISK"
-
-echo "Lab '$NAME' destroyed."
+echo "OK: Destroyed lab '$NAME'."
diff --git a/kvm-lab-destroy.bak b/kvm-lab-destroy.bak
new file mode 100755
index 0000000..3548ef9
--- /dev/null
+++ b/kvm-lab-destroy.bak
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+URI="qemu:///system"
+LABDIR="/var/lib/libvirt/images/lab"
+
+NAME="${1:-}"
+
+if [[ -z "$NAME" ]]; then
+ echo "Usage: kvm-lab-destroy <lab-name>"
+ exit 1
+fi
+
+if [[ "$NAME" == "work01" || "$NAME" == "debian13-template" ]]; then
+ echo "Refusing to destroy protected VM: $NAME"
+ exit 1
+fi
+
+if ! virsh -c "$URI" dominfo "$NAME" &>/dev/null; then
+ echo "Domain not found: $NAME"
+ exit 1
+fi
+
+DISK="$(virsh -c "$URI" domblklist "$NAME" --details | awk '$3=="vda"{print $4}')"
+
+if [[ -z "$DISK" || "$DISK" != "$LABDIR/"* ]]; then
+ echo "Refusing to destroy: disk not under $LABDIR"
+ echo "Detected disk: $DISK"
+ exit 1
+fi
+
+STATE="$(virsh -c "$URI" domstate "$NAME")"
+
+if [[ "$STATE" == "running" ]]; then
+ echo "Shutting down $NAME..."
+ virsh -c "$URI" shutdown "$NAME"
+ sleep 3
+fi
+
+if [[ "$(virsh -c "$URI" domstate "$NAME")" == "running" ]]; then
+ echo "Force stopping $NAME..."
+ virsh -c "$URI" destroy "$NAME"
+fi
+
+echo "Undefining $NAME..."
+virsh -c "$URI" undefine "$NAME"
+
+echo "Deleting disk: $DISK"
+sudo rm -v -- "$DISK"
+
+echo "Lab '$NAME' destroyed."
+
diff --git a/officium b/officium
index fae202f..c40c35e 100755
--- a/officium
+++ b/officium
@@ -1,9 +1,9 @@
#!/bin/bash
# Scriptum ad preces e Divinum Officium extrahendas
-# Auctor: Adam Kasprzak
+# Auctores: Adam Gomułka, Łukasz Kasprzak
# Data: 2026-01-13
-
+# Versio: 0.2
set -o pipefail
if [ $# -eq 0 ]; then