fork of whitequark.org/git-pages with mods for tangled

Consistently use context in `Main()`.

Without this, some of the slog lines end in `\n` and some do not, which
I find deeply irritating.

Changed files
+84 -70
src
+1 -1
src/backend_fs.go
··· 212 212 return filepath.Join(domain, ".frozen") 213 213 } 214 214 215 - func (fs *FSBackend) checkDomainFrozen(_ctx context.Context, domain string) error { 215 + func (fs *FSBackend) checkDomainFrozen(ctx context.Context, domain string) error { 216 216 if _, err := fs.siteRoot.Stat(domainFrozenMarkerName(domain)); err == nil { 217 217 return ErrDomainFrozen 218 218 } else if !errors.Is(err, os.ErrNotExist) {
+13 -2
src/log.go
··· 4 4 "context" 5 5 "fmt" 6 6 "log/slog" 7 + "os" 7 8 "runtime" 8 9 "time" 9 10 ) ··· 25 26 // skip [runtime.Callers, this method, method calling this method] 26 27 runtime.Callers(3, pcs[:]) 27 28 28 - r := slog.NewRecord(time.Now(), level, msg, pcs[0]) 29 - logger.Handler().Handle(ctx, r) 29 + record := slog.NewRecord(time.Now(), level, msg, pcs[0]) 30 + logger.Handler().Handle(ctx, record) 30 31 } 31 32 32 33 func (l slogWithCtx) Print(ctx context.Context, v ...any) { ··· 40 41 func (l slogWithCtx) Println(ctx context.Context, v ...any) { 41 42 l.log(ctx, slog.LevelInfo, fmt.Sprintln(v...)) 42 43 } 44 + 45 + func (l slogWithCtx) Fatalf(ctx context.Context, format string, v ...any) { 46 + l.log(ctx, slog.LevelError, fmt.Sprintf(format, v...)) 47 + os.Exit(1) 48 + } 49 + 50 + func (l slogWithCtx) Fatalln(ctx context.Context, v ...any) { 51 + l.log(ctx, slog.LevelError, fmt.Sprintln(v...)) 52 + os.Exit(1) 53 + }
+70 -67
src/main.go
··· 27 27 var fallback http.Handler 28 28 var backend Backend 29 29 30 - func configureFeatures() (err error) { 30 + func configureFeatures(ctx context.Context) (err error) { 31 31 if len(config.Features) > 0 { 32 - log.Println("features:", strings.Join(config.Features, ", ")) 32 + logc.Println(ctx, "features:", strings.Join(config.Features, ", ")) 33 33 } 34 34 return 35 35 } 36 36 37 - func configureMemLimit() (err error) { 37 + func configureMemLimit(ctx context.Context) (err error) { 38 38 // Avoid being OOM killed by not garbage collecting early enough. 39 39 memlimitBefore := datasize.ByteSize(debug.SetMemoryLimit(-1)) 40 40 automemlimit.SetGoMemLimitWithOpts( ··· 49 49 ) 50 50 memlimitAfter := datasize.ByteSize(debug.SetMemoryLimit(-1)) 51 51 if memlimitBefore == memlimitAfter { 52 - log.Println("memlimit: now", memlimitBefore.HR()) 52 + logc.Println(ctx, "memlimit: now", memlimitBefore.HR()) 53 53 } else { 54 - log.Println("memlimit: was", memlimitBefore.HR(), "now", memlimitAfter.HR()) 54 + logc.Println(ctx, "memlimit: was", memlimitBefore.HR(), "now", memlimitAfter.HR()) 55 55 } 56 56 return 57 57 } 58 58 59 - func configureWildcards() (err error) { 59 + func configureWildcards(_ context.Context) (err error) { 60 60 newWildcards, err := TranslateWildcards(config.Wildcard) 61 61 if err != nil { 62 62 return err ··· 66 66 } 67 67 } 68 68 69 - func configureFallback() (err error) { 69 + func configureFallback(_ context.Context) (err error) { 70 70 if config.Fallback.ProxyTo != "" { 71 71 var fallbackURL *url.URL 72 72 fallbackURL, err = url.Parse(config.Fallback.ProxyTo) ··· 91 91 return 92 92 } 93 93 94 - func listen(name string, listen string) net.Listener { 94 + func listen(ctx context.Context, name string, listen string) net.Listener { 95 95 if listen == "-" { 96 96 return nil 97 97 } 98 98 99 99 protocol, address, ok := strings.Cut(listen, "/") 100 100 if !ok { 101 - log.Fatalf("%s: %s: malformed endpoint", name, listen) 101 + logc.Fatalf(ctx, "%s: %s: malformed endpoint", name, listen) 102 102 } 103 103 104 104 listener, err := net.Listen(protocol, address) 105 105 if err != nil { 106 - log.Fatalf("%s: %s\n", name, err) 106 + logc.Fatalf(ctx, "%s: %s\n", name, err) 107 107 } 108 108 109 109 return listener ··· 125 125 }) 126 126 } 127 127 128 - func serve(listener net.Listener, handler http.Handler) { 128 + func serve(ctx context.Context, listener net.Listener, handler http.Handler) { 129 129 if listener != nil { 130 130 handler = panicHandler(handler) 131 131 ··· 135 135 if config.Feature("serve-h2c") { 136 136 server.Protocols.SetUnencryptedHTTP2(true) 137 137 } 138 - log.Fatalln(server.Serve(listener)) 138 + logc.Fatalln(ctx, server.Serve(listener)) 139 139 } 140 140 } 141 141 ··· 146 146 case 1: 147 147 return arg 148 148 default: 149 - log.Fatalf("webroot argument must be either 'domain.tld' or 'domain.tld/dir") 149 + logc.Fatalln(context.Background(), 150 + "webroot argument must be either 'domain.tld' or 'domain.tld/dir") 150 151 return "" 151 152 } 152 153 } ··· 158 159 } else { 159 160 writer, err = os.Create(flag.Arg(0)) 160 161 if err != nil { 161 - log.Fatalln(err) 162 + logc.Fatalln(context.Background(), err) 162 163 } 163 164 } 164 165 return ··· 178 179 } 179 180 180 181 func Main() { 182 + ctx := context.Background() 183 + 181 184 flag.Usage = usage 182 185 printConfigEnvVars := flag.Bool("print-config-env-vars", false, 183 186 "print every recognized configuration environment variable and exit") ··· 226 229 cliOperations += 1 227 230 } 228 231 if cliOperations > 1 { 229 - log.Fatalln("-get-blob, -get-manifest, -get-archive, -update-site, -freeze, and -unfreeze are mutually exclusive") 232 + logc.Fatalln(ctx, "-get-blob, -get-manifest, -get-archive, -update-site, -freeze, and -unfreeze are mutually exclusive") 230 233 } 231 234 232 235 if *configTomlPath != "" && *noConfig { 233 - log.Fatalln("-no-config and -config are mutually exclusive") 236 + logc.Fatalln(ctx, "-no-config and -config are mutually exclusive") 234 237 } 235 238 236 239 if *printConfigEnvVars { ··· 243 246 *configTomlPath = "config.toml" 244 247 } 245 248 if config, err = Configure(*configTomlPath); err != nil { 246 - log.Fatalln("config:", err) 249 + logc.Fatalln(ctx, "config:", err) 247 250 } 248 251 249 252 if *printConfig { ··· 255 258 defer FiniObservability() 256 259 257 260 if err = errors.Join( 258 - configureFeatures(), 259 - configureMemLimit(), 260 - configureWildcards(), 261 - configureFallback(), 261 + configureFeatures(ctx), 262 + configureMemLimit(ctx), 263 + configureWildcards(ctx), 264 + configureFallback(ctx), 262 265 ); err != nil { 263 - log.Fatalln(err) 266 + logc.Fatalln(ctx, err) 264 267 } 265 268 266 269 switch { 267 270 case *runMigration != "": 268 271 if backend, err = CreateBackend(&config.Storage); err != nil { 269 - log.Fatalln(err) 272 + logc.Fatalln(ctx, err) 270 273 } 271 274 272 - if err := RunMigration(context.Background(), *runMigration); err != nil { 273 - log.Fatalln(err) 275 + if err := RunMigration(ctx, *runMigration); err != nil { 276 + logc.Fatalln(ctx, err) 274 277 } 275 278 276 279 case *getBlob != "": 277 280 if backend, err = CreateBackend(&config.Storage); err != nil { 278 - log.Fatalln(err) 281 + logc.Fatalln(ctx, err) 279 282 } 280 283 281 - reader, _, _, err := backend.GetBlob(context.Background(), *getBlob) 284 + reader, _, _, err := backend.GetBlob(ctx, *getBlob) 282 285 if err != nil { 283 - log.Fatalln(err) 286 + logc.Fatalln(ctx, err) 284 287 } 285 288 io.Copy(fileOutputArg(), reader) 286 289 287 290 case *getManifest != "": 288 291 if backend, err = CreateBackend(&config.Storage); err != nil { 289 - log.Fatalln(err) 292 + logc.Fatalln(ctx, err) 290 293 } 291 294 292 295 webRoot := webRootArg(*getManifest) 293 - manifest, _, err := backend.GetManifest(context.Background(), webRoot, GetManifestOptions{}) 296 + manifest, _, err := backend.GetManifest(ctx, webRoot, GetManifestOptions{}) 294 297 if err != nil { 295 - log.Fatalln(err) 298 + logc.Fatalln(ctx, err) 296 299 } 297 300 fmt.Fprintln(fileOutputArg(), ManifestDebugJSON(manifest)) 298 301 299 302 case *getArchive != "": 300 303 if backend, err = CreateBackend(&config.Storage); err != nil { 301 - log.Fatalln(err) 304 + logc.Fatalln(ctx, err) 302 305 } 303 306 304 307 webRoot := webRootArg(*getArchive) 305 308 manifest, manifestMtime, err := 306 - backend.GetManifest(context.Background(), webRoot, GetManifestOptions{}) 309 + backend.GetManifest(ctx, webRoot, GetManifestOptions{}) 307 310 if err != nil { 308 - log.Fatalln(err) 311 + logc.Fatalln(ctx, err) 309 312 } 310 - CollectTar(context.Background(), fileOutputArg(), manifest, manifestMtime) 313 + CollectTar(ctx, fileOutputArg(), manifest, manifestMtime) 311 314 312 315 case *updateSite != "": 313 316 if backend, err = CreateBackend(&config.Storage); err != nil { 314 - log.Fatalln(err) 317 + logc.Fatalln(ctx, err) 315 318 } 316 319 317 320 if flag.NArg() != 1 { 318 - log.Fatalln("update source must be provided as the argument") 321 + logc.Fatalln(ctx, "update source must be provided as the argument") 319 322 } 320 323 321 324 sourceURL, err := url.Parse(flag.Arg(0)) 322 325 if err != nil { 323 - log.Fatalln(err) 326 + logc.Fatalln(ctx, err) 324 327 } 325 328 326 329 var result UpdateResult 327 330 if sourceURL.Scheme == "" { 328 331 file, err := os.Open(sourceURL.Path) 329 332 if err != nil { 330 - log.Fatalln(err) 333 + logc.Fatalln(ctx, err) 331 334 } 332 335 defer file.Close() 333 336 ··· 346 349 } 347 350 348 351 webRoot := webRootArg(*updateSite) 349 - result = UpdateFromArchive(context.Background(), webRoot, contentType, file) 352 + result = UpdateFromArchive(ctx, webRoot, contentType, file) 350 353 } else { 351 354 branch := "pages" 352 355 if sourceURL.Fragment != "" { ··· 354 357 } 355 358 356 359 webRoot := webRootArg(*updateSite) 357 - result = UpdateFromRepository(context.Background(), webRoot, sourceURL.String(), branch) 360 + result = UpdateFromRepository(ctx, webRoot, sourceURL.String(), branch) 358 361 } 359 362 360 363 switch result.outcome { 361 364 case UpdateError: 362 - log.Printf("error: %s\n", result.err) 365 + logc.Printf(ctx, "error: %s\n", result.err) 363 366 os.Exit(2) 364 367 case UpdateTimeout: 365 - log.Println("timeout") 368 + logc.Println(ctx, "timeout") 366 369 os.Exit(1) 367 370 case UpdateCreated: 368 - log.Println("created") 371 + logc.Println(ctx, "created") 369 372 case UpdateReplaced: 370 - log.Println("replaced") 373 + logc.Println(ctx, "replaced") 371 374 case UpdateDeleted: 372 - log.Println("deleted") 375 + logc.Println(ctx, "deleted") 373 376 case UpdateNoChange: 374 - log.Println("no-change") 377 + logc.Println(ctx, "no-change") 375 378 } 376 379 377 380 case *freezeDomain != "" || *unfreezeDomain != "": ··· 386 389 } 387 390 388 391 if backend, err = CreateBackend(&config.Storage); err != nil { 389 - log.Fatalln(err) 392 + logc.Fatalln(ctx, err) 390 393 } 391 394 392 - if err = backend.FreezeDomain(context.Background(), domain, freeze); err != nil { 393 - log.Fatalln(err) 395 + if err = backend.FreezeDomain(ctx, domain, freeze); err != nil { 396 + logc.Fatalln(ctx, err) 394 397 } 395 398 if freeze { 396 399 log.Println("frozen") ··· 408 411 // The backend is not recreated (this is intentional as it allows preserving the cache). 409 412 OnReload(func() { 410 413 if newConfig, err := Configure(*configTomlPath); err != nil { 411 - log.Println("config: reload err:", err) 414 + logc.Println(ctx, "config: reload err:", err) 412 415 } else { 413 416 // From https://go.dev/ref/mem: 414 417 // > A read r of a memory location x holding a value that is not larger than ··· 418 421 // > concurrent write. 419 422 config = newConfig 420 423 if err = errors.Join( 421 - configureFeatures(), 422 - configureMemLimit(), 423 - configureWildcards(), 424 - configureFallback(), 424 + configureFeatures(ctx), 425 + configureMemLimit(ctx), 426 + configureWildcards(ctx), 427 + configureFallback(ctx), 425 428 ); err != nil { 426 429 // At this point the configuration is in an in-between, corrupted state, so 427 430 // the only reasonable choice is to crash. 428 - log.Fatalln("config: reload fail:", err) 431 + logc.Fatalln(ctx, "config: reload fail:", err) 429 432 } else { 430 - log.Println("config: reload ok") 433 + logc.Println(ctx, "config: reload ok") 431 434 } 432 435 } 433 436 }) ··· 436 439 // spends some time initializing (which the S3 backend does) a proxy like Caddy can race 437 440 // with git-pages on startup and return errors for requests that would have been served 438 441 // just 0.5s later. 439 - pagesListener := listen("pages", config.Server.Pages) 440 - caddyListener := listen("caddy", config.Server.Caddy) 441 - metricsListener := listen("metrics", config.Server.Metrics) 442 + pagesListener := listen(ctx, "pages", config.Server.Pages) 443 + caddyListener := listen(ctx, "caddy", config.Server.Caddy) 444 + metricsListener := listen(ctx, "metrics", config.Server.Metrics) 442 445 443 446 if backend, err = CreateBackend(&config.Storage); err != nil { 444 - log.Fatalln(err) 447 + logc.Fatalln(ctx, err) 445 448 } 446 449 backend = NewObservedBackend(backend) 447 450 448 - go serve(pagesListener, ObserveHTTPHandler(http.HandlerFunc(ServePages))) 449 - go serve(caddyListener, ObserveHTTPHandler(http.HandlerFunc(ServeCaddy))) 450 - go serve(metricsListener, promhttp.Handler()) 451 + go serve(ctx, pagesListener, ObserveHTTPHandler(http.HandlerFunc(ServePages))) 452 + go serve(ctx, caddyListener, ObserveHTTPHandler(http.HandlerFunc(ServeCaddy))) 453 + go serve(ctx, metricsListener, promhttp.Handler()) 451 454 452 455 if config.Insecure { 453 - log.Println("serve: ready (INSECURE)") 456 + logc.Println(ctx, "serve: ready (INSECURE)") 454 457 } else { 455 - log.Println("serve: ready") 458 + logc.Println(ctx, "serve: ready") 456 459 } 457 460 458 461 WaitForInterrupt() 459 - log.Println("serve: exiting") 462 + logc.Println(ctx, "serve: exiting") 460 463 } 461 464 }