flake.nix: add rs-surface binary + OCI image outputs; fix Dockerfile base

flake.nix:
- rsSurface: pkgs.rustPlatform.buildRustPackage for
  4-Infrastructure/infra/embedded_surface/rs-surface — uses cargoLock.lockFile
  (no cargoHash/cargoSha256 for Cargo.lock-based fetching).
- rsSurfaceImage: pkgs.dockerTools.buildLayeredImage — pure Nix OCI tarball,
  no Alpine, no musl. Closure: rs-surface binary + cacert + coreutils.
  Healthcheck, ExposedPorts, Env, Labels all set.
- packages.${system}.rs-surface and .rs-surface-image registered.

Build:
  nix build .#rs-surface          # binary only
  nix build .#rs-surface-image    # OCI tarball → docker load < result

Dockerfile:
- Switch fallback runtime from alpine:3.20 → debian:bookworm-slim (glibc,
  no musl compat issues). Builder stage debian:bookworm-slim with libssl-dev.
- Added comment directing users to `nix build .#rs-surface-image` as the
  canonical build path on NixOS.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Brandon Schneider 2026-05-19 14:14:17 +00:00
parent a9f3d3d487
commit ec4166edb2
2 changed files with 104 additions and 10 deletions

View file

@ -1,18 +1,29 @@
# Dockerfile — fallback / quick-iteration path for rs-surface.
#
# The CANONICAL build is the Nix flake target:
# nix build .#rs-surface-image # produces a deterministic OCI tarball
# docker load < result
#
# This Dockerfile exists for environments where `nix build` is unavailable
# (e.g. raw Docker CI, RackNerd VPS before Nix is set up). It uses
# debian:bookworm-slim for glibc compatibility with the standard Rust toolchain.
#
# ── Stage 1: build the Rust surface binary ─────────────────────────────────
FROM rust:1.82-alpine AS builder
FROM rust:1.82-bookworm AS builder
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static pkgconfig
RUN apt-get update -qq && apt-get install -y --no-install-recommends \
pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY rs-surface/Cargo.toml rs-surface/Cargo.lock ./
# Pre-fetch deps (layer cache)
RUN mkdir src && echo 'fn main(){}' > src/main.rs && cargo build --release 2>/dev/null || true
RUN mkdir src && echo 'fn main(){}' > src/main.rs \
&& cargo build --release 2>/dev/null; rm -f target/release/rs-surface
COPY rs-surface/src ./src
RUN touch src/main.rs && cargo build --release
# ── Stage 2: minimal runtime image ─────────────────────────────────────────
FROM alpine:3.20
# ── Stage 2: minimal glibc runtime ─────────────────────────────────────────
FROM debian:bookworm-slim
ENV RS_SURFACE_PROFILE=/etc/rs-surface/node.json
ENV RS_SURFACE_STATE=/var/lib/rs-surface
@ -20,14 +31,18 @@ ENV RS_SURFACE_MOUNT=/mnt/topological-storage
ENV RS_SURFACE_HOST=0.0.0.0
ENV RS_SURFACE_PORT=8080
RUN adduser -D -H -u 988 surface \
&& mkdir -p /opt/rs-surface /etc/rs-surface /var/lib/rs-surface /var/log/rs-surface /run/rs-surface /mnt/topological-storage \
&& chown -R surface:surface /var/lib/rs-surface /var/log/rs-surface /run/rs-surface /mnt/topological-storage
RUN apt-get update -qq && apt-get install -y --no-install-recommends \
ca-certificates wget && rm -rf /var/lib/apt/lists/* \
&& adduser --system --no-create-home --uid 988 surface \
&& mkdir -p /opt/rs-surface /etc/rs-surface /var/lib/rs-surface \
/var/log/rs-surface /run/rs-surface /mnt/topological-storage \
&& chown -R surface:surface /var/lib/rs-surface /var/log/rs-surface \
/run/rs-surface /mnt/topological-storage
COPY --from=builder /build/target/release/rs-surface /opt/rs-surface/rs-surface
COPY profiles/racknerd-surface.json /etc/rs-surface/node.json
# Keep the Python server alongside for fallback / debugging
# Python server kept alongside as a debugging/fallback reference
COPY server.py /opt/rs-surface/server.py
USER surface

View file

@ -188,6 +188,81 @@
mkdir -p -m 1777 $out/tmp
'';
# ── rs-surface binary ───────────────────────────────────────────────────
# Rust port of 4-Infrastructure/infra/embedded_surface/server.py.
# Build the binary hermetically via rustPlatform.buildRustPackage.
#
# cargoHash: run `nix build .#rs-surface 2>&1 | grep "got:"` to get the
# real hash after any Cargo.lock change, then replace lib.fakeHash below.
rsSurface = pkgs.rustPlatform.buildRustPackage {
pname = "rs-surface";
version = "0.1.0";
src = pkgs.lib.cleanSource ./4-Infrastructure/infra/embedded_surface/rs-surface;
cargoLock.lockFile = ./4-Infrastructure/infra/embedded_surface/rs-surface/Cargo.lock;
nativeBuildInputs = [ pkgs.pkg-config ];
buildInputs = [ pkgs.openssl ];
meta = {
description = "Embedded node surface daemon (Rust)";
license = pkgs.lib.licenses.mit;
mainProgram = "rs-surface";
};
};
# ── Minimal OCI image for the embedded node surface ────────────────────
# Built entirely from the Nix store — no Dockerfile, no Alpine, no musl.
# The closure contains only the binary + its glibc runtime deps.
#
# Build: nix build .#rs-surface-image
# Load: docker load < result
# Run: docker run --rm -p 8080:8080 -v /etc/rs-surface:/etc/rs-surface rs-surface:latest
rsSurfaceImage = pkgs.dockerTools.buildLayeredImage {
name = "rs-surface";
tag = "latest";
contents = [
rsSurface
pkgs.cacert # TLS roots (for any outbound HTTPS)
pkgs.coreutils # minimal shell utilities for healthcheck
];
# /etc/rs-surface/node.json is volume-mounted at runtime; create the
# directory so the mount point exists in the image.
extraCommands = ''
mkdir -p etc/rs-surface var/lib/rs-surface mnt/topological-storage
'';
config = {
Entrypoint = [ "${rsSurface}/bin/rs-surface" ];
Env = [
"RS_SURFACE_PROFILE=/etc/rs-surface/node.json"
"RS_SURFACE_STATE=/var/lib/rs-surface"
"RS_SURFACE_MOUNT=/mnt/topological-storage"
"RS_SURFACE_HOST=0.0.0.0"
"RS_SURFACE_PORT=8080"
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
];
ExposedPorts = { "8080/tcp" = {}; };
Healthcheck = {
Test = [ "CMD" "${pkgs.coreutils}/bin/sh" "-c"
"wget -qO- http://127.0.0.1:8080/health || exit 1" ];
Interval = 10000000000; # 10 s in nanoseconds
Timeout = 3000000000; # 3 s
StartPeriod = 3000000000; # 3 s
Retries = 3;
};
Labels = {
"org.opencontainers.image.description" = "rs-surface embedded node daemon";
"org.opencontainers.image.source" = "4-Infrastructure/infra/embedded_surface/rs-surface";
};
};
maxLayers = 10;
};
in {
# ── Layered OCI image for the devcontainer ──────────────────────────────
packages.${system}.devcontainer = pkgs.dockerTools.buildLayeredImage {
@ -226,6 +301,10 @@
maxLayers = 120;
};
# rs-surface binary and OCI image
packages.${system}.rs-surface = rsSurface;
packages.${system}.rs-surface-image = rsSurfaceImage;
# Convenience alias: `nix build .#devcontainer`
defaultPackage.${system} = self.packages.${system}.devcontainer;