diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..e88c7ab --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:24.04 AS base + +# Includes a bunch of stuff hero will try to install later, so we don't have to +# do that on each new container launch +RUN apt update && apt install -y ssh wget unzip build-essential git redis-server sudo autoconf libtool iputils-ping net-tools rsync curl mc tmux libsqlite3-dev xz-utils git-lfs ufw libpq-dev + +RUN wget https://github.com/vlang/v/releases/latest/download/v_linux.zip && \ + unzip v_linux.zip && \ + cd v && \ + ./v symlink + +# Install bun.sh for docusaurus +RUN curl -fsSL https://bun.sh/install | bash + +# Make a new stage so we can invalidate the cache by passing --no-cache-filter +FROM base AS clone + +RUN git clone https://github.com/Incubaid/herolib /opt/herolib + +RUN git clone https://github.com/incubaid/docusaurus_template /root/code/github/incubaid/docusaurus_template + +# Warm the bun node modules cache +RUN cd /root/code/github/incubaid/docusaurus_template/template && /root/.bun/bin/bun install + +RUN mkdir -p /root/.vmodules/incubaid + +# Make a place for users to mount their ssh key file. We will copy to .ssh and +# change permissions in entrypoint script +RUN mkdir -p /root/ssh + +COPY entrypoint.sh /bin/entrypoint.sh + +RUN chmod +x /bin/entrypoint.sh + +# Make a new stage so we can invalidate the cache by passing --no-cache-filter +FROM clone AS fetch + +# Fetch to freshen the repos with minimal work +RUN cd /opt/herolib && git fetch +RUN cd /root/code/github/incubaid/docusaurus_template && git fetch + + +ENTRYPOINT ["/bin/entrypoint.sh"] diff --git a/docker/Readme.md b/docker/Readme.md new file mode 100644 index 0000000..1130eeb --- /dev/null +++ b/docker/Readme.md @@ -0,0 +1,268 @@ +# `hero-git` Docker Image + +This directory contains a reusable Docker image and entrypoint script used to build Hero / Horus binaries via the V-based **herolib** installers. + +It is designed for two main use cases: + +1. **Local development / testing** – run the herolib CLI (`hero`) and Horus installers in a clean, reproducible environment. +2. **CI / Release builds** – build Horus binaries inside this image (using `horus_full_install.vsh`) and export them as release artifacts. + +--- + +## Files + +### `Dockerfile` + +The `Dockerfile` builds an Ubuntu-based image with all dependencies needed for `herolib` and its installers. + +Key points: + +* Base: `ubuntu:24.04` +* Installs system tooling: + + * `ssh`, `wget`, `curl`, `unzip`, `build-essential`, `git`, `git-lfs` + * `redis-server`, `libsqlite3-dev`, `libpq-dev`, `autoconf`, `libtool`, `net-tools`, `iputils-ping`, `rsync`, `mc`, `tmux`, `ufw`, `xz-utils` +* Installs **V** compiler: + + * Downloads `v_linux.zip` from the official V GitHub releases + * Unzips it and runs `./v symlink` so `v` is on the `PATH` +* Installs **Bun** (used for the docusaurus docs template): + + * `curl -fsSL https://bun.sh/install | bash` +* Clones: + + * `https://github.com/Incubaid/herolib` into `/opt/herolib` + * `https://github.com/incubaid/docusaurus_template` into `/root/code/github/incubaid/docusaurus_template` +* Warms Bun dependencies for the docusaurus template (for faster re-use): + + * `cd /root/code/github/incubaid/docusaurus_template/template && bun install` +* Prepares directories: + + * `/root/.vmodules/incubaid` – used by V module resolution + * `/root/ssh` – optional mount point for SSH keys +* Copies `entrypoint.sh` into the image and marks it executable. + +The container’s entrypoint is set to the custom script `/bin/entrypoint.sh`. + +--- + +### `entrypoint.sh` + +The entrypoint script is responsible for: + +1. Starting **Redis** in the background. +2. Optionally configuring **SSH** (if keys are provided). +3. Selecting the correct `herolib` source (mounted or cloned). +4. Checking out the desired `herolib` branch. +5. Building the `hero` CLI with V and making it available as `/bin/hero`. +6. Finally, executing any command passed to `docker run`. + +#### Behavior in detail + +```bash +#!/bin/bash +set -euo pipefail + +# Start Redis in the background +redis-server --daemonize yes + +# Optional SSH setup: only if /root/ssh has keys +if [ -d /root/ssh ] && compgen -G "/root/ssh/*" > /dev/null; then + mkdir -p /root/.ssh + cp -r /root/ssh/* /root/.ssh/ + chmod 600 /root/.ssh/* + eval "$(ssh-agent)" + ssh-add /root/.ssh/* +fi + +# Support optionally bind-mounting a local herolib into the container. +# If /opt/herolib_mount exists, we use that; otherwise we use the cloned /opt/herolib +rm -f /root/.vmodules/freeflowuniverse/herolib +if [ -d "/opt/herolib_mount" ]; then + ln -s /opt/herolib_mount/lib /root/.vmodules/incubaid/herolib + cd /opt/herolib_mount +else + ln -s /opt/herolib/lib /root/.vmodules/incubaid/herolib + cd /opt/herolib + git fetch + git checkout "${HEROLIB_REF:-development}" + git pull +fi + +cd cli +echo "Building hero..." +v -enable-globals hero.v > build.log 2>&1 || (cat build.log && exit 1) +ln -s "$(realpath hero)" /bin/hero + +cd /root + +# If a command was provided to `docker run`, execute it as-is. +# Example: docker run hero-git:latest bash -lc 'cd /opt/herolib && ./examples/installers/horus/horus_full_install.vsh' +if [ "$#" -gt 0 ]; then + exec "$@" +else + # No command passed → give an interactive shell + exec bash +fi +``` + +#### Important environment variables + +* `HEROLIB_REF` + + * Git ref (branch, tag, or commit) used when checking out `/opt/herolib`. + * Default: `development`. + * Example for CI: `HEROLIB_REF=development_nile_installers`. + +#### Optional mounts + +* `/opt/herolib_mount` + + * If you bind-mount your local `herolib` repo here, the container will use it instead of the cloned `/opt/herolib`. + * Useful for local development when you want to test uncommitted changes. +* `/root/ssh` + + * Optional directory containing SSH keys (e.g. `id_ed25519`) if you need to access private repositories. + * If present and non-empty, keys are copied to `/root/.ssh` and added to an SSH agent. + +--- + +## What this image is used for + +### 1. Local development / experimentation + +You can drop into the container and manually run installers or commands against `herolib`: + +```bash +# Interactive shell with default branch +docker run -it --rm hero-git:latest + +# Use a specific herolib ref +docker run -it --rm -e HEROLIB_REF=development_nile_installers hero-git:latest + +# Mount a local herolib repo and use that instead of the cloned one +docker run -it --rm \ + -v "$PWD/herolib:/opt/herolib_mount" \ + hero-git:latest +``` + +Inside the container you can then run: + +```bash +cd /opt/herolib +./examples/installers/horus/horus_full_install.vsh +``` + +### 2. CI / Release builds for Horus + +In CI (e.g. the `release.yml` workflow), this image is used to build Horus binaries via the V-based installer script: + +* The workflow: + + 1. Ensures `hero-git:latest` is available on the self-hosted runner. + 2. Creates a local directory (e.g. `hero-bin/`) on the runner. + 3. Runs the container, mounting `hero-bin/` into `/root/hero/bin`. + 4. Inside the container, executes `./examples/installers/horus/horus_full_install.vsh` from `/opt/herolib`. + 5. The installer writes the compiled Horus binaries into `/root/hero/bin`, which appear on the host inside `hero-bin/`. + 6. The workflow then packages those binaries into `.tar.gz` archives and uploads them as release artifacts. + +Example CI step: + +```yaml +- name: Run horus_full_install installer in container + run: | + set -euxo pipefail + + docker run --rm \ + -v "$PWD/hero-bin:/root/hero/bin" \ + -e HEROLIB_REF=development_nile_installers \ + hero-git:latest \ + bash -lc ' + set -euxo pipefail + cd /opt/herolib + ./examples/installers/horus/horus_full_install.vsh + echo "===== AFTER INSTALL, ls -R /root/hero =====" + ls -R /root/hero || true + ' +``` + +After this step, `hero-bin/` on the host should contain binaries like: + +* `supervisor` +* `coordinator` +* `horus` +* `osiris` +* `herorunner` +* `runner_osiris` +* `runner_sal` + +These are then packaged and attached to the Gitea release. + +--- + +## How to build the image + +From the directory containing the `Dockerfile` and `entrypoint.sh`: + +```bash +# Build the image (force a clean rebuild) +docker build --no-cache -t hero-git:latest . + +# Verify entrypoint contents +docker run --rm --entrypoint cat hero-git:latest /bin/entrypoint.sh | tail -n 20 +``` + +You should see the `exec "$@"` block at the end of the script, which ensures that commands passed to `docker run` are executed as-is inside the container. + +--- + +## How to run the image (examples) + +### Basic interactive usage + +```bash +# Default branch (HEROLIB_REF=development) +docker run -it --rm hero-git:latest + +# Specific herolib ref +export HEROLIB_REF=development_nile_installers + +docker run -it --rm \ + -e HEROLIB_REF=$HEROLIB_REF \ + hero-git:latest +``` + +### Build Horus binaries into a host directory + +```bash +mkdir -p hero-bin + +docker run --rm \ + -v "$PWD/hero-bin:/root/hero/bin" \ + -e HEROLIB_REF=development_nile_installers \ + hero-git:latest \ + bash -lc ' + set -euxo pipefail + cd /opt/herolib + ./examples/installers/horus/horus_full_install.vsh + ls -al /root/hero/bin + ' + +ls -al hero-bin +``` + +You should now see the built Horus binaries on the host in `hero-bin/`. + +--- + +## Summary + +* The `hero-git:latest` image encapsulates all dependencies required to build Horus via `herolib`. +* `entrypoint.sh`: + + * Starts Redis + * Optionally configures SSH + * Selects and updates the `herolib` checkout + * Builds the `hero` CLI once + * Runs any command passed to the container (e.g. Horus installers) +* CI uses this image to run `horus_full_install.vsh` and collect Horus binaries from `/root/hero/bin` for releases. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..7b4863c --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -euo pipefail + +# Start Redis in the background +redis-server --daemonize yes + +# Optional SSH setup: only if /root/ssh has keys +if [ -d /root/ssh ] && compgen -G "/root/ssh/*" > /dev/null; then + mkdir -p /root/.ssh + cp -r /root/ssh/* /root/.ssh/ + chmod 600 /root/.ssh/* + eval "$(ssh-agent)" + ssh-add /root/.ssh/* +fi + +# Support optionally bind-mounting a local herolib into the container. +# If /opt/herolib_mount exists, we use that; otherwise we use the cloned /opt/herolib +rm -f /root/.vmodules/freeflowuniverse/herolib +if [ -d "/opt/herolib_mount" ]; then + ln -s /opt/herolib_mount/lib /root/.vmodules/incubaid/herolib + cd /opt/herolib_mount +else + ln -s /opt/herolib/lib /root/.vmodules/incubaid/herolib + cd /opt/herolib + git fetch + git checkout "${HEROLIB_REF:-development}" + git pull +fi + +# Build hero CLI once so it's available as /bin/hero +cd cli +echo "Building hero..." +v -enable-globals hero.v > build.log 2>&1 || (cat build.log && exit 1) +ln -s "$(realpath hero)" /bin/hero + +cd /root + +# If a command was provided to `docker run`, execute it as-is. +if [ "$#" -gt 0 ]; then + exec "$@" +else + # No command passed → give an interactive shell + exec bash +fi