diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md index 4b2f37d..d08f399 100644 --- a/docs/CHEATSHEET.md +++ b/docs/CHEATSHEET.md @@ -53,14 +53,21 @@ format = "json" # JSONL output (default: "text") ## Container ```bash -make build # Build image (only for dep changes) -make up # Start (podman-compose) -make down # Stop -make logs # Follow logs +tools/build # Build image +tools/build --no-cache # Rebuild from scratch +tools/start # Start (builds if no image) +tools/stop # Stop and remove container +tools/restart # Stop + rebuild + start +tools/restart --no-cache # Full clean restart +tools/logs # Tail logs (default 30 lines) +tools/logs 100 # Tail last 100 lines +tools/status # Container, image, mount state +tools/nuke # Full teardown (container + image) ``` Code, plugins, config, and data are bind-mounted. No rebuild needed for code changes -- restart the container or use `!reload` for plugins. +Rebuild only when `requirements.txt` or `Containerfile` change. ## Bot Commands diff --git a/tools/_common.sh b/tools/_common.sh new file mode 100644 index 0000000..2f25e94 --- /dev/null +++ b/tools/_common.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Shared helpers for derp container tools. +# Sourced, not executed. +# shellcheck disable=SC2034 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Compose command detection +if podman compose version &>/dev/null; then + COMPOSE="podman compose" +elif command -v podman-compose &>/dev/null; then + COMPOSE="podman-compose" +else + echo "error: podman compose or podman-compose required" >&2 + exit 1 +fi + +CONTAINER_NAME="derp" +# podman-compose names images _ +IMAGE_NAME="derp_derp" + +# Colors (suppressed if NO_COLOR is set or stdout isn't a tty) +if [[ -z "${NO_COLOR:-}" ]] && [[ -t 1 ]]; then + GRN='\e[38;5;108m' + RED='\e[38;5;131m' + BLU='\e[38;5;110m' + DIM='\e[2m' + RST='\e[0m' +else + GRN='' RED='' BLU='' DIM='' RST='' +fi + +info() { printf "${GRN}%s${RST} %s\n" "✓" "$*"; } +err() { printf "${RED}%s${RST} %s\n" "✗" "$*" >&2; } +dim() { printf "${DIM} %s${RST}\n" "$*"; } diff --git a/tools/build b/tools/build new file mode 100755 index 0000000..3d480f5 --- /dev/null +++ b/tools/build @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Build or rebuild the derp container image. +# Usage: tools/build [--no-cache] + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" +cd "$PROJECT_DIR" || exit 1 + +args=() +[[ "${1:-}" == "--no-cache" ]] && args+=(--no-cache) + +dim "Building image..." +$COMPOSE build "${args[@]}" + +size=$(podman image inspect "$IMAGE_NAME" --format '{{.Size}}' 2>/dev/null || true) +if [[ -n "$size" ]]; then + human=$(numfmt --to=iec-i --suffix=B "$size" 2>/dev/null || echo "${size} bytes") + info "Image built ($human)" +else + info "Image built" +fi diff --git a/tools/logs b/tools/logs new file mode 100755 index 0000000..33c401f --- /dev/null +++ b/tools/logs @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Tail container logs. +# Usage: tools/logs [N] + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +tail_n="${1:-30}" +podman logs -f --tail "$tail_n" "$CONTAINER_NAME" diff --git a/tools/nuke b/tools/nuke new file mode 100755 index 0000000..a93b161 --- /dev/null +++ b/tools/nuke @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Full teardown: stop container and remove image. +# Usage: tools/nuke + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" +cd "$PROJECT_DIR" || exit 1 + +dim "Stopping container..." +$COMPOSE down 2>/dev/null || true + +before=$(podman system df --format '{{.Size}}' 2>/dev/null | head -1 || true) + +dim "Removing image..." +podman rmi "$IMAGE_NAME" 2>/dev/null || true +# Also remove any dangling derp images +podman images --filter "reference=*derp*" --format '{{.ID}}' 2>/dev/null | \ + xargs -r podman rmi 2>/dev/null || true + +after=$(podman system df --format '{{.Size}}' 2>/dev/null | head -1 || true) + +if [[ -n "$before" && -n "$after" ]]; then + info "Teardown complete (images: $before -> $after)" +else + info "Teardown complete" +fi diff --git a/tools/restart b/tools/restart new file mode 100755 index 0000000..205d544 --- /dev/null +++ b/tools/restart @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Stop, rebuild, and start the derp container. +# Usage: tools/restart [--no-cache] + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +args=() +[[ "${1:-}" == "--no-cache" ]] && args+=("--no-cache") + +"$SCRIPT_DIR/stop" +echo +"$SCRIPT_DIR/build" "${args[@]}" +echo +"$SCRIPT_DIR/start" diff --git a/tools/start b/tools/start new file mode 100755 index 0000000..22a9c14 --- /dev/null +++ b/tools/start @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Start the derp container. +# Usage: tools/start + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" +cd "$PROJECT_DIR" || exit 1 + +# Build first if no image exists +if ! podman image exists "$IMAGE_NAME" 2>/dev/null; then + dim "No image found, building..." + "$SCRIPT_DIR/build" + echo +fi + +dim "Starting container..." +$COMPOSE up -d + +sleep 3 +dim "Recent logs:" +podman logs --tail 15 "$CONTAINER_NAME" 2>&1 || true +echo +info "Container started" diff --git a/tools/status b/tools/status new file mode 100755 index 0000000..d6cf976 --- /dev/null +++ b/tools/status @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Show container and image state. +# Usage: tools/status + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +# -- Container ---------------------------------------------------------------- +printf '%b%s%b\n' "$BLU" "Container" "$RST" +state=$(podman inspect "$CONTAINER_NAME" --format '{{.State.Status}}' 2>/dev/null || true) +if [[ -z "$state" ]]; then + dim "absent" +elif [[ "$state" == "running" ]]; then + uptime=$(podman inspect "$CONTAINER_NAME" --format '{{.State.StartedAt}}' 2>/dev/null || true) + info "running (since ${uptime%.*})" +else + info "$state" +fi + +echo + +# -- Image -------------------------------------------------------------------- +printf '%b%s%b\n' "$BLU" "Image" "$RST" +if podman image exists "$IMAGE_NAME" 2>/dev/null; then + img_info=$(podman image inspect "$IMAGE_NAME" --format '{{.Created}} {{.Size}}' 2>/dev/null || true) + created="${img_info%% *}" + size="${img_info##* }" + human=$(numfmt --to=iec-i --suffix=B "$size" 2>/dev/null || echo "${size}B") + info "$IMAGE_NAME ($human, ${created%T*})" +else + dim "no image" +fi + +echo + +# -- Volumes ------------------------------------------------------------------ +printf '%b%s%b\n' "$BLU" "Mounts" "$RST" +mounts=(src plugins config/derp.toml data secrets) +for m in "${mounts[@]}"; do + path="$PROJECT_DIR/$m" + if [[ -e "$path" ]]; then + info "$m" + else + err "$m (missing)" + fi +done diff --git a/tools/stop b/tools/stop new file mode 100755 index 0000000..5c7ce95 --- /dev/null +++ b/tools/stop @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Stop and remove the derp container. +# Usage: tools/stop + +# shellcheck source=tools/_common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" +cd "$PROJECT_DIR" || exit 1 + +dim "Stopping container..." +$COMPOSE down +info "Container stopped"