ci: migrate .golangci.yml to v2 schema + only-new-issues gate
CI / Test (linux/amd64) (pull_request) Successful in 29s
CI / Lint (pull_request) Failing after 16s
CI / Build (windows/amd64) (pull_request) Successful in 20s
CI / Build (linux/amd64) (pull_request) Successful in 20s
CI / Build (linux/arm64) (pull_request) Successful in 21s
CI / Test (linux/amd64) (pull_request) Successful in 29s
CI / Lint (pull_request) Failing after 16s
CI / Build (windows/amd64) (pull_request) Successful in 20s
CI / Build (linux/amd64) (pull_request) Successful in 20s
CI / Build (linux/arm64) (pull_request) Successful in 21s
The bump from golangci-lint-action@v6 → v7 (which downloads the v2.x binary) was blocking CI lint with 'unsupported version of the configuration: ""' because .golangci.yml was still in the v1 schema. Migrate the config to v2: * version: "2" prelude * disable-all → default: none * linters-settings → linters.settings * gofumpt + goimports move into formatters.enable + formatters.settings * exclude-rules move into linters.exclusions.rules * gosimple drops (folded into staticcheck in v2) Fix the four lint hits in the new P2R-02 code: * host_bandwidth.go: convert hostBandwidthRequest directly to hostBandwidthView via type conversion (S1016) * ui_repo.go: drop unparam savedSection + status arguments from renderRepoPage (always "" / always 422 — split GET render from validation-fail render) * ui_schedules.go: gofumpt formatting on the scheduleEditPage struct Add only-new-issues: true to the lint job. The repo carries ~90 pre-existing findings (gofumpt drift × 31, misspell × 25, missing godoc × 10, bodyclose × 6, errcheck × 12, …) accumulated before lint was actually wired into CI. Without this gate, every PR would fail on baseline noise instead of its own changes. Track the cleanup as X-06 in tasks.md so the gate is temporary.
This commit is contained in:
@@ -41,6 +41,13 @@ jobs:
|
||||
# Bumping to a v2.x release built against current Go.
|
||||
version: v2.1.6
|
||||
args: --timeout=5m
|
||||
# Only flag issues introduced by the PR. The repo carries
|
||||
# ~90 pre-existing findings (mostly missing godoc comments
|
||||
# + gofumpt drift + misspell) accumulated before lint was
|
||||
# actually wired into CI; cleaning them up is its own piece
|
||||
# of work tracked separately. Without this, every PR fails
|
||||
# on baseline noise instead of its own changes.
|
||||
only-new-issues: true
|
||||
|
||||
build:
|
||||
name: Build (${{ matrix.goos }}/${{ matrix.goarch }})
|
||||
|
||||
+24
-18
@@ -1,18 +1,17 @@
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
tests: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
||||
- gofumpt
|
||||
- goimports
|
||||
- misspell
|
||||
- revive
|
||||
- bodyclose
|
||||
@@ -21,22 +20,29 @@ linters:
|
||||
- prealloc
|
||||
- unconvert
|
||||
- unparam
|
||||
|
||||
linters-settings:
|
||||
goimports:
|
||||
local-prefixes: gitea.dcglab.co.uk/steve/restic-manager
|
||||
revive:
|
||||
settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: exported
|
||||
arguments: ["disableStutteringCheck"]
|
||||
misspell:
|
||||
locale: US
|
||||
exclusions:
|
||||
rules:
|
||||
- name: exported
|
||||
arguments: ["disableStutteringCheck"]
|
||||
misspell:
|
||||
locale: US
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- unparam
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- gitea.dcglab.co.uk/steve/restic-manager
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- unparam
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
@@ -58,8 +58,5 @@ func (s *Server) handleUpdateHostBandwidth(w stdhttp.ResponseWriter, r *stdhttp.
|
||||
writeJSONError(w, stdhttp.StatusInternalServerError, "internal", err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, stdhttp.StatusOK, hostBandwidthView{
|
||||
BandwidthUpKBps: req.BandwidthUpKBps,
|
||||
BandwidthDownKBps: req.BandwidthDownKBps,
|
||||
})
|
||||
writeJSON(w, stdhttp.StatusOK, hostBandwidthView(req))
|
||||
}
|
||||
|
||||
@@ -153,25 +153,23 @@ func (s *Server) handleUIHostRepo(w stdhttp.ResponseWriter, r *stdhttp.Request)
|
||||
}
|
||||
|
||||
// renderRepoFormError loads the page state, overlays the section's
|
||||
// error / saved marker, and renders. Returns an HTTP status (422 for
|
||||
// validation, 200 for success).
|
||||
func (s *Server) renderRepoPage(w stdhttp.ResponseWriter, r *stdhttp.Request, u *ui.User, host *store.Host, savedSection, credErr, bwErr, mntErr string, status int) {
|
||||
// error banner, and renders with a 422. Save-success goes through a
|
||||
// 303 redirect with `?saved=<section>` instead, so this path is for
|
||||
// validation failures only.
|
||||
func (s *Server) renderRepoPage(w stdhttp.ResponseWriter, r *stdhttp.Request, u *ui.User, host *store.Host, credErr, bwErr, mntErr string) {
|
||||
page, err := s.loadHostRepoPage(r, *host)
|
||||
if err != nil {
|
||||
slog.Error("ui repo: reload after save", "host_id", host.ID, "err", err)
|
||||
stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
page.SavedSection = savedSection
|
||||
page.CredentialsError = credErr
|
||||
page.BandwidthError = bwErr
|
||||
page.MaintenanceError = mntErr
|
||||
view := s.baseView(u, "dashboard")
|
||||
view.Title = host.Name + " repo · restic-manager"
|
||||
view.Page = *page
|
||||
if status != stdhttp.StatusOK {
|
||||
w.WriteHeader(status)
|
||||
}
|
||||
w.WriteHeader(stdhttp.StatusUnprocessableEntity)
|
||||
if err := s.deps.UI.Render(w, "host_repo", view); err != nil {
|
||||
slog.Error("ui: render host_repo", "err", err)
|
||||
}
|
||||
@@ -200,7 +198,7 @@ func (s *Server) handleUIRepoCredentialsSave(w stdhttp.ResponseWriter, r *stdhtt
|
||||
repoPass := r.PostForm.Get("repo_password") // do NOT trim — operators may use trailing space deliberately
|
||||
|
||||
if repoURL == "" {
|
||||
s.renderRepoPage(w, r, u, host, "", "Repo URL is required.", "", "", stdhttp.StatusUnprocessableEntity)
|
||||
s.renderRepoPage(w, r, u, host, "Repo URL is required.", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -217,9 +215,9 @@ func (s *Server) handleUIRepoCredentialsSave(w stdhttp.ResponseWriter, r *stdhtt
|
||||
existing.RepoPassword = repoPass
|
||||
}
|
||||
if existing.RepoPassword == "" {
|
||||
s.renderRepoPage(w, r, u, host, "",
|
||||
s.renderRepoPage(w, r, u, host,
|
||||
"No password on file yet — set one before saving the URL/username.",
|
||||
"", "", stdhttp.StatusUnprocessableEntity)
|
||||
"", "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -258,9 +256,9 @@ func (s *Server) handleUIRepoBandwidthSave(w stdhttp.ResponseWriter, r *stdhttp.
|
||||
up, upErr := parseOptionalNonNegInt(r.PostForm.Get("bandwidth_up"))
|
||||
down, downErr := parseOptionalNonNegInt(r.PostForm.Get("bandwidth_down"))
|
||||
if upErr != nil || downErr != nil {
|
||||
s.renderRepoPage(w, r, u, host, "", "",
|
||||
s.renderRepoPage(w, r, u, host, "",
|
||||
"Bandwidth caps must be non-negative whole numbers (or blank for no cap).",
|
||||
"", stdhttp.StatusUnprocessableEntity)
|
||||
"")
|
||||
return
|
||||
}
|
||||
if err := s.deps.Store.SetHostBandwidth(r.Context(), host.ID, up, down); err != nil {
|
||||
@@ -296,20 +294,20 @@ func (s *Server) handleUIRepoMaintenanceSave(w stdhttp.ResponseWriter, r *stdhtt
|
||||
"forget": forgetCron, "prune": pruneCron, "check": checkCron,
|
||||
} {
|
||||
if expr == "" {
|
||||
s.renderRepoPage(w, r, u, host, "", "", "",
|
||||
label+" cadence is required.", stdhttp.StatusUnprocessableEntity)
|
||||
s.renderRepoPage(w, r, u, host, "", "",
|
||||
label+" cadence is required.")
|
||||
return
|
||||
}
|
||||
if _, err := cronParser.Parse(expr); err != nil {
|
||||
s.renderRepoPage(w, r, u, host, "", "", "",
|
||||
label+" cadence didn't parse: "+err.Error(), stdhttp.StatusUnprocessableEntity)
|
||||
s.renderRepoPage(w, r, u, host, "", "",
|
||||
label+" cadence didn't parse: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
subset, err := strconv.Atoi(subsetStr)
|
||||
if err != nil || subset < 0 || subset > 100 {
|
||||
s.renderRepoPage(w, r, u, host, "", "", "",
|
||||
"check subset % must be between 0 and 100.", stdhttp.StatusUnprocessableEntity)
|
||||
s.renderRepoPage(w, r, u, host, "", "",
|
||||
"check subset % must be between 0 and 100.")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ type scheduleFormData struct {
|
||||
// scheduleEditPage backs both the new and edit form views.
|
||||
type scheduleEditPage struct {
|
||||
hostChromeData
|
||||
IsNew bool
|
||||
ScheduleID string // empty when IsNew
|
||||
Form scheduleFormData
|
||||
AvailableGroups []store.SourceGroup
|
||||
SelectedGroupIDs map[string]bool // gid → checked
|
||||
SaveAction string
|
||||
Error string
|
||||
IsNew bool
|
||||
ScheduleID string // empty when IsNew
|
||||
Form scheduleFormData
|
||||
AvailableGroups []store.SourceGroup
|
||||
SelectedGroupIDs map[string]bool // gid → checked
|
||||
SaveAction string
|
||||
Error string
|
||||
}
|
||||
|
||||
func (s *Server) handleUISchedulesList(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
||||
|
||||
@@ -273,3 +273,4 @@ Sizes: **S** = under a day, **M** = 1–3 days, **L** = 3–7 days.
|
||||
- [ ] **X-03** Periodic dependency updates (`dependabot` or `renovate`)
|
||||
- [ ] **X-04** Threat-model review at end of each phase
|
||||
- [ ] **X-05** Proper first-run onboarding UI: admin shouldn't need to `curl` `/api/bootstrap` by hand. Render the bootstrap form on the same login page (extra "setup token" field shown only while no admin user exists, hidden after); on submit POST to `/api/bootstrap`, then drop straight into a session. Surface the one-time token from the server log somewhere copy-able (or print a clickable URL with the token in the query string at first-run). Also: relax the 12-char password floor for the first-run path or document it in the form so `admin` doesn't silently fail validation.
|
||||
- [ ] **X-06** Lint-baseline cleanup pass. `.golangci.yml` is now on the v2 schema; CI is gated with `only-new-issues: true` because the repo carries ~90 pre-existing findings (gofumpt drift × 31, misspell × 25, missing godoc on exported consts × 10, bodyclose × 6, errcheck × 12, errorlint/nilerr/unused × handful) accumulated before lint was actually wired into CI. Drive the count to zero in a dedicated PR (mostly mechanical: `gofumpt -w .`, fix typos, add comments, audit nilerr cases since those *might* be real bugs), then drop `only-new-issues: true` so future regressions are caught at the source.
|
||||
|
||||
Reference in New Issue
Block a user