From 94a665738dce2e587715ffca5947c8e957907ebd Mon Sep 17 00:00:00 2001 From: Aaro Koinsaari <89689072+koinsaari@users.noreply.github.com> Date: Fri, 29 May 2026 19:12:05 +0300 Subject: [PATCH 1/3] ci: add dependabot with auto-merge --- .github/dependabot.yml | 29 +++++++++++++++++++++ .github/workflows/dependabot-auto-merge.yml | 25 ++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b1379b1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +version: 2 + +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + day: saturday + groups: + go-dependencies: + update-types: + - minor + - patch + labels: + - dependencies + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: saturday + groups: + github-actions: + update-types: + - minor + - patch + labels: + - dependencies + - area:ci diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..2dc7192 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,25 @@ +name: Dependabot auto-merge + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge for non-major updates + if: steps.meta.outputs.update-type != 'version-update:semver-major' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3d5b4612c6b066148a7a510735e5bb162011e63c Mon Sep 17 00:00:00 2001 From: Aaro Koinsaari <89689072+koinsaari@users.noreply.github.com> Date: Fri, 29 May 2026 22:35:09 +0300 Subject: [PATCH 2/3] ci: fix lint, docker, and gitleaks checks - Migrate .golangci.yml to v2 schema (golangci-lint v2 rejects v1 config) - Add Dockerfile (static build on distroless) so the docker-build job runs - Add .gitleaks.toml allowlisting dummy credentials in *_test.go files - Set ReadHeaderTimeout on the HTTP server (gosec G114) - Comment the blank sqlite driver import (revive) and apply gofmt Co-Authored-By: Claude Opus 4.7 --- .gitleaks.toml | 8 +++ .golangci.yml | 50 ++++++++++++------- Dockerfile | 12 +++++ cmd/api-proxy/main.go | 8 ++- internal/auth/jwt_test.go | 6 +-- internal/auth/lockout_test.go | 2 +- internal/auth/login.go | 1 - internal/auth/login_test.go | 2 +- internal/auth/service.go | 8 +-- .../clients/jellyfin/quickconnect_test.go | 2 +- internal/db/db.go | 2 +- 11 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 .gitleaks.toml create mode 100644 Dockerfile diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..f624b88 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,8 @@ +[extend] +useDefault = true + +[[allowlists]] +description = "Dummy credentials in Go unit tests" +paths = [ + '''_test\.go$''', +] diff --git a/.golangci.yml b/.golangci.yml index 4bcfd88..5021a0f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,28 +1,44 @@ +version: "2" run: - timeout: 5m go: "1.26" - linters: - disable-all: true + default: none enable: + - bodyclose - errcheck + - gosec - govet - ineffassign + - misspell + - nilerr + - revive + - rowserrcheck - staticcheck - unused + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - gosec + path: _test\.go + paths: + - internal/gen + - third_party$ + - builtin$ + - examples$ +formatters: + enable: - gofmt - goimports - - gosec - - misspell - - revive - - bodyclose - - nilerr - - rowserrcheck - -issues: - exclude-dirs: - - internal/gen - exclude-rules: - - path: _test\.go - linters: - - gosec + exclusions: + generated: lax + paths: + - internal/gen + - third_party$ + - builtin$ + - examples$ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c2cf0e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.26 AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/api-proxy ./cmd/api-proxy + +FROM gcr.io/distroless/static-debian13:nonroot +COPY --from=build /out/api-proxy /api-proxy +EXPOSE 8080 +USER nonroot:nonroot +ENTRYPOINT ["/api-proxy"] diff --git a/cmd/api-proxy/main.go b/cmd/api-proxy/main.go index 73322c7..3ede56b 100644 --- a/cmd/api-proxy/main.go +++ b/cmd/api-proxy/main.go @@ -4,6 +4,7 @@ import ( "log/slog" "net/http" "os" + "time" ) func healthz(w http.ResponseWriter, _ *http.Request) { @@ -20,7 +21,12 @@ func main() { } logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) logger.Info("api-proxy starting", "addr", addr) - if err := http.ListenAndServe(addr, mux); err != nil { + srv := &http.Server{ + Addr: addr, + Handler: mux, + ReadHeaderTimeout: 10 * time.Second, + } + if err := srv.ListenAndServe(); err != nil { logger.Error("server exited", "err", err) os.Exit(1) } diff --git a/internal/auth/jwt_test.go b/internal/auth/jwt_test.go index 09b5c14..b340966 100644 --- a/internal/auth/jwt_test.go +++ b/internal/auth/jwt_test.go @@ -8,9 +8,9 @@ import ( func newTestService(t *testing.T) *Service { t.Helper() return NewService(Options{ - SignKey: []byte("01234567890123456789012345678901"), - Clock: func() time.Time { return time.Unix(1_700_000_000, 0) }, - AccessTTL: time.Hour, + SignKey: []byte("01234567890123456789012345678901"), + Clock: func() time.Time { return time.Unix(1_700_000_000, 0) }, + AccessTTL: time.Hour, }) } diff --git a/internal/auth/lockout_test.go b/internal/auth/lockout_test.go index e0db285..14b6616 100644 --- a/internal/auth/lockout_test.go +++ b/internal/auth/lockout_test.go @@ -23,7 +23,7 @@ func newLockoutSvc(t *testing.T, now time.Time) *Service { d := openTestDB(t) return NewService(Options{ DB: d.DB, - SignKey: []byte("01234567890123456789012345678901"), + SignKey: []byte("01234567890123456789012345678901"), Clock: func() time.Time { return now }, }) } diff --git a/internal/auth/login.go b/internal/auth/login.go index 825cc56..cda2155 100644 --- a/internal/auth/login.go +++ b/internal/auth/login.go @@ -119,4 +119,3 @@ func sha256Hex(s string) string { sum := sha256.Sum256([]byte(s)) return hex.EncodeToString(sum[:]) } - diff --git a/internal/auth/login_test.go b/internal/auth/login_test.go index a81f5d1..ee5d7fb 100644 --- a/internal/auth/login_test.go +++ b/internal/auth/login_test.go @@ -28,7 +28,7 @@ func newLoginSvc(t *testing.T, jf JellyfinAuthenticator) *Service { return NewService(Options{ DB: d.DB, Jellyfin: jf, - SignKey: []byte("01234567890123456789012345678901"), + SignKey: []byte("01234567890123456789012345678901"), Clock: func() time.Time { return time.Unix(1_700_000_000, 0) }, }) } diff --git a/internal/auth/service.go b/internal/auth/service.go index a9bc9bf..36d6848 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -37,10 +37,10 @@ type Service struct { type Options struct { DB *sql.DB Jellyfin JellyfinAuthenticator - SignKey []byte - Clock Clock - AccessTTL time.Duration - RefreshTTL time.Duration + SignKey []byte + Clock Clock + AccessTTL time.Duration + RefreshTTL time.Duration } func NewService(opts Options) *Service { diff --git a/internal/clients/jellyfin/quickconnect_test.go b/internal/clients/jellyfin/quickconnect_test.go index e25d039..8ad3d7c 100644 --- a/internal/clients/jellyfin/quickconnect_test.go +++ b/internal/clients/jellyfin/quickconnect_test.go @@ -39,7 +39,7 @@ func TestQuickConnectAuthenticate_Approved(t *testing.T) { } _ = json.NewEncoder(w).Encode(map[string]any{ "AccessToken": "tok-qc", - "User": map[string]any{"Id": "jf-user-1", "Name": "alice"}, + "User": map[string]any{"Id": "jf-user-1", "Name": "alice"}, }) })) defer s.Close() diff --git a/internal/db/db.go b/internal/db/db.go index 51d0155..b105e9e 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -6,7 +6,7 @@ import ( "embed" "fmt" - _ "modernc.org/sqlite" + _ "modernc.org/sqlite" // registers the "sqlite" database/sql driver ) //go:embed migrations/*.sql From 899c5566f1795cd2e9773f893de8eae7ced35d91 Mon Sep 17 00:00:00 2001 From: Aaro Koinsaari <89689072+koinsaari@users.noreply.github.com> Date: Fri, 29 May 2026 22:46:48 +0300 Subject: [PATCH 3/3] ci: pin Dockerfile base image digests and track via Dependabot Pin golang and distroless base images to digests for reproducible builds, and add the docker ecosystem to dependabot.yml so the digests stay current. Co-Authored-By: Claude Opus 4.7 --- .github/dependabot.yml | 13 +++++++++++++ Dockerfile | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b1379b1..d501674 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,6 +14,19 @@ updates: labels: - dependencies + - package-ecosystem: docker + directory: / + schedule: + interval: weekly + day: saturday + groups: + docker-images: + update-types: + - minor + - patch + labels: + - dependencies + - package-ecosystem: github-actions directory: / schedule: diff --git a/Dockerfile b/Dockerfile index c2cf0e0..5b4250f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM golang:1.26 AS build +FROM golang:1.26@sha256:2d6c80227255c3112a4d08e67ba98e58efd3846daf15d9d7d4c389565d881b1a AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/api-proxy ./cmd/api-proxy -FROM gcr.io/distroless/static-debian13:nonroot +FROM gcr.io/distroless/static-debian13:nonroot@sha256:963fa6c544fe5ce420f1f54fb88b6fb01479f054c8056d0f74cc2c6000df5240 COPY --from=build /out/api-proxy /api-proxy EXPOSE 8080 USER nonroot:nonroot