app
Session Monitor
Public Made by Adomby adom
Drive your Claude Code sessions from your phone — every container's sessions in one board. Talk to any of them by voice or text, pick the model per session, and get a push when a session asks a question or is ready for you. Install this CLI to hook a container in and manage its keeper.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
#!/usr/bin/env bash
# adom-remote-control installer — drops the keeper into THIS container and keeps
# it running. Containers run code-server as PID 1 (no systemd, and crontab may be
# absent), so supervision is a nohup daemon loop that relaunches the keeper if it
# ever exits. Cron is added too when available (belt-and-suspenders).
#
# ./install.sh <service-url> e.g. ./install.sh https://monitor.adom.cloud
#
# The keeper then reads this container's Claude Code sessions from local files,
# reports them (owner-tagged) to the monitor service, and drains queued sends by
# injecting them headless via `claude --resume -p`. No claude.ai anywhere.
set -uo pipefail # NOT -e: a missing crontab must not abort the launch
HERE="$(cd "$(dirname "$0")" && pwd)"
DEST="$HOME/.session-keeper"
# Default to the shared Session Monitor service container. Override with arg1.
SERVICE_URL="${1:-${SM_SERVICE_URL:-https://session-monitor-21nypoktx513.adom.cloud}}"
# Token gates the (public) service — supply yours via arg2 or $SM_TOKEN. NOT
# baked into this package; ask whoever runs the monitor for the token.
TOKEN="${2:-${SM_TOKEN:-}}"
mkdir -p "$DEST"
# CLEAN REINSTALL: stop any existing keeper first (daemons FIRST so they don't
# relaunch the keeper we're about to kill, then keepers) — this clears OLD, pre-
# singleton daemons that would otherwise keep double-reporting stale data. Scoped
# to the keeper's own absolute paths — never a broad pkill.
for p in $(pgrep -f "$DEST/daemon.sh" 2>/dev/null); do kill "$p" 2>/dev/null || true; done
sleep 2
for p in $(pgrep -f "$DEST/keeper.py" 2>/dev/null); do kill "$p" 2>/dev/null || true; done
rm -f "$DEST/daemon.lock"
cp "$HERE/keeper.py" "$DEST/keeper.py"
# Snapshot the platform proxy URI (it carries the friendly container name, e.g.
# drew2-gallia-…) INTO the env file so a later cron/@reboot/daemon restart — which
# does NOT inherit the interactive VS Code shell env — still reports the real name
# instead of the hostname hash. Preserve an existing value if this run lacks the live env.
_OLD_PROXY="$(sed -n 's/^VSCODE_PROXY_URI=//p' "$DEST/env" 2>/dev/null | head -1)"
_PROXY="${VSCODE_PROXY_URI:-$_OLD_PROXY}"
_OLD_NAME="$(sed -n 's/^SM_CONTAINER_NAME=//p' "$DEST/env" 2>/dev/null | head -1)"
_NAME="${SM_CONTAINER_NAME:-$_OLD_NAME}"
{ printf 'SM_SERVICE_URL=%s\n' "$SERVICE_URL"
[ -n "$TOKEN" ] && printf 'SM_TOKEN=%s\n' "$TOKEN"
[ -n "$_PROXY" ] && printf 'VSCODE_PROXY_URI=%s\n' "$_PROXY"
[ -n "$_NAME" ] && printf 'SM_CONTAINER_NAME=%s\n' "$_NAME"; } > "$DEST/env"
case "$SERVICE_URL" in
https://*) [ -z "$TOKEN" ] && echo "⚠ no token given — the public service will 401. Re-run: /adom-remote-control $SERVICE_URL <token>";;
esac
# Supervisor: a loop that keeps the keeper alive. Relaunches it 3s after any exit.
# SINGLETON: an flock guard means only ONE daemon ever runs, no matter how many
# times install/the watchdog fire — this stops the duplicate double-reporting
# keepers. A second daemon grabs the lock, fails, logs, and exits immediately.
cat > "$DEST/daemon.sh" <<'DM'
#!/usr/bin/env bash
exec 9>"$HOME/.session-keeper/daemon.lock"
if ! flock -n 9; then
echo "[daemon $(date -u +%H:%M:%S)] another daemon holds the lock — exiting" >> "$HOME/.session-keeper/keeper.log"
exit 0
fi
set -a; source "$HOME/.session-keeper/env" 2>/dev/null || true; set +a
while true; do
# 9>&- : the keeper must NOT inherit the daemon's flock fd, or an orphaned keeper
# (daemon killed, keeper survives) would keep holding the lock and wedge the
# watchdog's relaunch ("another daemon holds the lock") until the orphan dies.
python3 "$HOME/.session-keeper/keeper.py" >> "$HOME/.session-keeper/keeper.log" 2>&1 9>&-
echo "[daemon $(date -u +%H:%M:%S)] keeper exited — restarting in 3s" >> "$HOME/.session-keeper/keeper.log"
sleep 3
done
DM
chmod +x "$DEST/daemon.sh"
# Watchdog: relaunch the daemon if it's ever gone. Safe to run repeatedly — the
# flock singleton makes a redundant launch a no-op. This is the defense against the
# daemon dying with nothing to restart it (the single-point-of-failure gap).
cat > "$DEST/watchdog.sh" <<'WD'
#!/usr/bin/env bash
D="$HOME/.session-keeper"
pgrep -f "$D/daemon.sh" >/dev/null 2>&1 || setsid nohup bash "$D/daemon.sh" >/dev/null 2>&1 &
WD
chmod +x "$DEST/watchdog.sh"
# Start the supervisor now (idempotent thanks to flock).
setsid nohup bash "$DEST/daemon.sh" >/dev/null 2>&1 &
# Cron self-heal: every-minute watchdog + @reboot (survive a container restart).
# Best-effort — containers have no systemd and cron may need starting. If cron
# can't run, the service-side keeper-down alert makes a dead keeper VISIBLE.
if command -v crontab >/dev/null 2>&1; then
( crontab -l 2>/dev/null | grep -v 'session-keeper/watchdog.sh'
echo "* * * * * $DEST/watchdog.sh"
echo "@reboot $DEST/watchdog.sh" ) | crontab - 2>/dev/null || true
sudo service cron start 2>/dev/null || service cron start 2>/dev/null || \
sudo cron 2>/dev/null || cron 2>/dev/null || true
fi
sleep 2
if pgrep -f "$DEST/keeper.py" >/dev/null 2>&1; then
echo "✓ keeper installed + supervised → $SERVICE_URL"
echo " log: $DEST/keeper.log"
else
echo "✗ keeper did not start — see $DEST/keeper.log"; exit 1
fi