mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-14 21:55:08 +00:00
refactor: manage.sh uses docker compose only — fixes #230
Remove all legacy docker run code paths. manage.sh is now a pure docker compose wrapper with no dual-mode branching. Removed: - COMPOSE_MODE flag and all if/else branches - get_docker_run_args(), get_data_mount_args(), recreate_container() - get_required_ports(), get_current_ports(), check_port_match() - CONTAINER_NAME, DATA_VOLUME, CADDY_VOLUME variables - All direct docker run/stop/start/rm invocations All commands now delegate to docker compose: - start → docker compose up -d prod - stop → docker compose down / docker compose stop - restart → docker compose up -d --force-recreate - update → docker compose build prod + up -d --force-recreate - reset → docker compose down --rmi local - backup/restore use bind mount path from .env (PROD_DATA_DIR) - verify_health, mqtt-test, status all use corescope-prod Net result: -248 lines, zero dual-mode logic, identical behavior to running docker compose directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
# Volume paths unified with manage.sh — see manage.sh lines 9-12, 55-62, 920, 944
|
||||
# All container config lives here. manage.sh is just a wrapper around docker compose.
|
||||
# Override defaults via .env or environment variables.
|
||||
# CRITICAL: All data mounts use bind mounts (~/path), NOT named volumes.
|
||||
# This ensures the DB and theme are visible on the host filesystem for backup.
|
||||
|
||||
@@ -2,26 +2,20 @@
|
||||
# CoreScope — Setup & Management Helper
|
||||
# Usage: ./manage.sh [command]
|
||||
#
|
||||
# All container management goes through docker compose.
|
||||
# Container config lives in docker-compose.yml — this script is just a wrapper.
|
||||
#
|
||||
# Idempotent: safe to cancel and re-run at any point.
|
||||
# Each step checks what's already done and skips it.
|
||||
set -e
|
||||
|
||||
CONTAINER_NAME="corescope"
|
||||
IMAGE_NAME="corescope"
|
||||
DATA_VOLUME="meshcore-data"
|
||||
CADDY_VOLUME="caddy-data"
|
||||
STATE_FILE=".setup-state"
|
||||
|
||||
# Source .env for port/path overrides (if present)
|
||||
# Source .env for port/path overrides (same file docker compose reads)
|
||||
[ -f .env ] && set -a && . ./.env && set +a
|
||||
|
||||
# Docker Compose mode detection
|
||||
COMPOSE_MODE=false
|
||||
if [ -f docker-compose.yml ]; then
|
||||
COMPOSE_MODE=true
|
||||
fi
|
||||
|
||||
# Resolved paths for prod/staging data
|
||||
# Resolved paths for prod/staging data (must match docker-compose.yml)
|
||||
PROD_DATA="${PROD_DATA_DIR:-$HOME/meshcore-data}"
|
||||
STAGING_DATA="${STAGING_DATA_DIR:-$HOME/meshcore-staging-data}"
|
||||
|
||||
@@ -51,76 +45,6 @@ is_done() { [ -f "$STATE_FILE" ] && grep -qx "$1" "$STATE_FILE" 2>/dev/null;
|
||||
|
||||
# ─── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
# Determine the correct data volume/mount args for docker run.
|
||||
# Always uses bind mounts so the DB is visible on the filesystem.
|
||||
get_data_mount_args() {
|
||||
# Always use bind mount (from .env or default)
|
||||
# Create the directory if it doesn't exist
|
||||
mkdir -p "$PROD_DATA"
|
||||
echo "-v $PROD_DATA:/app/data"
|
||||
}
|
||||
|
||||
# Determine the required port mappings from Caddyfile
|
||||
get_required_ports() {
|
||||
local caddyfile_domain
|
||||
caddyfile_domain=$(grep -v '^#' caddy-config/Caddyfile 2>/dev/null | head -1 | tr -d ' {')
|
||||
if echo "$caddyfile_domain" | grep -qE '^:[0-9]+$'; then
|
||||
# HTTP-only on a specific port (e.g., :80, :8080)
|
||||
echo "${caddyfile_domain#:}"
|
||||
else
|
||||
# Domain name — needs 80 + 443 for Caddy auto-TLS
|
||||
echo "80 443"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get current container port mappings (just the host ports)
|
||||
get_current_ports() {
|
||||
docker inspect "$CONTAINER_NAME" 2>/dev/null | \
|
||||
grep -oP '"HostPort":\s*"\K[0-9]+' | sort -u | tr '\n' ' ' | sed 's/ $//'
|
||||
}
|
||||
|
||||
# Check if container port mappings match what's needed.
|
||||
# Returns 0 if they match, 1 if mismatch.
|
||||
check_port_match() {
|
||||
local required current
|
||||
required=$(get_required_ports | tr ' ' '\n' | sort | tr '\n' ' ' | sed 's/ $//')
|
||||
current=$(get_current_ports | tr ' ' '\n' | sort | tr '\n' ' ' | sed 's/ $//')
|
||||
[ "$required" = "$current" ]
|
||||
}
|
||||
|
||||
# Build the docker run command args (ports + volumes)
|
||||
get_docker_run_args() {
|
||||
local ports_arg=""
|
||||
for port in $(get_required_ports); do
|
||||
ports_arg="$ports_arg -p ${port}:${port}"
|
||||
done
|
||||
|
||||
local data_mount
|
||||
data_mount=$(get_data_mount_args)
|
||||
|
||||
echo "$ports_arg \
|
||||
-v $(pwd)/config.json:/app/config.json:ro \
|
||||
-v $(pwd)/caddy-config/Caddyfile:/etc/caddy/Caddyfile:ro \
|
||||
$data_mount \
|
||||
-v ${CADDY_VOLUME}:/data/caddy"
|
||||
}
|
||||
|
||||
# Recreate the container with current settings
|
||||
recreate_container() {
|
||||
info "Stopping and removing old container..."
|
||||
docker stop "$CONTAINER_NAME" 2>/dev/null || true
|
||||
docker rm "$CONTAINER_NAME" 2>/dev/null || true
|
||||
|
||||
local run_args
|
||||
run_args=$(get_docker_run_args)
|
||||
|
||||
eval docker run -d \
|
||||
--name "$CONTAINER_NAME" \
|
||||
--restart unless-stopped \
|
||||
$run_args \
|
||||
"$IMAGE_NAME"
|
||||
}
|
||||
|
||||
# Check config.json for placeholder values
|
||||
check_config_placeholders() {
|
||||
if [ -f config.json ]; then
|
||||
@@ -133,7 +57,7 @@ check_config_placeholders() {
|
||||
|
||||
# Verify the running container is actually healthy
|
||||
verify_health() {
|
||||
local base_url="http://localhost:3000"
|
||||
local container="corescope-prod"
|
||||
local use_https=false
|
||||
|
||||
# Check if Caddyfile has a real domain (not :80)
|
||||
@@ -149,7 +73,7 @@ verify_health() {
|
||||
info "Waiting for server to respond..."
|
||||
local healthy=false
|
||||
for i in $(seq 1 45); do
|
||||
if docker exec "$CONTAINER_NAME" wget -qO- http://localhost:3000/api/stats &>/dev/null; then
|
||||
if docker exec "$container" wget -qO- http://localhost:3000/api/stats &>/dev/null; then
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
@@ -165,7 +89,7 @@ verify_health() {
|
||||
|
||||
# Check for MQTT errors in recent logs
|
||||
local mqtt_errors
|
||||
mqtt_errors=$(docker logs "$CONTAINER_NAME" --tail 50 2>&1 | grep -i 'mqtt.*error\|mqtt.*fail\|ECONNREFUSED.*1883' || true)
|
||||
mqtt_errors=$(docker logs "$container" --tail 50 2>&1 | grep -i 'mqtt.*error\|mqtt.*fail\|ECONNREFUSED.*1883' || true)
|
||||
if [ -n "$mqtt_errors" ]; then
|
||||
warn "MQTT errors detected in logs:"
|
||||
echo "$mqtt_errors" | head -5 | sed 's/^/ /'
|
||||
@@ -364,12 +288,12 @@ cmd_setup() {
|
||||
if [ -n "$IMAGE_EXISTS" ] && is_done "build"; then
|
||||
log "Image already built."
|
||||
if confirm "Rebuild? (only needed if you updated the code)"; then
|
||||
docker build --build-arg APP_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") --build-arg GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") --build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) -t "$IMAGE_NAME" .
|
||||
docker compose build prod
|
||||
log "Image rebuilt."
|
||||
fi
|
||||
else
|
||||
info "This takes 1-2 minutes the first time..."
|
||||
docker build --build-arg APP_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") --build-arg GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") --build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) -t "$IMAGE_NAME" .
|
||||
docker compose build prod
|
||||
log "Image built."
|
||||
fi
|
||||
mark_done "build"
|
||||
@@ -380,43 +304,13 @@ cmd_setup() {
|
||||
# Detect existing data directories
|
||||
if [ -d "$PROD_DATA" ] && [ -f "$PROD_DATA/meshcore.db" ]; then
|
||||
info "Found existing data at $PROD_DATA/ — will use bind mount."
|
||||
elif [ -d "$(pwd)/data" ] && [ -f "$(pwd)/data/meshcore.db" ]; then
|
||||
info "Found existing data at ./data/ — will use bind mount."
|
||||
fi
|
||||
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
if docker ps --format '{{.Names}}' | grep -q "^corescope-prod$"; then
|
||||
log "Container already running."
|
||||
# Check port mappings match
|
||||
if ! check_port_match; then
|
||||
warn "Container port mappings don't match Caddyfile configuration."
|
||||
warn "Current ports: $(get_current_ports)"
|
||||
warn "Required ports: $(get_required_ports)"
|
||||
if confirm "Recreate container with correct ports?"; then
|
||||
recreate_container
|
||||
log "Container recreated with correct ports."
|
||||
fi
|
||||
fi
|
||||
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
# Exists but stopped — check ports before starting
|
||||
if ! check_port_match; then
|
||||
warn "Stopped container has wrong port mappings."
|
||||
warn "Current ports: $(get_current_ports)"
|
||||
warn "Required ports: $(get_required_ports)"
|
||||
if confirm "Recreate container with correct ports?"; then
|
||||
recreate_container
|
||||
log "Container recreated with correct ports."
|
||||
else
|
||||
info "Starting existing container (ports unchanged)..."
|
||||
docker start "$CONTAINER_NAME"
|
||||
log "Started (with old port mappings)."
|
||||
fi
|
||||
else
|
||||
info "Container exists but is stopped. Starting..."
|
||||
docker start "$CONTAINER_NAME"
|
||||
log "Started."
|
||||
fi
|
||||
else
|
||||
recreate_container
|
||||
mkdir -p "$PROD_DATA"
|
||||
docker compose up -d prod
|
||||
log "Container started."
|
||||
fi
|
||||
mark_done "container"
|
||||
@@ -424,7 +318,7 @@ cmd_setup() {
|
||||
# ── Step 6: Verify ──
|
||||
step 6 "Verifying"
|
||||
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
if docker ps --format '{{.Names}}' | grep -q "^corescope-prod$"; then
|
||||
verify_health
|
||||
|
||||
CADDYFILE_DOMAIN=$(grep -v '^#' caddy-config/Caddyfile 2>/dev/null | head -1 | tr -d ' {')
|
||||
@@ -456,7 +350,7 @@ cmd_setup() {
|
||||
err "Container failed to start."
|
||||
echo ""
|
||||
echo " Check what went wrong:"
|
||||
echo " docker logs ${CONTAINER_NAME}"
|
||||
echo " docker compose logs prod"
|
||||
echo ""
|
||||
echo " Common fixes:"
|
||||
echo " • Invalid config.json — check JSON syntax"
|
||||
@@ -528,132 +422,72 @@ cmd_start() {
|
||||
WITH_STAGING=true
|
||||
fi
|
||||
|
||||
if $COMPOSE_MODE; then
|
||||
if $WITH_STAGING; then
|
||||
# Prepare staging data and config
|
||||
prepare_staging_db
|
||||
prepare_staging_config
|
||||
if $WITH_STAGING; then
|
||||
# Prepare staging data and config
|
||||
prepare_staging_db
|
||||
prepare_staging_config
|
||||
|
||||
info "Starting production container (corescope-prod) on ports ${PROD_HTTP_PORT:-80}/${PROD_HTTPS_PORT:-443}..."
|
||||
info "Starting staging container (corescope-staging) on port ${STAGING_HTTP_PORT:-81}..."
|
||||
docker compose --profile staging up -d
|
||||
log "Production started on ports ${PROD_HTTP_PORT:-80}/${PROD_HTTPS_PORT:-443}/${PROD_MQTT_PORT:-1883}"
|
||||
log "Staging started on port ${STAGING_HTTP_PORT:-81} (MQTT: ${STAGING_MQTT_PORT:-1884})"
|
||||
else
|
||||
info "Starting production container (corescope-prod) on ports ${PROD_HTTP_PORT:-80}/${PROD_HTTPS_PORT:-443}..."
|
||||
docker compose up -d prod
|
||||
log "Production started. Staging NOT running (use --with-staging to start both)."
|
||||
fi
|
||||
info "Starting production container (corescope-prod) on ports ${PROD_HTTP_PORT:-80}/${PROD_HTTPS_PORT:-443}..."
|
||||
info "Starting staging container (corescope-staging) on port ${STAGING_HTTP_PORT:-81}..."
|
||||
docker compose --profile staging up -d
|
||||
log "Production started on ports ${PROD_HTTP_PORT:-80}/${PROD_HTTPS_PORT:-443}/${PROD_MQTT_PORT:-1883}"
|
||||
log "Staging started on port ${STAGING_HTTP_PORT:-81} (MQTT: ${STAGING_MQTT_PORT:-1884})"
|
||||
else
|
||||
# Legacy single-container mode
|
||||
if $WITH_STAGING; then
|
||||
err "--with-staging requires docker-compose.yml. Run setup or add docker-compose.yml first."
|
||||
exit 1
|
||||
fi
|
||||
warn "No docker-compose.yml found — using legacy single-container mode."
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
warn "Already running."
|
||||
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
if ! check_port_match; then
|
||||
warn "Container port mappings don't match Caddyfile configuration."
|
||||
warn "Current ports: $(get_current_ports)"
|
||||
warn "Required ports: $(get_required_ports)"
|
||||
if confirm "Recreate container with correct ports?"; then
|
||||
recreate_container
|
||||
log "Container recreated and started with correct ports."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
docker start "$CONTAINER_NAME"
|
||||
log "Started."
|
||||
else
|
||||
err "Container doesn't exist. Run './manage.sh setup' first."
|
||||
exit 1
|
||||
fi
|
||||
info "Starting production container (corescope-prod) on ports ${PROD_HTTP_PORT:-80}/${PROD_HTTPS_PORT:-443}..."
|
||||
docker compose up -d prod
|
||||
log "Production started. Staging NOT running (use --with-staging to start both)."
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_stop() {
|
||||
local TARGET="${1:-all}"
|
||||
|
||||
if $COMPOSE_MODE; then
|
||||
case "$TARGET" in
|
||||
prod)
|
||||
info "Stopping production container (corescope-prod)..."
|
||||
docker compose stop prod
|
||||
log "Production stopped."
|
||||
;;
|
||||
staging)
|
||||
info "Stopping staging container (corescope-staging)..."
|
||||
docker compose stop staging
|
||||
log "Staging stopped."
|
||||
;;
|
||||
all)
|
||||
info "Stopping all containers..."
|
||||
docker compose --profile staging --profile staging-go down 2>/dev/null
|
||||
docker rm -f "$CONTAINER_NAME" 2>/dev/null
|
||||
log "All containers stopped."
|
||||
;;
|
||||
*)
|
||||
err "Usage: ./manage.sh stop [prod|staging|all]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Legacy mode
|
||||
docker stop "$CONTAINER_NAME" 2>/dev/null && log "Stopped." || warn "Not running."
|
||||
fi
|
||||
case "$TARGET" in
|
||||
prod)
|
||||
info "Stopping production container (corescope-prod)..."
|
||||
docker compose stop prod
|
||||
log "Production stopped."
|
||||
;;
|
||||
staging)
|
||||
info "Stopping staging container (corescope-staging)..."
|
||||
docker compose --profile staging stop staging
|
||||
log "Staging stopped."
|
||||
;;
|
||||
all)
|
||||
info "Stopping all containers..."
|
||||
docker compose --profile staging --profile staging-go down
|
||||
log "All containers stopped."
|
||||
;;
|
||||
*)
|
||||
err "Usage: ./manage.sh stop [prod|staging|all]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_restart() {
|
||||
if $COMPOSE_MODE; then
|
||||
local TARGET="${1:-prod}"
|
||||
case "$TARGET" in
|
||||
prod)
|
||||
info "Restarting production container (corescope-prod)..."
|
||||
docker compose up -d --force-recreate prod
|
||||
log "Production restarted."
|
||||
;;
|
||||
staging)
|
||||
info "Restarting staging container (corescope-staging)..."
|
||||
docker compose --profile staging up -d --force-recreate staging
|
||||
log "Staging restarted."
|
||||
;;
|
||||
all)
|
||||
info "Restarting all containers..."
|
||||
docker compose --profile staging up -d --force-recreate
|
||||
log "All containers restarted."
|
||||
;;
|
||||
*)
|
||||
err "Usage: ./manage.sh restart [prod|staging|all]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Legacy mode
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
if ! check_port_match; then
|
||||
warn "Port mappings have changed. Recreating container..."
|
||||
recreate_container
|
||||
log "Container recreated with correct ports."
|
||||
else
|
||||
docker restart "$CONTAINER_NAME"
|
||||
log "Restarted."
|
||||
fi
|
||||
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
if ! check_port_match; then
|
||||
warn "Port mappings have changed. Recreating container..."
|
||||
recreate_container
|
||||
log "Container recreated with correct ports."
|
||||
else
|
||||
docker start "$CONTAINER_NAME"
|
||||
log "Started."
|
||||
fi
|
||||
else
|
||||
err "Not running. Use './manage.sh setup'."
|
||||
local TARGET="${1:-prod}"
|
||||
case "$TARGET" in
|
||||
prod)
|
||||
info "Restarting production container (corescope-prod)..."
|
||||
docker compose up -d --force-recreate prod
|
||||
log "Production restarted."
|
||||
;;
|
||||
staging)
|
||||
info "Restarting staging container (corescope-staging)..."
|
||||
docker compose --profile staging up -d --force-recreate staging
|
||||
log "Staging restarted."
|
||||
;;
|
||||
all)
|
||||
info "Restarting all containers..."
|
||||
docker compose --profile staging up -d --force-recreate
|
||||
log "All containers restarted."
|
||||
;;
|
||||
*)
|
||||
err "Usage: ./manage.sh restart [prod|staging|all]"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Status ───────────────────────────────────────────────────────────────
|
||||
@@ -688,143 +522,68 @@ show_container_status() {
|
||||
|
||||
cmd_status() {
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════"
|
||||
echo " CoreScope Status"
|
||||
echo "═══════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if $COMPOSE_MODE; then
|
||||
echo "═══════════════════════════════════════"
|
||||
echo " CoreScope Status (Compose)"
|
||||
echo "═══════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# Production
|
||||
show_container_status "corescope-prod" "Production"
|
||||
echo ""
|
||||
|
||||
# Staging
|
||||
if container_running "corescope-staging"; then
|
||||
show_container_status "corescope-staging" "Staging"
|
||||
else
|
||||
info "Staging (corescope-staging): Not running (use --with-staging to start both)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Disk usage
|
||||
if [ -d "$PROD_DATA" ] && [ -f "$PROD_DATA/meshcore.db" ]; then
|
||||
local db_size
|
||||
db_size=$(du -h "$PROD_DATA/meshcore.db" 2>/dev/null | cut -f1)
|
||||
info "Production DB: ${db_size}"
|
||||
fi
|
||||
if [ -d "$STAGING_DATA" ] && [ -f "$STAGING_DATA/meshcore.db" ]; then
|
||||
local staging_db_size
|
||||
staging_db_size=$(du -h "$STAGING_DATA/meshcore.db" 2>/dev/null | cut -f1)
|
||||
info "Staging DB: ${staging_db_size}"
|
||||
fi
|
||||
# Production
|
||||
show_container_status "corescope-prod" "Production"
|
||||
echo ""
|
||||
|
||||
# Staging
|
||||
if container_running "corescope-staging"; then
|
||||
show_container_status "corescope-staging" "Staging"
|
||||
else
|
||||
# Legacy single-container status
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
log "Container is running."
|
||||
echo ""
|
||||
docker ps --filter "name=${CONTAINER_NAME}" --format " Status: {{.Status}}"
|
||||
docker ps --filter "name=${CONTAINER_NAME}" --format " Ports: {{.Ports}}"
|
||||
echo ""
|
||||
|
||||
info "Service health:"
|
||||
# Server
|
||||
if docker exec "$CONTAINER_NAME" wget -qO /dev/null http://localhost:3000/api/stats 2>/dev/null; then
|
||||
STATS=$(docker exec "$CONTAINER_NAME" wget -qO- http://localhost:3000/api/stats 2>/dev/null)
|
||||
PACKETS=$(echo "$STATS" | grep -oP '"totalPackets":\K[0-9]+' 2>/dev/null || echo "?")
|
||||
NODES=$(echo "$STATS" | grep -oP '"totalNodes":\K[0-9]+' 2>/dev/null || echo "?")
|
||||
log " Server — ${PACKETS} packets, ${NODES} nodes"
|
||||
else
|
||||
err " Server — not responding"
|
||||
fi
|
||||
|
||||
# Mosquitto
|
||||
if docker exec "$CONTAINER_NAME" pgrep mosquitto &>/dev/null; then
|
||||
log " Mosquitto — running"
|
||||
else
|
||||
err " Mosquitto — not running"
|
||||
fi
|
||||
|
||||
# Caddy
|
||||
if docker exec "$CONTAINER_NAME" pgrep caddy &>/dev/null; then
|
||||
log " Caddy — running"
|
||||
else
|
||||
err " Caddy — not running"
|
||||
fi
|
||||
|
||||
# Check for MQTT errors in recent logs
|
||||
MQTT_ERRORS=$(docker logs "$CONTAINER_NAME" --tail 50 2>&1 | grep -i 'mqtt.*error\|mqtt.*fail\|ECONNREFUSED.*1883' || true)
|
||||
if [ -n "$MQTT_ERRORS" ]; then
|
||||
echo ""
|
||||
warn "MQTT errors in recent logs:"
|
||||
echo "$MQTT_ERRORS" | head -3 | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
# Port mapping check
|
||||
if ! check_port_match; then
|
||||
echo ""
|
||||
warn "Port mappings don't match Caddyfile. Run './manage.sh restart' to fix."
|
||||
fi
|
||||
|
||||
# Disk usage
|
||||
DB_SIZE=$(docker exec "$CONTAINER_NAME" du -h /app/data/meshcore.db 2>/dev/null | cut -f1)
|
||||
if [ -n "$DB_SIZE" ]; then
|
||||
echo ""
|
||||
info "Database size: ${DB_SIZE}"
|
||||
fi
|
||||
else
|
||||
err "Container is not running."
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo " Start with: ./manage.sh start"
|
||||
else
|
||||
echo " Set up with: ./manage.sh setup"
|
||||
fi
|
||||
fi
|
||||
info "Staging (corescope-staging): Not running (use --with-staging to start both)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Disk usage
|
||||
if [ -d "$PROD_DATA" ] && [ -f "$PROD_DATA/meshcore.db" ]; then
|
||||
local db_size
|
||||
db_size=$(du -h "$PROD_DATA/meshcore.db" 2>/dev/null | cut -f1)
|
||||
info "Production DB: ${db_size}"
|
||||
fi
|
||||
if [ -d "$STAGING_DATA" ] && [ -f "$STAGING_DATA/meshcore.db" ]; then
|
||||
local staging_db_size
|
||||
staging_db_size=$(du -h "$STAGING_DATA/meshcore.db" 2>/dev/null | cut -f1)
|
||||
info "Staging DB: ${staging_db_size}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─── Logs ─────────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_logs() {
|
||||
if $COMPOSE_MODE; then
|
||||
local TARGET="${1:-prod}"
|
||||
local LINES="${2:-100}"
|
||||
case "$TARGET" in
|
||||
prod)
|
||||
info "Tailing production logs..."
|
||||
docker compose logs -f --tail="$LINES" prod
|
||||
;;
|
||||
staging)
|
||||
if container_running "corescope-staging"; then
|
||||
info "Tailing staging logs..."
|
||||
docker compose logs -f --tail="$LINES" staging
|
||||
else
|
||||
err "Staging container is not running."
|
||||
info "Start with: ./manage.sh start --with-staging"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
err "Usage: ./manage.sh logs [prod|staging] [lines]"
|
||||
local TARGET="${1:-prod}"
|
||||
local LINES="${2:-100}"
|
||||
case "$TARGET" in
|
||||
prod)
|
||||
info "Tailing production logs..."
|
||||
docker compose logs -f --tail="$LINES" prod
|
||||
;;
|
||||
staging)
|
||||
if container_running "corescope-staging"; then
|
||||
info "Tailing staging logs..."
|
||||
docker compose logs -f --tail="$LINES" staging
|
||||
else
|
||||
err "Staging container is not running."
|
||||
info "Start with: ./manage.sh start --with-staging"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Legacy mode
|
||||
docker logs -f "$CONTAINER_NAME" --tail "${1:-100}"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
err "Usage: ./manage.sh logs [prod|staging] [lines]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Promote ──────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_promote() {
|
||||
if ! $COMPOSE_MODE; then
|
||||
err "Promotion requires Docker Compose setup (docker-compose.yml)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
info "Promotion Flow: Staging → Production"
|
||||
echo ""
|
||||
@@ -899,10 +658,10 @@ cmd_update() {
|
||||
git pull
|
||||
|
||||
info "Rebuilding image..."
|
||||
docker build --build-arg APP_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") --build-arg GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") --build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) -t "$IMAGE_NAME" .
|
||||
docker compose build prod
|
||||
|
||||
info "Restarting with new image..."
|
||||
recreate_container
|
||||
docker compose up -d --force-recreate prod
|
||||
|
||||
log "Updated and restarted. Data preserved."
|
||||
}
|
||||
@@ -922,8 +681,8 @@ cmd_backup() {
|
||||
if [ -f "$DB_PATH" ]; then
|
||||
cp "$DB_PATH" "$BACKUP_DIR/meshcore.db"
|
||||
log "Database ($(du -h "$BACKUP_DIR/meshcore.db" | cut -f1))"
|
||||
elif docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
docker cp "${CONTAINER_NAME}:/app/data/meshcore.db" "$BACKUP_DIR/meshcore.db" 2>/dev/null && \
|
||||
elif container_running "corescope-prod"; then
|
||||
docker cp corescope-prod:/app/data/meshcore.db "$BACKUP_DIR/meshcore.db" 2>/dev/null && \
|
||||
log "Database (via docker cp)" || warn "Could not backup database"
|
||||
else
|
||||
warn "Database not found (container not running?)"
|
||||
@@ -1016,10 +775,9 @@ cmd_restore() {
|
||||
info "Backing up current state..."
|
||||
cmd_backup "./backups/corescope-pre-restore-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
docker stop "$CONTAINER_NAME" 2>/dev/null || true
|
||||
docker compose stop prod 2>/dev/null || true
|
||||
|
||||
# Restore database
|
||||
# Always use bind mount path (from .env or default)
|
||||
mkdir -p "$PROD_DATA"
|
||||
DEST_DB="$PROD_DATA/meshcore.db"
|
||||
cp "$DB_FILE" "$DEST_DB"
|
||||
@@ -1045,20 +803,20 @@ cmd_restore() {
|
||||
log "theme.json restored"
|
||||
fi
|
||||
|
||||
docker start "$CONTAINER_NAME"
|
||||
docker compose up -d prod
|
||||
log "Restored and restarted."
|
||||
}
|
||||
|
||||
# ─── MQTT Test ────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_mqtt_test() {
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
if ! container_running "corescope-prod"; then
|
||||
err "Container not running. Start with: ./manage.sh start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Listening for MQTT messages (10 second timeout)..."
|
||||
MSG=$(docker exec "$CONTAINER_NAME" mosquitto_sub -h localhost -t 'meshcore/#' -C 1 -W 10 2>/dev/null)
|
||||
MSG=$(docker exec corescope-prod mosquitto_sub -h localhost -t 'meshcore/#' -C 1 -W 10 2>/dev/null)
|
||||
if [ -n "$MSG" ]; then
|
||||
log "Received MQTT message:"
|
||||
echo " $MSG" | head -c 200
|
||||
@@ -1075,17 +833,15 @@ cmd_mqtt_test() {
|
||||
|
||||
cmd_reset() {
|
||||
echo ""
|
||||
warn "This will remove the container, image, and setup state."
|
||||
warn "Your config.json, Caddyfile, and data volume are NOT deleted."
|
||||
warn "This will remove all containers, images, and setup state."
|
||||
warn "Your config.json, Caddyfile, and data directory are NOT deleted."
|
||||
echo ""
|
||||
if ! confirm "Continue?"; then
|
||||
echo " Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docker stop "$CONTAINER_NAME" 2>/dev/null || true
|
||||
docker rm "$CONTAINER_NAME" 2>/dev/null || true
|
||||
docker rmi "$IMAGE_NAME" 2>/dev/null || true
|
||||
docker compose --profile staging --profile staging-go down --rmi local 2>/dev/null || true
|
||||
rm -f "$STATE_FILE"
|
||||
|
||||
log "Reset complete. Run './manage.sh setup' to start over."
|
||||
@@ -1119,11 +875,7 @@ cmd_help() {
|
||||
echo " restore <d> Restore from backup dir or .db file"
|
||||
echo " mqtt-test Check if MQTT data is flowing"
|
||||
echo ""
|
||||
if $COMPOSE_MODE; then
|
||||
info "Docker Compose mode detected (docker-compose.yml present)."
|
||||
else
|
||||
warn "Legacy mode (no docker-compose.yml). Some commands unavailable."
|
||||
fi
|
||||
echo "All commands use docker compose with docker-compose.yml."
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user