From 4d4806e19451ee97bd513a2dd717d910d4a4550e Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 26 Sep 2025 19:32:40 +0100 Subject: [PATCH 1/5] upgrade golangci-lint to v2 --- .golangci.bck.yml | 28 +++++++ .golangci.yml | 78 ++++++++++++++++++-- Makefile | 4 +- batt/battery.go | 5 +- commands_display.go | 2 +- commands_generate.go | 1 + complete_test.go | 1 + config/config.go | 4 +- config/config_test.go | 1 + config/group.go | 2 +- config/info.go | 7 +- config/info_customizer.go | 5 +- config/jsonschema/model.go | 15 +++- config/jsonschema/schema_test.go | 6 +- config/profile.go | 121 ++++++++++++++++--------------- config/schedule.go | 8 +- config/show_test.go | 3 +- config/template.go | 2 + crond/crontab_test.go | 7 +- filesearch/filesearch.go | 5 +- integration_test.go | 2 +- lock/lock.go | 4 +- lock/lock_test.go | 6 +- main_test.go | 9 ++- monitor/hook/context.go | 1 + monitor/status/profile.go | 1 + own_command_error_test.go | 2 +- own_commands.go | 1 + priority/prority_test.go | 6 +- restic/commands.go | 1 + restic/downloader_test.go | 1 - restic/generator/main.go | 7 +- schedule/handler_systemd.go | 8 +- schedule/handler_windows.go | 5 +- shell/command_test.go | 6 +- systemd/generate.go | 1 + util/dotenv.go | 2 +- 37 files changed, 261 insertions(+), 107 deletions(-) create mode 100644 .golangci.bck.yml diff --git a/.golangci.bck.yml b/.golangci.bck.yml new file mode 100644 index 00000000..f7c624f8 --- /dev/null +++ b/.golangci.bck.yml @@ -0,0 +1,28 @@ +linters: + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - errname + - gocheckcompilerdirectives + - gosec + # - maintidx + - misspell + - nilnil + - noctx + - nolintlint + - predeclared + - reassign + - sloglint + - spancheck + - unconvert + - unparam + - usestdlibvars + +linters-settings: + gosec: + excludes: + - G204 # Audit the use of command execution + - G404 # Insecure random number source (rand) diff --git a/.golangci.yml b/.golangci.yml index f7c624f8..bf71fe61 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,28 +1,90 @@ +version: "2" linters: enable: - asasalint - asciicheck - bidichk - bodyclose + - canonicalheader + - containedctx - contextcheck + - copyloopvar + - decorder + - durationcheck + - embeddedstructfieldcheck + # - err113 + - errcheck + - errchkjson - errname + # - errorlint + - exptostd + - fatcontext + # - forcetypeassert - gocheckcompilerdirectives + - gochecksumtype + # - gocritic + # - godoclint + - goheader + - gomoddirectives - gosec - # - maintidx + - govet + - grouper + - iface + - importas + - inamedparam + - ineffassign + # - intrange + # - ireturn + - loggercheck + - makezero + - mirror - misspell + - nilerr + - nilnesserr - nilnil - - noctx + # - nlreturn + # - noctx - nolintlint + - nosprintfhostport + # - perfsprint + - prealloc - predeclared + - promlinter - reassign + # - revive + - rowserrcheck - sloglint - spancheck + - staticcheck + - testableexamples + # - testifylint + - thelper - unconvert - unparam + - unused - usestdlibvars - -linters-settings: - gosec: - excludes: - - G204 # Audit the use of command execution - - G404 # Insecure random number source (rand) + # - usetesting + - wastedassign + settings: + gosec: + excludes: + - G204 # Audit use of command execution + - G404 # Insecure random number source (rand) + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 93240909..bc847dc6 100644 --- a/Makefile +++ b/Makefile @@ -94,11 +94,11 @@ $(GOBIN)/github-markdown-toc.go: verify $(GOBIN)/eget $(GOBIN)/mockery: verify $(GOBIN)/eget @echo "[*] $@" - "$(GOBIN)/eget" vektra/mockery --upgrade-only --to '$(GOBIN)' + "$(GOBIN)/eget" vektra/mockery --tag v3.5.5 --upgrade-only --to '$(GOBIN)' $(GOBIN)/golangci-lint: verify $(GOBIN)/eget @echo "[*] $@" - "$(GOBIN)/eget" golangci/golangci-lint --tag v1.64.8 --asset=tar.gz --upgrade-only --to '$(GOBIN)' + "$(GOBIN)/eget" golangci/golangci-lint --tag v2.5.0 --asset=tar.gz --upgrade-only --to '$(GOBIN)' $(GOBIN)/hugo: $(GOBIN)/eget @echo "[*] $@" diff --git a/batt/battery.go b/batt/battery.go index 6c2d612b..786dc549 100644 --- a/batt/battery.go +++ b/batt/battery.go @@ -78,9 +78,10 @@ func detectRunningOnBattery(batteries []battery.Battery) bool { pluggedIn := false discharging := false for _, bat := range batteries { - if bat.State.Raw == battery.Discharging || bat.State.Raw == battery.Empty { + switch bat.State.Raw { + case battery.Discharging, battery.Empty: discharging = true - } else if bat.State.Raw == battery.Charging || bat.State.Raw == battery.Full || bat.State.Raw == battery.Idle || bat.State.Raw == battery.Unknown { + case battery.Charging, battery.Full, battery.Idle, battery.Unknown: pluggedIn = true } } diff --git a/commands_display.go b/commands_display.go index 6c3f9657..15d13f0e 100644 --- a/commands_display.go +++ b/commands_display.go @@ -367,7 +367,7 @@ func newLineLengthWriter(writer io.Writer, maxLineLength int) *lineLengthWriter } func (l *lineLengthWriter) Write(p []byte) (n int, err error) { - written := 0 + var written int inAnsi := false offset := l.lineLength lineLength := func() int { return l.lineLength - l.ansiLength } diff --git a/commands_generate.go b/commands_generate.go index 4b56d83e..c032928c 100644 --- a/commands_generate.go +++ b/commands_generate.go @@ -206,6 +206,7 @@ func generateJsonSchema(output io.Writer, args []string) (err error) { // SectionInfoData is used as data for go templates that render profile section references type SectionInfoData struct { templates.DefaultData + Section config.SectionInfo Weight int } diff --git a/complete_test.go b/complete_test.go index 12354723..5ec906bc 100644 --- a/complete_test.go +++ b/complete_test.go @@ -111,6 +111,7 @@ func TestCompleter(t *testing.T) { testValues := func(flagName string, expected []string) func(t *testing.T) { return func(t *testing.T) { + t.Helper() t.Run("ReturnsAllValues", func(t *testing.T) { actual := completer.Complete(newArgs(fmt.Sprintf("--%s", flagName), "")) diff --git a/config/config.go b/config/config.go index aa7a92f0..02c213af 100644 --- a/config/config.go +++ b/config/config.go @@ -247,7 +247,7 @@ func (c *Config) applyMixinsToProfile(profileName string) error { } func (c *Config) applyMatchingMixinsOnce(matcher func(useKey string) bool) error { - var matchingUses []map[string][]*mixinUse + matchingUses := make([]map[string][]*mixinUse, 0, len(c.mixinUses)) for _, allUses := range c.mixinUses { usesToApply := make(map[string][]*mixinUse) @@ -756,7 +756,7 @@ func traceConfig(profileName, name string, replace bool, config *bytes.Buffer) { gutter = "%4d: " } for i := 0; i < len(lines); i++ { - output.WriteString(fmt.Sprintf(gutter, i+1)) + fmt.Fprintf(output, gutter, i+1) output.Write(lines[i]) output.WriteString("\n") } diff --git a/config/config_test.go b/config/config_test.go index 4ac352ab..7b19bdb9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -201,6 +201,7 @@ profile: func TestBoolPointer(t *testing.T) { fixtures := []struct { testTemplate + continueOnError maybe.Bool }{ { diff --git a/config/group.go b/config/group.go index 91e20123..c0c6306e 100644 --- a/config/group.go +++ b/config/group.go @@ -46,7 +46,7 @@ func (g *Group) Schedules() map[string]*Schedule { } // SchedulableCommands returns the list of commands that can be scheduled (whether they have schedules or not) -func (g Group) SchedulableCommands() []string { +func (g *Group) SchedulableCommands() []string { // once the deprecated retention schedule is removed, we can use the list from profiles // return NewProfile(g.config, "").SchedulableCommands() return []string{ diff --git a/config/info.go b/config/info.go index 29d46baa..4c3ba13d 100644 --- a/config/info.go +++ b/config/info.go @@ -125,6 +125,7 @@ type NumericRange struct { // profileInfo implements ProfileInfo type profileInfo struct { propertySet + sections map[string]SectionInfo } @@ -137,6 +138,7 @@ func (p *profileInfo) Sections() []string { // sectionInfo implements SectionInfo type sectionInfo struct { namedPropertySet + command restic.CommandIf } @@ -172,6 +174,7 @@ func (p *propertySet) IsAllOptions() bool { // namedPropertySet extends propertySet with Named type namedPropertySet struct { propertySet + name, description string } @@ -181,7 +184,7 @@ func (p *namedPropertySet) Description() string { return p.description } // accessibleProperty provides package local access to basicPropertyInfo and the backing struct field (when available) type accessibleProperty interface { // sectionField returns or sets the field that declares the PropertySet that this property is in, or nil if unknown. - sectionField(*reflect.StructField) *reflect.StructField + sectionField(f *reflect.StructField) *reflect.StructField // field returns the field that declares this property, or nil if the property is not based on a field field() *reflect.StructField // basic returns the mutable basicPropertyInfo (is never nil) @@ -278,6 +281,7 @@ func (b *basicPropertyInfo) resetTypeInfo() *basicPropertyInfo { // propertyInfo implements PropertyInfo type propertyInfo struct { basicPropertyInfo + description string defaults []string originField *reflect.StructField @@ -302,6 +306,7 @@ func (p *propertyInfo) sectionField(f *reflect.StructField) *reflect.StructField // resticPropertyInfo implements PropertyInfo for properties derived from restic.Option type resticPropertyInfo struct { basicPropertyInfo + originField *reflect.StructField optionFlag restic.Option skipDefaultValue bool diff --git a/config/info_customizer.go b/config/info_customizer.go index 2fd61c44..3f34453d 100644 --- a/config/info_customizer.go +++ b/config/info_customizer.go @@ -82,14 +82,15 @@ func init() { note = fmt.Sprintf(`Boolean true is replaced with the %ss from section "backup".`, propertyName) } - if sectionName == constants.CommandBackup { + switch sectionName { + case constants.CommandBackup: if propertyName != constants.ParameterHost { info.examples = info.examples[1:] // remove "true" from examples of backup section note = `Boolean true is unsupported in section "backup".` } else { note += suffixDefaultTrueV2 } - } else if sectionName == constants.SectionConfigurationRetention { + case constants.SectionConfigurationRetention: if propertyName == constants.ParameterHost { note = `Boolean true is replaced with the hostname that applies in section "backup".` } diff --git a/config/jsonschema/model.go b/config/jsonschema/model.go index 52cefe32..4a0cef91 100644 --- a/config/jsonschema/model.go +++ b/config/jsonschema/model.go @@ -24,10 +24,11 @@ const ( ) type schemaRoot struct { - Schema string `json:"$schema"` - Id string `json:"$id"` - Defs map[string]SchemaType `json:"$defs,omitempty"` - schemaObject // cannot use SchemaType here as long as ",inline" support is missing in json.Marshal + schemaObject // cannot use SchemaType here as long as ",inline" support is missing in json.Marshal + + Schema string `json:"$schema"` + Id string `json:"$id"` + Defs map[string]SchemaType `json:"$defs,omitempty"` } func newSchema(version config.Version, id string, content *schemaObject) (root *schemaRoot, err error) { @@ -213,6 +214,7 @@ func (s *schemaTypeWithoutBase) describe(title, description string) { type schemaTypeList struct { schemaTypeWithoutBase + OneOf []SchemaType `json:"oneOf,omitempty"` AnyOf []SchemaType `json:"anyOf,omitempty"` } @@ -256,6 +258,7 @@ func newSchemaTypeList(anyType bool, types ...SchemaType) *schemaTypeList { type schemaReference struct { schemaTypeWithoutBase + Ref string `json:"$ref"` } @@ -265,6 +268,7 @@ func newSchemaBool() *schemaTypeBase { type schemaObject struct { schemaTypeBase + AdditionalProperties any `json:"additionalProperties,omitempty"` PatternProperties map[string]SchemaType `json:"patternProperties,omitempty"` Properties map[string]SchemaType `json:"properties,omitempty"` @@ -315,6 +319,7 @@ func (s *schemaObject) verify() (err error) { type schemaArray struct { schemaTypeBase + Items SchemaType `json:"items"` MinItems uint64 `json:"minItems"` MaxItems *uint64 `json:"maxItems,omitempty"` @@ -341,6 +346,7 @@ func (a *schemaArray) verify() (err error) { type schemaNumber struct { schemaTypeBase + MultipleOf *float64 `json:"multipleOf,omitempty"` Minimum *float64 `json:"minimum,omitempty"` Maximum *float64 `json:"maximum,omitempty"` @@ -380,6 +386,7 @@ var validFormatNames = []stringFormat{ type schemaString struct { schemaTypeBase + MinLength uint64 `json:"minLength"` MaxLength *uint64 `json:"maxLength,omitempty"` ContentEncoding string `json:"contentEncoding,omitempty"` diff --git a/config/jsonschema/schema_test.go b/config/jsonschema/schema_test.go index 4e428405..61d0ae78 100644 --- a/config/jsonschema/schema_test.go +++ b/config/jsonschema/schema_test.go @@ -4,6 +4,7 @@ package jsonschema import ( "bytes" + "context" "fmt" "io/fs" "maps" @@ -52,7 +53,10 @@ func npmRunner(t *testing.T) npmRunnerFunc { t.Helper() t.Log(args) if npmExecutable != "" { - cmd := exec.Command(npmExecutable, args...) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + cmd := exec.CommandContext(ctx, npmExecutable, args...) cmd.Dir = path.Join(".", ".node-env") _ = os.MkdirAll(cmd.Dir, 0755) diff --git a/config/profile.go b/config/profile.go index 533545bd..e7e9a39f 100644 --- a/config/profile.go +++ b/config/profile.go @@ -67,43 +67,44 @@ type resolver interface { type Profile struct { RunShellCommandsSection `mapstructure:",squash"` OtherFlagsSection `mapstructure:",squash"` - config *Config - resticVersion *semver.Version - Name string - Description string `mapstructure:"description" description:"Describes the profile"` - BaseDir string `mapstructure:"base-dir" description:"Sets the working directory for this profile. The profile will fail when the working directory cannot be changed. Leave empty to use the current directory instead"` - Quiet bool `mapstructure:"quiet" argument:"quiet"` - Verbose int `mapstructure:"verbose" argument:"verbose"` - KeyHint string `mapstructure:"key-hint" argument:"key-hint"` - Repository ConfidentialValue `mapstructure:"repository" argument:"repo"` - RepositoryFile string `mapstructure:"repository-file" argument:"repository-file"` - PasswordFile string `mapstructure:"password-file" argument:"password-file"` - PasswordCommand string `mapstructure:"password-command" argument:"password-command"` - CacheDir string `mapstructure:"cache-dir" argument:"cache-dir"` - CACert string `mapstructure:"cacert" argument:"cacert"` - TLSClientCert string `mapstructure:"tls-client-cert" argument:"tls-client-cert"` - Initialize bool `mapstructure:"initialize" default:"" description:"Initialize the restic repository if missing"` - Inherit string `mapstructure:"inherit" show:"noshow" description:"Name of the profile to inherit all of the settings from"` - Lock string `mapstructure:"lock" description:"Path to the lock file to use with resticprofile locks"` - ForceLock bool `mapstructure:"force-inactive-lock" description:"Allows to lock when the existing lock is considered stale"` - StreamError []StreamErrorSection `mapstructure:"stream-error" description:"Run shell command(s) when a pattern matches the stderr of restic"` - StatusFile string `mapstructure:"status-file" description:"Path to the status file to update with a summary of last restic command result"` - PrometheusSaveToFile string `mapstructure:"prometheus-save-to-file" description:"Path to the prometheus metrics file to update with a summary of the last restic command result"` - PrometheusPush string `mapstructure:"prometheus-push" format:"uri" description:"URL of the prometheus push gateway to send the summary of the last restic command result to"` - PrometheusPushJob string `mapstructure:"prometheus-push-job" description:"Prometheus push gateway job name. $command placeholder is replaced with restic command"` - PrometheusPushFormat string `mapstructure:"prometheus-push-format" default:"text" enum:"text;protobuf" description:"Prometheus push gateway request format"` - PrometheusLabels map[string]string `mapstructure:"prometheus-labels" description:"Additional prometheus labels to set"` - SystemdDropInFiles []string `mapstructure:"systemd-drop-in-files" default:"" description:"Files containing systemd drop-in (override) files - see https://creativeprojects.github.io/resticprofile/schedules/systemd/"` - Environment map[string]ConfidentialValue `mapstructure:"env" description:"Additional environment variables to set in any child process. Inline env variables take precedence over dotenv files declared with \"env-file\"."` - EnvironmentFiles []string `mapstructure:"env-file" description:"Additional dotenv files to load and set as environment in any child process"` - Init *InitSection `mapstructure:"init"` - Backup *BackupSection `mapstructure:"backup"` - Retention *RetentionSection `mapstructure:"retention" command:"forget"` - Check *GenericSectionWithSchedule `mapstructure:"check"` - Prune *GenericSectionWithSchedule `mapstructure:"prune"` - Forget *GenericSectionWithSchedule `mapstructure:"forget"` - Copy *CopySection `mapstructure:"copy"` - OtherSections map[string]*GenericSection `show:",remain"` + + config *Config + resticVersion *semver.Version + Name string + Description string `mapstructure:"description" description:"Describes the profile"` + BaseDir string `mapstructure:"base-dir" description:"Sets the working directory for this profile. The profile will fail when the working directory cannot be changed. Leave empty to use the current directory instead"` + Quiet bool `mapstructure:"quiet" argument:"quiet"` + Verbose int `mapstructure:"verbose" argument:"verbose"` + KeyHint string `mapstructure:"key-hint" argument:"key-hint"` + Repository ConfidentialValue `mapstructure:"repository" argument:"repo"` + RepositoryFile string `mapstructure:"repository-file" argument:"repository-file"` + PasswordFile string `mapstructure:"password-file" argument:"password-file"` + PasswordCommand string `mapstructure:"password-command" argument:"password-command"` + CacheDir string `mapstructure:"cache-dir" argument:"cache-dir"` + CACert string `mapstructure:"cacert" argument:"cacert"` + TLSClientCert string `mapstructure:"tls-client-cert" argument:"tls-client-cert"` + Initialize bool `mapstructure:"initialize" default:"" description:"Initialize the restic repository if missing"` + Inherit string `mapstructure:"inherit" show:"noshow" description:"Name of the profile to inherit all of the settings from"` + Lock string `mapstructure:"lock" description:"Path to the lock file to use with resticprofile locks"` + ForceLock bool `mapstructure:"force-inactive-lock" description:"Allows to lock when the existing lock is considered stale"` + StreamError []StreamErrorSection `mapstructure:"stream-error" description:"Run shell command(s) when a pattern matches the stderr of restic"` + StatusFile string `mapstructure:"status-file" description:"Path to the status file to update with a summary of last restic command result"` + PrometheusSaveToFile string `mapstructure:"prometheus-save-to-file" description:"Path to the prometheus metrics file to update with a summary of the last restic command result"` + PrometheusPush string `mapstructure:"prometheus-push" format:"uri" description:"URL of the prometheus push gateway to send the summary of the last restic command result to"` + PrometheusPushJob string `mapstructure:"prometheus-push-job" description:"Prometheus push gateway job name. $command placeholder is replaced with restic command"` + PrometheusPushFormat string `mapstructure:"prometheus-push-format" default:"text" enum:"text;protobuf" description:"Prometheus push gateway request format"` + PrometheusLabels map[string]string `mapstructure:"prometheus-labels" description:"Additional prometheus labels to set"` + SystemdDropInFiles []string `mapstructure:"systemd-drop-in-files" default:"" description:"Files containing systemd drop-in (override) files - see https://creativeprojects.github.io/resticprofile/schedules/systemd/"` + Environment map[string]ConfidentialValue `mapstructure:"env" description:"Additional environment variables to set in any child process. Inline env variables take precedence over dotenv files declared with \"env-file\"."` + EnvironmentFiles []string `mapstructure:"env-file" description:"Additional dotenv files to load and set as environment in any child process"` + Init *InitSection `mapstructure:"init"` + Backup *BackupSection `mapstructure:"backup"` + Retention *RetentionSection `mapstructure:"retention" command:"forget"` + Check *GenericSectionWithSchedule `mapstructure:"check"` + Prune *GenericSectionWithSchedule `mapstructure:"prune"` + Forget *GenericSectionWithSchedule `mapstructure:"forget"` + Copy *CopySection `mapstructure:"copy"` + OtherSections map[string]*GenericSection `show:",remain"` } // GenericSection is used for all restic commands that are not covered in specific section types @@ -121,7 +122,8 @@ func (g *GenericSection) IsEmpty() bool { return g == nil } // InitSection contains the specific configuration to the 'init' command type InitSection struct { - GenericSection `mapstructure:",squash"` + GenericSection `mapstructure:",squash"` + CopyChunkerParams bool `mapstructure:"copy-chunker-params" argument:"copy-chunker-params"` FromKeyHint string `mapstructure:"from-key-hint" argument:"from-key-hint"` FromRepository ConfidentialValue `mapstructure:"from-repository" argument:"from-repo"` @@ -172,23 +174,24 @@ func (i *InitSection) getCommandFlags(profile *Profile) (flags *shell.Args) { // BackupSection contains the specific configuration to the 'backup' command type BackupSection struct { GenericSectionWithSchedule `mapstructure:",squash"` - unresolvedSource []string - CheckBefore bool `mapstructure:"check-before" description:"Check the repository before starting the backup command"` - CheckAfter bool `mapstructure:"check-after" description:"Check the repository after the backup command succeeded"` - UseStdin bool `mapstructure:"stdin" argument:"stdin"` - StdinCommand []string `mapstructure:"stdin-command" description:"Shell command(s) that generate content to redirect into the stdin of restic. When set, the flag \"stdin\" is always set to \"true\"."` - SourceRelative bool `mapstructure:"source-relative" description:"Enable backup with relative source paths. This will change the working directory of the \"restic backup\" command to \"source-base\", and will not expand \"source\" to an absolute path."` - SourceBase string `mapstructure:"source-base" examples:"/;$PWD;C:\\;%cd%" description:"The base path to resolve relative backup paths against. Defaults to current directory if unset or empty (see also \"base-dir\" in profile)"` - Source []string `mapstructure:"source" examples:"/opt/;/home/user/;C:\\Users\\User\\Documents" description:"The paths to backup"` - Exclude []string `mapstructure:"exclude" argument:"exclude" argument-type:"no-glob"` - Iexclude []string `mapstructure:"iexclude" argument:"iexclude" argument-type:"no-glob"` - ExcludeFile []string `mapstructure:"exclude-file" argument:"exclude-file"` - IexcludeFile []string `mapstructure:"iexclude-file" argument:"iexclude-file"` - FilesFrom []string `mapstructure:"files-from" argument:"files-from"` - FilesFromRaw []string `mapstructure:"files-from-raw" argument:"files-from-raw"` - FilesFromVerbatim []string `mapstructure:"files-from-verbatim" argument:"files-from-verbatim"` - ExtendedStatus bool `mapstructure:"extended-status" argument:"json"` - NoErrorOnWarning bool `mapstructure:"no-error-on-warning" description:"Do not fail the backup when some files could not be read"` + + unresolvedSource []string + CheckBefore bool `mapstructure:"check-before" description:"Check the repository before starting the backup command"` + CheckAfter bool `mapstructure:"check-after" description:"Check the repository after the backup command succeeded"` + UseStdin bool `mapstructure:"stdin" argument:"stdin"` + StdinCommand []string `mapstructure:"stdin-command" description:"Shell command(s) that generate content to redirect into the stdin of restic. When set, the flag \"stdin\" is always set to \"true\"."` + SourceRelative bool `mapstructure:"source-relative" description:"Enable backup with relative source paths. This will change the working directory of the \"restic backup\" command to \"source-base\", and will not expand \"source\" to an absolute path."` + SourceBase string `mapstructure:"source-base" examples:"/;$PWD;C:\\;%cd%" description:"The base path to resolve relative backup paths against. Defaults to current directory if unset or empty (see also \"base-dir\" in profile)"` + Source []string `mapstructure:"source" examples:"/opt/;/home/user/;C:\\Users\\User\\Documents" description:"The paths to backup"` + Exclude []string `mapstructure:"exclude" argument:"exclude" argument-type:"no-glob"` + Iexclude []string `mapstructure:"iexclude" argument:"iexclude" argument-type:"no-glob"` + ExcludeFile []string `mapstructure:"exclude-file" argument:"exclude-file"` + IexcludeFile []string `mapstructure:"iexclude-file" argument:"iexclude-file"` + FilesFrom []string `mapstructure:"files-from" argument:"files-from"` + FilesFromRaw []string `mapstructure:"files-from-raw" argument:"files-from-raw"` + FilesFromVerbatim []string `mapstructure:"files-from-verbatim" argument:"files-from-verbatim"` + ExtendedStatus bool `mapstructure:"extended-status" argument:"json"` + NoErrorOnWarning bool `mapstructure:"no-error-on-warning" description:"Do not fail the backup when some files could not be read"` } func (s *BackupSection) IsEmpty() bool { return s == nil } @@ -241,8 +244,9 @@ func (s *BackupSection) setRootPath(p *Profile, rootPath string) { type RetentionSection struct { ScheduleBaseSection `mapstructure:",squash" deprecated:"0.11.0"` OtherFlagsSection `mapstructure:",squash"` - BeforeBackup maybe.Bool `mapstructure:"before-backup" description:"Apply retention before starting the backup command"` - AfterBackup maybe.Bool `mapstructure:"after-backup" description:"Apply retention after the backup command succeeded. Defaults to true in configuration format v2 if any \"keep-*\" flag is set and \"before-backup\" is unset"` + + BeforeBackup maybe.Bool `mapstructure:"before-backup" description:"Apply retention before starting the backup command"` + AfterBackup maybe.Bool `mapstructure:"after-backup" description:"Apply retention after the backup command succeeded. Defaults to true in configuration format v2 if any \"keep-*\" flag is set and \"before-backup\" is unset"` } func (r *RetentionSection) IsEmpty() bool { return r == nil } @@ -348,7 +352,8 @@ func (s *ScheduleBaseSection) getScheduleConfig(p *Profile, command string) *Sch // CopySection contains the source or destination parameters for a copy command type CopySection struct { - GenericSectionWithSchedule `mapstructure:",squash"` + GenericSectionWithSchedule `mapstructure:",squash"` + Initialize bool `mapstructure:"initialize" description:"Initialize the secondary repository if missing"` InitializeCopyChunkerParams maybe.Bool `mapstructure:"initialize-copy-chunker-params" default:"true" description:"Copy chunker parameters when initializing the secondary repository"` FromRepository ConfidentialValue `mapstructure:"from-repository" argument:"from-repo" description:"Source repository to copy snapshots from"` diff --git a/config/schedule.go b/config/schedule.go index be8ede51..9dbd3e27 100644 --- a/config/schedule.go +++ b/config/schedule.go @@ -168,10 +168,11 @@ func ScheduleOrigin(name, command string, kind ...ScheduleOriginType) (s Schedul // ScheduleConfig is the user configuration of a specific schedule bound to a command in a profile or group. type ScheduleConfig struct { - normalized bool - origin ScheduleConfigOrigin `show:"noshow"` - Schedules []string `mapstructure:"at" examples:"hourly;daily;weekly;monthly;10:00,14:00,18:00,22:00;Wed,Fri 17:48;*-*-15 02:45;Mon..Fri 00:30" description:"Set the times at which the scheduled command is run (times are specified in systemd timer format)"` ScheduleBaseConfig `mapstructure:",squash"` + + normalized bool + origin ScheduleConfigOrigin `show:"noshow"` + Schedules []string `mapstructure:"at" examples:"hourly;daily;weekly;monthly;10:00,14:00,18:00,22:00;Wed,Fri 17:48;*-*-15 02:45;Mon..Fri 00:30" description:"Set the times at which the scheduled command is run (times are specified in systemd timer format)"` } // NewDefaultScheduleConfig returns a new schedule configuration that is initialized with defaults @@ -268,6 +269,7 @@ type Schedulable interface { // Schedule is the configuration used in profiles and groups for passing the user config to the scheduler system. type Schedule struct { ScheduleConfig + ConfigFile string `show:"noshow"` Environment []string `show:"noshow"` Flags map[string]string `show:"noshow"` diff --git a/config/show_test.go b/config/show_test.go index 86b354f1..9d23efbb 100644 --- a/config/show_test.go +++ b/config/show_test.go @@ -47,7 +47,8 @@ type testPointer struct { type testEmbedded struct { EmbeddedStruct `mapstructure:",squash"` - InlineValue int `mapstructure:"inline"` + + InlineValue int `mapstructure:"inline"` } type EmbeddedStruct struct { diff --git a/config/template.go b/config/template.go index af81fe97..8c46d030 100644 --- a/config/template.go +++ b/config/template.go @@ -14,6 +14,7 @@ import ( // TemplateData contain the variables fed to a config template type TemplateData struct { templates.DefaultData + Profile ProfileTemplateData Schedule ScheduleTemplateData ConfigDir string @@ -53,6 +54,7 @@ func newTemplateData(configFile, profileName, scheduleName string) TemplateData // TemplateInfoData is used as data for go templates that render config references type TemplateInfoData struct { templates.DefaultData + Global, Group PropertySet Profile ProfileInfo KnownResticVersions []string diff --git a/crond/crontab_test.go b/crond/crontab_test.go index 4dfac189..19b42d6f 100644 --- a/crond/crontab_test.go +++ b/crond/crontab_test.go @@ -3,6 +3,7 @@ package crond import ( + "context" "fmt" "os" "os/exec" @@ -10,6 +11,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/creativeprojects/resticprofile/calendar" "github.com/creativeprojects/resticprofile/platform" @@ -308,10 +310,13 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin } func TestUseCrontabBinary(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + binary := filepath.Join(t.TempDir(), platform.Executable("crontab")) defer func() { _ = os.Remove(binary) }() - cmd := exec.Command("go", "build", "-buildvcs=false", "-o", binary, "./mock") + cmd := exec.CommandContext(ctx, "go", "build", "-buildvcs=false", "-o", binary, "./mock") require.NoError(t, cmd.Run()) crontab := NewCrontab(nil) diff --git a/filesearch/filesearch.go b/filesearch/filesearch.go index b4ec9486..7dbd7ed2 100644 --- a/filesearch/filesearch.go +++ b/filesearch/filesearch.go @@ -80,9 +80,10 @@ func NewFinder() Finder { // FindConfigurationFile returns the path of the configuration file // If the file doesn't have an extension, it will search for all possible extensions func (f Finder) FindConfigurationFile(configFile string) (string, error) { - found := "" + var found, displayFile string + extension := filepath.Ext(configFile) - displayFile := "" + if extension != "" { displayFile = fmt.Sprintf("'%s'", configFile) // Search only once through the paths diff --git a/integration_test.go b/integration_test.go index 9eb38254..5e8aea48 100644 --- a/integration_test.go +++ b/integration_test.go @@ -23,7 +23,7 @@ func TestFromConfigFileToCommandLine(t *testing.T) { // we can use the same files to test a glob pattern globFiles := "\"" + strings.Join(files, "\" \"") + "\"" - globFilesOnWindows := strings.Replace(globFiles, `\`, `\\`, -1) + globFilesOnWindows := strings.ReplaceAll(globFiles, `\`, `\\`) integrationData := []struct { profileName string diff --git a/lock/lock.go b/lock/lock.go index da600ba6..2805aca0 100644 --- a/lock/lock.go +++ b/lock/lock.go @@ -88,7 +88,7 @@ func (l *Lock) SetPID(pid int32) { return } // just add the PID on a newline - _, _ = l.file.WriteString(fmt.Sprintf("\n%d", pid)) + _, _ = fmt.Fprintf(l.file, "\n%d", pid) } // HasLocked check this instance (and only this one) has locked the file @@ -142,7 +142,7 @@ func (l *Lock) lock() bool { now := time.Now().Format(time.RFC850) // No error checking... it's not a big deal if we cannot write that - _, _ = l.file.WriteString(fmt.Sprintf("%s on %s from %s", username, now, hostname)) + _, _ = fmt.Fprintf(l.file, "%s on %s from %s", username, now, hostname) return true } diff --git a/lock/lock_test.go b/lock/lock_test.go index 47a25599..3da20455 100644 --- a/lock/lock_test.go +++ b/lock/lock_test.go @@ -2,6 +2,7 @@ package lock import ( "bytes" + "context" "fmt" "os" "os/exec" @@ -26,6 +27,9 @@ var ( func TestMain(m *testing.M) { // using an anonymous function to handle defer statements before os.Exit() exitCode := func() int { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + tempDir, err := os.MkdirTemp("", "resticprofile-lock") if err != nil { fmt.Fprintf(os.Stderr, "cannot create temp dir: %v\n", err) @@ -36,7 +40,7 @@ func TestMain(m *testing.M) { helperBinary = filepath.Join(tempDir, platform.Executable("locktest")) - cmd := exec.Command("go", "build", "-buildvcs=false", "-o", helperBinary, "./test") + cmd := exec.CommandContext(ctx, "go", "build", "-buildvcs=false", "-o", helperBinary, "./test") if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error building helper binary: %s\n", err) return 1 diff --git a/main_test.go b/main_test.go index 84e5b997..74d24c93 100644 --- a/main_test.go +++ b/main_test.go @@ -1,12 +1,14 @@ package main import ( + "context" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" + "time" "github.com/creativeprojects/resticprofile/config" "github.com/creativeprojects/resticprofile/constants" @@ -23,6 +25,9 @@ var ( func TestMain(m *testing.M) { // using an anonymous function to handle defer statements before os.Exit() exitCode := func() int { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + tempDir, err := os.MkdirTemp("", "resticprofile-main") if err != nil { fmt.Fprintf(os.Stderr, "cannot create temp dir: %v\n", err) @@ -35,7 +40,7 @@ func TestMain(m *testing.M) { if platform.IsWindows() { mockBinary += ".exe" } - cmdMock := exec.Command("go", "build", "-buildvcs=false", "-o", mockBinary, "./shell/mock") + cmdMock := exec.CommandContext(ctx, "go", "build", "-buildvcs=false", "-o", mockBinary, "./shell/mock") if output, err := cmdMock.CombinedOutput(); err != nil { fmt.Fprintf(os.Stderr, "Error building shell/mock binary: %s\nCommand output: %s\n", err, string(output)) return 1 @@ -45,7 +50,7 @@ func TestMain(m *testing.M) { if platform.IsWindows() { echoBinary += ".exe" } - cmdEcho := exec.Command("go", "build", "-buildvcs=false", "-o", echoBinary, "./shell/echo") + cmdEcho := exec.CommandContext(ctx, "go", "build", "-buildvcs=false", "-o", echoBinary, "./shell/echo") if output, err := cmdEcho.CombinedOutput(); err != nil { fmt.Fprintf(os.Stderr, "Error building shell/echo binary: %s\nCommand output: %s\n", err, string(output)) return 1 diff --git a/monitor/hook/context.go b/monitor/hook/context.go index 51e3353e..45048ead 100644 --- a/monitor/hook/context.go +++ b/monitor/hook/context.go @@ -4,6 +4,7 @@ import "github.com/creativeprojects/resticprofile/util/templates" type Context struct { templates.DefaultData + ProfileName string ProfileCommand string Error ErrorContext diff --git a/monitor/status/profile.go b/monitor/status/profile.go index 24dd66bd..b0c0c7f5 100644 --- a/monitor/status/profile.go +++ b/monitor/status/profile.go @@ -30,6 +30,7 @@ type CommandStatus struct { // BackupStatus contains the last backup status type BackupStatus struct { CommandStatus + FilesNew int `json:"files_new"` FilesChanged int `json:"files_changed"` FilesUnmodified int `json:"files_unmodified"` diff --git a/own_command_error_test.go b/own_command_error_test.go index 6d87957a..22d6264c 100644 --- a/own_command_error_test.go +++ b/own_command_error_test.go @@ -8,7 +8,7 @@ import ( ) func TestOwnCommandError(t *testing.T) { - var wrap error = errors.New("wrap") + wrap := errors.New("wrap") var err error = newOwnCommandError(wrap, 10) assert.Equal(t, "wrap", err.Error()) diff --git a/own_commands.go b/own_commands.go index ff23beae..de565a9f 100644 --- a/own_commands.go +++ b/own_commands.go @@ -10,6 +10,7 @@ import ( // commandContext is the context for running a command. type commandContext struct { Context + ownCommands *OwnCommands } diff --git a/priority/prority_test.go b/priority/prority_test.go index c241679f..59d47481 100644 --- a/priority/prority_test.go +++ b/priority/prority_test.go @@ -2,6 +2,7 @@ package priority import ( "bytes" + "context" "io" "os/exec" "testing" @@ -71,7 +72,10 @@ func TestStartProcessWithPriority(t *testing.T) { } func runChildProcess() (string, error) { - cmd := exec.Command("go", "run", "./check") + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + cmd := exec.CommandContext(ctx, "go", "run", "./check") buffer := &bytes.Buffer{} cmd.Stdout = buffer cmd.Stderr = buffer diff --git a/restic/commands.go b/restic/commands.go index 66290ea1..b622ff30 100644 --- a/restic/commands.go +++ b/restic/commands.go @@ -96,6 +96,7 @@ func (c *command) sortOptions() { type commandAtVersion struct { command + includeRemoved bool actualVersion *semver.Version } diff --git a/restic/downloader_test.go b/restic/downloader_test.go index 0cb38240..eab923e5 100644 --- a/restic/downloader_test.go +++ b/restic/downloader_test.go @@ -24,7 +24,6 @@ func TestDownloadBinary(t *testing.T) { } for _, version := range versions { - version := version t.Run(version, func(t *testing.T) { t.Parallel() executable := platform.Executable(filepath.Join(t.TempDir(), "restic")) diff --git a/restic/generator/main.go b/restic/generator/main.go index cb17669f..6b97fac4 100644 --- a/restic/generator/main.go +++ b/restic/generator/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "log" @@ -9,6 +10,7 @@ import ( "path" "path/filepath" "strconv" + "time" "github.com/creativeprojects/resticprofile/restic" ) @@ -60,6 +62,9 @@ func generate() (err error) { } func install(includeManual bool) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + executable := path.Join(installDir, restic.Executable) // Install if required @@ -90,7 +95,7 @@ func install(includeManual bool) error { manPage := path.Join(manualDir, "restic.1") if _, err := os.Stat(manPage); os.IsNotExist(err) { fmt.Println("creating man pages: " + manualDir) - cmd := exec.Command(executable, "generate", "--man", manualDir) + cmd := exec.CommandContext(ctx, executable, "generate", "--man", manualDir) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { diff --git a/schedule/handler_systemd.go b/schedule/handler_systemd.go index 68327a3e..0c352e1e 100644 --- a/schedule/handler_systemd.go +++ b/schedule/handler_systemd.go @@ -113,7 +113,7 @@ func (h *HandlerSystemd) DisplayStatus(profileName string) error { } if err != nil || status == "" || strings.Contains(status, "0 timers") { // fail silently - return nil + return nil //nolint:nilerr } fmt.Fprintf(term.GetOutput(), "\nTimers summary\n===============\n%s\n", status) return nil @@ -142,11 +142,11 @@ func (h *HandlerSystemd) CreateJob(job *Config, schedules []*calendar.Event, per clog.Infof("removing existing unit with different permission") err := h.disableJob(job, otherUnitType, timerFile) if err != nil { - return fmt.Errorf("cannot stop or disable existing unit before scheduling with different permission. You might want to retry using sudo.") + return fmt.Errorf("cannot stop or disable existing unit before scheduling with different permission, you might want to retry using sudo") } err = h.removeJobFiles(job, otherUnitType, timerFile, systemd.GetServiceFile(job.ProfileName, job.CommandName)) if err != nil { - return fmt.Errorf("cannot remove existing unit before scheduling with different permission. You might want to retry using sudo.") + return fmt.Errorf("cannot remove existing unit before scheduling with different permission, you might want to retry using sudo") } } } @@ -250,7 +250,7 @@ func (h *HandlerSystemd) removeJobFiles(job *Config, unitType systemd.UnitType, if unitType == systemd.UserUnit { systemdPath, err = unit.GetUserDir() if err != nil { - return nil + return nil //nolint:nilerr } } diff --git a/schedule/handler_windows.go b/schedule/handler_windows.go index be3fa282..363fdf27 100644 --- a/schedule/handler_windows.go +++ b/schedule/handler_windows.go @@ -51,9 +51,10 @@ func (h *HandlerWindows) DisplayStatus(profileName string) error { func (h *HandlerWindows) CreateJob(job *Config, schedules []*calendar.Event, permission Permission) error { // default permission will be system perm := schtasks.SystemAccount - if permission == PermissionUserBackground { + switch permission { + case PermissionUserBackground: perm = schtasks.UserAccount - } else if permission == PermissionUserLoggedOn { + case PermissionUserLoggedOn: perm = schtasks.UserLoggedOnAccount } diff --git a/shell/command_test.go b/shell/command_test.go index 42bf96d5..8c6653ab 100644 --- a/shell/command_test.go +++ b/shell/command_test.go @@ -2,6 +2,7 @@ package shell import ( "bytes" + "context" "fmt" "io" "os" @@ -26,6 +27,9 @@ var ( func TestMain(m *testing.M) { // using an anonymous function to handle defer statements before os.Exit() exitCode := func() int { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + tempDir, err := os.MkdirTemp("", "resticprofile-shell") if err != nil { fmt.Fprintf(os.Stderr, "cannot create temp dir: %v\n", err) @@ -38,7 +42,7 @@ func TestMain(m *testing.M) { if platform.IsWindows() { mockBinary += ".exe" } - cmd := exec.Command("go", "build", "-buildvcs=false", "-o", mockBinary, "./mock") + cmd := exec.CommandContext(ctx, "go", "build", "-buildvcs=false", "-o", mockBinary, "./mock") if output, err := cmd.CombinedOutput(); err != nil { fmt.Fprintf(os.Stderr, "Error building mock binary: %s\nCommand output: %s\n", err, string(output)) return 1 diff --git a/systemd/generate.go b/systemd/generate.go index b03ace9b..badf6b2e 100644 --- a/systemd/generate.go +++ b/systemd/generate.go @@ -74,6 +74,7 @@ const ( // templateInfo to create systemd unit type templateInfo struct { templates.DefaultData + JobDescription string TimerDescription string WorkingDirectory string diff --git a/util/dotenv.go b/util/dotenv.go index 26fe6ec2..a5db8145 100644 --- a/util/dotenv.go +++ b/util/dotenv.go @@ -70,7 +70,7 @@ func (f *EnvironmentFile) Name() string { return f.filename } // Valid returns true as the loaded values are in-sync with underlying dotenv file func (f *EnvironmentFile) Valid() bool { if s, err := os.Stat(f.filename); err == nil && !s.IsDir() { - return f.fileInfo.Size() == s.Size() && f.fileInfo.ModTime() == s.ModTime() + return f.fileInfo.Size() == s.Size() && f.fileInfo.ModTime().Equal(s.ModTime()) } return false } From c2336fdebf7b35f1ed5ee5d3dc51e3c502c2c9d7 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 27 Sep 2025 13:07:01 +0100 Subject: [PATCH 2/5] refactor: remove embeddedstructfieldcheck linter and reorder ScheduleConfig fields --- .golangci.yml | 1 - config/schedule.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index bf71fe61..c82587b2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,7 +11,6 @@ linters: - copyloopvar - decorder - durationcheck - - embeddedstructfieldcheck # - err113 - errcheck - errchkjson diff --git a/config/schedule.go b/config/schedule.go index 9dbd3e27..65cfc3fe 100644 --- a/config/schedule.go +++ b/config/schedule.go @@ -168,11 +168,11 @@ func ScheduleOrigin(name, command string, kind ...ScheduleOriginType) (s Schedul // ScheduleConfig is the user configuration of a specific schedule bound to a command in a profile or group. type ScheduleConfig struct { - ScheduleBaseConfig `mapstructure:",squash"` - normalized bool origin ScheduleConfigOrigin `show:"noshow"` Schedules []string `mapstructure:"at" examples:"hourly;daily;weekly;monthly;10:00,14:00,18:00,22:00;Wed,Fri 17:48;*-*-15 02:45;Mon..Fri 00:30" description:"Set the times at which the scheduled command is run (times are specified in systemd timer format)"` + + ScheduleBaseConfig `mapstructure:",squash"` } // NewDefaultScheduleConfig returns a new schedule configuration that is initialized with defaults From 641b41b92a830107ddb8fb60992f9c58a92d4c9d Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 27 Sep 2025 13:18:43 +0100 Subject: [PATCH 3/5] feat: introduce DefaultTestTimeout constant and refactor timeout usage in tests --- .vscode/settings.json | 3 ++- constants/global.go | 14 -------------- constants/testing.go | 7 +++++++ crond/crontab_test.go | 4 ++-- lock/lock_test.go | 3 ++- main.go | 2 +- main_test.go | 3 +-- priority/priority.go | 12 ++++++++++++ priority/prority_test.go | 3 ++- shell/command_test.go | 3 ++- 10 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 constants/testing.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 3af104a0..c978fa81 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,6 @@ "go.lintTool": "golangci-lint", "githubPullRequests.ignoredPullRequestBranches": [ "master" - ] + ], + "makefile.configureOnOpen": false } \ No newline at end of file diff --git a/constants/global.go b/constants/global.go index 2ce3703e..ce6c0a8d 100644 --- a/constants/global.go +++ b/constants/global.go @@ -2,8 +2,6 @@ package constants import ( "time" - - "github.com/creativeprojects/resticprofile/priority" ) // Scheduler type @@ -16,18 +14,6 @@ const ( SchedulerOSDefault = "" ) -var ( - // PriorityValues is the map between the name and the value - PriorityValues = map[string]int{ - "idle": priority.Idle, - "background": priority.Background, - "low": priority.Low, - "normal": priority.Normal, - "high": priority.High, - "highest": priority.Highest, - } -) - // Limits for restic lock handling (stale locks and retry on lock failure) const ( MinResticLockRetryDelay = 15 * time.Second diff --git a/constants/testing.go b/constants/testing.go new file mode 100644 index 00000000..699209e8 --- /dev/null +++ b/constants/testing.go @@ -0,0 +1,7 @@ +package constants + +import "time" + +const ( + DefaultTestTimeout = 2 * time.Minute +) diff --git a/crond/crontab_test.go b/crond/crontab_test.go index 19b42d6f..190f5b51 100644 --- a/crond/crontab_test.go +++ b/crond/crontab_test.go @@ -11,9 +11,9 @@ import ( "path/filepath" "strings" "testing" - "time" "github.com/creativeprojects/resticprofile/calendar" + "github.com/creativeprojects/resticprofile/constants" "github.com/creativeprojects/resticprofile/platform" "github.com/spf13/afero" "github.com/stretchr/testify/assert" @@ -310,7 +310,7 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin } func TestUseCrontabBinary(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultTestTimeout) defer cancel() binary := filepath.Join(t.TempDir(), platform.Executable("crontab")) diff --git a/lock/lock_test.go b/lock/lock_test.go index 3da20455..43ada7c4 100644 --- a/lock/lock_test.go +++ b/lock/lock_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/creativeprojects/resticprofile/constants" "github.com/creativeprojects/resticprofile/platform" "github.com/creativeprojects/resticprofile/shell" "github.com/shirou/gopsutil/v3/process" @@ -27,7 +28,7 @@ var ( func TestMain(m *testing.M) { // using an anonymous function to handle defer statements before os.Exit() exitCode := func() int { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultTestTimeout) defer cancel() tempDir, err := os.MkdirTemp("", "resticprofile-lock") diff --git a/main.go b/main.go index 40b91b74..d778a53e 100644 --- a/main.go +++ b/main.go @@ -305,7 +305,7 @@ func setPriority(nice int, class string) error { var err error if class != "" { - if classID, ok := constants.PriorityValues[strings.ToLower(class)]; ok { + if classID, ok := priority.Values[strings.ToLower(class)]; ok { err = priority.SetClass(classID) if err != nil { return err diff --git a/main_test.go b/main_test.go index 74d24c93..a21f538f 100644 --- a/main_test.go +++ b/main_test.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strings" "testing" - "time" "github.com/creativeprojects/resticprofile/config" "github.com/creativeprojects/resticprofile/constants" @@ -25,7 +24,7 @@ var ( func TestMain(m *testing.M) { // using an anonymous function to handle defer statements before os.Exit() exitCode := func() int { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultTestTimeout) defer cancel() tempDir, err := os.MkdirTemp("", "resticprofile-main") diff --git a/priority/priority.go b/priority/priority.go index 425394f2..a5de7d33 100644 --- a/priority/priority.go +++ b/priority/priority.go @@ -10,3 +10,15 @@ const ( High = -10 Highest = -20 ) + +var ( + // Values is the map between the name and the value + Values = map[string]int{ + "idle": Idle, + "background": Background, + "low": Low, + "normal": Normal, + "high": High, + "highest": Highest, + } +) diff --git a/priority/prority_test.go b/priority/prority_test.go index 59d47481..82e603c9 100644 --- a/priority/prority_test.go +++ b/priority/prority_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/creativeprojects/resticprofile/constants" "github.com/creativeprojects/resticprofile/platform" "github.com/stretchr/testify/assert" ) @@ -72,7 +73,7 @@ func TestStartProcessWithPriority(t *testing.T) { } func runChildProcess() (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultTestTimeout) defer cancel() cmd := exec.CommandContext(ctx, "go", "run", "./check") diff --git a/shell/command_test.go b/shell/command_test.go index 8c6653ab..1849414b 100644 --- a/shell/command_test.go +++ b/shell/command_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/creativeprojects/resticprofile/constants" "github.com/creativeprojects/resticprofile/platform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +28,7 @@ var ( func TestMain(m *testing.M) { // using an anonymous function to handle defer statements before os.Exit() exitCode := func() int { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultTestTimeout) defer cancel() tempDir, err := os.MkdirTemp("", "resticprofile-shell") From 65b4f1a4d9b90cfd457e289426852c8f99502670 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 27 Sep 2025 13:50:44 +0100 Subject: [PATCH 4/5] chore: upgrade golangci-lint to v2.5.0 and remove backup configuration file --- .github/workflows/build.yml | 2 +- .golangci.bck.yml | 28 ---------------------------- config/jsonschema/model.go | 9 ++++----- 3 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 .golangci.bck.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac199c83..d686769d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.64.8 + version: v2.5.0 args: --timeout=30m - name: Test diff --git a/.golangci.bck.yml b/.golangci.bck.yml deleted file mode 100644 index f7c624f8..00000000 --- a/.golangci.bck.yml +++ /dev/null @@ -1,28 +0,0 @@ -linters: - enable: - - asasalint - - asciicheck - - bidichk - - bodyclose - - contextcheck - - errname - - gocheckcompilerdirectives - - gosec - # - maintidx - - misspell - - nilnil - - noctx - - nolintlint - - predeclared - - reassign - - sloglint - - spancheck - - unconvert - - unparam - - usestdlibvars - -linters-settings: - gosec: - excludes: - - G204 # Audit the use of command execution - - G404 # Insecure random number source (rand) diff --git a/config/jsonschema/model.go b/config/jsonschema/model.go index 4a0cef91..55b0d9d4 100644 --- a/config/jsonschema/model.go +++ b/config/jsonschema/model.go @@ -24,11 +24,10 @@ const ( ) type schemaRoot struct { - schemaObject // cannot use SchemaType here as long as ",inline" support is missing in json.Marshal - - Schema string `json:"$schema"` - Id string `json:"$id"` - Defs map[string]SchemaType `json:"$defs,omitempty"` + Schema string `json:"$schema"` + Id string `json:"$id"` + Defs map[string]SchemaType `json:"$defs,omitempty"` + schemaObject // cannot use SchemaType here as long as ",inline" support is missing in json.Marshal } func newSchema(version config.Version, id string, content *schemaObject) (root *schemaRoot, err error) { From 0c9cdea01594d35e0f7214013bc4b30e8306fa46 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 27 Sep 2025 14:00:09 +0100 Subject: [PATCH 5/5] chore: upgrade golangci-lint action from v6 to v8 in build workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d686769d..7d5a2c01 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: node-version: "22" - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: version: v2.5.0 args: --timeout=30m