fix: config.json lives in data dir, not bind-mounted as file

Removes the separate config.json file bind mount from both compose
files. The data directory mount already covers it, and the Go server
searches /app/data/config.json via LoadConfig.

- Entrypoint symlinks /app/data/config.json for ingestor compatibility
- manage.sh setup creates config in data dir, prompts admin if missing
- manage.sh start checks config exists before starting, offers to create
- deploy.yml simplified — no more sudo rm or directory cleanup
- Backup/restore updated to use data dir path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kpa-clawbot
2026-03-30 09:58:22 -07:00
parent 86b5d4e175
commit 57ebd76070
5 changed files with 110 additions and 41 deletions

View File

@@ -264,8 +264,7 @@ jobs:
- name: Deploy staging
run: |
# Use docker compose down (not just stop/rm) to properly clean up
# the old container, network, and release memory before starting new one
# Stop old container and release memory
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)
@@ -277,14 +276,14 @@ jobs:
done
sleep 5 # extra pause for OS memory reclaim
# Ensure staging config exists (docker creates a directory if bind mount source missing)
# Ensure staging data dir exists (config.json lives here, no separate file mount)
STAGING_DATA="${STAGING_DATA_DIR:-$HOME/meshcore-staging-data}"
mkdir -p "$STAGING_DATA"
# Remove directory-masquerading-as-file left by failed docker mount
[ -d "$STAGING_DATA/config.json" ] && rm -rf "$STAGING_DATA/config.json"
# If no config exists, copy the example (CI doesn't have a real prod config)
if [ ! -f "$STAGING_DATA/config.json" ]; then
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"
echo "Staging config missing — copying config.example.json"
cp config.example.json "$STAGING_DATA/config.json" 2>/dev/null || true
fi
docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging up -d staging-go

View File

@@ -25,7 +25,6 @@ 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:

View File

@@ -21,7 +21,6 @@ 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

View File

@@ -1,16 +1,12 @@
#!/bin/sh
# 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
# 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"
fi
# theme.json: check data/ volume (admin-editable on host)

118
manage.sh
View File

@@ -68,10 +68,11 @@ is_done() { [ -f "$STATE_FILE" ] && grep -qx "$1" "$STATE_FILE" 2>/dev/null;
# Check config.json for placeholder values
check_config_placeholders() {
if [ -f config.json ]; then
if grep -qE 'your-username|your-password|your-secret|example\.com|changeme' config.json 2>/dev/null; then
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
warn "config.json contains placeholder values."
warn "Edit config.json and replace placeholder values before deploying."
warn "Edit ${cfg} and replace placeholder values before deploying."
fi
fi
}
@@ -179,19 +180,27 @@ cmd_setup() {
# ── Step 2: Config ──
step 2 "Configuration"
if [ -f config.json ]; then
log "config.json already exists (not overwriting)."
if [ -f "$PROD_DATA/config.json" ]; then
log "config.json found in data directory."
# Sanity check the JSON
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
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
err "config.json has invalid JSON. Fix it and re-run setup."
exit 1
fi
log "config.json is valid JSON."
check_config_placeholders
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"
else
info "Creating config.json from example..."
cp config.example.json config.json
info "Creating config.json in data directory from example..."
mkdir -p "$PROD_DATA"
cp config.example.json "$PROD_DATA/config.json"
# Generate a random API key
if command -v openssl &> /dev/null; then
@@ -201,14 +210,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}/" config.json
sed -i "s/your-secret-api-key-here/${API_KEY}/" "$PROD_DATA/config.json"
fi
log "Created config.json with random API key."
check_config_placeholders
check_config_placeholders "$PROD_DATA/config.json"
echo ""
echo " You can customize config.json later (map center, branding, etc)."
echo " Edit with: nano config.json"
echo " Config saved to: ${PROD_DATA}/config.json"
echo " Edit with: nano ${PROD_DATA}/config.json"
echo ""
fi
mark_done "config"
@@ -402,10 +411,15 @@ prepare_staging_db() {
# Copy config.prod.json → config.staging.json with siteName change
prepare_staging_config() {
local prod_config="./config.json"
local prod_config="$PROD_DATA/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 config.json found at ${prod_config} — staging may not start correctly."
warn "No production config at ${prod_config} — staging may use defaults."
return
fi
if [ ! -f "$staging_config" ] || [ "$prod_config" -nt "$staging_config" ]; then
@@ -439,12 +453,71 @@ 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
@@ -736,10 +809,13 @@ cmd_backup() {
warn "Database not found (container not running?)"
fi
# Config
if [ -f config.json ]; then
cp config.json "$BACKUP_DIR/config.json"
# 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
cp config.json "$BACKUP_DIR/config.json"
log "config.json (legacy repo root)"
fi
# Caddyfile
@@ -833,8 +909,8 @@ cmd_restore() {
# Restore config if present
if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
cp "$CONFIG_FILE" ./config.json
log "config.json restored"
cp "$CONFIG_FILE" "$PROD_DATA/config.json"
log "config.json restored to ${PROD_DATA}/"
fi
# Restore Caddyfile if present