release: deploy the single-node hosted root to Fly
commit
2fb135drelease: deploy the single-node hosted root to Fly
git-ents itself (roots.single-node-hosted), served behind stock
git’s smart-HTTP transport (nginx+fcgiwrap in front of
git-http-backend), with setup --hosted wiring the mandatory gate
into pre-receive/post-receive on first boot.
No Rust toolchain in the image: docker/bin/git-ents is a musl static
binary cross-compiled on the host (cargo zigbuild --target
x86_64-unknown-linux-musl) and materialized from the blob recorded at
refs/meta/releases/<source-commit-sha> — gitignored, never a normal
tracked file.
release: add Dockerfile, docker/entrypoint.sh, docker/nginx.conf release: repoint .config/fly.toml at the new Dockerfile and app release: ignore docker/bin/ (materialized release binary)
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
.dockerignore
@@ -1,2 +1,6 @@
-fly.toml
-.git/
+# The Dockerfile only ever COPYs docker/*; ignore everything else so the
+# build context stays a few KB instead of the whole workspace (including
+# target/, which alone can run into the tens of GB).
+*
+!docker/
+!docker/**
.gitignore
@@ -1,2 +1,5 @@
.DS_Store
target/
+# Materialized from refs/meta/releases/<sha> just before `docker build`;
+# never a normal tracked file.
+docker/bin/
.config/fly.toml
@@ -1,20 +1,25 @@
-# fly.toml app configuration file generated for git-ents-server on 2026-06-20T12:17:33-04:00
+# fly.toml app configuration file for git-ents-hosted.
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
+# `git-ents-server` (no `-c` needed, its own separate app) is the legacy
+# pre-redo deployment, left alone. This app is the single-node hosted root
+# (`roots.single-node-hosted`, phase 6), a dedicated app so its shared
+# anycast proxy never round-robins traffic onto legacy's stopped machine.
-app = 'git-ents-server'
+app = 'git-ents-hosted'
primary_region = 'iad'
[build]
- # crates/git-ents-server doesn't exist yet post-redo; path is stale until it's rebuilt
- dockerfile = "../crates/git-ents-server/Dockerfile"
+ # The single-node hosted root (`roots.single-node-hosted`, phase 6):
+ # `git-ents` itself, served behind stock git's smart-HTTP transport.
+ dockerfile = "../Dockerfile"
[env]
PORT = '8080'
[mounts]
- source = 'odb'
+ source = 'git_ents_hosted'
destination = '/data'
[http_service]
Dockerfile
@@ -1,0 +1,34 @@
+# The single-node hosted root (`roots.single-node-hosted`,
+# docs/development-plan.adoc phase 6): the `git-ents` binary itself, wired
+# so stock git's own smart-HTTP transport (`git http-backend`, via
+# nginx+fcgiwrap) invokes its `pre-receive`/`post-receive` hooks. No
+# Postgres/Tigris/gix-receive here — that is `git-ents-server`, phase 8.
+#
+# No Rust toolchain, no cargo build, in this image: `docker/bin/git-ents` is
+# a musl static binary cross-compiled on the host (`cargo zigbuild --target
+# x86_64-unknown-linux-musl`) and materialized here from the on-disk blob
+# recorded at `refs/meta/releases/<source-commit-sha>` — never a normal
+# tracked file on `refs/heads` (see `.gitignore`).
+FROM debian:bookworm-slim AS runtime
+WORKDIR /app
+# git: the bare repo + git-http-backend CGI itself.
+# nginx+fcgiwrap+spawn-fcgi: the smart-HTTP transport (Phase 0's bootstrap,
+# still the transport Phase 6 rides per docs/development-plan.adoc).
+# curl: installs the sprite CLI the post-receive hook shells out to.
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ git ca-certificates curl nginx fcgiwrap spawn-fcgi \
+ && rm -rf /var/lib/apt/lists/*
+# The sprite CLI runs post-receive's checks in a Sprite; it reads
+# SPRITES_TOKEN from the env. The installer drops the binary in
+# $HOME/.local/bin and never touches PATH, so point it at /usr/local/bin
+# (already on PATH) where the hosted root can spawn it.
+RUN curl -fsSL https://sprites.dev/install.sh \
+ | env SPRITE_INSTALL_PREFERRED_DIRS=/usr/local/bin \
+ SPRITE_INSTALL_DEFAULT_BIN_DIR=/usr/local/bin bash
+COPY docker/bin/git-ents /usr/local/bin/git-ents
+RUN chmod +x /usr/local/bin/git-ents
+COPY docker/nginx.conf /etc/git-ents/nginx.conf
+COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
docker/entrypoint.sh
@@ -1,0 +1,22 @@
+#!/bin/sh
+# Bootstraps the single-node hosted root (`roots.single-node-hosted`) on
+# first boot, then serves it over stock git's smart-HTTP transport.
+set -eu
+
+repo=/data/repo.git
+key=/data/hosted_signing_key
+
+if [ ! -d "$repo" ]; then
+ git init --quiet --bare "$repo"
+fi
+git -C "$repo" config http.receivepack true
+git -C "$repo" config http.uploadpack true
+
+# Idempotent: reuses $key if it already exists (persisted on the /data
+# volume across deploys), and always reinstalls hooks pointing at this
+# binary's own current path.
+git-ents setup --hosted --key "$key" "$repo"
+
+mkdir -p /run
+spawn-fcgi -s /run/fcgiwrap.sock -M 766 -- /usr/sbin/fcgiwrap
+exec nginx -c /etc/git-ents/nginx.conf -g "daemon off;"
docker/nginx.conf
@@ -1,0 +1,33 @@
+worker_processes 1;
+pid /run/nginx.pid;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ access_log /dev/stdout;
+ error_log /dev/stderr;
+
+ server {
+ listen 8080;
+
+ location / {
+ # git push can be large; never buffer it into a temp file.
+ client_max_body_size 0;
+ gzip off;
+
+ include /etc/nginx/fastcgi_params;
+ fastcgi_pass unix:/run/fcgiwrap.sock;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
+ fastcgi_param GIT_HTTP_EXPORT_ALL "";
+ fastcgi_param GIT_PROJECT_ROOT /data;
+ fastcgi_param PATH_INFO $uri;
+ # git-http-backend spawns receive-pack, which runs our hooks,
+ # which shell to the sprite CLI (`ents-effect`'s
+ # `SpriteExecutor`) — fcgiwrap otherwise hands CGI processes a
+ # minimal environment with no PATH at all.
+ fastcgi_param PATH "/usr/local/bin:/usr/bin:/bin";
+ }
+ }
+}