diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d204d04c..71f59f35 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -264,7 +264,8 @@ jobs: - name: Deploy staging run: | - # Stop old container and release memory + # Use docker compose down (not just stop/rm) to properly clean up + # the old container, network, and release memory before starting new one docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging down --timeout 30 2>/dev/null || true # Wait for container to be fully gone and OS to reclaim memory (3GB limit) @@ -276,14 +277,14 @@ jobs: done sleep 5 # extra pause for OS memory reclaim - # Ensure staging data dir exists (config.json lives here, no separate file mount) + # Ensure staging config exists (docker creates a directory if bind mount source missing) STAGING_DATA="${STAGING_DATA_DIR:-$HOME/meshcore-staging-data}" mkdir -p "$STAGING_DATA" - - # If no config exists, copy the example (CI doesn't have a real prod config) + # Remove directory-masquerading-as-file left by failed docker mount + [ -d "$STAGING_DATA/config.json" ] && rm -rf "$STAGING_DATA/config.json" if [ ! -f "$STAGING_DATA/config.json" ]; then - echo "Staging config missing — copying config.example.json" - cp config.example.json "$STAGING_DATA/config.json" 2>/dev/null || true + echo "Staging config missing — copying from repo config.json" + cp config.json "$STAGING_DATA/config.json" 2>/dev/null || cp config.example.json "$STAGING_DATA/config.json" fi docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging up -d staging-go diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index f14560a9..e2fd19ae 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -25,6 +25,7 @@ services: - "6060:6060" # pprof server - "6061:6061" # pprof ingestor volumes: + - ${STAGING_DATA_DIR:-~/meshcore-staging-data}/config.json:/app/config.json:ro - ${STAGING_DATA_DIR:-~/meshcore-staging-data}:/app/data - caddy-data-staging-go:/data/caddy environment: diff --git a/docker-compose.yml b/docker-compose.yml index b1269a37..f89de0ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - "${PROD_HTTPS_PORT:-443}:443" - "${PROD_MQTT_PORT:-1883}:1883" volumes: + - ./config.json:/app/config.json:ro - ./caddy-config/Caddyfile:/etc/caddy/Caddyfile:ro - ${PROD_DATA_DIR:-~/meshcore-data}:/app/data - caddy-data:/data/caddy diff --git a/docker/entrypoint-go.sh b/docker/entrypoint-go.sh index 9a6ce875..36dad083 100644 --- a/docker/entrypoint-go.sh +++ b/docker/entrypoint-go.sh @@ -1,12 +1,16 @@ #!/bin/sh -# Config lives in the data directory (bind-mounted from host) -# The Go server already searches /app/data/config.json via LoadConfig -# but the ingestor expects a direct path — symlink for compatibility -if [ -f /app/data/config.json ]; then - ln -sf /app/data/config.json /app/config.json -elif [ ! -f /app/config.json ]; then - echo "[entrypoint] No config.json found in /app/data/ — using built-in defaults" +# Fix: Docker creates a directory when bind-mounting a non-existent file. +# If config.json is a directory (from a failed mount), remove it and use the example. +if [ -d /app/config.json ]; then + echo "[entrypoint] WARNING: config.json is a directory (broken bind mount) — removing and using example" + rm -rf /app/config.json +fi + +# Copy example config if no config.json exists (not bind-mounted) +if [ ! -f /app/config.json ]; then + echo "[entrypoint] No config.json found, copying from config.example.json" + cp /app/config.example.json /app/config.json fi # theme.json: check data/ volume (admin-editable on host) diff --git a/manage.sh b/manage.sh index 6c834752..760275ee 100755 --- a/manage.sh +++ b/manage.sh @@ -68,11 +68,10 @@ is_done() { [ -f "$STATE_FILE" ] && grep -qx "$1" "$STATE_FILE" 2>/dev/null; # Check config.json for placeholder values check_config_placeholders() { - local cfg="${1:-$PROD_DATA/config.json}" - if [ -f "$cfg" ]; then - if grep -qE 'your-username|your-password|your-secret|example\.com|changeme' "$cfg" 2>/dev/null; then + if [ -f config.json ]; then + if grep -qE 'your-username|your-password|your-secret|example\.com|changeme' config.json 2>/dev/null; then warn "config.json contains placeholder values." - warn "Edit ${cfg} and replace placeholder values before deploying." + warn "Edit config.json and replace placeholder values before deploying." fi fi } @@ -180,27 +179,19 @@ cmd_setup() { # ── Step 2: Config ── step 2 "Configuration" - if [ -f "$PROD_DATA/config.json" ]; then - log "config.json found in data directory." + if [ -f config.json ]; then + log "config.json already exists (not overwriting)." # Sanity check the JSON - if ! python3 -c "import json; json.load(open('$PROD_DATA/config.json'))" 2>/dev/null && \ - ! node -e "JSON.parse(require('fs').readFileSync('$PROD_DATA/config.json'))" 2>/dev/null; then + if ! python3 -c "import json; json.load(open('config.json'))" 2>/dev/null && \ + ! node -e "JSON.parse(require('fs').readFileSync('config.json'))" 2>/dev/null; then err "config.json has invalid JSON. Fix it and re-run setup." exit 1 fi log "config.json is valid JSON." - check_config_placeholders "$PROD_DATA/config.json" - elif [ -f config.json ]; then - # Legacy: config in repo root — move it to data dir - info "Found config.json in repo root — moving to data directory..." - mkdir -p "$PROD_DATA" - cp config.json "$PROD_DATA/config.json" - log "Config moved to ${PROD_DATA}/config.json" - check_config_placeholders "$PROD_DATA/config.json" + check_config_placeholders else - info "Creating config.json in data directory from example..." - mkdir -p "$PROD_DATA" - cp config.example.json "$PROD_DATA/config.json" + info "Creating config.json from example..." + cp config.example.json config.json # Generate a random API key if command -v openssl &> /dev/null; then @@ -210,14 +201,14 @@ cmd_setup() { fi # Replace the placeholder API key if command -v sed &> /dev/null; then - sed -i "s/your-secret-api-key-here/${API_KEY}/" "$PROD_DATA/config.json" + sed -i "s/your-secret-api-key-here/${API_KEY}/" config.json fi log "Created config.json with random API key." - check_config_placeholders "$PROD_DATA/config.json" + check_config_placeholders echo "" - echo " Config saved to: ${PROD_DATA}/config.json" - echo " Edit with: nano ${PROD_DATA}/config.json" + echo " You can customize config.json later (map center, branding, etc)." + echo " Edit with: nano config.json" echo "" fi mark_done "config" @@ -411,15 +402,10 @@ prepare_staging_db() { # Copy config.prod.json → config.staging.json with siteName change prepare_staging_config() { - local prod_config="$PROD_DATA/config.json" + local prod_config="./config.json" local staging_config="$STAGING_DATA/config.json" - mkdir -p "$STAGING_DATA" - - # Docker may have created config.json as a directory - [ -d "$staging_config" ] && rmdir "$staging_config" 2>/dev/null || true - if [ ! -f "$prod_config" ]; then - warn "No production config at ${prod_config} — staging may use defaults." + warn "No config.json found at ${prod_config} — staging may not start correctly." return fi if [ ! -f "$staging_config" ] || [ "$prod_config" -nt "$staging_config" ]; then @@ -453,71 +439,12 @@ container_health() { # ─── Start / Stop / Restart ────────────────────────────────────────────── -# Ensure config.json exists in the data directory before starting -ensure_config() { - local data_dir="$1" - local config="$data_dir/config.json" - mkdir -p "$data_dir" - - # Docker may have created config.json as a directory from a prior failed mount - [ -d "$config" ] && rmdir "$config" 2>/dev/null || true - - if [ -f "$config" ]; then - return 0 - fi - - # Try to copy from repo root (legacy location) - if [ -f ./config.json ]; then - info "No config in data directory — copying from ./config.json" - cp ./config.json "$config" - return 0 - fi - - # Prompt admin - echo "" - warn "No config.json found in ${data_dir}/" - echo "" - echo " CoreScope needs a config.json to connect to MQTT brokers." - echo "" - echo " Options:" - echo " 1) Create from example (you'll edit MQTT settings after)" - echo " 2) I'll put one there myself (abort for now)" - echo "" - read -p " Choose [1/2]: " -n 1 -r - echo "" - - case $REPLY in - 1) - cp config.example.json "$config" - # Generate a random API key - if command -v openssl &>/dev/null; then - API_KEY=$(openssl rand -hex 16) - else - API_KEY=$(head -c 32 /dev/urandom | xxd -p | head -c 32) - fi - sed -i "s/your-secret-api-key-here/${API_KEY}/" "$config" 2>/dev/null || true - log "Created ${config} from example with random API key." - warn "Edit MQTT settings before connecting observers:" - echo " nano ${config}" - echo "" - ;; - *) - echo " Place your config.json at: ${config}" - echo " Then run this command again." - exit 0 - ;; - esac -} - cmd_start() { local WITH_STAGING=false if [ "$1" = "--with-staging" ]; then WITH_STAGING=true fi - # Always check prod config - ensure_config "$PROD_DATA" - if $WITH_STAGING; then # Prepare staging data and config prepare_staging_db @@ -809,13 +736,10 @@ cmd_backup() { warn "Database not found (container not running?)" fi - # Config (now lives in data dir) - if [ -f "$PROD_DATA/config.json" ]; then - cp "$PROD_DATA/config.json" "$BACKUP_DIR/config.json" - log "config.json" - elif [ -f config.json ]; then + # Config + if [ -f config.json ]; then cp config.json "$BACKUP_DIR/config.json" - log "config.json (legacy repo root)" + log "config.json" fi # Caddyfile @@ -909,8 +833,8 @@ cmd_restore() { # Restore config if present if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then - cp "$CONFIG_FILE" "$PROD_DATA/config.json" - log "config.json restored to ${PROD_DATA}/" + cp "$CONFIG_FILE" ./config.json + log "config.json restored" fi # Restore Caddyfile if present