all: upgrade to Go 1.26rc2 and modernize codebase

This commit upgrades the codebase from Go 1.25.5 to Go 1.26rc2 and
adopts new language features.

Toolchain updates:
- go.mod: go 1.25.5 → go 1.26rc2
- flake.nix: buildGo125Module → buildGo126Module, go_1_25 → go_1_26
- flake.nix: build golangci-lint from source with Go 1.26
- Dockerfile.integration: golang:1.25-trixie → golang:1.26rc2-trixie
- Dockerfile.tailscale-HEAD: golang:1.25-alpine → golang:1.26rc2-alpine
- Dockerfile.derper: golang:alpine → golang:1.26rc2-alpine
- .goreleaser.yml: go mod tidy -compat=1.25 → -compat=1.26
- cmd/hi/run.go: fallback Go version 1.25 → 1.26rc2
- .pre-commit-config.yaml: simplify golangci-lint hook entry

Code modernization using Go 1.26 features:
- Replace tsaddr.SortPrefixes with slices.SortFunc + netip.Prefix.Compare
- Replace ptr.To(x) with new(x) syntax
- Replace errors.As with errors.AsType[T]

Lint rule updates:
- Add forbidigo rules to prevent regression to old patterns
This commit is contained in:
Kristoffer Dalby
2026-02-06 21:39:35 +00:00
parent 20dff82f95
commit 0f6d312ada
50 changed files with 508 additions and 521 deletions

View File

@@ -333,7 +333,7 @@ func NodeOnline(nodeID types.NodeID) Change {
PeerPatches: []*tailcfg.PeerChange{
{
NodeID: nodeID.NodeID(),
Online: ptrTo(true),
Online: new(true),
},
},
}
@@ -346,7 +346,7 @@ func NodeOffline(nodeID types.NodeID) Change {
PeerPatches: []*tailcfg.PeerChange{
{
NodeID: nodeID.NodeID(),
Online: ptrTo(false),
Online: new(false),
},
},
}
@@ -367,7 +367,7 @@ func KeyExpiry(nodeID types.NodeID, expiry *time.Time) Change {
// ptrTo returns a pointer to the given value.
func ptrTo[T any](v T) *T {
return &v
return new(v)
}
// High-level change constructors

View File

@@ -16,8 +16,8 @@ func TestChange_FieldSync(t *testing.T) {
typ := reflect.TypeFor[Change]()
boolCount := 0
for i := range typ.NumField() {
if typ.Field(i).Type.Kind() == reflect.Bool {
for field := range typ.Fields() {
if field.Type.Kind() == reflect.Bool {
boolCount++
}
}

View File

@@ -407,8 +407,7 @@ func LoadConfig(path string, isFile bool) error {
err := viper.ReadInConfig()
if err != nil {
var configFileNotFoundError viper.ConfigFileNotFoundError
if errors.As(err, &configFileNotFoundError) {
if _, ok := errors.AsType[viper.ConfigFileNotFoundError](err); ok {
log.Warn().Msg("no config file found, using defaults")
return nil
}

View File

@@ -1104,7 +1104,7 @@ func (nv NodeView) TailNode(
primaryRoutes := primaryRouteFunc(nv.ID())
allowedIPs := slices.Concat(nv.Prefixes(), primaryRoutes, nv.ExitRoutes())
tsaddr.SortPrefixes(allowedIPs)
slices.SortFunc(allowedIPs, netip.Prefix.Compare)
capMap := tailcfg.NodeCapMap{
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},

View File

@@ -6,7 +6,6 @@ import (
"github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
"tailscale.com/types/ptr"
)
// TestNodeIsTagged tests the IsTagged() method for determining if a node is tagged.
@@ -69,7 +68,7 @@ func TestNodeIsTagged(t *testing.T) {
{
name: "node with user and no tags - not tagged",
node: Node{
UserID: ptr.To(uint(42)),
UserID: new(uint(42)),
Tags: []string{},
},
want: false,
@@ -112,7 +111,7 @@ func TestNodeViewIsTagged(t *testing.T) {
{
name: "user-owned node",
node: Node{
UserID: ptr.To(uint(1)),
UserID: new(uint(1)),
},
want: false,
},
@@ -223,7 +222,7 @@ func TestNodeTagsImmutableAfterRegistration(t *testing.T) {
// Test that a user-owned node is not tagged
userNode := Node{
ID: 2,
UserID: ptr.To(uint(42)),
UserID: new(uint(42)),
Tags: []string{},
RegisterMethod: util.RegisterMethodOIDC,
}
@@ -243,7 +242,7 @@ func TestNodeOwnershipModel(t *testing.T) {
name: "tagged node has tags, UserID is informational",
node: Node{
ID: 1,
UserID: ptr.To(uint(5)), // "created by" user 5
UserID: new(uint(5)), // "created by" user 5
Tags: []string{"tag:server"},
},
wantIsTagged: true,
@@ -253,7 +252,7 @@ func TestNodeOwnershipModel(t *testing.T) {
name: "user-owned node has no tags",
node: Node{
ID: 2,
UserID: ptr.To(uint(5)),
UserID: new(uint(5)),
Tags: []string{},
},
wantIsTagged: false,
@@ -265,7 +264,7 @@ func TestNodeOwnershipModel(t *testing.T) {
name: "node with only authkey tags - not tagged (tags should be copied)",
node: Node{
ID: 3,
UserID: ptr.To(uint(5)), // "created by" user 5
UserID: new(uint(5)), // "created by" user 5
AuthKey: &PreAuthKey{
Tags: []string{"tag:database"},
},

View File

@@ -110,9 +110,7 @@ func TestCanUsePreAuthKey(t *testing.T) {
if err == nil {
t.Errorf("expected error but got none")
} else {
var httpErr PAKError
ok := errors.As(err, &httpErr)
httpErr, ok := errors.AsType[PAKError](err)
if !ok {
t.Errorf("expected HTTPError but got %T", err)
} else {