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>
141 lines
4.5 KiB
Bash
Executable File
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"
|