linux/amd64 image: deno-compile build stage (committed iroh prebuilts, no Rust toolchain), debian-slim glibc runtime, non-root user, /data volume, gate HEALTHCHECK. ci.yml gains a container smoke that requires mesh: up inside the container; publish.yml builds, smokes, and pushes ghcr.io/felinestatemachine/lofi-node on each tag. linux-arm64 is dropped from the release binary matrix: jazz-napi ships no linux-arm64-gnu platform package, so that binary could not boot at all (verified against the v0.2.0 asset). It returns when upstream publishes the package; until then the gap is documented instead of shipped.
- commit
- f2438b0083255f7c020cae7d519d59adfaf48567
- parent
- 985da89b6c42c5d10c4fc1c3ffe88648f4ea7457
- author
- Dami <dami.etoile@gmail.com>
| A | .dockerignore | 17 | +++++++++ |
| M | .github/ | 27 | +++++++++++++++ |
| M | .github/ | 51 | +++++++++++++++++++++++++++-- |
| M | CONTRIBUTING.md | 8 | +++-- |
| A | Dockerfile | 54 | ++++++++++++++++++++++++++++++ |
| M | README.md | 19 | +++++++++- |
| A | compose.yaml | 25 | ++++++++++++++ |
@@ -0,0 +1,17 @@
1+# Keep the build context to what the compile actually needs. The committed2+# prebuilt addons (native/iroh-js/prebuilt) MUST stay in the context; the3+# Rust sources and build output need not.4+.git5+.github6+dist7+docs8+lofi-node-data9+node_modules10+tests11+tools12+native/iroh-js/src13+native/iroh-js/target14+native/iroh-js/Cargo.toml15+native/iroh-js/Cargo.lock16+native/iroh-js/build.rs17+*.md@@ -33,3 +33,30 @@ jobs:
3333 3434 - name: Compile self-contained binary3535 run: deno task compile && dist/lofi-node --help >/dev/null 2>&1 || true36+37+ docker:38+ runs-on: ubuntu-latest39+ steps:40+ - uses: actions/checkout@v441+42+ - name: Build image43+ run: docker build -t lofi-node:ci .44+45+ - name: Container smoke — init, start, health, mesh up, ticket issue46+ run: |47+ docker volume create smoke48+ docker run --rm -v smoke:/data lofi-node:ci \49+ init --dir /data --port 4802 --public-url http://127.0.0.1:480250+ docker run -d --name smoke -p 4802:4802 -v smoke:/data lofi-node:ci51+ for i in $(seq 1 30); do52+ curl -fsS http://127.0.0.1:4802/health >/dev/null 2>&1 && break53+ sleep 254+ done55+ curl -fsS http://127.0.0.1:4802/health56+ docker logs smoke57+ # The native transport must come up inside the container; a LAN-only58+ # fallback here is a packaging regression, not an acceptable state.59+ docker logs smoke 2>&1 | grep -q "mesh: up"60+ docker exec smoke lofi-node ticket issue --dir /data --label ci \61+ | grep -q "lofisync1\."62+ docker rm -f smoke@@ -56,18 +56,19 @@ jobs:
5656 - name: Publish to JSR5757 run: deno publish5858 59- - name: Compile release binaries (3 targets)59+ # No linux-arm64 binary: jazz-napi ships no linux-arm64-gnu platform60+ # package, so that target cannot boot the Jazz server. Restore it when61+ # upstream publishes the package.62+ - name: Compile release binaries6063 run: |6164 deno task compile:darwin-arm646265 deno task compile:linux-x6463- deno task compile:linux-arm646466 6567 - name: Stage release assets6668 run: |6769 mkdir -p dist/assets6870 cp dist/lofi-node-aarch64-apple-darwin dist/assets/6971 cp dist/lofi-node-x86_64-unknown-linux-gnu dist/assets/70- cp dist/lofi-node-aarch64-unknown-linux-gnu dist/assets/7172 # Raw addon artifacts under the names artifacts.ts pins.7273 cp native/iroh-js/prebuilt/aarch64-apple-darwin/libnumber0_iroh.dylib \7374 dist/assets/libnumber0_iroh-aarch64-apple-darwin.dylib@@ -99,3 +100,47 @@ jobs:
99100 deno run --minimum-dependency-age=0 -A "jsr:@nzip/lofi-node@$version/cli" | head -3100101 deno eval --minimum-dependency-age=0 "import { looksLikeAppTicket } from 'jsr:@nzip/lofi-node@$version'; if (!looksLikeAppTicket('lofisync1.abcdef')) throw new Error('lib smoke failed'); console.log('library import ok')"101102 deno x --minimum-dependency-age=0 -A "jsr:@nzip/lofi-node@$version/cli" && echo "deno x subpath form works" || echo "::warning::deno x probe failed (expected while the fresh version is inside the minimum-dependency-age window; deno x does not accept the override yet)"103+104+ docker:105+ needs: publish106+ runs-on: ubuntu-latest107+ permissions:108+ contents: read109+ packages: write # GHCR push110+ steps:111+ - uses: actions/checkout@v4112+113+ - name: Build image (linux/amd64)114+ run: |115+ version="${GITHUB_REF_NAME#v}"116+ image="ghcr.io/felinestatemachine/lofi-node"117+ docker build -t "$image:$version" -t "$image:latest" .118+119+ - name: Image smoke — init, start, health, mesh up120+ run: |121+ version="${GITHUB_REF_NAME#v}"122+ image="ghcr.io/felinestatemachine/lofi-node"123+ docker volume create smoke124+ docker run --rm -v smoke:/data "$image:$version" \125+ init --dir /data --port 4802 --public-url http://127.0.0.1:4802126+ docker run -d --name smoke -p 4802:4802 -v smoke:/data "$image:$version"127+ for i in $(seq 1 30); do128+ curl -fsS http://127.0.0.1:4802/health >/dev/null 2>&1 && break129+ sleep 2130+ done131+ curl -fsS http://127.0.0.1:4802/health132+ docker logs smoke 2>&1 | grep -q "mesh: up"133+ docker rm -f smoke134+135+ - uses: docker/login-action@v3136+ with:137+ registry: ghcr.io138+ username: ${{ github.actor }}139+ password: ${{ secrets.GITHUB_TOKEN }}140+141+ - name: Push image142+ run: |143+ version="${GITHUB_REF_NAME#v}"144+ image="ghcr.io/felinestatemachine/lofi-node"145+ docker push "$image:$version"146+ docker push "$image:latest"@@ -46,7 +46,9 @@ Procedure (tags on `main`, no dev branch):
4646 2. `deno task check && deno task test`, then inspect the artifact: `deno task publish:dry`.4747 3. Commit, push, wait for ci green.4848 4. `git tag v<version> && git push origin v<version>` — publish.yml verifies the tag matches49- `deno.json`, publishes to JSR via OIDC, compiles the three target binaries, and creates the50- GitHub release with binaries + raw addon assets + `SHA256SUMS`, then smokes the fresh publish51- from the registry.49+ `deno.json`, publishes to JSR via OIDC, compiles the release binaries (darwin-arm64 and50+ linux-x64; linux-arm64 is withheld until jazz-napi publishes a linux-arm64-gnu package), creates51+ the GitHub release with binaries + raw addon assets + `SHA256SUMS`, smokes the fresh publish from52+ the registry, and builds, smokes, and pushes the container image to53+ `ghcr.io/felinestatemachine/lofi-node` (`<version>` and `latest` tags).5254 5. JSR versions are immutable — mistakes fix forward as the next patch version.@@ -0,0 +1,54 @@
1+# Container image for lofi-node. The build stage compiles the self-contained2+# binary on the target platform (the committed prebuilt iroh addons make this3+# possible without a Rust toolchain); the runtime stage is a minimal glibc4+# base. The addons are gnu builds, so the base must be glibc — do not switch5+# to musl/alpine.6+#7+# linux/amd64 only: jazz-napi publishes no linux-arm64-gnu binary, so an8+# arm64 image cannot boot the Jazz server. The guard below fails the build9+# rather than producing an image that dies at runtime. arm64 hosts can run10+# the amd64 image under emulation (Docker Desktop Rosetta / qemu).11+#12+# Build: docker build -t lofi-node .13+# Run: see compose.yaml and https://lofi.host/node/docs/beyond-the-lan14+15+FROM denoland/deno:latest AS build16+ARG TARGETARCH17+RUN if [ "$TARGETARCH" != "amd64" ]; then \18+ echo "lofi-node images are linux/amd64 only: jazz-napi ships no" \19+ "linux-arm64-gnu binary (upstream gap). Run the amd64 image" \20+ "under emulation instead." >&2; \21+ exit 1; \22+ fi23+WORKDIR /src24+COPY deno.json deno.lock cli.ts mod.ts ./25+COPY src ./src26+COPY testing ./testing27+COPY native/iroh-js/prebuilt ./native/iroh-js/prebuilt28+# Materialize node_modules (jazz-napi's platform package) before compiling;29+# deno compile embeds it into the binary's vfs.30+RUN deno install31+RUN deno task compile:linux-x64 \32+ && mv dist/lofi-node-x86_64-unknown-linux-gnu dist/lofi-node33+34+FROM debian:bookworm-slim35+LABEL org.opencontainers.image.source="https://github.com/FelineStateMachine/lofi-node" \36+ org.opencontainers.image.description="Self-hostable sync node for lofi apps" \37+ org.opencontainers.image.licenses="MIT"38+# ca-certificates: TLS to iroh relays and upstream URLs. curl: HEALTHCHECK.39+RUN apt-get update \40+ && apt-get install -y --no-install-recommends ca-certificates curl \41+ && rm -rf /var/lib/apt/lists/* \42+ && useradd --uid 1000 --create-home lofi \43+ && mkdir /data && chown lofi:lofi /data44+COPY --from=build /src/dist/lofi-node /usr/local/bin/lofi-node45+USER lofi46+VOLUME /data47+# The documented container port convention; init with --port 4802 (see docs).48+EXPOSE 480249+# Probes the gate; unauthenticated in both access modes. Override the port in50+# compose if the node was initialized with a different one.51+HEALTHCHECK --interval=30s --timeout=5s --start-period=30s \52+ CMD curl -fsS http://127.0.0.1:4802/health || exit 153+ENTRYPOINT ["lofi-node"]54+CMD ["start", "--dir", "/data"]@@ -40,8 +40,23 @@ deno install -g -A -n lofi-node jsr:@nzip/lofi-node/cli # persistent `lofi-no
4040 On first start the native iroh layer is downloaded from this repo's GitHub release for the matching4141 version and sha256-verified against digests pinned inside the package. No Deno at all? Grab a4242 compiled binary from the [releases](https://github.com/FelineStateMachine/lofi-node/releases) (macOS43-arm64, Linux x86_64/aarch64; the macOS binary is unsigned — clear quarantine with44-`xattr -d com.apple.quarantine lofi-node-*`).43+arm64 and Linux x86_64; the macOS binary is unsigned — clear quarantine with44+`xattr -d com.apple.quarantine lofi-node-*`). Linux aarch64 is pending an upstream jazz-napi45+`linux-arm64-gnu` build.46+47+Or run the container image:48+49+```sh50+docker run --rm -v lofi-node-data:/data ghcr.io/felinestatemachine/lofi-node \51+ init --dir /data --port 480252+docker run -d --name lofi-node --network host -v lofi-node-data:/data \53+ ghcr.io/felinestatemachine/lofi-node54+```55+56+The image is linux/amd64 (arm64 hosts run it under emulation, per the jazz-napi gap above);57+[compose.yaml](compose.yaml) is the reference deployment, and58+[the deployment guide](https://lofi.host/node/docs/beyond-the-lan) covers networking and persistence59+choices.4560 4661 As a library:4762 @@ -0,0 +1,25 @@
1+# Reference deployment for a lofi-node container.2+#3+# First run (writes /data/config.json into the volume):4+# docker compose run --rm lofi-node init --dir /data --port 4802 \5+# --public-url https://sync.example.net6+# Then:7+# docker compose up -d8+# Issue an app ticket:9+# docker compose exec lofi-node lofi-node ticket issue --dir /data --label phone10+#11+# host networking: iroh hole-punching works best without the bridge NAT in12+# the path. Bridge mode with a published port serves apps fine, but paired13+# nodes are more likely to fall back to relayed traffic. See14+# https://lofi.host/node/docs/beyond-the-lan15+16+services:17+ lofi-node:18+ image: ghcr.io/felinestatemachine/lofi-node:latest19+ network_mode: host20+ volumes:21+ - lofi-node-data:/data22+ restart: unless-stopped23+24+volumes:25+ lofi-node-data: