All checks were successful
Release / Scraper / Test (push) Successful in 10s
Release / UI / Build (push) Successful in 26s
Release / v2 / Build ui-v2 (push) Successful in 17s
Release / Scraper / Docker (push) Successful in 47s
Release / UI / Docker (push) Successful in 56s
CI / Scraper / Lint (pull_request) Successful in 7s
CI / Scraper / Test (pull_request) Successful in 8s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
Release / v2 / Docker / ui-v2 (push) Successful in 56s
Release / v2 / Test backend (push) Successful in 4m35s
iOS CI / Build (pull_request) Successful in 4m28s
Release / v2 / Docker / backend (push) Successful in 1m29s
Release / v2 / Docker / runner (push) Successful in 1m39s
iOS CI / Test (pull_request) Successful in 9m51s
- backend/: Go API server and runner binaries with PocketBase + MinIO storage - ui-v2/: SvelteKit frontend rewrite - docker-compose-new.yml: compose file for the v2 stack - .gitea/workflows/release-v2.yaml: CI/CD for backend, runner, and ui-v2 Docker Hub images - scripts/pb-init.sh: migrate from wget to curl, add superuser bootstrap for fresh installs - .env.example: document DOCKER_BUILDKIT=1 for Colima users
90 lines
2.4 KiB
Go
90 lines
2.4 KiB
Go
// healthcheck is a static binary used by Docker HEALTHCHECK CMD in distroless
|
|
// images (which have no shell, wget, or curl).
|
|
//
|
|
// Two modes:
|
|
//
|
|
// 1. HTTP mode (default):
|
|
// /healthcheck <url>
|
|
// Performs GET <url>; exits 0 if HTTP 2xx/3xx, 1 otherwise.
|
|
// Example: /healthcheck http://localhost:8080/health
|
|
//
|
|
// 2. File-liveness mode:
|
|
// /healthcheck file <path> <max_age_seconds>
|
|
// Reads <path>, parses its content as RFC3339 timestamp, and exits 1 if the
|
|
// timestamp is older than <max_age_seconds>. Used by the runner service which
|
|
// writes /tmp/runner.alive on every successful poll.
|
|
// Example: /healthcheck file /tmp/runner.alive 120
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) > 1 && os.Args[1] == "file" {
|
|
checkFile()
|
|
return
|
|
}
|
|
checkHTTP()
|
|
}
|
|
|
|
// checkHTTP performs a GET request and exits 0 on success, 1 on failure.
|
|
func checkHTTP() {
|
|
url := "http://localhost:8080/health"
|
|
if len(os.Args) > 1 {
|
|
url = os.Args[1]
|
|
}
|
|
resp, err := http.Get(url) //nolint:gosec,noctx
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "healthcheck: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
fmt.Fprintf(os.Stderr, "healthcheck: status %d\n", resp.StatusCode)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// checkFile reads a timestamp from a file and exits 1 if it is older than the
|
|
// given max age. Usage: /healthcheck file <path> <max_age_seconds>
|
|
func checkFile() {
|
|
if len(os.Args) < 4 {
|
|
fmt.Fprintln(os.Stderr, "healthcheck file: usage: /healthcheck file <path> <max_age_seconds>")
|
|
os.Exit(1)
|
|
}
|
|
path := os.Args[2]
|
|
maxAgeSec, err := strconv.ParseInt(os.Args[3], 10, 64)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "healthcheck file: invalid max_age_seconds %q: %v\n", os.Args[3], err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "healthcheck file: cannot read %s: %v\n", path, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ts, err := time.Parse(time.RFC3339, string(data))
|
|
if err != nil {
|
|
// Fallback: use file mtime if content is not a valid timestamp.
|
|
info, statErr := os.Stat(path)
|
|
if statErr != nil {
|
|
fmt.Fprintf(os.Stderr, "healthcheck file: cannot stat %s: %v\n", path, statErr)
|
|
os.Exit(1)
|
|
}
|
|
ts = info.ModTime()
|
|
}
|
|
|
|
age := time.Since(ts)
|
|
if age > time.Duration(maxAgeSec)*time.Second {
|
|
fmt.Fprintf(os.Stderr, "healthcheck file: %s is %.0fs old (max %ds)\n", path, age.Seconds(), maxAgeSec)
|
|
os.Exit(1)
|
|
}
|
|
}
|