# hopper in containers (docs/deploy.md "In containers, with Podman", issues #119 and #125): its
# Postgres and the hopper's public image. The container is not a machine (issue #141): jobs run on
# attached machines. No host install, nothing to build, nothing to set before the first start:
#
#   curl -fsSLO https://henningfutrell.github.io/hopper/compose.yaml
#   podman compose up -d
#   then open http://localhost:4790/ and sign in with GitHub: the first person to do so is the admin
#
# `docker compose` works the same. Settings and secrets, all optional, go in a .env file beside this
# one: HOPPER_* settings, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY (an anthropic-api escalation level), … (GitHub needs none: the GitHub account
# you sign in with is the connection). HOPPER_PORT moves the UI port;
# HOPPER_IMAGE runs another image (one built from a checkout: `bash scripts/build-image.sh`,
# HOPPER_IMAGE=localhost/hopper). Upgrade, removing the image it replaces:
#   podman compose pull && podman compose up -d && podman image prune -f --filter label=org.opencontainers.image.title=hopper
name: hopper

services:
  # Postgres makes the database password on its first start, kept in the secrets volume, before the
  # database starts. Each file is readable only by the one service that needs it (postgres: uid 70;
  # the hopper: uid 1000). No one-shot service for it: podman-compose cannot start a container whose
  # dependency has exited.
  postgres:
    image: postgres:17-alpine
    restart: unless-stopped
    entrypoint: [sh, -euc]
    command:
      - |
        s=/run/hopper-secrets
        (
          umask 077
          [ -s $$s/postgres_password ] || head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n' > $$s/postgres_password
          printf 'postgres://hopper:%s@postgres:5432/hopper' "$$(cat $$s/postgres_password)" > $$s/database_url
        )
        chown 70:70 $$s/postgres_password
        chown 1000:1000 $$s/database_url
        exec docker-entrypoint.sh postgres
    environment:
      POSTGRES_USER: hopper
      POSTGRES_DB: hopper
      POSTGRES_PASSWORD_FILE: /run/hopper-secrets/postgres_password
    volumes:
      - postgres:/var/lib/postgresql/data
      - secrets:/run/hopper-secrets
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U hopper -d hopper"]
      interval: 5s
      timeout: 3s
      retries: 20

  hopper:
    image: ${HOPPER_IMAGE:-ghcr.io/henningfutrell/hopper:latest}
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    env_file:
      - path: .env
        required: false
    environment:
      HOPPER_DATABASE_URL_FILE: /run/hopper-secrets/database_url
      # The same port inside and out: the Host the browser sends must name the daemon's port.
      HOPPER_PORT: ${HOPPER_PORT:-4790}
      # The published port reaches the daemon from the compose network, not its loopback: those
      # requests are LAN requests and read the API only with a UI session (docs/design.md
      # "Reaching the UI across the LAN"). `hopper` is the name other containers reach it by.
      HOPPER_LAN_NAMES: ${HOPPER_LAN_NAMES:-hopper}
      HOPPER_LAN_PEERS: ${HOPPER_LAN_PEERS:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
    volumes:
      - secrets:/run/hopper-secrets:ro
      # Its home: the claude sign-in.
      - home:/home/node
    ports:
      - "127.0.0.1:${HOPPER_PORT:-4790}:${HOPPER_PORT:-4790}"

volumes:
  secrets:
  postgres:
  home:
