feat: container management tools in tools/

Shell scripts for build, start, stop, restart, nuke, logs, status.
Shared helpers in _common.sh (colours, compose detection, project root).
Updated CHEATSHEET.md with new tool references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-22 11:40:53 +01:00
parent b88a459142
commit 3afeace6e7
9 changed files with 200 additions and 4 deletions

View File

@@ -53,14 +53,21 @@ format = "json" # JSONL output (default: "text")
## Container ## Container
```bash ```bash
make build # Build image (only for dep changes) tools/build # Build image
make up # Start (podman-compose) tools/build --no-cache # Rebuild from scratch
make down # Stop tools/start # Start (builds if no image)
make logs # Follow logs 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, plugins, config, and data are bind-mounted. No rebuild needed for
code changes -- restart the container or use `!reload` for plugins. code changes -- restart the container or use `!reload` for plugins.
Rebuild only when `requirements.txt` or `Containerfile` change.
## Bot Commands ## Bot Commands

38
tools/_common.sh Normal file
View File

@@ -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 <project>_<service>
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" "$*"; }

21
tools/build Executable file
View File

@@ -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

9
tools/logs Executable file
View File

@@ -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"

26
tools/nuke Executable file
View File

@@ -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

15
tools/restart Executable file
View File

@@ -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"

23
tools/start Executable file
View File

@@ -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"

46
tools/status Executable file
View File

@@ -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

11
tools/stop Executable file
View File

@@ -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"