Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .claude/agents/headscale-integration-tester.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ go test ./integration -timeout 45m
**Timeout Guidelines by Test Type**:
- **Basic functionality tests**: `--timeout=900s` (15 minutes minimum)
- **Route/ACL tests**: `--timeout=1200s` (20 minutes)
- **HA/failover tests**: `--timeout=1800s` (30 minutes)
- **HA/failover tests**: `--timeout=1800s` (30 minutes)
- **Long-running tests**: `--timeout=2100s` (35 minutes)
- **Full test suite**: `-timeout 45m` (45 minutes)

Expand Down Expand Up @@ -433,7 +433,7 @@ When you understand a test's purpose through debugging, always add comprehensive
//
// The test verifies:
// - Route announcements are received and tracked
// - ACL policies control route approval correctly
// - ACL policies control route approval correctly
// - Only approved routes appear in peer network maps
// - Route state persists correctly in the database
func TestSubnetRoutes(t *testing.T) {
Expand Down Expand Up @@ -535,7 +535,7 @@ var nodeKey key.NodePublic
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)

for _, node := range nodes {
if node.GetName() == "router" {
routeNode = node
Expand All @@ -550,7 +550,7 @@ assert.EventuallyWithT(t, func(c *assert.CollectT) {
assert.EventuallyWithT(t, func(c *assert.CollectT) {
status, err := client.Status()
assert.NoError(c, err)

peerStatus, ok := status.Peer[nodeKey]
assert.True(c, ok, "peer should exist in status")
requirePeerSubnetRoutesWithCollect(c, peerStatus, expectedPrefixes)
Expand All @@ -566,7 +566,7 @@ assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, nodes, 2)

// Second unrelated external call - WRONG!
status, err := client.Status()
assert.NoError(c, err)
Expand All @@ -577,7 +577,7 @@ assert.EventuallyWithT(t, func(c *assert.CollectT) {
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)

// NEVER do this!
assert.EventuallyWithT(t, func(c2 *assert.CollectT) {
status, _ := client.Status()
Expand Down Expand Up @@ -666,11 +666,11 @@ When working within EventuallyWithT blocks where you need to prevent panics:
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)

// For array bounds - use require with t to prevent panic
assert.Len(c, nodes, 6) // Test expectation
require.GreaterOrEqual(t, len(nodes), 3, "need at least 3 nodes to avoid panic")

// For nil pointer access - use require with t before dereferencing
assert.NotNil(c, srs1PeerStatus.PrimaryRoutes) // Test expectation
require.NotNil(t, srs1PeerStatus.PrimaryRoutes, "primary routes must be set to avoid panic")
Expand All @@ -681,7 +681,7 @@ assert.EventuallyWithT(t, func(c *assert.CollectT) {
}, 5*time.Second, 200*time.Millisecond, "checking route state")
```

**Key Principle**:
**Key Principle**:
- Use `assert` with `c` (*assert.CollectT) for test expectations that can be retried
- Use `require` with `t` (*testing.T) for MUST conditions that prevent panics
- Within EventuallyWithT, both are available - choose based on whether failure would cause a panic
Expand All @@ -704,7 +704,7 @@ assert.EventuallyWithT(t, func(c *assert.CollectT) {
assert.EventuallyWithT(t, func(c *assert.CollectT) {
status, err := client.Status()
assert.NoError(c, err)

// Check all peers have expected routes
for _, peerKey := range status.Peers() {
peerStatus := status.Peer[peerKey]
Expand Down
27 changes: 27 additions & 0 deletions .golangci-lint-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Wrapper script for golangci-lint pre-commit hook
# Finds where the current branch diverged from the main branch

set -euo pipefail

# Try to find the main branch reference in order of preference:
# 1. upstream/main (common in forks)
# 2. origin/main (common in direct clones)
# 3. main (local branch)
for ref in upstream/main origin/main main; do
if git rev-parse --verify "$ref" >/dev/null 2>&1; then
MAIN_REF="$ref"
break
fi
done

# If we couldn't find any main branch, just check the last commit
if [ -z "${MAIN_REF:-}" ]; then
MAIN_REF="HEAD~1"
fi

# Find where current branch diverged from main
MERGE_BASE=$(git merge-base HEAD "$MAIN_REF" 2>/dev/null || echo "HEAD~1")

# Run golangci-lint only on changes since branch point
exec golangci-lint run --new-from-rev="$MERGE_BASE" --timeout=5m --fix
24 changes: 5 additions & 19 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,31 @@
"claude-code-mcp": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@steipete/claude-code-mcp@latest"
],
"args": ["-y", "@steipete/claude-code-mcp@latest"],
"env": {}
},
"sequential-thinking": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
],
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"],
"env": {}
},
"nixos": {
"type": "stdio",
"command": "uvx",
"args": [
"mcp-nixos"
],
"args": ["mcp-nixos"],
"env": {}
},
"context7": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@upstash/context7-mcp"
],
"args": ["-y", "@upstash/context7-mcp"],
"env": {}
},
"git": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@cyanheads/git-mcp-server"
],
"args": ["-y", "@cyanheads/git-mcp-server"],
"env": {}
}
}
Expand Down
75 changes: 75 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# prek/pre-commit configuration for headscale
# See: https://prek.j178.dev/quickstart/
# See: https://prek.j178.dev/builtin/

# Global exclusions - ignore docs and generated code
exclude: ^(docs/|gen/)

repos:
# Built-in hooks from pre-commit/pre-commit-hooks
# prek will use fast-path optimized versions automatically
# See: https://prek.j178.dev/builtin/
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-json
- id: check-merge-conflict
- id: check-symlinks
- id: check-toml
- id: check-xml
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: mixed-line-ending
- id: trailing-whitespace

# Local hooks for project-specific tooling
- repo: local
hooks:
# nixpkgs-fmt for Nix files
- id: nixpkgs-fmt
name: nixpkgs-fmt
entry: nixpkgs-fmt
language: system
files: \.nix$

# Prettier for formatting
- id: prettier
name: prettier
entry: prettier --write --list-different
language: system
types_or:
[
javascript,
jsx,
ts,
tsx,
yaml,
json,
toml,
html,
css,
scss,
sass,
markdown,
]
exclude: ^CHANGELOG\.md$

# Prettier for CHANGELOG.md with special formatting
- id: prettier-changelog
name: prettier-changelog
entry: prettier --write --print-width 80 --prose-wrap always
language: system
files: ^CHANGELOG\.md$

# golangci-lint for Go code quality
- id: golangci-lint
name: golangci-lint
entry: .golangci-lint-hook.sh
language: system
types: [go]
pass_filenames: false
Loading
Loading