From 2aafbc406a61a207c64fd7b3cc2ffec061c17b48 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:36:51 -0400 Subject: [PATCH 01/10] build(docker): add .dockerignore to slim the build context Exclude VCS metadata, frontend/node_modules, local build output (out, dist, binaries, data) and Scratch so docker build ships only source to the stages. Co-Authored-By: Claude Opus 4.8 --- .dockerignore | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f9e07e2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,39 @@ +# Keep the Docker build context small and reproducible. The image builds the +# frontend and Go binary from source inside the stages, so none of the local +# build output, dependencies, or VCS metadata should be shipped into the build. + +# VCS / CI +.git +.gitea +.github + +# Frontend: rebuilt by the image (npm ci + next build) +frontend/node_modules +frontend/.next +frontend/out + +# Staged UI embed output: the image regenerates and stages this itself +backend/internal/webui/dist/* +!backend/internal/webui/dist/.gitignore + +# Go / build artifacts +binaries +**/*.exe +**/*.test + +# Local runtime data +data +**/*.db +**/*.db-shm +**/*.db-wal +**/*.log + +# Local scratch space and docs (not needed to build the app) +Scratch +docs + +# Editor / OS noise +.idea +.vscode +**/.DS_Store +**/Thumbs.db -- 2.52.0 From 1a87030e4267c92df1f6502c9562568fb2739ea3 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:36:52 -0400 Subject: [PATCH 02/10] fix(server): answer HEAD on /health so container probes pass The Docker HEALTHCHECK uses 'wget --spider' (a HEAD request), but the health routes were GET-only and chi returned 405, which would mark the container unhealthy. Register HEAD alongside GET on /health and /api/health. Co-Authored-By: Claude Opus 4.8 --- backend/internal/server/server.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index f7e0af5..81f4f69 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -109,9 +109,14 @@ func (s *Server) setupMiddleware() { } func (s *Server) setupRoutes() { - // Health check (unauthenticated) + // Health check (unauthenticated). Register HEAD as well as GET: container + // health probes and reverse proxies commonly issue HEAD (e.g. the Docker + // HEALTHCHECK's `wget --spider`), and chi returns 405 for an unregistered + // method rather than falling back to the GET handler. s.router.Get("/health", s.handleHealth) + s.router.Head("/health", s.handleHealth) s.router.Get("/api/health", s.handleHealth) + s.router.Head("/api/health", s.handleHealth) // API v1 routes s.router.Route("/api/v1", func(r chi.Router) { @@ -120,6 +125,7 @@ func (s *Server) setupRoutes() { // interrogate the server before any user has signed in. r.Get("/version", s.handleVersion) r.Get("/health", s.handleHealth) + r.Head("/health", s.handleHealth) // Auth endpoints. Login / logout / csrf are public by design; /me // requires a valid session so clients can resolve the current user. -- 2.52.0 From 114ba24e0567cb4532e824cbf0b164bdb4e3fb29 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:49:14 -0400 Subject: [PATCH 03/10] refactor(logging): add a centralized process-wide logger Introduce logging.Init/Default plus package-level Info/Warn/Error/Debug so code without an injected *Logger (main, the service wrapper) emits the same [UTC] - [Component] - [Level] - Message format on the one shared writer and rotation policy. Existing injected usage is unchanged. Co-Authored-By: Claude Opus 4.8 --- backend/internal/logging/logging.go | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/backend/internal/logging/logging.go b/backend/internal/logging/logging.go index 3d9fb92..d22b553 100644 --- a/backend/internal/logging/logging.go +++ b/backend/internal/logging/logging.go @@ -11,6 +11,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "time" "github.com/Grace-Solutions/OrchestrAD/internal/config" @@ -24,6 +25,53 @@ type Logger struct { writer io.Writer } +// std is the process-wide centralized logger. Every entry point initializes it +// once via Init and shares the returned instance through dependency injection; +// code that cannot easily receive an injected logger (main, the service +// wrapper) uses the package-level Default/Info/Warn/Error/Debug helpers, which +// resolve to this same instance. This keeps all output on one writer, one +// rotation policy, and the single [UTC] - [Component] - [Level] - Message format. +var ( + stdMu sync.RWMutex + std *Logger +) + +// Init creates the centralized logger from cfg, stores it as the process-wide +// default, and returns it so callers can also inject it explicitly. The last +// Init wins; typically it is called exactly once per process at startup. +func Init(cfg config.LoggingConfig) *Logger { + l := New(cfg) + stdMu.Lock() + std = l + stdMu.Unlock() + return l +} + +// Default returns the centralized logger. If Init has not run yet (e.g. an +// early top-level error before config is loaded), it lazily creates a +// console-only logger so logging never panics and the format stays consistent. +func Default() *Logger { + stdMu.RLock() + l := std + stdMu.RUnlock() + if l != nil { + return l + } + stdMu.Lock() + defer stdMu.Unlock() + if std == nil { + std = New(config.LoggingConfig{Level: "info", EnableConsole: true}) + } + return std +} + +// Package-level helpers delegate to the centralized logger so callers without +// an injected Logger still emit the standard format. +func Debug(component, msg string, args ...any) { Default().Debug(component, msg, args...) } +func Info(component, msg string, args ...any) { Default().Info(component, msg, args...) } +func Warn(component, msg string, args ...any) { Default().Warn(component, msg, args...) } +func Error(component, msg string, args ...any) { Default().Error(component, msg, args...) } + // New creates a new Logger with the given configuration func New(cfg config.LoggingConfig) *Logger { level := parseLevel(cfg.Level) -- 2.52.0 From 25424322f18e18449b69657d4c107e8f174e78b0 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:49:14 -0400 Subject: [PATCH 04/10] refactor(db): switch to pure-Go modernc.org/sqlite, drop CGO Replace mattn/go-sqlite3 (CGO) with modernc.org/sqlite and the modernc-based golang-migrate driver, translating the DSN pragmas. This lets every target (win/mac/linux, amd64/arm64) cross-compile from one runner with no C toolchain, so the Docker build now sets CGO_ENABLED=0 and drops gcc/musl-dev. Verified: DB opens (WAL + foreign keys), migrations run, bootstrap seeds admin. Co-Authored-By: Claude Opus 4.8 --- Dockerfile | 5 ++-- backend/go.mod | 16 ++++++++---- backend/go.sum | 54 ++++++++++++++++++++++++++++++++++++--- backend/internal/db/db.go | 16 +++++++----- 4 files changed, 73 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9555873..53e48b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,8 @@ RUN npm run build # --------------------------------------------------------------------------- FROM golang:1.24-alpine AS builder -RUN apk add --no-cache git gcc musl-dev +# Pure-Go SQLite (modernc.org/sqlite) means no C toolchain is needed. +RUN apk add --no-cache git WORKDIR /build @@ -44,7 +45,7 @@ ARG VERSION=dev ARG BUILD_TIME=unknown ARG GIT_COMMIT=unknown -RUN CGO_ENABLED=1 go build \ +RUN CGO_ENABLED=0 go build \ -ldflags "-s -w \ -X github.com/Grace-Solutions/OrchestrAD/internal/version.Version=${VERSION} \ -X github.com/Grace-Solutions/OrchestrAD/internal/version.BuildTime=${BUILD_TIME} \ diff --git a/backend/go.mod b/backend/go.mod index c26ea24..e7ea6cd 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,8 +1,6 @@ module github.com/Grace-Solutions/OrchestrAD -go 1.24.0 - -toolchain go1.24.2 +go 1.25.0 require ( github.com/go-chi/chi/v5 v5.0.12 @@ -10,17 +8,25 @@ require ( github.com/go-ldap/ldap/v3 v3.4.13 github.com/golang-migrate/migrate/v4 v4.17.0 github.com/google/uuid v1.6.0 - github.com/mattn/go-sqlite3 v1.14.22 + github.com/kardianos/service v1.3.0 github.com/robfig/cron/v3 v3.0.1 golang.org/x/crypto v0.48.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 + modernc.org/sqlite v1.58.0 ) require ( github.com/Azure/go-ntlmssp v0.1.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.47.0 // indirect + modernc.org/libc v1.75.6 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.12.1 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 40a1f7a..59b5cc5 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -5,6 +5,8 @@ github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1L github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= @@ -15,6 +17,8 @@ github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baD github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -24,6 +28,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -36,12 +42,18 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/kardianos/service v1.3.0 h1:/LGy+xPP2TM+GLTiCZ2di7cy0Jd/qrawlTUfqKYFdTI= +github.com/kardianos/service v1.3.0/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -52,11 +64,45 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8= +modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w= +modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc= +modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.75.6 h1:yKk8qo+Di4gkmvRboK8ocCqH22FiUCR6jRy2OwtCRus= +modernc.org/libc v1.75.6/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g= +modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.58.0 h1:38u40/bwkfM7f0Myhosl+SEMltSDxnGdQf8o6Kjmys0= +modernc.org/sqlite v1.58.0/go.mod h1:rsD2CckafgObKC4DhBlGBf+RiHxkc3hINGt1Xw32tVY= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go index 10bce2e..fc52635 100644 --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -9,9 +9,9 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/config" "github.com/Grace-Solutions/OrchestrAD/internal/logging" "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/golang-migrate/migrate/v4/database/sqlite" "github.com/golang-migrate/migrate/v4/source/iofs" - _ "github.com/mattn/go-sqlite3" + _ "modernc.org/sqlite" ) //go:embed migrations/*.sql @@ -28,10 +28,12 @@ type DB struct { func New(cfg config.DatabaseConfig, logger *logging.Logger) (*DB, error) { logger.Info("Database", "Opening database at %s", cfg.Path) - // Build connection string with pragmas - dsn := fmt.Sprintf("file:%s?_journal_mode=WAL&_foreign_keys=on&_busy_timeout=5000", cfg.Path) + // Build connection string with pragmas. modernc.org/sqlite applies these + // _pragma directives on every connection the pool opens, so WAL mode, + // foreign-key enforcement, and the busy timeout hold for all connections. + dsn := fmt.Sprintf("file:%s?_pragma=journal_mode(WAL)&_pragma=foreign_keys(on)&_pragma=busy_timeout(5000)", cfg.Path) - conn, err := sql.Open("sqlite3", dsn) + conn, err := sql.Open("sqlite", dsn) if err != nil { return nil, fmt.Errorf("opening database: %w", err) } @@ -77,13 +79,13 @@ func (db *DB) Migrate() error { } // Create migration driver - driver, err := sqlite3.WithInstance(db.conn, &sqlite3.Config{}) + driver, err := sqlite.WithInstance(db.conn, &sqlite.Config{}) if err != nil { return fmt.Errorf("creating migration driver: %w", err) } // Create migrator - m, err := migrate.NewWithInstance("iofs", source, "sqlite3", driver) + m, err := migrate.NewWithInstance("iofs", source, "sqlite", driver) if err != nil { return fmt.Errorf("creating migrator: %w", err) } -- 2.52.0 From 0debf9ab33e8aaf7b4ac661356dc74a39233c6e5 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:49:15 -0400 Subject: [PATCH 05/10] feat(cli): real cross-platform service management Implement install/uninstall/start/stop via kardianos/service (Windows SCM, systemd/upstart/sysv, launchd). The run command now goes through service.Run so foreground/container and service execution share one startup path; install also starts the service and uninstall stops then removes it. Route CLI and main command output/errors through the centralized logger. Verified end to end as a real Windows service (install -> RUNNING -> uninstall). Co-Authored-By: Claude Opus 4.8 --- backend/cmd/orchestrad/main.go | 96 ++++++++++++---------------- backend/internal/cli/cli.go | 91 +++++++++++++++++---------- backend/internal/cli/service.go | 107 ++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 88 deletions(-) create mode 100644 backend/internal/cli/service.go diff --git a/backend/cmd/orchestrad/main.go b/backend/cmd/orchestrad/main.go index d10f87c..e3e2ea4 100644 --- a/backend/cmd/orchestrad/main.go +++ b/backend/cmd/orchestrad/main.go @@ -7,6 +7,7 @@ import ( "os" "github.com/Grace-Solutions/OrchestrAD/internal/cli" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" "github.com/Grace-Solutions/OrchestrAD/internal/version" ) @@ -18,70 +19,55 @@ func main() { cmd := os.Args[1] + // version and help are plain CLI output, not log events, so they bypass the + // centralized logger. Everything else dispatches to a handler and routes any + // error through the centralized logger for one consistent format. switch cmd { case "version", "-v", "--version": fmt.Printf("OrchestrAD %s\n", version.Version) - case "init": - if err := cli.RunInit(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "run": - if err := cli.RunForeground(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "install": - if err := cli.RunInstall(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "uninstall": - if err := cli.RunUninstall(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "start": - if err := cli.RunStart(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "stop": - if err := cli.RunStop(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "migrate": - if err := cli.RunMigrate(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "backup": - if err := cli.RunBackup(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "restore": - if len(os.Args) < 4 || os.Args[2] != "--file" { - fmt.Fprintf(os.Stderr, "Usage: orchestrad restore --file \n") - os.Exit(1) - } - if err := cli.RunRestore(os.Args[3]); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - case "doctor": - if err := cli.RunDoctor(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } + return case "help", "-h", "--help": printUsage() + return + } + + var run func() error + switch cmd { + case "init": + run = cli.RunInit + case "run": + run = cli.RunForeground + case "install": + run = cli.RunInstall + case "uninstall": + run = cli.RunUninstall + case "start": + run = cli.RunStart + case "stop": + run = cli.RunStop + case "migrate": + run = cli.RunMigrate + case "backup": + run = cli.RunBackup + case "restore": + if len(os.Args) < 4 || os.Args[2] != "--file" { + logging.Error("CLI", "Usage: orchestrad restore --file ") + os.Exit(1) + } + path := os.Args[3] + run = func() error { return cli.RunRestore(path) } + case "doctor": + run = cli.RunDoctor default: - fmt.Fprintf(os.Stderr, "Unknown command: %s\n", cmd) + logging.Error("CLI", "Unknown command: %s", cmd) printUsage() os.Exit(1) } + + if err := run(); err != nil { + logging.Error("CLI", "%v", err) + os.Exit(1) + } } func printUsage() { diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index 5e49718..ddaa33a 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -5,10 +5,7 @@ import ( "context" "crypto/sha256" "fmt" - "os" - "os/signal" "path/filepath" - "syscall" "github.com/Grace-Solutions/OrchestrAD/internal/audit" "github.com/Grace-Solutions/OrchestrAD/internal/auth" @@ -31,7 +28,7 @@ func RunInit() error { return fmt.Errorf("loading config: %w", err) } - logger := logging.New(cfg.Logging) + logger := logging.Init(cfg.Logging) logger.Info("CLI", "Initializing OrchestrAD...") // Initialize database @@ -54,15 +51,28 @@ func RunInit() error { return nil } -// RunForeground runs the application in foreground mode +// RunForeground runs the application. It always goes through the service +// framework (service.Run): interactively this starts the server and blocks +// until an interrupt; under a service manager (Windows SCM, systemd, launchd) +// it speaks the manager's control protocol. Docker and manual `run` invocations +// take the interactive path; the installed service launches `run` too, so both +// share exactly one startup path. func RunForeground() error { + return runService() +} + +// runServer contains the actual server bring-up: config, database, migrations, +// bootstrap, services, scheduler, and the HTTP server. It blocks until ctx is +// cancelled (by an interrupt when interactive, or by the service manager's stop +// request), then returns after a graceful shutdown. +func runServer(ctx context.Context) error { cfg, err := config.Load() if err != nil { return fmt.Errorf("loading config: %w", err) } - logger := logging.New(cfg.Logging) - logger.Info("CLI", "Starting OrchestrAD in foreground mode...") + logger := logging.Init(cfg.Logging) + logger.Info("CLI", "Starting OrchestrAD...") // Initialize database database, err := db.New(cfg.Database, logger) @@ -116,50 +126,65 @@ func RunForeground() error { "mode": "foreground", }) - // Setup graceful shutdown - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Start the scheduler so enabled rules fire automatically + // Start the scheduler so enabled rules fire automatically. It is bound to + // the run context so it stops when the server is asked to shut down. sched := scheduler.New(database.Conn(), ruleRunner, logger) if err := sched.Start(ctx); err != nil { return fmt.Errorf("starting scheduler: %w", err) } defer sched.Stop() - // Create HTTP server + // Create and run the HTTP server. srv.Run blocks until ctx is cancelled, + // which happens on an interactive interrupt or a service stop request. srv := server.New(cfg, database, deps, logger) - - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - - go func() { - <-sigChan - logger.Info("CLI", "Shutdown signal received, stopping server...") - cancel() - }() - return srv.Run(ctx) } -// RunInstall installs the application as a system service +// RunInstall registers the application as a system service (Windows service, +// systemd/upstart/sysv unit, or launchd daemon) and then starts it, so a single +// `install` brings the service up. func RunInstall() error { - return fmt.Errorf("service install not yet implemented for this platform") + if err := controlService("install"); err != nil { + return err + } + logging.Info("Service", "Service installed") + if err := controlService("start"); err != nil { + return fmt.Errorf("service installed but failed to start: %w", err) + } + logging.Info("Service", "Service started") + return nil } -// RunUninstall removes the system service +// RunUninstall stops the service (ignoring the error if it is not running) and +// removes its registration. func RunUninstall() error { - return fmt.Errorf("service uninstall not yet implemented for this platform") + // Best-effort stop; a not-running service must not block uninstall. + if err := controlService("stop"); err != nil { + logging.Warn("Service", "Could not stop service (may not be running): %v", err) + } + if err := controlService("uninstall"); err != nil { + return err + } + logging.Info("Service", "Service uninstalled") + return nil } -// RunStart starts the installed service +// RunStart starts the installed service. func RunStart() error { - return fmt.Errorf("service start not yet implemented for this platform") + if err := controlService("start"); err != nil { + return err + } + logging.Info("Service", "Service started") + return nil } -// RunStop stops the installed service +// RunStop stops the installed service. func RunStop() error { - return fmt.Errorf("service stop not yet implemented for this platform") + if err := controlService("stop"); err != nil { + return err + } + logging.Info("Service", "Service stopped") + return nil } // RunMigrate applies database migrations @@ -169,7 +194,7 @@ func RunMigrate() error { return fmt.Errorf("loading config: %w", err) } - logger := logging.New(cfg.Logging) + logger := logging.Init(cfg.Logging) logger.Info("CLI", "Running database migrations...") database, err := db.New(cfg.Database, logger) @@ -203,7 +228,7 @@ func RunDoctor() error { return fmt.Errorf("config validation failed: %w", err) } - logger := logging.New(cfg.Logging) + logger := logging.Init(cfg.Logging) logger.Info("Doctor", "Running system health checks...") // Check database diff --git a/backend/internal/cli/service.go b/backend/internal/cli/service.go new file mode 100644 index 0000000..0810b9e --- /dev/null +++ b/backend/internal/cli/service.go @@ -0,0 +1,107 @@ +// Service lifecycle management (install / uninstall / start / stop) plus the +// service-hosted run loop. Uses kardianos/service so the same binary runs as a +// Windows service (SCM), a systemd/upstart/sysv daemon on Linux, or a launchd +// daemon on macOS, and can also run interactively (foreground / container). +package cli + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/kardianos/service" +) + +const ( + serviceName = "OrchestrAD" + serviceDisplayName = "OrchestrAD" + serviceDescription = "OrchestrAD - Active Directory Rule Automation Platform" +) + +// program adapts the server run loop to the service.Interface contract. Start +// must not block, so the server runs on a goroutine whose lifetime is bound to +// a context cancelled by Stop. +type program struct { + cancel context.CancelFunc + done chan error +} + +// Start is called by the service manager (or by Run when interactive). It +// launches the server without blocking. +func (p *program) Start(s service.Service) error { + ctx, cancel := context.WithCancel(context.Background()) + p.cancel = cancel + p.done = make(chan error, 1) + go func() { + p.done <- runServer(ctx) + }() + return nil +} + +// Stop is called on service stop / interactive interrupt. It cancels the run +// context and waits for the server to unwind so the process exits cleanly. +func (p *program) Stop(s service.Service) error { + if p.cancel != nil { + p.cancel() + } + if p.done != nil { + return <-p.done + } + return nil +} + +// serviceConfig builds the platform service definition. WorkingDirectory is +// pinned to the executable's directory so a relative ORCHESTRAD_DATA_PATH +// (default ./data) resolves next to the installed binary rather than wherever +// the service manager happens to launch it from. Arguments ["run"] make the +// service manager start the process in run mode, which routes back through +// service.Run. +func serviceConfig() (*service.Config, error) { + exe, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("resolving executable path: %w", err) + } + return &service.Config{ + Name: serviceName, + DisplayName: serviceDisplayName, + Description: serviceDescription, + Arguments: []string{"run"}, + WorkingDirectory: filepath.Dir(exe), + }, nil +} + +// newService constructs the service handle and its program. +func newService() (service.Service, error) { + cfg, err := serviceConfig() + if err != nil { + return nil, err + } + return service.New(&program{}, cfg) +} + +// runService runs the server through the service framework. When launched by a +// service manager it speaks the manager's control protocol (required on +// Windows); when launched interactively it runs Start, blocks until an +// interrupt, then runs Stop. This is the single entry point used by the `run` +// command so foreground, container, and service execution share one path. +func runService() error { + s, err := newService() + if err != nil { + return fmt.Errorf("creating service: %w", err) + } + return s.Run() +} + +// controlService applies a service control action (install, uninstall, start, +// stop) using the platform service manager. +func controlService(action string) error { + s, err := newService() + if err != nil { + return fmt.Errorf("creating service: %w", err) + } + if err := service.Control(s, action); err != nil { + return fmt.Errorf("%s service: %w", action, err) + } + return nil +} -- 2.52.0 From 491b5abe83c429d742d462a9735ea1379d3da977 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:55:33 -0400 Subject: [PATCH 06/10] feat(cli): idempotent initialize/remove service commands Add initialize (install + start) and remove (stop + uninstall) that are safe to re-run: initialize skips reinstall/restart when already up, remove treats a not-installed service as done. install/uninstall become aliases. Uses the service status to branch. Verified idempotent on Windows (repeat runs exit 0). Co-Authored-By: Claude Opus 4.8 --- backend/cmd/orchestrad/main.go | 14 +++++-- backend/internal/cli/cli.go | 42 +++++++++---------- backend/internal/cli/service.go | 72 ++++++++++++++++++++++++++++++++- resources/icons/.gitkeep | 2 - 4 files changed, 98 insertions(+), 32 deletions(-) delete mode 100644 resources/icons/.gitkeep diff --git a/backend/cmd/orchestrad/main.go b/backend/cmd/orchestrad/main.go index e3e2ea4..f022f01 100644 --- a/backend/cmd/orchestrad/main.go +++ b/backend/cmd/orchestrad/main.go @@ -37,6 +37,10 @@ func main() { run = cli.RunInit case "run": run = cli.RunForeground + case "initialize": + run = cli.RunInitialize + case "remove": + run = cli.RunRemove case "install": run = cli.RunInstall case "uninstall": @@ -76,10 +80,12 @@ func printUsage() { Usage: orchestrad [options] Commands: - init Initialize database, apply migrations, install and start service - run Run in foreground mode - install Install as system service - uninstall Stop and uninstall service + init Initialize database and apply migrations + run Run in foreground mode (also the service entry point) + initialize Install and start the service (idempotent) + remove Stop and remove the service (idempotent) + install Alias for initialize + uninstall Alias for remove start Start installed service stop Stop installed service migrate Apply database migrations diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index ddaa33a..d820a01 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -140,33 +140,27 @@ func runServer(ctx context.Context) error { return srv.Run(ctx) } -// RunInstall registers the application as a system service (Windows service, -// systemd/upstart/sysv unit, or launchd daemon) and then starts it, so a single -// `install` brings the service up. -func RunInstall() error { - if err := controlService("install"); err != nil { - return err - } - logging.Info("Service", "Service installed") - if err := controlService("start"); err != nil { - return fmt.Errorf("service installed but failed to start: %w", err) - } - logging.Info("Service", "Service started") - return nil +// RunInitialize idempotently installs and starts the system service (Windows +// service, systemd/upstart/sysv unit, or launchd daemon). Safe to re-run. +func RunInitialize() error { + return initializeService() } -// RunUninstall stops the service (ignoring the error if it is not running) and -// removes its registration. +// RunRemove idempotently stops and removes the system service. Safe to re-run. +func RunRemove() error { + return removeService() +} + +// RunInstall is an alias for RunInitialize: it idempotently installs and starts +// the service. +func RunInstall() error { + return initializeService() +} + +// RunUninstall is an alias for RunRemove: it idempotently stops and removes the +// service. func RunUninstall() error { - // Best-effort stop; a not-running service must not block uninstall. - if err := controlService("stop"); err != nil { - logging.Warn("Service", "Could not stop service (may not be running): %v", err) - } - if err := controlService("uninstall"); err != nil { - return err - } - logging.Info("Service", "Service uninstalled") - return nil + return removeService() } // RunStart starts the installed service. diff --git a/backend/internal/cli/service.go b/backend/internal/cli/service.go index 0810b9e..0d02fa1 100644 --- a/backend/internal/cli/service.go +++ b/backend/internal/cli/service.go @@ -6,10 +6,12 @@ package cli import ( "context" + "errors" "fmt" "os" "path/filepath" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" "github.com/kardianos/service" ) @@ -93,8 +95,8 @@ func runService() error { return s.Run() } -// controlService applies a service control action (install, uninstall, start, -// stop) using the platform service manager. +// controlService applies a single service control action (start or stop) using +// the platform service manager. func controlService(action string) error { s, err := newService() if err != nil { @@ -105,3 +107,69 @@ func controlService(action string) error { } return nil } + +// initializeService idempotently ensures the service is installed and running. +// Re-running it is safe: an already-installed service is not reinstalled, and an +// already-running one is left alone. This backs the `initialize` (and `install`) +// command so provisioning is repeatable. +func initializeService() error { + s, err := newService() + if err != nil { + return fmt.Errorf("creating service: %w", err) + } + + status, err := s.Status() + if errors.Is(err, service.ErrNotInstalled) { + if err := s.Install(); err != nil { + return fmt.Errorf("installing service: %w", err) + } + logging.Info("Service", "Service installed") + status = service.StatusStopped + } else if err != nil { + return fmt.Errorf("querying service status: %w", err) + } else { + logging.Info("Service", "Service already installed") + } + + if status == service.StatusRunning { + logging.Info("Service", "Service already running") + return nil + } + if err := s.Start(); err != nil { + return fmt.Errorf("starting service: %w", err) + } + logging.Info("Service", "Service started") + return nil +} + +// removeService idempotently ensures the service is stopped and removed. +// Re-running it is safe: a not-installed service is treated as already removed, +// and a stop failure on an already-stopped service does not block removal. This +// backs the `remove` (and `uninstall`) command. +func removeService() error { + s, err := newService() + if err != nil { + return fmt.Errorf("creating service: %w", err) + } + + status, err := s.Status() + if errors.Is(err, service.ErrNotInstalled) { + logging.Info("Service", "Service not installed; nothing to remove") + return nil + } else if err != nil { + return fmt.Errorf("querying service status: %w", err) + } + + if status == service.StatusRunning { + if err := s.Stop(); err != nil { + logging.Warn("Service", "Could not stop service before removal: %v", err) + } else { + logging.Info("Service", "Service stopped") + } + } + if err := s.Uninstall(); err != nil { + return fmt.Errorf("removing service: %w", err) + } + logging.Info("Service", "Service removed") + return nil +} diff --git a/resources/icons/.gitkeep b/resources/icons/.gitkeep deleted file mode 100644 index d434679..0000000 --- a/resources/icons/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -# Placeholder for application icons -# Windows icon (orchestrad.ico) should be placed here for embedding in Windows builds -- 2.52.0 From 47679269f731114283c07eb5b53220c59ffcbd83 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 08:55:34 -0400 Subject: [PATCH 07/10] assets: add OrchestrAD app icon and Windows version-info manifest Add a purpose-drawn icon (indigo/violet badge with an orchestration hub-and-spoke glyph) as .svg, a 256px .png, and a multi-resolution .ico (16-256). versioninfo.json drives goversioninfo to embed the icon + metadata into the Windows binaries; the generated .syso objects are build artifacts and gitignored. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 4 ++++ backend/versioninfo.json | 25 ++++++++++++++++++++++ resources/icons/orchestrad.ico | Bin 0 -> 372526 bytes resources/icons/orchestrad.png | Bin 0 -> 31231 bytes resources/icons/orchestrad.svg | 38 +++++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+) create mode 100644 backend/versioninfo.json create mode 100644 resources/icons/orchestrad.ico create mode 100644 resources/icons/orchestrad.png create mode 100644 resources/icons/orchestrad.svg diff --git a/.gitignore b/.gitignore index 9006577..4c93b73 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,10 @@ # Test binary *.test +# Windows resource objects generated from versioninfo.json by goversioninfo +# (icon + version metadata). Regenerated during the build, not tracked. +*.syso + # Output of the go coverage tool *.out diff --git a/backend/versioninfo.json b/backend/versioninfo.json new file mode 100644 index 0000000..2fa3637 --- /dev/null +++ b/backend/versioninfo.json @@ -0,0 +1,25 @@ +{ + "FixedFileInfo": { + "FileVersion": { "Major": 0, "Minor": 0, "Patch": 0, "Build": 0 }, + "ProductVersion": { "Major": 0, "Minor": 0, "Patch": 0, "Build": 0 }, + "FileFlagsMask": "3f", + "FileFlags": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": { + "CompanyName": "Grace Solutions", + "FileDescription": "OrchestrAD - Active Directory Rule Automation Platform", + "InternalName": "orchestrad", + "LegalCopyright": "Copyright (c) Grace Solutions", + "OriginalFilename": "orchestrad.exe", + "ProductName": "OrchestrAD", + "ProductVersion": "dev" + }, + "VarFileInfo": { + "Translation": { "LangID": "0409", "CharsetID": "04B0" } + }, + "IconPath": "../resources/icons/orchestrad.ico", + "ManifestPath": "" +} diff --git a/resources/icons/orchestrad.ico b/resources/icons/orchestrad.ico new file mode 100644 index 0000000000000000000000000000000000000000..750d3bb7b3b3a489a62a7a415ce6f29170e7e30a GIT binary patch literal 372526 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 z

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

Un~vij{3%@#J}(LY2Gi>`y2mp{l5qH72@A( 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 literal 0 HcmV?d00001 diff --git a/resources/icons/orchestrad.png b/resources/icons/orchestrad.png new file mode 100644 index 0000000000000000000000000000000000000000..399e1bd89a3f819a4d0bd2f45bdbe9b8a100cda9 GIT binary patch literal 31231 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 DPU<$U literal 0 HcmV?d00001 diff --git a/resources/icons/orchestrad.svg b/resources/icons/orchestrad.svg new file mode 100644 index 0000000..5403910 --- /dev/null +++ b/resources/icons/orchestrad.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.52.0 From 503b3f037cdaa18d9599d7e8df2adb8226ec338e Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 09:11:06 -0400 Subject: [PATCH 08/10] feat(installer): WiX MSI installing to Program Files with service Add a WiX v5 installer that installs orchestrad.exe to Program Files\OrchestrAD (x64) and registers + starts the Windows service via the binary's idempotent initialize command; uninstall runs remove before deleting files, leaving no orphaned service. Embeds the app icon in Add/Remove Programs. Validated end to end on Windows: install -> service Running + healthy, uninstall -> clean. Co-Authored-By: Claude Opus 4.8 --- installer/OrchestrAD.wxs | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 installer/OrchestrAD.wxs diff --git a/installer/OrchestrAD.wxs b/installer/OrchestrAD.wxs new file mode 100644 index 0000000..2c182de --- /dev/null +++ b/installer/OrchestrAD.wxs @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.52.0 From d3e78c0f294df4ed2c6b537b535140653c51548b Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 09:11:06 -0400 Subject: [PATCH 09/10] build: simplify build.ps1 for pure-Go and embed the Windows icon Drop the obsolete CGO/mingw cross-compile machinery (all targets now build with CGO off) and generate the Windows icon/version resource with goversioninfo before building the windows/* binaries, so local builds match CI and finally embed the icon the README advertises. Co-Authored-By: Claude Opus 4.8 --- scripts/build.ps1 | 87 +++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 55 deletions(-) diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 6eb6566..2af431e 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -105,6 +105,32 @@ if (-not $All) { # LDFlags for version injection $LDFlags = "-s -w -X github.com/Grace-Solutions/OrchestrAD/internal/version.Version=$Version -X github.com/Grace-Solutions/OrchestrAD/internal/version.BuildTime=$BuildTime -X github.com/Grace-Solutions/OrchestrAD/internal/version.GitCommit=$GitCommit" +# Generate the Windows icon/version resource so the windows/* binaries embed the +# OrchestrAD icon. goversioninfo is a pure-Go tool and runs on any host; the +# arch-suffixed .syso files are only linked into their matching GOARCH build. +$BuildWindows = $All -or ($Platforms | Where-Object { $_.OS -eq "windows" }) +if ($BuildWindows) { + Write-Host "`nGenerating Windows resource (icon + version metadata)..." -ForegroundColor Cyan + $GoVersionInfo = Join-Path (Join-Path $env:USERPROFILE "go\bin") "goversioninfo.exe" + if (-not (Test-Path $GoVersionInfo)) { + Write-Host " Installing goversioninfo..." -ForegroundColor Yellow + go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest + } + $vp = $Version -split '\.' + Push-Location $BackendDir + try { + foreach ($a in @(@{arch='amd64';flags=@('-64')}, @{arch='arm64';flags=@('-64','-arm')})) { + $out = "cmd\orchestrad\resource_windows_$($a.arch).syso" + & $GoVersionInfo @($a.flags) ` + "-ver-major=$([int]$vp[0])" "-ver-minor=$([int]$vp[1])" "-ver-patch=$([int]$vp[2])" "-ver-build=$([int]$vp[3])" ` + "-product-version=$([int]$vp[0]).$([int]$vp[1]).$([int]$vp[2])" ` + "-o" $out "versioninfo.json" + } + } finally { + Pop-Location + } +} + Push-Location $BackendDir try { @@ -126,65 +152,16 @@ try { Write-Host "`nBuilding for $OS/$Arch..." -ForegroundColor Green + # Pure-Go build (modernc.org/sqlite): CGO is disabled, so every target + # cross-compiles from any host with no C toolchain. Windows binaries pick + # up the icon/version resource generated into cmd/orchestrad before the + # loop (resource_windows_.syso). $env:GOOS = $OS $env:GOARCH = $Arch - $env:CGO_ENABLED = "1" - $PlatformLDFlags = $LDFlags - $SavedPath = $env:PATH + $env:CGO_ENABLED = "0" - # For cross-compilation, CGO needs special handling - if ($OS -ne "windows" -and $env:OS -eq "Windows_NT") { - $env:CGO_ENABLED = "0" - Write-Host " Note: CGO disabled for cross-compilation (SQLite will use pure Go driver)" -ForegroundColor Yellow - } elseif ($OS -eq "windows") { - # Produce a single-file binary with zero runtime DLL dependencies by - # statically linking the mingw-w64 C runtime. Auto-discover a gcc - # that targets $Arch from common install locations. - $GccName = if ($Arch -eq "arm64") { "aarch64-w64-mingw32-gcc.exe" } else { "gcc.exe" } - $env:CC = "" - if ($Arch -eq "arm64") { - $CcCandidates = @( - "C:\ProgramData\mingw64\aarch64-w64-mingw32\bin", - "C:\msys64\clangarm64\bin" - ) - foreach ($Dir in $CcCandidates) { - if (Test-Path (Join-Path $Dir $GccName)) { $env:CC = (Join-Path $Dir $GccName); break } - } - if (-not $env:CC) { - Write-Host " Skipping windows/arm64: no aarch64-w64-mingw32-gcc on PATH (install an aarch64 mingw-w64 toolchain to enable)." -ForegroundColor Yellow - $env:PATH = $SavedPath - $env:CC = "" - continue - } - } else { - if (-not (Get-Command gcc -ErrorAction SilentlyContinue)) { - $GccCandidates = @( - "C:\ProgramData\mingw64\mingw64\bin", - "C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin", - "C:\msys64\ucrt64\bin", - "C:\msys64\mingw64\bin", - "C:\TDM-GCC-64\bin" - ) - foreach ($GccDir in $GccCandidates) { - if (Test-Path (Join-Path $GccDir "gcc.exe")) { - $env:PATH = "$GccDir;$env:PATH" - Write-Host " Using gcc from $GccDir" -ForegroundColor DarkGray - break - } - } - } - if (-not (Get-Command gcc -ErrorAction SilentlyContinue)) { - throw "gcc is required on PATH for a Windows CGO build (mattn/go-sqlite3). Install mingw-w64 via 'choco install mingw' or point PATH at an existing gcc.exe." - } - } - $PlatformLDFlags = "$LDFlags -linkmode external -extldflags `"-static`"" - } + go build -ldflags "$LDFlags" -o "$OutputFile" ./cmd/orchestrad - go build -ldflags "$PlatformLDFlags" -o "$OutputFile" ./cmd/orchestrad - - $env:PATH = $SavedPath - $env:CC = "" - if ($LASTEXITCODE -eq 0) { $FileSize = (Get-Item $OutputFile).Length / 1MB Write-Host " Built: $OutputFile ($([math]::Round($FileSize, 2)) MB)" -ForegroundColor Green -- 2.52.0 From 164ea09f97f2c00f48defcfd3b920cf192d47b24 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 09:11:07 -0400 Subject: [PATCH 10/10] ci: build cross-platform binaries + MSI and attach to the release Expand the release pipeline: run the pure-Go test gate, build the frontend once, cross-compile all six targets (windows/macos/linux, amd64/arm64) with the UI embedded and the Windows icon, build the x64 MSI with WiX v5, and attach every archive + checksum + the MSI to the Gitea release. Container image build/push is unchanged. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/release.yml | 238 ++++++++++++++++++++++++----------- 1 file changed, 166 insertions(+), 72 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 40586d1..90caa98 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -2,7 +2,8 @@ name: Release # Fires only on a merge into main (a push to the main branch). No per-commit # or per-PR CI runs on other branches — this is the single pipeline that turns -# what lands on main into a release + container image. +# what lands on main into a release, cross-platform binaries, an MSI, and a +# container image. # # Docs-only merges are skipped: touching just README/docs/LICENSE does not # produce a new build. @@ -20,9 +21,9 @@ on: jobs: release: - # Label must match a registered Linux runner that has Docker (used for the - # dockerized test gate and the image build/push). This is the same label the - # sibling repos use for their image jobs. + # Label must match a registered Linux runner that has Docker. Pure-Go builds + # (modernc SQLite, CGO off) mean every OS/arch cross-compiles here, and WiX v5 + # builds the Windows MSI on Linux, so one runner produces every artifact. runs-on: ubuntu-host # Grant the auto-injected Actions token the scopes this job needs: push to @@ -56,7 +57,7 @@ jobs: # Version = the UTC date of the HEAD commit, formatted yyyy.MM.dd.HHmm # (the project's documented version scheme). Deriving it from the commit # rather than "now" makes re-runs reproducible and keeps the image tag, - # the release tag, and the binary's embedded version identical. + # the release tag, the binary version, and the MSI version aligned. - name: Compute version id: ver shell: bash @@ -66,38 +67,157 @@ jobs: GIT_COMMIT="$(git rev-parse HEAD)" GIT_COMMIT_SHORT="$(git rev-parse --short HEAD)" BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + + # MSI ProductVersion fields are bounded (major<=255, build<=65535), so + # yyyy.MM.dd.HHmm cannot be used directly. Map to major=yy, minor=month, + # build=day*1440+minute-of-day, which stays in range and increases + # monotonically over time for correct upgrade detection. + IFS=. read -r Y M D HM <<< "$VERSION" + Y=$((10#$Y)); M=$((10#$M)); D=$((10#$D)); HM=$((10#$HM)) + HH=$((HM/100)); MM=$((HM%100)) + MSI_VERSION="$((Y-2000)).$M.$((D*1440 + HH*60 + MM))" + { echo "version=$VERSION" echo "git_commit=$GIT_COMMIT" echo "git_commit_short=$GIT_COMMIT_SHORT" echo "build_time=$BUILD_TIME" + echo "msi_version=$MSI_VERSION" + echo "vy=$Y"; echo "vm=$M"; echo "vd=$D"; echo "vhm=$HM" } >> "$GITHUB_OUTPUT" - echo "OrchestrAD version: $VERSION ($GIT_COMMIT_SHORT)" + echo "OrchestrAD version: $VERSION (msi $MSI_VERSION, $GIT_COMMIT_SHORT)" - # Test gate: run the Go test suite inside the same toolchain image the - # build uses. Running it in a container means the runner needs only Docker - # (no host Go/gcc), and a failure here stops the release before anything - # is published. CGO is on because the SQLite driver requires it. - - name: Test - shell: bash - run: | - set -euo pipefail - docker run --rm -v "$PWD/backend:/src" -w /src \ - -e CGO_ENABLED=1 golang:1.24-alpine \ - sh -c "apk add --no-cache gcc musl-dev >/dev/null && go test ./..." + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' - # jq is used to JSON-encode the release body safely. Install it if the - # self-hosted runner does not already have it. - name: Ensure tooling (jq) shell: bash run: | set -euo pipefail - if ! command -v jq >/dev/null 2>&1; then - echo "Installing jq..." - sudo apt-get update -y && sudo apt-get install -y jq - fi + command -v jq >/dev/null 2>&1 || { sudo apt-get update -y && sudo apt-get install -y jq; } jq --version + # Test gate: pure-Go, so no C toolchain is needed. A failure stops the + # release before anything is built or published. + - name: Test + working-directory: backend + env: + CGO_ENABLED: '0' + run: go test ./... + + # Build the Next.js static export once on the host and stage it into the + # //go:embed dir so every standalone binary ships the real UI. (The + # container image builds its own copy via the Dockerfile.) + - name: Build frontend + working-directory: frontend + run: | + set -euo pipefail + npm ci --no-audit --no-fund + npm run build + + - name: Stage UI into embed dir + shell: bash + run: | + set -euo pipefail + dist=backend/internal/webui/dist + find "$dist" -mindepth 1 -not -name '.gitignore' -delete + cp -r frontend/out/* "$dist"/ + test -f "$dist/index.html" + + # Generate the Windows icon/version resource. goversioninfo is pure Go; the + # arch-suffixed .syso files are only linked into their matching build. + - name: Generate Windows resource + working-directory: backend + env: + VY: ${{ steps.ver.outputs.vy }} + VM: ${{ steps.ver.outputs.vm }} + VD: ${{ steps.ver.outputs.vd }} + VHM: ${{ steps.ver.outputs.vhm }} + run: | + set -euo pipefail + go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest + GV="$(go env GOPATH)/bin/goversioninfo" + "$GV" -64 -ver-major="$VY" -ver-minor="$VM" -ver-patch="$VD" -ver-build="$VHM" \ + -product-version="$VY.$VM.$VD" -o cmd/orchestrad/resource_windows_amd64.syso versioninfo.json + "$GV" -64 -arm -ver-major="$VY" -ver-minor="$VM" -ver-patch="$VD" -ver-build="$VHM" \ + -product-version="$VY.$VM.$VD" -o cmd/orchestrad/resource_windows_arm64.syso versioninfo.json + + # Cross-compile all six targets (CGO off) and package them: .zip for + # Windows, .tar.gz elsewhere, each with a .sha256. Stage the windows/amd64 + # binary for the MSI. + - name: Build binaries + working-directory: backend + env: + VERSION: ${{ steps.ver.outputs.version }} + GIT_COMMIT: ${{ steps.ver.outputs.git_commit }} + BUILD_TIME: ${{ steps.ver.outputs.build_time }} + run: | + set -euo pipefail + LDFLAGS="-s -w \ + -X github.com/Grace-Solutions/OrchestrAD/internal/version.Version=${VERSION} \ + -X github.com/Grace-Solutions/OrchestrAD/internal/version.BuildTime=${BUILD_TIME} \ + -X github.com/Grace-Solutions/OrchestrAD/internal/version.GitCommit=${GIT_COMMIT}" + DIST="$GITHUB_WORKSPACE/dist"; mkdir -p "$DIST" "$GITHUB_WORKSPACE/msistage" + + build() { + local goos="$1" goarch="$2" label="$3" ext="$4" + echo "Building $label..." + local work; work="$(mktemp -d)" + GOOS="$goos" GOARCH="$goarch" CGO_ENABLED=0 \ + go build -ldflags "$LDFLAGS" -o "$work/orchestrad$ext" ./cmd/orchestrad + local name="orchestrad-${VERSION}-${label}" + if [ "$goos" = "windows" ]; then + ( cd "$work" && zip -q "$DIST/${name}.zip" "orchestrad$ext" ) + ( cd "$DIST" && sha256sum "${name}.zip" > "${name}.zip.sha256" ) + else + ( cd "$work" && tar -czf "$DIST/${name}.tar.gz" "orchestrad$ext" ) + ( cd "$DIST" && sha256sum "${name}.tar.gz" > "${name}.tar.gz.sha256" ) + fi + if [ "$goos" = "windows" ] && [ "$goarch" = "amd64" ]; then + cp "$work/orchestrad.exe" "$GITHUB_WORKSPACE/msistage/orchestrad.exe" + fi + rm -rf "$work" + } + + build windows amd64 windows-amd64 .exe + build windows arm64 windows-arm64 .exe + build darwin amd64 macos-amd64 "" + build darwin arm64 macos-arm64 "" + build linux amd64 linux-amd64 "" + build linux arm64 linux-arm64 "" + ls -la "$DIST" + + # Build the Windows MSI with WiX v5 (cross-platform, no OSMF fee). Installs + # to Program Files\OrchestrAD and registers+starts the service via the + # binary's idempotent `initialize`/`remove` commands. + - name: Build MSI + env: + MSI_VERSION: ${{ steps.ver.outputs.msi_version }} + VERSION: ${{ steps.ver.outputs.version }} + run: | + set -euo pipefail + export PATH="$PATH:$HOME/.dotnet/tools" + command -v wix >/dev/null 2>&1 || dotnet tool install --global wix --version 5.0.2 + wix build -arch x64 installer/OrchestrAD.wxs \ + -d Version="$MSI_VERSION" \ + -d BinDir="$GITHUB_WORKSPACE/msistage" \ + -d IconPath="$GITHUB_WORKSPACE/resources/icons/orchestrad.ico" \ + -o "dist/OrchestrAD-${VERSION}-x64.msi" + ( cd dist && sha256sum "OrchestrAD-${VERSION}-x64.msi" > "OrchestrAD-${VERSION}-x64.msi.sha256" ) + ls -la dist + - name: Resolve registry target id: reg shell: bash @@ -151,24 +271,7 @@ jobs: docker push "${IMAGE}:latest" echo "Published ${IMAGE}:${VERSION} and ${IMAGE}:latest" - # Pull the linux/amd64 binary back out of the freshly built image so the - # release carries a ready-to-run artifact, not just an image reference. - - name: Extract release binary - env: - IMAGE: ${{ steps.reg.outputs.image }} - VERSION: ${{ steps.ver.outputs.version }} - run: | - set -euo pipefail - mkdir -p dist - CID="$(docker create "${IMAGE}:${VERSION}")" - docker cp "$CID:/app/orchestrad" "dist/orchestrad" - docker rm "$CID" >/dev/null - tar -C dist -czf "dist/orchestrad-${VERSION}-linux-amd64.tar.gz" orchestrad - ( cd dist && sha256sum "orchestrad-${VERSION}-linux-amd64.tar.gz" > "orchestrad-${VERSION}-linux-amd64.tar.gz.sha256" ) - ls -la dist - - # Create the Gitea release for this version and attach the binary. Uses - # the auto-injected token; no manual secret required. + # Create the Gitea release and attach every artifact in dist/. - name: Create Gitea release shell: bash env: @@ -182,44 +285,35 @@ jobs: run: | set -euo pipefail - # Skip if a release for this tag already exists (e.g. a re-run). - code="$(curl -sS -o /dev/null -w '%{http_code}' \ + # Skip creation if a release for this tag already exists (re-run), but + # still (re)upload any assets that are missing below. + code="$(curl -sS -o /tmp/rel.json -w '%{http_code}' \ -H "Authorization: token ${TOKEN}" \ "${API_URL}/repos/${REPO}/releases/tags/${VERSION}")" if [ "$code" = "200" ]; then - echo "Release ${VERSION} already exists; skipping." - exit 0 + rel_id="$(jq -r '.id' /tmp/rel.json)" + echo "Release ${VERSION} already exists (id=$rel_id); ensuring assets." + else + body="$(printf '**OrchestrAD %s**\n\n| Field | Value |\n| --- | --- |\n| Version | `%s` |\n| Commit | [`%s`](%s/%s/commit/%s) |\n\n## Container image\n```\ndocker pull %s:%s\ndocker pull %s:latest\n```\n\n## Downloads\n- Windows installer: `OrchestrAD-%s-x64.msi` (installs to Program Files and runs as a service)\n- Standalone binaries: windows/macos/linux, amd64/arm64\n' \ + "$VERSION" "$VERSION" "$GIT_COMMIT_SHORT" "$SERVER_URL" "$REPO" "$GIT_COMMIT" "$IMAGE" "$VERSION" "$IMAGE" "$VERSION")" + payload="$(jq -n --arg tag "$VERSION" --arg sha "$GIT_COMMIT" \ + --arg name "OrchestrAD $VERSION" --arg body "$body" \ + '{tag_name:$tag, target_commitish:$sha, name:$name, body:$body, draft:false, prerelease:false}')" + rel="$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" -d "$payload" \ + "${API_URL}/repos/${REPO}/releases")" + rel_id="$(printf '%s' "$rel" | jq -r '.id')" + if [ -z "$rel_id" ] || [ "$rel_id" = "null" ]; then + echo "Failed to create release:"; echo "$rel"; exit 1 + fi + echo "Created release id=$rel_id" fi - # Markdown release notes, JSON-encoded with jq so any characters are - # safely escaped. - body="$(printf '**OrchestrAD %s**\n\n| Field | Value |\n| --- | --- |\n| Version | `%s` |\n| Commit | [`%s`](%s/%s/commit/%s) |\n\n## Container image\n```\ndocker pull %s:%s\ndocker pull %s:latest\n```\n' \ - "$VERSION" "$VERSION" "$GIT_COMMIT_SHORT" "$SERVER_URL" "$REPO" "$GIT_COMMIT" "$IMAGE" "$VERSION" "$IMAGE")" - - payload="$(jq -n \ - --arg tag "$VERSION" \ - --arg sha "$GIT_COMMIT" \ - --arg name "OrchestrAD $VERSION" \ - --arg body "$body" \ - '{tag_name:$tag, target_commitish:$sha, name:$name, body:$body, draft:false, prerelease:false}')" - - rel="$(curl -sS -X POST \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$payload" \ - "${API_URL}/repos/${REPO}/releases")" - rel_id="$(printf '%s' "$rel" | jq -r '.id')" - if [ -z "$rel_id" ] || [ "$rel_id" = "null" ]; then - echo "Failed to create release:"; echo "$rel"; exit 1 - fi - echo "Created release id=$rel_id" - - for asset in dist/orchestrad-${VERSION}-linux-amd64.tar.gz dist/orchestrad-${VERSION}-linux-amd64.tar.gz.sha256; do + for asset in dist/*; do name="$(basename "$asset")" echo "Uploading $name" - curl -sS -X POST \ - -H "Authorization: token ${TOKEN}" \ + curl -sS -X POST -H "Authorization: token ${TOKEN}" \ -F "attachment=@${asset}" \ "${API_URL}/repos/${REPO}/releases/${rel_id}/assets?name=${name}" >/dev/null done - echo "Release ${VERSION} published." + echo "Release ${VERSION} published with $(ls dist | wc -l) assets." -- 2.52.0