@@ -7,12 +7,12 @@ utilities for configuration management in containerized environments.
77
88## Features
99
10+ - ** Unified runtime** - Single API for both HTTP servers and Lambda functions
1011- ** Web-based installer** - User-friendly UI for creating GitHub Apps with
1112 pre-configured permissions
1213- ** Multiple storage backends** - AWS SSM Parameter Store, ` .env ` files, or
1314 individual files
14- - ** Hot reload support** - Reload configuration via SIGHUP or programmatic
15- triggers
15+ - ** Hot reload support** - Reload configuration via SIGHUP or installer callback
1616- ** SSM ARN resolution** - Resolve AWS SSM Parameter Store ARNs in environment
1717 variables (useful for Lambda)
1818- ** Ready gate** - HTTP middleware that returns 503 until configuration is
@@ -28,67 +28,94 @@ go get github.com/cruxstack/github-app-setup-go
2828
2929| Package | Description |
3030| ---------------| -----------------------------------------------------------|
31+ | ` ghappsetup ` | ** Unified runtime** for HTTP servers and Lambda functions |
3132| ` installer ` | HTTP handler implementing the GitHub App Manifest flow |
3233| ` configstore ` | Storage backends for GitHub App credentials |
33- | ` configwait ` | Startup wait logic, ready gate middleware, and reload |
34+ | ` configwait ` | Startup wait logic and ready gate middleware |
3435| ` ssmresolver ` | Resolves SSM Parameter Store ARNs in environment vars |
3536
3637## Quick Start
3738
39+ The ` ghappsetup.Runtime ` provides unified lifecycle management for both HTTP
40+ servers and Lambda functions:
41+
3842``` go
3943package main
4044
4145import (
4246 " context"
47+ " fmt"
4348 " log"
4449 " net/http"
50+ " os"
4551
46- " github.com/cruxstack/github-app-setup-go/configstore"
47- " github.com/cruxstack/github-app-setup-go/configwait"
52+ " github.com/cruxstack/github-app-setup-go/ghappsetup"
4853 " github.com/cruxstack/github-app-setup-go/installer"
4954)
5055
5156func main () {
5257 ctx := context.Background ()
5358
54- // Create a storage backend (uses STORAGE_MODE env var, defaults to .env file)
55- store , err := configstore.NewFromEnv ()
59+ // Create runtime with unified lifecycle management
60+ runtime , err := ghappsetup.NewRuntime (ghappsetup.Config {
61+ LoadFunc: loadConfig,
62+ AllowedPaths: []string {" /healthz" , " /setup" , " /callback" , " /" },
63+ })
5664 if err != nil {
5765 log.Fatal (err)
5866 }
5967
60- // Define the GitHub App manifest with required permissions
61- manifest := installer.Manifest {
62- URL: " https://example.com" ,
63- Public: false ,
64- DefaultPerms: map [string ]string {
65- " contents" : " read" ,
66- " pull_requests" : " write" ,
68+ // Set up routes
69+ mux := http.NewServeMux ()
70+ mux.HandleFunc (" /healthz" , runtime.HealthHandler ())
71+ mux.HandleFunc (" /webhook" , webhookHandler)
72+
73+ // Create installer using convenience method (auto-wires Store and reload callback)
74+ installerHandler , err := runtime.InstallerHandler (installer.Config {
75+ Manifest: installer.Manifest {
76+ URL: " https://example.com" ,
77+ Public: false ,
78+ DefaultPerms: map [string ]string {
79+ " contents" : " read" ,
80+ " pull_requests" : " write" ,
81+ },
82+ DefaultEvents: []string {" pull_request" , " push" },
6783 },
68- DefaultEvents: []string {" pull_request" , " push" },
69- }
70-
71- // Create the installer handler
72- installerHandler , err := installer.New (installer.Config {
73- Store: store,
74- Manifest: manifest,
7584 AppDisplayName: " My GitHub App" ,
7685 })
7786 if err != nil {
7887 log.Fatal (err)
7988 }
8089
81- // Set up routes
82- mux := http.NewServeMux ()
8390 mux.Handle (" /setup" , installerHandler)
8491 mux.Handle (" /callback" , installerHandler)
8592
86- // Create a ready gate that allows /setup through before app is configured
87- gate := configwait.NewReadyGate (mux, []string {" /setup" , " /callback" , " /healthz" })
93+ // Start HTTP server with ReadyGate middleware
94+ srv := &http.Server {
95+ Addr: " :8080" ,
96+ Handler: runtime.Handler (mux),
97+ }
98+ go srv.ListenAndServe ()
99+
100+ // Block until config loads, then listen for SIGHUP reloads
101+ if err := runtime.Start (ctx); err != nil {
102+ log.Fatal (err)
103+ }
104+ log.Println (" Configuration loaded, service is ready" )
105+ runtime.ListenForReloads (ctx)
106+ }
107+
108+ func loadConfig (ctx context .Context ) error {
109+ // Validate required environment variables are present
110+ if os.Getenv (" GITHUB_APP_ID" ) == " " {
111+ return fmt.Errorf (" GITHUB_APP_ID not set" )
112+ }
113+ return nil
114+ }
88115
89- // Start the server
90- log. Println ( " Starting server on :8080 " )
91- log. Fatal (http.ListenAndServe ( " :8080 " , gate) )
116+ func webhookHandler ( w http . ResponseWriter , r * http . Request ) {
117+ // Handle GitHub webhooks
118+ w. WriteHeader (http.StatusOK )
92119}
93120```
94121
@@ -158,26 +185,54 @@ store := configstore.NewLocalFileStore("./secrets/")
158185
159186## Hot Reload
160187
161- The library supports hot-reloading configuration via SIGHUP signals or
162- programmatic triggers:
188+ The Runtime supports hot-reloading configuration via SIGHUP signals. When the
189+ installer saves new credentials, it automatically triggers a reload via the
190+ callback:
163191
164192``` go
165- // Create a reloader that calls your reload function
166- reloader := configwait.NewReloader (ctx, gate, func (ctx context.Context ) error {
167- // Reload your configuration here
168- newHandler := buildHandler ()
169- gate.SetHandler (newHandler)
170- gate.SetReady ()
171- return nil
172- })
193+ // ListenForReloads handles both SIGHUP signals and installer callbacks
194+ runtime.ListenForReloads (ctx)
195+ ```
196+
197+ For manual reload triggering:
198+
199+ ``` go
200+ // Trigger a reload programmatically
201+ runtime.Reload ()
202+ ```
203+
204+ ## Lambda Usage
173205
174- // Set as global reloader (allows installer to trigger reload after saving)
175- configwait.SetGlobalReloader (reloader)
206+ For AWS Lambda functions, use ` EnsureLoaded() ` for lazy initialization:
176207
177- // Start listening for SIGHUP
178- reloader.Start ()
208+ ``` go
209+ var runtime *ghappsetup.Runtime
210+
211+ func init () {
212+ runtime, _ = ghappsetup.NewRuntime (ghappsetup.Config {
213+ LoadFunc: func (ctx context.Context ) error {
214+ // Resolve SSM parameters passed as ARNs
215+ if err := ssmresolver.ResolveEnvironmentWithDefaults (ctx); err != nil {
216+ return err
217+ }
218+ return validateConfig ()
219+ },
220+ })
221+ }
222+
223+ func handler (ctx context .Context , req events .APIGatewayV2HTTPRequest ) (Response , error ) {
224+ // Lazy-load config with retries (idempotent after first success)
225+ if err := runtime.EnsureLoaded (ctx); err != nil {
226+ return Response{StatusCode: 503 , Body: " Service unavailable" }, nil
227+ }
228+ return handleRequest (ctx, req)
229+ }
179230```
180231
232+ The Runtime auto-detects Lambda environments and adjusts retry settings:
233+ - ** HTTP** : 30 retries, 2-second intervals (suitable for startup)
234+ - ** Lambda** : 5 retries, 1-second intervals (suitable for cold starts)
235+
181236## SSM ARN Resolution
182237
183238For Lambda deployments where secrets are passed as SSM ARNs:
0 commit comments