#!/bin/sh
# Docker sandbox entrypoint for MulmoClaude.
#
# Runs as root to perform setup steps that require elevated privileges,
# then drops to the host user's UID:GID via setpriv (part of util-linux,
# already in node:22-slim — no extra install).
#
# Why not just `--user ${UID}:${GID}`?
#   That flag runs the ENTIRE container (including this entrypoint)
#   as the non-root user, which prevents writing to /etc/passwd and
#   fixing socket permissions. The entrypoint-then-drop pattern is
#   the standard Docker solution for "I need root setup but non-root
#   runtime". See #259 for the full motivation.

set -e

# When HOST_UID is unset, the container was started with `--user`
# (non-SSH mode). Skip all root setup and exec directly — we're
# already running as the target user with zero capabilities.
if [ -z "${HOST_UID:-}" ]; then
  exec "$@"
fi

TARGET_UID="$HOST_UID"
TARGET_GID="${HOST_GID:-1000}"

# 1. Add a /etc/passwd entry for the target UID if it doesn't exist.
#    SSH refuses to operate when the running user has no passwd entry
#    ("No user exists for uid NNN"). On macOS the host UID is
#    typically 501, which isn't in the container's passwd (only root=0
#    and node=1000 are).
if ! getent passwd "$TARGET_UID" > /dev/null 2>&1; then
  echo "sandbox:x:${TARGET_UID}:${TARGET_GID}::/home/node:/bin/sh" >> /etc/passwd
fi

# 1b. Ensure /home/node is writable by the target user.
#     The base image (node:22-slim) creates this directory owned by
#     node:node (1000:1000). When running as a different UID (e.g. 501
#     on macOS), Claude CLI and git/ssh can't create config files
#     there (.ssh/, .gitconfig, etc.) without this chown.
chown -R "$TARGET_UID:$TARGET_GID" /home/node 2>/dev/null || true

# 2. Make the SSH agent socket accessible to the target user.
#    Docker Desktop for Mac's magic socket (/run/host-services/
#    ssh-auth.sock) is created as root:root mode 660. The target UID
#    (e.g. 501) has no group membership to read it. A broad chmod is
#    acceptable here because we're inside an isolated container with
#    --cap-drop ALL — there's no other user to protect against.
if [ -S "${SSH_AUTH_SOCK:-}" ]; then
  chmod 666 "$SSH_AUTH_SOCK" 2>/dev/null || true
fi

# 3. Restrict SSH agent usage to whitelisted hosts only.
#    Without this, the container could use the forwarded agent to
#    authenticate to ANY SSH server the host has access to (production
#    servers, private infrastructure, etc.). The SSH config sets
#    `IdentityAgent none` as the default and only enables the agent
#    for explicitly allowed hosts.
#
#    SANDBOX_SSH_ALLOWED_HOSTS: comma-separated list of hostnames.
#    Default: github.com (the most common use case for git+ssh).
#    Example: github.com,gitlab.com,bitbucket.org
if [ -S "${SSH_AUTH_SOCK:-}" ]; then
  ALLOWED_HOSTS="${SANDBOX_SSH_ALLOWED_HOSTS:-github.com}"
  SSH_DIR="/home/node/.ssh"
  SSH_CONFIG="${SSH_DIR}/config"

  mkdir -p "$SSH_DIR"
  # SSH uses first-match-wins, so whitelisted hosts MUST come BEFORE
  # the catch-all `Host *` block. Otherwise `IdentityAgent none`
  # matches first and the per-host overrides are never reached.
  cat > "$SSH_CONFIG" <<SSHEOF
# Auto-generated by sandbox-entrypoint.sh — do not edit.
# Restrict SSH agent forwarding to whitelisted hosts only.
# Override via SANDBOX_SSH_ALLOWED_HOSTS env var.

SSHEOF

  # Per-host overrides FIRST (most-specific → least-specific)
  echo "$ALLOWED_HOSTS" | tr ',' '\n' | while read -r host; do
    host=$(echo "$host" | tr -d ' ')
    if [ -n "$host" ]; then
      printf "Host %s\n  IdentityAgent %s\n  StrictHostKeyChecking accept-new\n\n" "$host" "$SSH_AUTH_SOCK" >> "$SSH_CONFIG"
    fi
  done

  # Catch-all LAST: block agent for everything else
  cat >> "$SSH_CONFIG" <<SSHEOF
Host *
  IdentityAgent none
  StrictHostKeyChecking accept-new
SSHEOF

  chmod 700 "$SSH_DIR"
  chmod 600 "$SSH_CONFIG"
  chown -R "${TARGET_UID}:${TARGET_GID}" "$SSH_DIR"
fi

# 4. Drop privileges and exec the actual command (typically `claude`).
#    setpriv is part of util-linux, already present in node:22-slim.
#    --init-groups initialises supplementary groups from /etc/group.
#    --inh-caps=-all clears inheritable capabilities so the child
#    process (claude) runs with zero capabilities even though the
#    container was started with CHOWN/FOWNER/DAC_OVERRIDE/SETUID/SETGID
#    for the entrypoint's setup steps.
exec setpriv --reuid="$TARGET_UID" --regid="$TARGET_GID" --init-groups --inh-caps=-all -- "$@"
