Files
openwork/packaging/docker/dev-up.sh

246 lines
7.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Bring up a dev stack with random host ports.
#
# Usage (from _repos/openwork repo root):
# packaging/docker/dev-up.sh
#
# Defaults to isolated OpenCode dev state inside the container.
# Escape hatch: set OPENWORK_DOCKER_DEV_MOUNT_HOST_OPENCODE=1 to import host
# OpenCode config/auth into the isolated dev state for this stack.
#
# Outputs:
# - Web UI URL
# - OpenWork server URL
# - Share service URL
# - Token file path
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
COMPOSE_FILE="$ROOT_DIR/packaging/docker/docker-compose.dev.yml"
WORKSPACE_DIR="$ROOT_DIR/packaging/docker/workspace"
DEV_RUNTIME_DIR="$ROOT_DIR/tmp/docker-dev"
MOUNT_HOST_OPENCODE="${OPENWORK_DOCKER_DEV_MOUNT_HOST_OPENCODE:-0}"
resolve_opencode_config_dir() {
local override="${OPENWORK_OPENCODE_CONFIG_DIR:-}"
if [ -n "$override" ]; then
if [ -d "$override" ]; then
printf '%s\n' "$override"
return 0
fi
echo "warning: OPENWORK_OPENCODE_CONFIG_DIR is not a directory: $override" >&2
fi
local candidates=()
if [ -n "${XDG_CONFIG_HOME:-}" ]; then
candidates+=("${XDG_CONFIG_HOME}/opencode")
fi
candidates+=("${HOME}/.config/opencode")
if [ "$(uname -s)" = "Darwin" ]; then
candidates+=("${HOME}/Library/Application Support/opencode")
fi
local files=("opencode.jsonc" "opencode.json" "config.json" "AGENTS.md")
local candidate file
for candidate in "${candidates[@]}"; do
[ -d "$candidate" ] || continue
for file in "${files[@]}"; do
if [ -f "$candidate/$file" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
if [ -n "$(ls -A "$candidate" 2>/dev/null)" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
return 1
}
resolve_opencode_data_dir() {
local override="${OPENWORK_OPENCODE_DATA_DIR:-}"
if [ -n "$override" ]; then
if [ -d "$override" ]; then
printf '%s\n' "$override"
return 0
fi
echo "warning: OPENWORK_OPENCODE_DATA_DIR is not a directory: $override" >&2
fi
local candidates=()
if [ -n "${XDG_DATA_HOME:-}" ]; then
candidates+=("${XDG_DATA_HOME}/opencode")
fi
candidates+=("${HOME}/.local/share/opencode")
if [ "$(uname -s)" = "Darwin" ]; then
candidates+=("${HOME}/Library/Application Support/opencode")
fi
local files=("auth.json" "mcp-auth.json")
local candidate file
for candidate in "${candidates[@]}"; do
[ -d "$candidate" ] || continue
for file in "${files[@]}"; do
if [ -f "$candidate/$file" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
done
return 1
}
if ! command -v docker >/dev/null 2>&1; then
echo "docker is required" >&2
exit 1
fi
pick_port() {
node -e "
const net = require('net');
const s = net.createServer();
s.listen(0, '127.0.0.1', () => {
const { port } = s.address();
console.log(port);
s.close();
});
"
}
detect_public_host() {
if [ -n "${OPENWORK_PUBLIC_HOST:-}" ]; then
printf '%s\n' "$OPENWORK_PUBLIC_HOST"
return
fi
local host
host="$(hostname -s 2>/dev/null || hostname 2>/dev/null || true)"
host="${host//$'\n'/}"
host="${host// /}"
if [ -n "$host" ]; then
printf '%s\n' "$host"
return
fi
printf '%s\n' "localhost"
}
detect_lan_ipv4() {
node -e '
const os = require("os");
const nets = os.networkInterfaces();
for (const entries of Object.values(nets)) {
for (const entry of entries || []) {
if (!entry || entry.internal || entry.family !== "IPv4") continue;
if (entry.address.startsWith("127.")) continue;
process.stdout.write(entry.address);
process.exit(0);
}
}
'
}
DEV_ID="$(node -e "console.log(require('crypto').randomUUID().slice(0, 8))")"
PROJECT="openwork-dev-$DEV_ID"
mkdir -p "$WORKSPACE_DIR"
mkdir -p "$DEV_RUNTIME_DIR"
OPENCODE_CONFIG_FALLBACK_DIR="$DEV_RUNTIME_DIR/host-opencode-config"
OPENCODE_DATA_FALLBACK_DIR="$DEV_RUNTIME_DIR/host-opencode-data"
mkdir -p "$OPENCODE_CONFIG_FALLBACK_DIR" "$OPENCODE_DATA_FALLBACK_DIR"
HOST_OPENCODE_CONFIG_DIR="$OPENCODE_CONFIG_FALLBACK_DIR"
HOST_OPENCODE_DATA_DIR="$OPENCODE_DATA_FALLBACK_DIR"
if [ "$MOUNT_HOST_OPENCODE" = "1" ]; then
HOST_OPENCODE_CONFIG_DIR="$(resolve_opencode_config_dir || true)"
HOST_OPENCODE_DATA_DIR="$(resolve_opencode_data_dir || true)"
if [ -z "$HOST_OPENCODE_CONFIG_DIR" ]; then
HOST_OPENCODE_CONFIG_DIR="$OPENCODE_CONFIG_FALLBACK_DIR"
fi
if [ -z "$HOST_OPENCODE_DATA_DIR" ]; then
HOST_OPENCODE_DATA_DIR="$OPENCODE_DATA_FALLBACK_DIR"
fi
fi
OPENWORK_PORT="$(pick_port)"
WEB_PORT="$(pick_port)"
if [ "$WEB_PORT" = "$OPENWORK_PORT" ]; then
WEB_PORT="$(pick_port)"
fi
SHARE_PORT="${SHARE_PORT:-$(pick_port)}"
if [ "$SHARE_PORT" = "$OPENWORK_PORT" ] || [ "$SHARE_PORT" = "$WEB_PORT" ]; then
SHARE_PORT="$(pick_port)"
fi
PUBLIC_HOST="$(detect_public_host)"
LAN_IPV4="$(detect_lan_ipv4 || true)"
echo "Starting Docker Compose project: $PROJECT" >&2
echo "- OPENWORK_PORT=$OPENWORK_PORT" >&2
echo "- WEB_PORT=$WEB_PORT" >&2
echo "- SHARE_PORT=$SHARE_PORT" >&2
echo "- OPENWORK_DEV_MODE=1" >&2
if [ "$MOUNT_HOST_OPENCODE" = "1" ]; then
echo "- Host OpenCode import: enabled" >&2
else
echo "- Host OpenCode import: disabled (isolated dev state)" >&2
fi
start_stack() {
local config_dir="$1"
local data_dir="$2"
OPENWORK_DEV_ID="$DEV_ID" OPENWORK_PORT="$OPENWORK_PORT" WEB_PORT="$WEB_PORT" SHARE_PORT="$SHARE_PORT" \
OPENWORK_DEV_MODE="1" \
OPENWORK_HOST_OPENCODE_CONFIG_DIR="$config_dir" \
OPENWORK_HOST_OPENCODE_DATA_DIR="$data_dir" \
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d
}
ACTIVE_OPENCODE_CONFIG_DIR="$HOST_OPENCODE_CONFIG_DIR"
ACTIVE_OPENCODE_DATA_DIR="$HOST_OPENCODE_DATA_DIR"
echo "- OPENWORK_HOST_OPENCODE_CONFIG_DIR=$ACTIVE_OPENCODE_CONFIG_DIR" >&2
echo "- OPENWORK_HOST_OPENCODE_DATA_DIR=$ACTIVE_OPENCODE_DATA_DIR" >&2
if ! start_stack "$ACTIVE_OPENCODE_CONFIG_DIR" "$ACTIVE_OPENCODE_DATA_DIR"; then
if [ "$ACTIVE_OPENCODE_CONFIG_DIR" != "$OPENCODE_CONFIG_FALLBACK_DIR" ] || [ "$ACTIVE_OPENCODE_DATA_DIR" != "$OPENCODE_DATA_FALLBACK_DIR" ]; then
echo "Detected host OpenCode config mount failed; retrying with empty fallback dirs." >&2
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down >/dev/null 2>&1 || true
ACTIVE_OPENCODE_CONFIG_DIR="$OPENCODE_CONFIG_FALLBACK_DIR"
ACTIVE_OPENCODE_DATA_DIR="$OPENCODE_DATA_FALLBACK_DIR"
echo "- OPENWORK_HOST_OPENCODE_CONFIG_DIR=$ACTIVE_OPENCODE_CONFIG_DIR" >&2
echo "- OPENWORK_HOST_OPENCODE_DATA_DIR=$ACTIVE_OPENCODE_DATA_DIR" >&2
start_stack "$ACTIVE_OPENCODE_CONFIG_DIR" "$ACTIVE_OPENCODE_DATA_DIR"
else
exit 1
fi
fi
echo "" >&2
echo "OpenWork web UI: http://localhost:$WEB_PORT" >&2
echo "OpenWork web UI (LAN/public): http://$PUBLIC_HOST:$WEB_PORT" >&2
if [ -n "$LAN_IPV4" ]; then
echo "OpenWork web UI (LAN IP): http://$LAN_IPV4:$WEB_PORT" >&2
fi
echo "OpenWork server: http://localhost:$OPENWORK_PORT" >&2
echo "OpenWork server (LAN/public): http://$PUBLIC_HOST:$OPENWORK_PORT" >&2
if [ -n "$LAN_IPV4" ]; then
echo "OpenWork server (LAN IP): http://$LAN_IPV4:$OPENWORK_PORT" >&2
fi
echo "Share service: http://localhost:$SHARE_PORT" >&2
echo "Share service (LAN/public): http://$PUBLIC_HOST:$SHARE_PORT" >&2
if [ -n "$LAN_IPV4" ]; then
echo "Share service (LAN IP): http://$LAN_IPV4:$SHARE_PORT" >&2
fi
echo "Token file: $ROOT_DIR/tmp/.dev-env-$DEV_ID" >&2
echo "" >&2
echo "To stop this stack:" >&2
echo " docker compose -p $PROJECT -f $COMPOSE_FILE down" >&2