diff --git a/.dockerignore b/.dockerignore index 6caa674..370aa1b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ .git .gitea -deploy +docker-compose*.yml data *.md .env diff --git a/.env.agent.example b/.env.agent.example new file mode 100644 index 0000000..131569d --- /dev/null +++ b/.env.agent.example @@ -0,0 +1,24 @@ +# HODA on a second Docker host (the agent). Copy to .env next to docker-compose.agent.yml. +# The main host then sets HODA_AGENT_URL=http://:. + +# Secret shared with the main host: the same value as its HODA_PEER_TOKEN. +HODA_PEER_TOKEN= + +# This host's LAN IP. The agent's port is published on it only, e.g. 10.20.30.11 +HODA_AGENT_BIND_IP= + +# Port the main host reaches the agent on. +HODA_AGENT_PORT=8081 + +# Name of this host on tiles and in search. +HODA_NODE_NAME=host-2 + +# Image tag: latest, a release such as 1.0.0, or a build such as sha-9d6a921. +HODA_TAG=latest + +# Bind-mount the icon cache to a host directory instead of a named volume +# (sudo chown -R 65532:65532 the directory first). +# HODA_DATA_DIR=/home/msemes/AppData/hoda-agent + +# debug logs every icon lookup. +HODA_LOG_LEVEL=info diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a2d27e6 --- /dev/null +++ b/.env.example @@ -0,0 +1,31 @@ +# HODA on the main host. Copy to .env next to docker-compose.yml. + +# Where HODA listens, and where the Pangolin resource for apps.mariosem.es points: +# http://10.20.30.10:10040. Use 127.0.0.1 to keep it off the LAN when Pangolin runs on this host. +HODA_BIND_IP=10.20.30.10 +HODA_PORT=10040 + +# Name of this host on tiles and in search. +HODA_NODE_NAME=main + +# Page header and browser tab title. +HODA_TITLE=apps.mariosem.es + +# --- Only with a second Docker host (it runs docker-compose.agent.yml) ------------------- +# Leave both empty while there is one host. +# The agent's address on the second host: http://:, +# e.g. http://10.20.30.11:8081 +HODA_AGENT_URL= +# Secret the two hosts share, the same value on both: openssl rand -hex 32 +HODA_PEER_TOKEN= +# ---------------------------------------------------------------------------------------- + +# Image tag: latest, a release such as 1.0.0, or a build such as sha-9d6a921. +HODA_TAG=latest + +# Bind-mount the icon cache to a host directory instead of a named volume +# (sudo chown -R 65532:65532 the directory first). +# HODA_DATA_DIR=/home/msemes/AppData/hoda + +# debug logs every icon lookup. +HODA_LOG_LEVEL=info diff --git a/.gitignore b/.gitignore index 5385bc7..ba43324 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .env .env.* !.env.example +!.env.agent.example # local builds /hoda diff --git a/deploy/.env.example b/deploy/.env.example deleted file mode 100644 index 44c728a..0000000 --- a/deploy/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# Copy to .env next to each compose.yml. Both hosts need the same token; -# generate one with: openssl rand -hex 32 -HODA_PEER_TOKEN= -# Pin a version instead of latest, e.g. 1.0.0 or sha-abc1234. -HODA_TAG=latest diff --git a/deploy/agent/compose.yml b/deploy/agent/compose.yml deleted file mode 100644 index 917a478..0000000 --- a/deploy/agent/compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -# HODA agent, on Docker host 2. Reports this host's labeled apps and their icons -# to the primary on host 1; it serves no dashboard of its own. -# -# Set HODA_PEER_TOKEN in a .env file next to this one (the same value as on host 1), -# and replace so the port is only reachable on the LAN. -services: - hoda-agent: - image: git.mariosem.es/msemes/hoda:${HODA_TAG:-latest} - restart: unless-stopped - environment: - MODE: agent - DOCKER_HOST: tcp://socket-proxy:2375 - NODE_NAME: host-2 - PEER_TOKEN: ${HODA_PEER_TOKEN:?set HODA_PEER_TOKEN in .env} - volumes: - - hoda-data:/data - ports: - - ":8081:8080" - depends_on: - - socket-proxy - - # Read-only Docker API: container lists and events, nothing else. - socket-proxy: - image: tecnativa/docker-socket-proxy - restart: unless-stopped - environment: - CONTAINERS: 1 - EVENTS: 1 - POST: 0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - -volumes: - hoda-data: diff --git a/deploy/primary/compose.yml b/deploy/primary/compose.yml deleted file mode 100644 index 559aad1..0000000 --- a/deploy/primary/compose.yml +++ /dev/null @@ -1,45 +0,0 @@ -# HODA primary, on Docker host 1. Serves apps.mariosem.es through Pangolin and -# merges in the apps the agent on host 2 reports. -# -# Set HODA_PEER_TOKEN in a .env file next to this one (the same value as on host 2), -# and replace . There is no published port: Pangolin reaches hoda:8080 -# over a Docker network it shares with this stack. Create the apps.mariosem.es -# resource in the Pangolin UI with SSO on, or keep the pangolin.* labels below if -# Pangolin's site connector reads this host's Docker labels. -services: - hoda: - image: git.mariosem.es/msemes/hoda:${HODA_TAG:-latest} - restart: unless-stopped - environment: - MODE: primary - DOCKER_HOST: tcp://socket-proxy:2375 - DASHBOARD_TITLE: apps.mariosem.es - NODE_NAME: host-1 - PEERS: http://:8081 - PEER_TOKEN: ${HODA_PEER_TOKEN:?set HODA_PEER_TOKEN in .env} - volumes: - - hoda-data:/data - depends_on: - - socket-proxy - labels: - pangolin.public-resources.hoda.name: HODA - pangolin.public-resources.hoda.mode: http - pangolin.public-resources.hoda.full-domain: apps.mariosem.es - pangolin.public-resources.hoda.auth.sso-enabled: "true" - pangolin.public-resources.hoda.targets[0].method: http - pangolin.public-resources.hoda.targets[0].hostname: hoda - pangolin.public-resources.hoda.targets[0].port: "8080" - - # Read-only Docker API: container lists and events, nothing else. - socket-proxy: - image: tecnativa/docker-socket-proxy - restart: unless-stopped - environment: - CONTAINERS: 1 - EVENTS: 1 - POST: 0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - -volumes: - hoda-data: diff --git a/docker-compose.agent.yml b/docker-compose.agent.yml new file mode 100644 index 0000000..eba2363 --- /dev/null +++ b/docker-compose.agent.yml @@ -0,0 +1,68 @@ +# HODA — a second Docker host, the agent: reports this host's labeled apps and their icons to +# the dashboard on the main host (docker-compose.yml). It serves no dashboard of its own, and a +# setup with one host doesn't need it. +# +# Usage: +# cp .env.agent.example .env # then set HODA_PEER_TOKEN and HODA_AGENT_BIND_IP +# docker compose -f docker-compose.agent.yml pull +# docker compose -f docker-compose.agent.yml up -d +# +# The main host's HODA_AGENT_URL must point here: http://:, +# with the same HODA_PEER_TOKEN. + +name: hoda-agent + +x-logging: &logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + hoda-agent: + image: git.mariosem.es/msemes/hoda:${HODA_TAG:-latest} + container_name: hoda-agent + restart: unless-stopped + # `:latest` is mutable; `always` makes `docker compose up -d` check the registry for a newer + # image. If you pin HODA_TAG to a fixed tag, `missing` is enough. + pull_policy: always + depends_on: + - socket-proxy + environment: + MODE: agent + DOCKER_HOST: tcp://socket-proxy:2375 + NODE_NAME: ${HODA_NODE_NAME:-host-2} + PEER_TOKEN: ${HODA_PEER_TOKEN:?HODA_PEER_TOKEN must be set in .env (the same value as on the main host).} + LOG_LEVEL: ${HODA_LOG_LEVEL:-info} + ports: + # Published on the LAN IP only, and every request needs the peer token as well. + - "${HODA_AGENT_BIND_IP:?HODA_AGENT_BIND_IP must be set in .env to this host's LAN IP (e.g. 10.20.30.11).}:${HODA_AGENT_PORT:-8081}:8080" + volumes: + # Icon cache. Set HODA_DATA_DIR to bind-mount a host directory instead of the named volume; + # it must be owned by the image's user first: sudo chown -R 65532:65532 /path/to/dir + - ${HODA_DATA_DIR:-hoda_data}:/data + # The binary only writes to /data, and needs no privileges. + read_only: true + cap_drop: [ALL] + security_opt: ["no-new-privileges:true"] + mem_limit: 128m + cpus: 0.5 + logging: *logging + + # Read-only Docker API for HODA: container lists and events, nothing else. No published port. + socket-proxy: + image: tecnativa/docker-socket-proxy:latest + container_name: hoda-agent-socket-proxy + restart: unless-stopped + environment: + CONTAINERS: 1 + EVENTS: 1 + POST: 0 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + mem_limit: 64m + logging: *logging + +volumes: + # Fallback when HODA_DATA_DIR is unset; a no-op if /data is bind-mounted to a host path. + hoda_data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0ac8ad5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,75 @@ +# HODA — the main host: serves the dashboard of every labeled app on this host's Docker. +# +# Usage: +# cp .env.example .env # the defaults fit the main host +# docker compose pull # grab the latest image from the Gitea registry +# docker compose up -d +# +# Point the Pangolin resource for apps.mariosem.es at http://HODA_BIND_IP:HODA_PORT +# (http://10.20.30.10:10040 with .env.example) and turn SSO on: HODA has no login of its own. +# +# One host needs nothing else. A second Docker host runs docker-compose.agent.yml, and this +# host then gets HODA_AGENT_URL and HODA_PEER_TOKEN in .env to pull that host's apps. +# +# The image is built and published by .gitea/workflows/ci.yml. + +name: hoda + +x-logging: &logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + hoda: + image: git.mariosem.es/msemes/hoda:${HODA_TAG:-latest} + container_name: hoda + restart: unless-stopped + # `:latest` is mutable; `always` makes `docker compose up -d` check the registry for a newer + # image. If you pin HODA_TAG to a fixed tag, `missing` is enough. + pull_policy: always + depends_on: + - socket-proxy + environment: + MODE: primary + DOCKER_HOST: tcp://socket-proxy:2375 + NODE_NAME: ${HODA_NODE_NAME:-host-1} + DASHBOARD_TITLE: ${HODA_TITLE:-apps.mariosem.es} + # Only with a second host: its agent's URL and the token both hosts share. HODA refuses to + # start with an agent URL but no token. + PEERS: ${HODA_AGENT_URL:-} + PEER_TOKEN: ${HODA_PEER_TOKEN:-} + LOG_LEVEL: ${HODA_LOG_LEVEL:-info} + ports: + # Published on one address only: loopback by default, or the LAN IP Pangolin reaches. + - "${HODA_BIND_IP:-127.0.0.1}:${HODA_PORT:-8090}:8080" + volumes: + # Icon cache. Set HODA_DATA_DIR to bind-mount a host directory instead of the named volume; + # it must be owned by the image's user first: sudo chown -R 65532:65532 /path/to/dir + - ${HODA_DATA_DIR:-hoda_data}:/data + # The binary only writes to /data, and needs no privileges. + read_only: true + cap_drop: [ALL] + security_opt: ["no-new-privileges:true"] + mem_limit: 128m + cpus: 0.5 + logging: *logging + + # Read-only Docker API for HODA: container lists and events, nothing else. No published port. + socket-proxy: + image: tecnativa/docker-socket-proxy:latest + container_name: hoda-socket-proxy + restart: unless-stopped + environment: + CONTAINERS: 1 + EVENTS: 1 + POST: 0 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + mem_limit: 64m + logging: *logging + +volumes: + # Fallback when HODA_DATA_DIR is unset; a no-op if /data is bind-mounted to a host path. + hoda_data: