diff --git a/docs/hosting/mirofish-hermes-integration-contract.md b/docs/hosting/mirofish-hermes-integration-contract.md new file mode 100644 index 00000000..435e0990 --- /dev/null +++ b/docs/hosting/mirofish-hermes-integration-contract.md @@ -0,0 +1,93 @@ +# MiroFish Hermes Integration Contract + +## Purpose + +This document defines the minimum stable contract for Hermes and OpenClaw to use the MiroFish host safely. + +## Canonical Paths + +- Runtime: `/Users/Shared/OpenClaw/mirofish-runtime` +- Transcribes: `/Users/Shared/OpenClaw/transcribes` +- Source only: `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish` + +## Service Identity + +- Service label: `com.openclaw.mirofish` +- Runtime owner: `airstride` +- Access model: LAN + SSH only + +## Network Endpoints + +- Frontend LAN URL: `http://10.0.0.161:3000` +- Backend LAN URL: `http://10.0.0.161:5001` +- Backend health: `http://10.0.0.161:5001/health` +- Frontend proxy health path: `http://10.0.0.161:3000/api/graph/project/list` + +## SSH Access + +- SSH alias: `openclaw-mirofish` +- SSH user: `airstride` + +Recommended tunnels: + +```bash +ssh -L 3000:127.0.0.1:3000 openclaw-mirofish +ssh -L 5001:127.0.0.1:5001 openclaw-mirofish +``` + +Tunnel-based local URLs: + +- Frontend: `http://127.0.0.1:3000` +- Backend: `http://127.0.0.1:5001` + +## Required Health Checks + +Before Hermes or OpenClaw send operational work to MiroFish, the host should pass: + +```bash +sudo launchctl print system/com.openclaw.mirofish +curl -sS http://127.0.0.1:5001/health +curl -sSI http://127.0.0.1:3000/ +curl -sS http://127.0.0.1:3000/api/graph/project/list +``` + +Expected results: + +- LaunchDaemon present in `system` +- Backend returns `{"service":"MiroFish Backend","status":"ok"}` +- Frontend returns `HTTP/1.1 200 OK` +- Frontend proxy returns a successful JSON payload + +## Smoke Test + +Use the canonical smoke test after reboot or maintenance: + +```bash +/Users/Shared/OpenClaw/mirofish-runtime/scripts/host-smoke-test.sh +``` + +## Operational Data Rules + +- Do not run MiroFish from iCloud. +- Do not write operational runtime state back into the iCloud source tree. +- Treat `/Users/Shared/OpenClaw/mirofish-runtime/backend/uploads` as persistent operational state. +- Treat `/Users/Shared/OpenClaw/transcribes` as the canonical local operational transcribes dataset. + +## Consumption Rules + +- Prefer SSH tunnels for operator access from other machines. +- Use LAN URLs only inside the trusted local network. +- Do not expose ports `3000` or `5001` through router forwarding or public internet ingress. +- Hermes and OpenClaw should assume MiroFish is a long-lived host service, not an ephemeral dev process. + +## Operational Commands + +From `/Users/Shared/OpenClaw/mirofish-runtime`: + +```bash +./scripts/host-start.sh +./scripts/host-stop.sh +./scripts/host-status.sh +tail -f runtime/logs/backend.log +tail -f runtime/logs/frontend.log +``` diff --git a/docs/hosting/mirofish-host-runbook.md b/docs/hosting/mirofish-host-runbook.md new file mode 100644 index 00000000..bf642073 --- /dev/null +++ b/docs/hosting/mirofish-host-runbook.md @@ -0,0 +1,145 @@ +# MiroFish Host Runbook + +## Canonical Runtime + +- Shared source of truth: `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish` +- Canonical operational runtime: `/Users/Shared/OpenClaw/mirofish-runtime` +- Canonical operational transcribes: `/Users/Shared/OpenClaw/transcribes` + +The runtime should live outside iCloud because always-on services need stable local files, virtual environments, logs, and uploads that are not subject to iCloud eviction or placeholder behavior. + +The iCloud source for breathwork transcribes remains: + +- `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/BreathWork/Transcribes` + +That iCloud tree should be treated as source only. The shared operational copy should live in `/Users/Shared/OpenClaw/transcribes`. + +## Ports + +- Frontend: `3000` +- Backend: `5001` + +## Commands + +Run from the runtime root: + +```bash +./scripts/host-start.sh +./scripts/host-stop.sh +./scripts/host-status.sh +tail -f runtime/logs/backend.log +tail -f runtime/logs/frontend.log +./scripts/host-smoke-test.sh +``` + +Sync transcribes from iCloud source to the shared operational path: + +```bash +./scripts/sync-transcribes.sh +``` + +## SSH Tunnels + +```bash +ssh -L 3000:127.0.0.1:3000 openclaw-mirofish +ssh -L 5001:127.0.0.1:5001 openclaw-mirofish +``` + +## Suggested SSH Alias + +```sshconfig +Host openclaw-mirofish + HostName 10.0.0.161 + User airstride + ServerAliveInterval 30 + ServerAliveCountMax 3 + StrictHostKeyChecking accept-new +``` + +## Legacy LaunchAgent Template + +The current persistent service is the system LaunchDaemon below, running as `airstride`. +Use this LaunchAgent template only for a user-session fallback. + +The validated plist is: + +- `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish/ops/launchd/airshare.mirofish.plist` + +It should be installed by a privileged shell or by a shell already running as `airstride`. If you are running as `airstride`, use: + +```bash +cp /Users/adrianlat/Library/Mobile\ Documents/com~apple~CloudDocs/airShare/MiroFish/ops/launchd/airshare.mirofish.plist ~/Library/LaunchAgents/airshare.mirofish.plist +launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/airshare.mirofish.plist +launchctl kickstart -k gui/$(id -u)/airshare.mirofish +``` + +If you are running from another admin account, bootstrap into `airstride` requires root privileges or an interactive `airstride` session. + +Template: + +```xml + + + + + Label + airshare.mirofish + ProgramArguments + + /bin/zsh + /Users/Shared/OpenClaw/mirofish-runtime/scripts/host-start.sh + /Users/Shared/OpenClaw/mirofish-runtime + + WorkingDirectory + /Users/Shared/OpenClaw/mirofish-runtime + RunAtLoad + + KeepAlive + + AbandonProcessGroup + + StandardOutPath + /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.log + StandardErrorPath + /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.err.log + + +``` + +## Validated Constraints + +- MiroFish is not run from iCloud. +- Runtime path is local and shared. +- SSH is LAN-only and should not be published through router/NAT. +- Docker was not available during validation, so source deployment is the active path. + +## LaunchDaemon Migration + +For a true always-on host, prefer the system LaunchDaemon: + +- Source plist: `/Users/Shared/OpenClaw/mirofish-runtime/ops/launchd/com.openclaw.mirofish.plist` +- Installed plist: `/Library/LaunchDaemons/com.openclaw.mirofish.plist` +- Label: `com.openclaw.mirofish` +- User: `airstride` + +Install with: + +```bash +sudo /Users/Shared/OpenClaw/mirofish-runtime/scripts/install-mirofish-launchdaemon.sh +``` + +Validate with: + +```bash +sudo launchctl print system/com.openclaw.mirofish +curl -sS http://127.0.0.1:5001/health +curl -sSI http://127.0.0.1:3000/ +curl -sS http://127.0.0.1:3000/api/graph/project/list +``` + +## Hermes Integration + +The canonical integration contract for Hermes and OpenClaw lives at: + +- `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish/docs/hosting/mirofish-hermes-integration-contract.md` +- `/Users/Shared/OpenClaw/mirofish-runtime/docs/hosting/mirofish-hermes-integration-contract.md` diff --git a/docs/hosting/mirofish-ops-contract.md b/docs/hosting/mirofish-ops-contract.md new file mode 100644 index 00000000..0901f868 --- /dev/null +++ b/docs/hosting/mirofish-ops-contract.md @@ -0,0 +1,47 @@ +# MiroFish Ops Contract + +## Canonical Paths + +- Runtime: `/Users/Shared/OpenClaw/mirofish-runtime` +- Transcribes: `/Users/Shared/OpenClaw/transcribes` +- Source only: `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish` + +## Ownership + +- Service owner: `airstride` +- LaunchDaemon label: `com.openclaw.mirofish` + +## Local Health Checks + +```bash +sudo launchctl print system/com.openclaw.mirofish +curl -sS http://127.0.0.1:5001/health +curl -sSI http://127.0.0.1:3000/ +curl -sS http://127.0.0.1:3000/api/graph/project/list +``` + +## Post-Reboot Smoke Test + +```bash +/Users/Shared/OpenClaw/mirofish-runtime/scripts/host-smoke-test.sh +``` + +## LAN + SSH Access + +- Frontend: `http://10.0.0.161:3000` +- Backend: `http://10.0.0.161:5001` +- SSH alias: `openclaw-mirofish` +- SSH user: `airstride` + +Recommended tunnels: + +```bash +ssh -L 3000:127.0.0.1:3000 openclaw-mirofish +ssh -L 5001:127.0.0.1:5001 openclaw-mirofish +``` + +## Operational Rule + +- Do not run MiroFish from iCloud. +- Do not change canonical runtime or transcribes paths without migration. +- Treat `backend/uploads` and shared transcribes as operational state. diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index e840e116..4a04d20b 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -3,7 +3,7 @@ import i18n from '../i18n' // 创建axios实例 const service = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5001', + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 300000, // 5分钟超时(本体生成可能需要较长时间) headers: { 'Content-Type': 'application/json' diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8f1e4c11..c3452587 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -12,8 +12,20 @@ export default defineConfig({ } }, server: { + host: '0.0.0.0', + port: 3000, + open: false, + proxy: { + '/api': { + target: 'http://localhost:5001', + changeOrigin: true, + secure: false + } + } + }, + preview: { + host: '0.0.0.0', port: 3000, - open: true, proxy: { '/api': { target: 'http://localhost:5001', diff --git a/ops/launchd/airshare.mirofish.plist b/ops/launchd/airshare.mirofish.plist new file mode 100644 index 00000000..63b50fe4 --- /dev/null +++ b/ops/launchd/airshare.mirofish.plist @@ -0,0 +1,26 @@ + + + + + Label + airshare.mirofish + ProgramArguments + + /bin/zsh + /Users/Shared/OpenClaw/mirofish-runtime/scripts/host-start.sh + /Users/Shared/OpenClaw/mirofish-runtime + + WorkingDirectory + /Users/Shared/OpenClaw/mirofish-runtime + RunAtLoad + + KeepAlive + + AbandonProcessGroup + + StandardOutPath + /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.log + StandardErrorPath + /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.err.log + + diff --git a/ops/launchd/com.openclaw.mirofish.plist b/ops/launchd/com.openclaw.mirofish.plist new file mode 100644 index 00000000..5f95b34c --- /dev/null +++ b/ops/launchd/com.openclaw.mirofish.plist @@ -0,0 +1,41 @@ + + + + + Label + com.openclaw.mirofish + ProgramArguments + + /bin/zsh + /Users/Shared/OpenClaw/mirofish-runtime/scripts/host-start.sh + /Users/Shared/OpenClaw/mirofish-runtime + + UserName + airstride + WorkingDirectory + /Users/Shared/OpenClaw/mirofish-runtime + EnvironmentVariables + + HOME + /Users/airstride + CODEX_HOME + /Users/airstride/.codex-openclaw + PATH + /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + + RunAtLoad + + KeepAlive + + ProcessType + Background + AbandonProcessGroup + + StandardOutPath + /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.log + StandardErrorPath + /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.err.log + ThrottleInterval + 15 + + diff --git a/scripts/host-smoke-test.sh b/scripts/host-smoke-test.sh new file mode 100755 index 00000000..9e71af2e --- /dev/null +++ b/scripts/host-smoke-test.sh @@ -0,0 +1,119 @@ +#!/bin/zsh + +set -euo pipefail + +ROOT_DIR="${1:-/Users/Shared/OpenClaw/mirofish-runtime}" +TRANSCRIBES_DIR="${2:-/Users/Shared/OpenClaw/transcribes}" +DAEMON_LABEL="${3:-com.openclaw.mirofish}" + +export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + +CURL_BIN="${CURL_BIN:-$(command -v curl)}" +LSOF_BIN="${LSOF_BIN:-$(command -v lsof)}" +NC_BIN="${NC_BIN:-$(command -v nc || true)}" +PLUTIL_BIN="${PLUTIL_BIN:-$(command -v plutil)}" + +pass() { + printf '[PASS] %s\n' "$1" +} + +fail() { + printf '[FAIL] %s\n' "$1" >&2 + exit 1 +} + +run_with_optional_sudo() { + "$@" 2>/tmp/mirofish-command.err || sudo -n "$@" 2>/tmp/mirofish-command.err +} + +check_port() { + local port="$1" + local output_file="/tmp/mirofish-port${port}.txt" + + if run_with_optional_sudo "$LSOF_BIN" -nP "-iTCP:$port" -sTCP:LISTEN >"$output_file"; then + cat "$output_file" + pass "Port $port is listening" + return 0 + fi + + if [[ -n "$NC_BIN" ]] && "$NC_BIN" -z 127.0.0.1 "$port" >/dev/null 2>&1; then + pass "Port $port accepts TCP connections" + return 0 + fi + + cat /tmp/mirofish-command.err >&2 || true + fail "Port $port is not listening" +} + +printf 'MiroFish smoke test\n' +printf 'runtime=%s\n' "$ROOT_DIR" +printf 'transcribes=%s\n' "$TRANSCRIBES_DIR" +printf 'daemon=%s\n\n' "$DAEMON_LABEL" + +if run_with_optional_sudo launchctl print "system/$DAEMON_LABEL" >/tmp/mirofish-daemon.txt; then + sed -n '1,40p' /tmp/mirofish-daemon.txt + if grep -Eq 'state = (running|spawn scheduled)' /tmp/mirofish-daemon.txt; then + pass "LaunchDaemon is installed and active in launchd" + else + fail "LaunchDaemon is not active in launchd" + fi +else + cat /tmp/mirofish-command.err >&2 || true + fail "LaunchDaemon is not installed or not readable" +fi + +if "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/tmp/mirofish-backend.json; then + cat /tmp/mirofish-backend.json + pass "Backend health endpoint responds" +else + fail "Backend health endpoint failed" +fi + +if "$CURL_BIN" -fsSI http://127.0.0.1:3000/ >/tmp/mirofish-frontend.headers; then + sed -n '1,8p' /tmp/mirofish-frontend.headers + pass "Frontend root responds" +else + fail "Frontend root failed" +fi + +if "$CURL_BIN" -fsS http://127.0.0.1:3000/api/graph/project/list >/tmp/mirofish-proxy.json; then + cat /tmp/mirofish-proxy.json + pass "Frontend proxy to backend responds" +else + fail "Frontend proxy failed" +fi + +check_port 3000 +check_port 5001 + +if [[ -r "$ROOT_DIR/runtime/logs/backend.log" && -r "$ROOT_DIR/runtime/logs/frontend.log" ]]; then + tail -n 5 "$ROOT_DIR/runtime/logs/backend.log" || true + printf '\n' + tail -n 5 "$ROOT_DIR/runtime/logs/frontend.log" || true + pass "Logs are present" +else + fail "Runtime logs are missing" +fi + +if [[ -d "$TRANSCRIBES_DIR" ]]; then + transcribe_count="$(find "$TRANSCRIBES_DIR" -type f 2>/dev/null | wc -l | tr -d ' ')" + transcribe_size="$(du -sh "$TRANSCRIBES_DIR" 2>/dev/null | awk '{print $1}')" + printf 'transcribes_files=%s\n' "$transcribe_count" + printf 'transcribes_size=%s\n' "$transcribe_size" + if [[ "$transcribe_count" -gt 0 ]]; then + pass "Transcribes path is populated" + else + fail "Transcribes path is empty" + fi +else + fail "Transcribes path does not exist" +fi + +if [[ -f "/Library/LaunchDaemons/$DAEMON_LABEL.plist" ]]; then + "$PLUTIL_BIN" -lint "/Library/LaunchDaemons/$DAEMON_LABEL.plist" + pass "Installed LaunchDaemon plist is valid" +else + fail "Installed LaunchDaemon plist is missing" +fi + +printf '\nMiroFish smoke test completed successfully.\n' diff --git a/scripts/host-start.sh b/scripts/host-start.sh new file mode 100755 index 00000000..1fb334e5 --- /dev/null +++ b/scripts/host-start.sh @@ -0,0 +1,86 @@ +#!/bin/zsh + +set -euo pipefail + +ROOT_DIR="${1:-$(cd "$(dirname "$0")/.." && pwd)}" +LOG_DIR="$ROOT_DIR/runtime/logs" +PID_DIR="$ROOT_DIR/runtime/pids" +export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +UV_BIN="${UV_BIN:-$(command -v uv)}" +NPM_BIN="${NPM_BIN:-$(command -v npm)}" +NPX_BIN="${NPX_BIN:-$(command -v npx)}" +CURL_BIN="${CURL_BIN:-$(command -v curl)}" +LSOF_BIN="${LSOF_BIN:-$(command -v lsof)}" + +mkdir -p "$LOG_DIR" "$PID_DIR" "$ROOT_DIR/backend/uploads/reports" "$ROOT_DIR/backend/uploads/simulations" + +monitor_existing_runtime() { + echo "MiroFish already running" + while true; do + if ! "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then + echo "Backend health check failed" + exit 1 + fi + if ! "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then + echo "Frontend health check failed" + exit 1 + fi + sleep 30 + done +} + +if "$LSOF_BIN" -nP -iTCP:3000 -sTCP:LISTEN >/dev/null 2>&1; then + if "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1 && \ + "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then + monitor_existing_runtime + fi + echo "Port 3000 already in use by an unhealthy process" + exit 1 +fi + +if "$LSOF_BIN" -nP -iTCP:5001 -sTCP:LISTEN >/dev/null 2>&1; then + if "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1 && \ + "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then + monitor_existing_runtime + fi + echo "Port 5001 already in use by an unhealthy process" + exit 1 +fi + +cd "$ROOT_DIR/backend" +env FLASK_DEBUG=False PYTHONUNBUFFERED=1 "$UV_BIN" run --no-sync python run.py < /dev/null > "$LOG_DIR/backend.log" 2>&1 & +BACKEND_PID=$! +echo "$BACKEND_PID" > "$PID_DIR/backend.pid" + +for _ in {1..30}; do + if "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then + break + fi + sleep 1 +done + +if ! "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then + echo "Backend failed to start" + exit 1 +fi + +cd "$ROOT_DIR/frontend" +"$NPM_BIN" run build >/dev/null +"$NPX_BIN" vite preview --host 0.0.0.0 --port 3000 --strictPort < /dev/null > "$LOG_DIR/frontend.log" 2>&1 & +FRONTEND_PID=$! +echo "$FRONTEND_PID" > "$PID_DIR/frontend.pid" + +for _ in {1..30}; do + if "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then + break + fi + sleep 1 +done + +if ! "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then + echo "Frontend failed to start" + exit 1 +fi + +echo "MiroFish started" +wait "$BACKEND_PID" "$FRONTEND_PID" diff --git a/scripts/host-status.sh b/scripts/host-status.sh new file mode 100755 index 00000000..1a329584 --- /dev/null +++ b/scripts/host-status.sh @@ -0,0 +1,23 @@ +#!/bin/zsh + +set -euo pipefail + +ROOT_DIR="${1:-$(cd "$(dirname "$0")/.." && pwd)}" +PID_DIR="$ROOT_DIR/runtime/pids" +export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +CURL_BIN="${CURL_BIN:-$(command -v curl)}" +LSOF_BIN="${LSOF_BIN:-$(command -v lsof)}" + +echo "Backend health:" +"$CURL_BIN" -fsS http://127.0.0.1:5001/health || true +echo +echo +echo "Frontend headers:" +"$CURL_BIN" -fsSI http://127.0.0.1:3000/ || true +echo +echo "Ports:" +"$LSOF_BIN" -nP -iTCP:3000 -sTCP:LISTEN || true +"$LSOF_BIN" -nP -iTCP:5001 -sTCP:LISTEN || true +echo +echo "PID files:" +ls -la "$PID_DIR" 2>/dev/null || true diff --git a/scripts/host-stop.sh b/scripts/host-stop.sh new file mode 100755 index 00000000..dfc71965 --- /dev/null +++ b/scripts/host-stop.sh @@ -0,0 +1,35 @@ +#!/bin/zsh + +set -euo pipefail + +ROOT_DIR="${1:-$(cd "$(dirname "$0")/.." && pwd)}" +PID_DIR="$ROOT_DIR/runtime/pids" +export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +PKILL_BIN="${PKILL_BIN:-$(command -v pkill)}" + +stop_pid_file() { + local pid_file="$1" + if [[ -f "$pid_file" ]]; then + local pid + pid="$(cat "$pid_file")" + if kill -0 "$pid" >/dev/null 2>&1; then + kill "$pid" >/dev/null 2>&1 || true + for _ in {1..20}; do + if ! kill -0 "$pid" >/dev/null 2>&1; then + break + fi + sleep 1 + done + kill -9 "$pid" >/dev/null 2>&1 || true + fi + rm -f "$pid_file" + fi +} + +stop_pid_file "$PID_DIR/frontend.pid" +stop_pid_file "$PID_DIR/backend.pid" + +"$PKILL_BIN" -f "vite preview --host 0.0.0.0 --port 3000" >/dev/null 2>&1 || true +"$PKILL_BIN" -f "uv run --no-sync python run.py" >/dev/null 2>&1 || true + +echo "MiroFish stopped" diff --git a/scripts/install-mirofish-launchdaemon.sh b/scripts/install-mirofish-launchdaemon.sh new file mode 100755 index 00000000..03b5507a --- /dev/null +++ b/scripts/install-mirofish-launchdaemon.sh @@ -0,0 +1,27 @@ +#!/bin/zsh + +set -euo pipefail + +PLIST_SOURCE="/Users/Shared/OpenClaw/mirofish-runtime/ops/launchd/com.openclaw.mirofish.plist" +PLIST_TARGET="/Library/LaunchDaemons/com.openclaw.mirofish.plist" + +if [[ ! -f "$PLIST_SOURCE" ]]; then + echo "Missing source plist: $PLIST_SOURCE" + exit 1 +fi + +if [[ "$(id -u)" -ne 0 ]]; then + echo "Run this script with sudo." + exit 1 +fi + +cp "$PLIST_SOURCE" "$PLIST_TARGET" +chown root:wheel "$PLIST_TARGET" +chmod 644 "$PLIST_TARGET" +plutil -lint "$PLIST_TARGET" + +launchctl bootout system/com.openclaw.mirofish 2>/dev/null || true +launchctl bootstrap system "$PLIST_TARGET" +launchctl kickstart -k system/com.openclaw.mirofish + +echo "Installed LaunchDaemon at $PLIST_TARGET" diff --git a/scripts/install-openclaw-launchagent.sh b/scripts/install-openclaw-launchagent.sh new file mode 100755 index 00000000..94f77ac7 --- /dev/null +++ b/scripts/install-openclaw-launchagent.sh @@ -0,0 +1,27 @@ +#!/bin/zsh + +set -euo pipefail + +if [[ "$(id -un)" != "airstride" ]]; then + echo "Run this script as airstride." + exit 1 +fi + +RUNTIME_ROOT="/Users/Shared/OpenClaw/mirofish-runtime" +PLIST_SOURCE="$RUNTIME_ROOT/ops/launchd/airshare.mirofish.plist" +PLIST_TARGET="$HOME/Library/LaunchAgents/airshare.mirofish.plist" + +mkdir -p "$HOME/Library/LaunchAgents" +cp "$PLIST_SOURCE" "$PLIST_TARGET" + +if launchctl print "gui/$(id -u)" >/dev/null 2>&1; then + DOMAIN="gui/$(id -u)" +else + DOMAIN="user/$(id -u)" +fi + +launchctl bootout "$DOMAIN/airshare.mirofish" 2>/dev/null || true +launchctl bootstrap "$DOMAIN" "$PLIST_TARGET" +launchctl kickstart -k "$DOMAIN/airshare.mirofish" + +echo "Installed LaunchAgent at $PLIST_TARGET in $DOMAIN" diff --git a/scripts/sync-transcribes.sh b/scripts/sync-transcribes.sh new file mode 100755 index 00000000..6b5e934c --- /dev/null +++ b/scripts/sync-transcribes.sh @@ -0,0 +1,14 @@ +#!/bin/zsh + +set -euo pipefail + +SOURCE_DIR="${1:-/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/BreathWork/Transcribes}" +TARGET_DIR="${2:-/Users/Shared/OpenClaw/transcribes}" + +mkdir -p "$TARGET_DIR" +rsync -a --delete --human-readable "$SOURCE_DIR/" "$TARGET_DIR/" +chgrp -R openclawshare "$TARGET_DIR" +chmod -R g+rwX "$TARGET_DIR" +find "$TARGET_DIR" -type d -exec chmod g+s {} + + +echo "Transcribes synced to $TARGET_DIR"