Files
coordinator/scripts/add-rounds-mount-to-daemons.sh
ClaudeBotandClaude Opus 4.7 645778d2b7 Switch mount paths from /mnt/cache to /mnt/user; broaden daemon mount to multica/
On unraid, /mnt/user is the FUSE layer that applies share-routing policy
(cache-first, mover, include/exclude). /mnt/cache bypasses that routing.
Default rule: container bind mounts use /mnt/user/... unless there's a
specific reason to pin to cache.

Also broadens the daemon mount from just /multica/rounds to the whole
/multica namespace. This means future subpaths (agents/<slug>/ for the
credentials migration, coordinator-state/, etc.) are already visible
without another daemon recreate. Per user: "multica both includes shared
and not shared" — one mount covers both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 01:33:04 +02:00

141 lines
4.5 KiB
Bash
Executable File

#!/bin/bash
# Idempotently add the multica-namespace mount to every multica daemon.
#
# Mounts the broad parent (/mnt/user/appdata/multica) rather than only
# /multica/rounds, so any future subpath (agents/<slug>/, coordinator-state/,
# etc.) is automatically visible without another docker recreate. Per user:
# "multica both includes shared and not shared" — one mount covers both.
#
# /mnt/user/ is unraid's FUSE routing layer; using it respects unraid's
# share policy rather than bypassing it via direct /mnt/cache/ access.
#
# For each daemon container this script:
# 1. Reads the full existing `docker inspect` config (image, env, cmd,
# network, restart policy, ALL existing bind mounts, hostname).
# 2. Checks whether the multica mount is already there → skip if so.
# 3. Stops + removes the container.
# 4. Recreates it with the SAME config plus the new bind mount.
#
# Usage: ./add-rounds-mount-to-daemons.sh [list of container names]
# Default list: all 7 multica daemons currently running.
set -euo pipefail
MULTICA_DIR="/mnt/user/appdata/multica"
ROUNDS_DIR="${MULTICA_DIR}/rounds"
MOUNT_SPEC="${MULTICA_DIR}:${MULTICA_DIR}:rw"
mkdir -p "${ROUNDS_DIR}"
DEFAULT_DAEMONS=(
multica-daemon
multica-daemon-codex
multica-daemon-openai
multica-daemon-minimax
multica-daemon-ollama
multica-daemon-copilot-claude
multica-daemon-copilot-gemini
)
if [[ $# -gt 0 ]]; then
DAEMONS=("$@")
else
DAEMONS=("${DEFAULT_DAEMONS[@]}")
fi
recreate_one() {
local name="$1"
if ! docker inspect "${name}" >/dev/null 2>&1; then
echo " SKIP: container ${name} not found"
return 0
fi
# Check if the mount is already there.
if docker inspect "${name}" --format '{{range .HostConfig.Binds}}{{println .}}{{end}}' \
| grep -qx "${MOUNT_SPEC}"; then
echo " OK: ${name} already has ${ROUNDS_DIR} mount"
return 0
fi
echo " RECREATE: ${name}"
# Capture everything we need to re-create the container identically.
local image hostname network restart_policy user
image=$(docker inspect "${name}" --format '{{.Config.Image}}')
hostname=$(docker inspect "${name}" --format '{{.Config.Hostname}}')
network=$(docker inspect "${name}" --format '{{range $k, $_ := .NetworkSettings.Networks}}{{$k}}{{end}}')
restart_policy=$(docker inspect "${name}" --format '{{.HostConfig.RestartPolicy.Name}}')
user=$(docker inspect "${name}" --format '{{.Config.User}}')
# Env: one KEY=VALUE per line.
local envfile
envfile=$(mktemp)
trap "rm -f ${envfile}" RETURN
docker inspect "${name}" --format '{{range .Config.Env}}{{println .}}{{end}}' > "${envfile}"
# Binds: one per line.
local binds=()
while IFS= read -r b; do
[[ -n "${b}" ]] && binds+=(-v "${b}")
done < <(docker inspect "${name}" --format '{{range .HostConfig.Binds}}{{println .}}{{end}}')
# Add the new mount.
binds+=(-v "${MOUNT_SPEC}")
# Entrypoint + command.
local entrypoint cmd
entrypoint=$(docker inspect "${name}" --format '{{json .Config.Entrypoint}}')
cmd=$(docker inspect "${name}" --format '{{json .Config.Cmd}}')
# Stop + remove.
docker rm -f "${name}" >/dev/null
# Build the run command.
local -a run_args=(
run -d
--name "${name}"
--hostname "${hostname}"
--env-file "${envfile}"
"${binds[@]}"
)
if [[ -n "${restart_policy}" && "${restart_policy}" != "no" ]]; then
run_args+=(--restart "${restart_policy}")
fi
if [[ -n "${user}" ]]; then
run_args+=(--user "${user}")
fi
if [[ -n "${network}" && "${network}" != "bridge" ]]; then
run_args+=(--network "${network}")
fi
# Entrypoint/cmd preservation via --entrypoint + positional cmd only when set.
# Bash JSON→array is painful; only override if actually non-null.
local ep_val=""
if [[ "${entrypoint}" != "null" && "${entrypoint}" != "[]" ]]; then
# strip brackets/quotes; we only use the first element
ep_val=$(echo "${entrypoint}" | jq -r '.[0] // empty' 2>/dev/null || echo "")
[[ -n "${ep_val}" ]] && run_args+=(--entrypoint "${ep_val}")
fi
run_args+=("${image}")
# Append command args.
if [[ "${cmd}" != "null" && "${cmd}" != "[]" ]]; then
local -a cmd_args
mapfile -t cmd_args < <(echo "${cmd}" | jq -r '.[]' 2>/dev/null)
run_args+=("${cmd_args[@]}")
fi
docker "${run_args[@]}" >/dev/null
echo " OK: ${name} recreated with ${ROUNDS_DIR} mount"
}
echo "adding ${ROUNDS_DIR} mount to daemons:"
for d in "${DAEMONS[@]}"; do
recreate_one "${d}"
done
echo
echo "done. verify with:"
echo " for c in ${DAEMONS[*]}; do docker inspect \$c --format '{{\$c}}: {{.HostConfig.Binds}}' ; done"