Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
da1e05b
feat: add edgenode-harvester container variant
evangineer Mar 19, 2026
de03936
test: add Tier 1 integration tests for edgenode-harvester
evangineer Mar 19, 2026
a41db8b
test: add e2e pipeline test for log-sender → log-collector → harvester
evangineer Mar 19, 2026
b5ad7e1
docs: document harvester test suite in TESTING.md and quickstart
evangineer Mar 19, 2026
aebcf55
fix: move config to /data, fix s6 shebangs, add GHA build caching
evangineer Mar 19, 2026
82c6b6d
ci: clone unytco/log-collector in CI before building
evangineer Mar 19, 2026
5957d6f
ci: authenticate log-collector checkout with HARVESTER_REPO_TOKEN
evangineer Mar 19, 2026
791dd38
ci: add Dockerfile.log-collector to repo, reference from compose and CI
evangineer Mar 19, 2026
a7b5e8f
ci: fix harvester build secret — use secrets: not secret-envs:
evangineer Mar 19, 2026
87e6adb
ci: use run: step for harvester build to fix secret passing
evangineer Mar 19, 2026
1027b59
fix: pre-clone log-harvester source instead of using Docker build sec…
evangineer Mar 19, 2026
b12d5a6
fix: start only edgenode+log-collector in run_tests_multi.sh
evangineer Mar 19, 2026
e760781
fix: apply D1 schema on log-collector startup
evangineer Mar 19, 2026
edf3e97
fix: pass ADMIN_SECRET to wrangler dev via --var
evangineer Mar 19, 2026
00fc842
fix: exclude harvester tests from run_tests_multi.sh; add setup guards
evangineer Mar 19, 2026
864a3c3
fix: prevent wrangler crash cascade in edgenode test suite
evangineer Mar 19, 2026
94cc3d7
docs: update CHANGELOG, TESTING, and quickstart for harvester additions
evangineer Mar 19, 2026
6317964
docs: fix stale Dockerfile.unyt and latest-unyt references
evangineer Mar 19, 2026
ae0c351
chore: resolve merge conflict with main in README.md
evangineer Apr 17, 2026
0ce7d56
fix: resolve review issues in harvester CI and compose config
evangineer Apr 17, 2026
1aaf95c
fix: use GITHUB_TOKEN for log-harvester clone with clear error on fai…
evangineer Apr 17, 2026
e1b42ad
fix: restore log-collector checkout in test-docker-image job
evangineer Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 75 additions & 5 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,83 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Checkout log-collector
uses: actions/checkout@v4
with:
repository: unytco/log-collector
path: docker/log-collector
token: ${{ secrets.HARVESTER_REPO_TOKEN }}

- name: Build log-collector (cached)
uses: docker/build-push-action@v5
with:
context: docker/log-collector
file: docker/Dockerfile.log-collector
load: true
tags: edgenode-log-collector:latest
cache-from: type=gha,scope=log-collector
cache-to: type=gha,mode=max,scope=log-collector

- name: Build image for testing
run: |
docker buildx build docker/ --file docker/Dockerfile \
--platform linux/amd64 \
--load \
--tag local-edgenode
uses: docker/build-push-action@v5
with:
context: docker/
file: docker/Dockerfile
load: true
tags: local-edgenode
cache-from: type=gha,scope=edgenode
cache-to: type=gha,mode=max,scope=edgenode

- name: Run tests
run: |
CI_RELEASE_TEST=true ./docker/run_tests_multi.sh local-edgenode

test-harvester-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Checkout log-collector
uses: actions/checkout@v4
with:
repository: unytco/log-collector
path: docker/log-collector
token: ${{ secrets.HARVESTER_REPO_TOKEN }}

- name: Build log-collector (cached)
uses: docker/build-push-action@v5
with:
context: docker/log-collector
file: docker/Dockerfile.log-collector
load: true
tags: edgenode-log-collector:latest
cache-from: type=gha,scope=log-collector
cache-to: type=gha,mode=max,scope=log-collector

- name: Checkout log-harvester
uses: actions/checkout@v4
with:
repository: unytco/log-harvester
path: docker/log-harvester-src
token: ${{ secrets.HARVESTER_REPO_TOKEN }}

- name: Build harvester image for testing
uses: docker/build-push-action@v5
with:
context: docker/
file: docker/Dockerfile.harvester
load: true
tags: local-edgenode-harvester
cache-from: type=gha,scope=edgenode-harvester
cache-to: type=gha,mode=max,scope=edgenode-harvester

- name: Run harvester tests
run: |
CI_RELEASE_TEST=true ./docker/run_harvester_tests.sh local-edgenode-harvester
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,47 @@ jobs:
--push \
--tag "${VERSION_TAG}" \
--tag "${LATEST_TAG}"

build-and-push-harvester-image:
runs-on: ubuntu-latest
needs: create-release
if: github.ref_type == 'tag' || github.event_name == 'workflow_dispatch'
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.tag }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Checkout log-harvester
uses: actions/checkout@v4
with:
repository: unytco/log-harvester
path: docker/log-harvester-src
token: ${{ secrets.HARVESTER_REPO_TOKEN }}

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push harvester image
run: |
VERSION_TAG="ghcr.io/holo-host/edgenode-harvester:${{ needs.create-release.outputs.tag }}"
LATEST_TAG="ghcr.io/holo-host/edgenode-harvester:latest"

docker buildx build docker/ --file docker/Dockerfile.harvester \
--platform linux/amd64,linux/arm64 \
--push \
--tag "${VERSION_TAG}" \
--tag "${LATEST_TAG}"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ holo-data-test/
.gemini/
docker/happ_config_file
docker/log-collector/
docker/log-harvester-src/
AGENTS.md
log_fix.md
UNYT_TEST_ANALYSIS.md
Expand Down
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This repo contains the tooling needed to deploy and operate always-on nodes for

The tooling consists of:

Edge Node - A Docker container specification for running Holochain with hApps in an OCI-compliant containerized environment.
Edge Node - Docker container specifications for running Holochain with hApps in an OCI-compliant containerized environment. Two variants: `edgenode` (standard, with log-sender) and `edgenode-harvester` (with log-harvester for Unyt invoice aggregation).

For a detailed overview and usage instructions [see here](/USAGE.md).

Expand All @@ -22,8 +22,9 @@ For a detailed overview and usage instructions [see here](/USAGE.md).

A [Docker-based container](docker/README.md) that delivers Edge Node, ready to run hApps:

- Holochain conductor configured to automatically run via `tini`.
- Holochain conductor managed by s6-overlay, starting automatically on container launch.
- Tools for installing and managing hApps from configuration files.
- Two variants: standard (`edgenode`) and harvester (`edgenode-harvester`).

### Tools

Expand All @@ -32,9 +33,9 @@ A [Docker-based container](docker/README.md) that delivers Edge Node, ready to r

## Quick Start

### To test the Edge Node container:
### Standard Edge Node

1. Pull the Docker image:
1. Pull the image:

```sh
docker pull ghcr.io/holo-host/edgenode
Expand All @@ -53,8 +54,34 @@ docker exec -it edgenode su - nonroot
ps -ef
```

### Harvester Edge Node

1. Pull the image:

```sh
docker pull ghcr.io/holo-host/edgenode-harvester
```

2. Launch with your log-collector credentials:

```sh
docker run --name harvester -dit \
-v $(pwd)/holo-data:/data \
-p 4444:4444 \
-p 4445:4445 \
-e COLLECTOR_URL=https://your-log-collector.unyt.dev \
-e ADMIN_SECRET=your-admin-secret \
-e LAIR_PASSWORD=your-lair-password \
ghcr.io/holo-host/edgenode-harvester
```

See [docker/LOG_HARVESTER_QUICKSTART.md](docker/LOG_HARVESTER_QUICKSTART.md) for full setup instructions.


## Documentation

- [Detailed overview and usage instructions](/USAGE.md)
- [Edge Node Container Instructions](docker/README.md)
- [Log-Sender Quickstart (Unyt resource accounting)](docker/LOG_SENDER_QUICKSTART.md)
- [Log-Harvester Quickstart (Unyt invoice aggregation)](docker/LOG_HARVESTER_QUICKSTART.md)
- [Tools for working with Edge Nodes](tools/README.md)
16 changes: 16 additions & 0 deletions docker/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `edgenode-harvester` container variant (`Dockerfile.harvester`) — bundles `log-harvester` (unytco/log-harvester) instead of `log-sender` for Unyt invoice aggregation
- `s6-overlay-harvester/` service tree: self-contained s6 services for the harvester variant (`conductor`, `log-harvester`, `logrotate-cron`, `setup`)
- `log-harvester` s6 longrun service: waits for conductor, installs `unyt.happ`, attaches app websocket on port 4445, initializes/refreshes harvester config, runs harvester loop
- `unyt.happ` baked into the harvester image from latest `unytco/unyt-sandbox` release; pinnable via `UNYT_HAPP_VERSION` build arg
- `Dockerfile.log-collector` — Dockerfile for the `unytco/log-collector` Cloudflare Worker service; applies D1 schema on startup and passes `ADMIN_SECRET` via wrangler `--var`
- `run_harvester_tests.sh` — dedicated test runner for the harvester variant; auto-clones `log-harvester-src` if not present, starts all three services, waits for readiness
- Harvester BATS test suite: `harvester_startup.bats`, `harvester_process.bats`, `harvester_integration.bats`, `harvester_e2e.bats`
- `LOG_HARVESTER_QUICKSTART.md` quickstart guide for the harvester variant
- CI `build-and-push-harvester-image` job in release workflow publishing `ghcr.io/holo-host/edgenode-harvester`
- CI PR checks now build and test both `edgenode` and `edgenode-harvester` images with GHA layer caching

### Changed
- `run_tests_multi.sh` runs each `.bats` file individually with a log-collector health-check between files, preventing wrangler crash cascades from affecting subsequent test files
- `docker-compose.yml`: log-collector service gets `restart: unless-stopped` so Docker auto-recovers after wrangler dev crashes under concurrent D1 load

## [0.1.0-alpha1] - 2026-03-13

### Added
Expand Down
106 changes: 106 additions & 0 deletions docker/Dockerfile.harvester
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# syntax=docker/dockerfile:1
# Dockerfile for the edge node harvester variant.
# Replaces log-sender with log-harvester (unytco/log-harvester).
# Requires log-harvester source at log-harvester-src/ in the build context:
# CI: pre-cloned via actions/checkout (unytco/log-harvester)
# Local: run_harvester_tests.sh clones it automatically, or:
# git clone --depth 1 https://github.com/unytco/log-harvester.git log-harvester-src

FROM cgr.dev/chainguard/wolfi-base

VOLUME ["/data"]

RUN apk update && apk upgrade && apk add --no-cache bash curl wget git htop jq strace tcpdump coreutils logrotate shadow gosu uuidgen xz libstdc++ nodejs npm

ARG TARGETARCH
ARG S6_OVERLAY_VERSION=3.2.0.2

RUN case "${TARGETARCH}" in \
amd64) S6_ARCH="x86_64" ;; \
arm64) S6_ARCH="aarch64" ;; \
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
esac && \
wget https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz && \
wget https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz && \
tar -C / -Jxpf s6-overlay-noarch.tar.xz && \
tar -C / -Jxpf s6-overlay-${S6_ARCH}.tar.xz && \
rm -f s6-overlay-*.tar.xz

ARG HOLOCHAIN_VERSION=0.6.1-rc.3
ARG HC_VERSION=0.6.1-rc.3

RUN case "${TARGETARCH}" in \
amd64) HC_ARCH="x86_64-unknown-linux-gnu" ;; \
arm64) HC_ARCH="aarch64-unknown-linux-gnu" ;; \
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
esac && \
wget https://github.com/holochain/holochain/releases/download/holochain-${HOLOCHAIN_VERSION}/holochain-${HC_ARCH} && \
wget https://github.com/holochain/holochain/releases/download/holochain-${HC_VERSION}/hc-${HC_ARCH} && \
mv holochain-${HC_ARCH} /bin/holochain && \
mv hc-${HC_ARCH} /bin/hc && \
chmod +x /bin/holochain && \
chmod +x /bin/hc

# log-harvester source must be present at log-harvester-src/ in the build context.
# CI: pre-cloned via actions/checkout (unytco/log-harvester)
# Local: git clone --depth 1 https://github.com/unytco/log-harvester.git log-harvester-src
COPY log-harvester-src /app/src
RUN cd /app/src && \
npm ci && \
npm run build && \
cp -r dist /app/dist && \
npm prune --omit=dev && \
cp -r node_modules /app/node_modules && \
cd / && rm -rf /app/src

ARG UNYT_HAPP_VERSION=latest

RUN if [ "${UNYT_HAPP_VERSION}" = "latest" ]; then \
wget -O /app/unyt.happ \
https://github.com/unytco/unyt-sandbox/releases/latest/download/unyt.happ; \
else \
wget -O /app/unyt.happ \
https://github.com/unytco/unyt-sandbox/releases/download/${UNYT_HAPP_VERSION}/unyt.happ; \
fi

COPY happ_tool /usr/local/bin/happ_tool
RUN chmod +x /usr/local/bin/happ_tool
RUN ln -sf /usr/local/bin/happ_tool /usr/local/bin/install_happ && \
ln -sf /usr/local/bin/happ_tool /usr/local/bin/uninstall_happ && \
ln -sf /usr/local/bin/happ_tool /usr/local/bin/enable_happ && \
ln -sf /usr/local/bin/happ_tool /usr/local/bin/disable_happ && \
ln -sf /usr/local/bin/happ_tool /usr/local/bin/list_happs && \
chmod +x /usr/local/bin/install_happ /usr/local/bin/list_happs /usr/local/bin/uninstall_happ /usr/local/bin/enable_happ /usr/local/bin/disable_happ

ARG HAPP_CONFIG_FILE_VERSION=0.3.0

RUN case "${TARGETARCH}" in \
amd64) HCF_ARCH="linux-x86_64" ;; \
arm64) HCF_ARCH="linux-aarch64" ;; \
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
esac && \
wget "https://github.com/Holo-Host/edgenode/releases/download/tools%2Fhapp_config_file-v${HAPP_CONFIG_FILE_VERSION}/happ_config_file-${HCF_ARCH}" && \
mv "happ_config_file-${HCF_ARCH}" /usr/local/bin/happ_config_file && \
chmod +x /usr/local/bin/happ_config_file

RUN mkdir -p /usr/local/share/holochain
COPY conductor-config-0.6.1.template.yaml /usr/local/share/holochain/conductor-config.template.yaml
COPY logrotate.d/holochain.conf /etc/logrotate.d/holochain.conf
RUN chown -R nonroot:nonroot /usr/local/share/holochain

COPY s6-overlay-harvester/s6-rc.d/ /etc/s6-overlay/s6-rc.d/
RUN chmod +x \
/etc/s6-overlay/s6-rc.d/setup/init.sh \
/etc/s6-overlay/s6-rc.d/conductor/run \
/etc/s6-overlay/s6-rc.d/log-harvester/run \
/etc/s6-overlay/s6-rc.d/logrotate-cron/run

ENV HC_ADMIN_PORT=4444
ENV HC_APP_PORT=4445
ENV HAPP_PATH=/app/unyt.happ
ENV CONFIG_PATH=/data/log-harvester/config.json

SHELL ["/bin/sh", "-c"]
EXPOSE 4444 4445

ENTRYPOINT ["/init"]
27 changes: 27 additions & 0 deletions docker/Dockerfile.log-collector
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# syntax=docker/dockerfile:1
# Dockerfile for the log-collector service.
# Build context should be a checkout of unytco/log-collector.

FROM node:20-slim

# Install wrangler, curl for health check, and sqlite3 for debugging
RUN npm install -g wrangler@4.45.4 && \
apt-get update && \
apt-get install -y curl sqlite3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy worker source from build context (unytco/log-collector checkout)
WORKDIR /usr/src/app
COPY . .

# Install dependencies
RUN npm install

EXPOSE 8787

RUN printf '#!/bin/bash\nset -e\necho "[log-collector] Applying D1 schema..."\nnpx wrangler d1 execute log-collector-db --local --file=schema.sql 2>&1 || true\necho "[log-collector] Starting wrangler dev server..."\nexec wrangler dev --env development --ip 0.0.0.0 --port 8787 \\\n --var ADMIN_SECRET:"${ADMIN_SECRET:-test_admin_secret}"\n' \
> /usr/local/bin/docker-entrypoint.sh && \
chmod +x /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["docker-entrypoint.sh"]
Loading
Loading