ci: migrate .golangci.yml to v2 schema + only-new-issues gate

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:
2026-05-03 15:00:24 +01:00
parent 8b57b8a06d
commit 41c3ec7c6f
6 changed files with 56 additions and 47 deletions
+1 -4
View File
@@ -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))
}
+16 -18
View File
@@ -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
}
+7 -7
View File
@@ -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) {