detached 63f8a7f

build(dev): add compose and air hot reload

commit
63f8a7f0b837606433f75ea6cca64250b222dd14
author
brice · · 2026-10-10 11:55 UTC
parent
470cf12
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +245 -5
A .air.toml +25 -0
@@ -0,0 +1,25 @@
1+# Hot reload for `docker compose up` (see compose.yaml). Run from the module root.
2+root = "."
3+tmp_dir = "tmp"
4+
5+[build]
6+ # templ files are generated when the module pins templ as a tool (web UI);
7+ # until then this is a no-op, so the same config works before and after.
8+ pre_cmd = ["if grep -q 'a-h/templ' go.mod; then go tool templ generate; fi"]
9+ cmd = "go build -o ./tmp/ermite ./cmd/ermite"
10+ entrypoint = ["./tmp/ermite", "serve"]
11+ include_ext = ["go", "templ"]
12+ exclude_dir = ["gen", "tmp", "scripts", "proto", ".git", ".worktrees"]
13+ # *_templ.go is the output of templ generate: a .templ change triggers the
14+ # rebuild, the generated Go file does not trigger a second one.
15+ exclude_regex = ["_test\\.go", "_templ\\.go"]
16+ # Let the server finish in-flight requests, then stop it.
17+ send_interrupt = true
18+ kill_delay = "2s"
19+ delay = 300
20+
21+[log]
22+ time = false
23+
24+[misc]
25+ clean_on_exit = true
@@ -0,0 +1,25 @@
1+# Hot reload for `docker compose up` (see compose.yaml). Run from the module root.
2+root = "."
3+tmp_dir = "tmp"
4+
5+[build]
6+ # templ files are generated when the module pins templ as a tool (web UI);
7+ # until then this is a no-op, so the same config works before and after.
8+ pre_cmd = ["if grep -q 'a-h/templ' go.mod; then go tool templ generate; fi"]
9+ cmd = "go build -o ./tmp/ermite ./cmd/ermite"
10+ entrypoint = ["./tmp/ermite", "serve"]
11+ include_ext = ["go", "templ"]
12+ exclude_dir = ["gen", "tmp", "scripts", "proto", ".git", ".worktrees"]
13+ # *_templ.go is the output of templ generate: a .templ change triggers the
14+ # rebuild, the generated Go file does not trigger a second one.
15+ exclude_regex = ["_test\\.go", "_templ\\.go"]
16+ # Let the server finish in-flight requests, then stop it.
17+ send_interrupt = true
18+ kill_delay = "2s"
19+ delay = 300
20+
21+[log]
22+ time = false
23+
24+[misc]
25+ clean_on_exit = true
A compose.yaml +37 -0
@@ -0,0 +1,37 @@
1+# Development server with hot reload: `docker compose up`, then http://localhost:7341.
2+# The module is built on its own (no go.work), like CI. Private modules bnema.dev/ermite-*
3+# are fetched from git.bnema.dev, which the container reaches through the host's DNS and routes.
4+services:
5+ ermite:
6+ build:
7+ dockerfile_inline: |
8+ FROM golang:1.27-alpine
9+ RUN apk add --no-cache git git-daemon
10+ working_dir: /src
11+ command: ["go", "tool", "air", "-c", ".air.toml"]
12+ ports:
13+ - "127.0.0.1:7341:7341"
14+ environment:
15+ CGO_ENABLED: "0"
16+ GOWORK: "off"
17+ GOTOOLCHAIN: local
18+ GOPRIVATE: bnema.dev/ermite*
19+ # The mounted .git of a worktree points outside the container.
20+ GOFLAGS: -buildvcs=false
21+ ERMITE_DATA_DIR: /data
22+ ERMITE_ADDR: ":7341"
23+ ERMITE_PUBLIC_URL: http://localhost:7341
24+ # Development placeholder, not a secret: the port is bound to the loopback.
25+ ERMITE_ADMIN_TOKEN: dev-only-admin-token-do-not-use-0123456789
26+ ERMITE_LOG_FORMAT: console
27+ ERMITE_LOG_LEVEL: debug
28+ volumes:
29+ - .:/src
30+ - go-mod:/go/pkg/mod
31+ - go-build:/root/.cache/go-build
32+ - data:/data
33+
34+volumes:
35+ go-mod:
36+ go-build:
37+ data:
@@ -0,0 +1,37 @@
1+# Development server with hot reload: `docker compose up`, then http://localhost:7341.
2+# The module is built on its own (no go.work), like CI. Private modules bnema.dev/ermite-*
3+# are fetched from git.bnema.dev, which the container reaches through the host's DNS and routes.
4+services:
5+ ermite:
6+ build:
7+ dockerfile_inline: |
8+ FROM golang:1.27-alpine
9+ RUN apk add --no-cache git git-daemon
10+ working_dir: /src
11+ command: ["go", "tool", "air", "-c", ".air.toml"]
12+ ports:
13+ - "127.0.0.1:7341:7341"
14+ environment:
15+ CGO_ENABLED: "0"
16+ GOWORK: "off"
17+ GOTOOLCHAIN: local
18+ GOPRIVATE: bnema.dev/ermite*
19+ # The mounted .git of a worktree points outside the container.
20+ GOFLAGS: -buildvcs=false
21+ ERMITE_DATA_DIR: /data
22+ ERMITE_ADDR: ":7341"
23+ ERMITE_PUBLIC_URL: http://localhost:7341
24+ # Development placeholder, not a secret: the port is bound to the loopback.
25+ ERMITE_ADMIN_TOKEN: dev-only-admin-token-do-not-use-0123456789
26+ ERMITE_LOG_FORMAT: console
27+ ERMITE_LOG_LEVEL: debug
28+ volumes:
29+ - .:/src
30+ - go-mod:/go/pkg/mod
31+ - go-build:/root/.cache/go-build
32+ - data:/data
33+
34+volumes:
35+ go-mod:
36+ go-build:
37+ data:
M go.mod +18 -1
@@ -3,6 +3,7 @@ module bnema.dev/ermite
33 go 1.27.1
44
55 tool (
6+ github.com/air-verse/air
67 github.com/bnema/hexcheck/cmd/hexcheck
78 github.com/bufbuild/buf/cmd/buf
89 github.com/vektra/mockery/v3
@@ -38,8 +39,14 @@ require (
3839 cel.dev/cel-go v0.32.0 // indirect
3940 cel.dev/expr v0.25.3 // indirect
4041 connectrpc.com/otelconnect v0.9.0 // indirect
42+ dario.cat/mergo v1.0.2 // indirect
4143 github.com/Microsoft/go-winio v0.6.2 // indirect
44+ github.com/air-verse/air v1.67.4 // indirect
45+ github.com/andybalholm/brotli v1.2.0 // indirect
4246 github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
47+ github.com/bep/godartsass/v2 v2.5.0 // indirect
48+ github.com/bep/golibsass v1.2.0 // indirect
49+ github.com/bits-and-blooms/bitset v1.24.5 // indirect
4350 github.com/bnema/hexcheck v0.3.1 // indirect
4451 github.com/brunoga/deep v1.3.1 // indirect
4552 github.com/bufbuild/buf v1.73.0 // indirect
@@ -47,7 +54,7 @@ require (
4754 github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect
4855 github.com/cespare/xxhash/v2 v2.3.0 // indirect
4956 github.com/cli/browser v1.3.0 // indirect
50- github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
57+ github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
5158 github.com/containerd/errdefs v1.0.0 // indirect
5259 github.com/containerd/errdefs/pkg v0.3.0 // indirect
5360 github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
@@ -56,19 +63,24 @@ require (
5663 github.com/docker/docker-credential-helpers v0.9.9 // indirect
5764 github.com/docker/go-connections v0.8.1 // indirect
5865 github.com/docker/go-units v0.5.0 // indirect
66+ github.com/fatih/color v1.18.0 // indirect
5967 github.com/fatih/structs v1.1.0 // indirect
6068 github.com/felixge/httpsnoop v1.1.0 // indirect
6169 github.com/fsnotify/fsnotify v1.9.0 // indirect
6270 github.com/go-logr/logr v1.4.4 // indirect
6371 github.com/go-logr/stdr v1.2.2 // indirect
6472 github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
73+ github.com/gobwas/glob v0.2.3 // indirect
6574 github.com/gofrs/flock v0.13.1 // indirect
75+ github.com/gohugoio/hashstructure v0.6.0 // indirect
76+ github.com/gohugoio/hugo v0.164.0 // indirect
6677 github.com/google/go-containerregistry v0.22.1 // indirect
6778 github.com/google/uuid v1.6.0 // indirect
6879 github.com/huandu/xstrings v1.5.0 // indirect
6980 github.com/inconshreveable/mousetrap v1.1.0 // indirect
7081 github.com/jdx/go-netrc v1.0.0 // indirect
7182 github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect
83+ github.com/joho/godotenv v1.5.1 // indirect
7284 github.com/klauspost/compress v1.20.0 // indirect
7385 github.com/klauspost/pgzip v1.2.6 // indirect
7486 github.com/knadh/koanf/maps v0.1.2 // indirect
@@ -88,6 +100,8 @@ require (
88100 github.com/moby/moby/client v0.6.0 // indirect
89101 github.com/opencontainers/go-digest v1.0.0 // indirect
90102 github.com/opencontainers/image-spec v1.1.1 // indirect
103+ github.com/pelletier/go-toml v1.9.5 // indirect
104+ github.com/pelletier/go-toml/v2 v2.4.3 // indirect
91105 github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect
92106 github.com/quic-go/qpack v0.6.0 // indirect
93107 github.com/quic-go/quic-go v0.62.0 // indirect
@@ -98,8 +112,11 @@ require (
98112 github.com/segmentio/asm v1.2.1 // indirect
99113 github.com/segmentio/encoding v0.5.4 // indirect
100114 github.com/sirupsen/logrus v1.10.2 // indirect
115+ github.com/spf13/afero v1.15.0 // indirect
116+ github.com/spf13/cast v1.10.0 // indirect
101117 github.com/spf13/pflag v1.0.10 // indirect
102118 github.com/stretchr/objx v0.5.3 // indirect
119+ github.com/tdewolff/parse/v2 v2.8.12 // indirect
103120 github.com/tetratelabs/wazero v1.12.0 // indirect
104121 github.com/tidwall/btree v1.8.1 // indirect
105122 github.com/vektra/mockery/v3 v3.8.0 // indirect
@@ -3,6 +3,7 @@ module bnema.dev/ermite
3 go 1.27.13 go 1.27.1
4 4
5 tool (5 tool (
6+ github.com/air-verse/air
6 github.com/bnema/hexcheck/cmd/hexcheck7 github.com/bnema/hexcheck/cmd/hexcheck
7 github.com/bufbuild/buf/cmd/buf8 github.com/bufbuild/buf/cmd/buf
8 github.com/vektra/mockery/v39 github.com/vektra/mockery/v3
@@ -38,8 +39,14 @@ require (
38 cel.dev/cel-go v0.32.0 // indirect39 cel.dev/cel-go v0.32.0 // indirect
39 cel.dev/expr v0.25.3 // indirect40 cel.dev/expr v0.25.3 // indirect
40 connectrpc.com/otelconnect v0.9.0 // indirect41 connectrpc.com/otelconnect v0.9.0 // indirect
42+ dario.cat/mergo v1.0.2 // indirect
41 github.com/Microsoft/go-winio v0.6.2 // indirect43 github.com/Microsoft/go-winio v0.6.2 // indirect
44+ github.com/air-verse/air v1.67.4 // indirect
45+ github.com/andybalholm/brotli v1.2.0 // indirect
42 github.com/antlr4-go/antlr/v4 v4.13.1 // indirect46 github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
47+ github.com/bep/godartsass/v2 v2.5.0 // indirect
48+ github.com/bep/golibsass v1.2.0 // indirect
49+ github.com/bits-and-blooms/bitset v1.24.5 // indirect
43 github.com/bnema/hexcheck v0.3.1 // indirect50 github.com/bnema/hexcheck v0.3.1 // indirect
44 github.com/brunoga/deep v1.3.1 // indirect51 github.com/brunoga/deep v1.3.1 // indirect
45 github.com/bufbuild/buf v1.73.0 // indirect52 github.com/bufbuild/buf v1.73.0 // indirect
@@ -47,7 +54,7 @@ require (
47 github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect54 github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect
48 github.com/cespare/xxhash/v2 v2.3.0 // indirect55 github.com/cespare/xxhash/v2 v2.3.0 // indirect
49 github.com/cli/browser v1.3.0 // indirect56 github.com/cli/browser v1.3.0 // indirect
50- github.com/clipperhouse/uax29/v2 v2.2.0 // indirect57+ github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
51 github.com/containerd/errdefs v1.0.0 // indirect58 github.com/containerd/errdefs v1.0.0 // indirect
52 github.com/containerd/errdefs/pkg v0.3.0 // indirect59 github.com/containerd/errdefs/pkg v0.3.0 // indirect
53 github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect60 github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
@@ -56,19 +63,24 @@ require (
56 github.com/docker/docker-credential-helpers v0.9.9 // indirect63 github.com/docker/docker-credential-helpers v0.9.9 // indirect
57 github.com/docker/go-connections v0.8.1 // indirect64 github.com/docker/go-connections v0.8.1 // indirect
58 github.com/docker/go-units v0.5.0 // indirect65 github.com/docker/go-units v0.5.0 // indirect
66+ github.com/fatih/color v1.18.0 // indirect
59 github.com/fatih/structs v1.1.0 // indirect67 github.com/fatih/structs v1.1.0 // indirect
60 github.com/felixge/httpsnoop v1.1.0 // indirect68 github.com/felixge/httpsnoop v1.1.0 // indirect
61 github.com/fsnotify/fsnotify v1.9.0 // indirect69 github.com/fsnotify/fsnotify v1.9.0 // indirect
62 github.com/go-logr/logr v1.4.4 // indirect70 github.com/go-logr/logr v1.4.4 // indirect
63 github.com/go-logr/stdr v1.2.2 // indirect71 github.com/go-logr/stdr v1.2.2 // indirect
64 github.com/go-viper/mapstructure/v2 v2.5.0 // indirect72 github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
73+ github.com/gobwas/glob v0.2.3 // indirect
65 github.com/gofrs/flock v0.13.1 // indirect74 github.com/gofrs/flock v0.13.1 // indirect
75+ github.com/gohugoio/hashstructure v0.6.0 // indirect
76+ github.com/gohugoio/hugo v0.164.0 // indirect
66 github.com/google/go-containerregistry v0.22.1 // indirect77 github.com/google/go-containerregistry v0.22.1 // indirect
67 github.com/google/uuid v1.6.0 // indirect78 github.com/google/uuid v1.6.0 // indirect
68 github.com/huandu/xstrings v1.5.0 // indirect79 github.com/huandu/xstrings v1.5.0 // indirect
69 github.com/inconshreveable/mousetrap v1.1.0 // indirect80 github.com/inconshreveable/mousetrap v1.1.0 // indirect
70 github.com/jdx/go-netrc v1.0.0 // indirect81 github.com/jdx/go-netrc v1.0.0 // indirect
71 github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect82 github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect
83+ github.com/joho/godotenv v1.5.1 // indirect
72 github.com/klauspost/compress v1.20.0 // indirect84 github.com/klauspost/compress v1.20.0 // indirect
73 github.com/klauspost/pgzip v1.2.6 // indirect85 github.com/klauspost/pgzip v1.2.6 // indirect
74 github.com/knadh/koanf/maps v0.1.2 // indirect86 github.com/knadh/koanf/maps v0.1.2 // indirect
@@ -88,6 +100,8 @@ require (
88 github.com/moby/moby/client v0.6.0 // indirect100 github.com/moby/moby/client v0.6.0 // indirect
89 github.com/opencontainers/go-digest v1.0.0 // indirect101 github.com/opencontainers/go-digest v1.0.0 // indirect
90 github.com/opencontainers/image-spec v1.1.1 // indirect102 github.com/opencontainers/image-spec v1.1.1 // indirect
103+ github.com/pelletier/go-toml v1.9.5 // indirect
104+ github.com/pelletier/go-toml/v2 v2.4.3 // indirect
91 github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect105 github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect
92 github.com/quic-go/qpack v0.6.0 // indirect106 github.com/quic-go/qpack v0.6.0 // indirect
93 github.com/quic-go/quic-go v0.62.0 // indirect107 github.com/quic-go/quic-go v0.62.0 // indirect
@@ -98,8 +112,11 @@ require (
98 github.com/segmentio/asm v1.2.1 // indirect112 github.com/segmentio/asm v1.2.1 // indirect
99 github.com/segmentio/encoding v0.5.4 // indirect113 github.com/segmentio/encoding v0.5.4 // indirect
100 github.com/sirupsen/logrus v1.10.2 // indirect114 github.com/sirupsen/logrus v1.10.2 // indirect
115+ github.com/spf13/afero v1.15.0 // indirect
116+ github.com/spf13/cast v1.10.0 // indirect
101 github.com/spf13/pflag v1.0.10 // indirect117 github.com/spf13/pflag v1.0.10 // indirect
102 github.com/stretchr/objx v0.5.3 // indirect118 github.com/stretchr/objx v0.5.3 // indirect
119+ github.com/tdewolff/parse/v2 v2.8.12 // indirect
103 github.com/tetratelabs/wazero v1.12.0 // indirect120 github.com/tetratelabs/wazero v1.12.0 // indirect
104 github.com/tidwall/btree v1.8.1 // indirect121 github.com/tidwall/btree v1.8.1 // indirect
105 github.com/vektra/mockery/v3 v3.8.0 // indirect122 github.com/vektra/mockery/v3 v3.8.0 // indirect
M go.sum +165 -4 generated file: collapsed

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 44

↑↓ moveenter openesc close