diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 617355d6..00000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Lint - -on: [pull_request] - -concurrency: - group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - -jobs: - golangci-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - - name: Get changed files - id: changed-files - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - with: - filters: | - files: - - '*.nix' - - 'go.*' - - '**/*.go' - - 'integration_test/' - - 'config-example.yaml' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - if: steps.changed-files.outputs.files == 'true' - - - name: golangci-lint - if: steps.changed-files.outputs.files == 'true' - run: golangci-lint run - --new-from-rev=${{github.event.pull_request.base.sha}} - --output.text.path=stdout - --output.text.print-linter-name - --output.text.print-issued-lines - --output.text.colors - - prettier-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 2 - - name: Get changed files - id: changed-files - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - with: - filters: | - files: - - '*.nix' - - '**/*.md' - - '**/*.yml' - - '**/*.yaml' - - '**/*.ts' - - '**/*.js' - - '**/*.sass' - - '**/*.css' - - '**/*.scss' - - '**/*.html' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - if: steps.changed-files.outputs.files == 'true' - - - name: Prettify code - if: steps.changed-files.outputs.files == 'true' - run: prettier --no-error-on-unmatched-pattern - --ignore-unknown --check **/*.{ts,js,md,yaml,yml,sass,css,scss,html} diff --git a/.github/workflows/nix-checks.yml b/.github/workflows/nix-checks.yml new file mode 100644 index 00000000..03c4b8e8 --- /dev/null +++ b/.github/workflows/nix-checks.yml @@ -0,0 +1,56 @@ +name: Nix Flake Checks + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +# Each job only runs `nix build .#checks..`; the check logic lives +# in flake.nix via the flake-checks library. The fileset-filtered checks hit the +# hestia cache when their inputs are unchanged, so no changed-files gating. +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - name: build + run: nix build -L .#checks.x86_64-linux.build + + gotest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - name: gotest + run: nix build -L .#checks.x86_64-linux.gotest + + golangci-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - name: golangci-lint + run: nix build -L .#checks.x86_64-linux.golangci-lint + + formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - name: formatting + run: nix build -L .#checks.x86_64-linux.formatting diff --git a/.github/workflows/servertest.yml b/.github/workflows/servertest.yml new file mode 100644 index 00000000..795b2959 --- /dev/null +++ b/.github/workflows/servertest.yml @@ -0,0 +1,36 @@ +name: Server Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +# hscontrol/servertest is excluded from the sandboxed gotest flake check: it is +# slow (10s+ convergence cases) and timing-sensitive (race/stress/HA property +# tests), so it runs here in the devShell with a generous timeout instead. +defaults: + run: + shell: nix develop --fallback --command bash -e {0} + +permissions: + contents: read + +jobs: + servertest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + + - name: go test ./hscontrol/servertest + env: + CGO_ENABLED: "0" + run: go test -timeout=20m ./hscontrol/servertest/... diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index dadacddc..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Tests - -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 2 - - - name: Get changed files - id: changed-files - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - with: - filters: | - files: - - '*.nix' - - 'go.*' - - '**/*.go' - - 'integration_test/' - - 'config-example.yaml' - - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - if: steps.changed-files.outputs.files == 'true' - - - name: Run tests - if: steps.changed-files.outputs.files == 'true' - env: - # As of 2025-01-06, these env vars was not automatically - # set anymore which breaks the initdb for postgres on - # some of the database migration tests. - LC_ALL: "en_US.UTF-8" - LC_CTYPE: "en_US.UTF-8" - run: gotestsum diff --git a/flake.lock b/flake.lock index 81b43806..c723d287 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,27 @@ { "nodes": { + "flake-checks": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1781951072, + "narHash": "sha256-hA9u6hB4QzpReP8hudxqiaa4vLhoRvtXi6YmUgJRGEs=", + "owner": "kradalby", + "repo": "flake-checks", + "rev": "3d2882efec5cf10f8b5a8a035d93ba6ba9f21717", + "type": "github" + }, + "original": { + "owner": "kradalby", + "repo": "flake-checks", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -18,6 +40,24 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1781153106, @@ -36,7 +76,8 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", + "flake-checks": "flake-checks", + "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs" } }, @@ -54,6 +95,42 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "flake-checks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 78daf742..0513aed7 100644 --- a/flake.nix +++ b/flake.nix @@ -9,12 +9,17 @@ # once it ships go_1_26 >= 1.26.4. nixpkgs.url = "github:NixOS/nixpkgs/staging-next-26.05"; flake-utils.url = "github:numtide/flake-utils"; + # Reusable Go flake checks (build/test/lint/format); CI runs them via + # `nix build .#checks..` instead of bespoke per-tool steps. + flake-checks.url = "github:kradalby/flake-checks"; + flake-checks.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = { self , nixpkgs , flake-utils + , flake-checks , ... }: let @@ -176,6 +181,59 @@ contents = [ pkgs.headscale ]; config.Entrypoint = [ (pkgs.headscale + "/bin/headscale") ]; }; + + # Go flake checks from the flake-checks library. CI gates on + # `nix build .#checks..`; the logic lives here, not in + # bespoke workflow steps. Linux-only: parts of the tree are + # Linux-specific and the pure unit subset is validated by CI. + fc = flake-checks.lib; + common = { + inherit pkgs; + root = ./.; + pname = "headscale"; + version = headscaleVersion; + vendorHash = (builtins.fromJSON (builtins.readFile ./flakehashes.json)).vendor.sri; + goPkg = pkgs.go_1_26; + # //go:embed targets and test-read files outside the default whitelist. + embedDirs = [ ./hscontrol/assets ./hscontrol/db/schema.sql ./config-example.yaml ]; + extraSrc = [ + ./hscontrol/testdata + ./hscontrol/types/testdata + ./hscontrol/db/testdata + ./hscontrol/policy/v2/testdata + ]; + }; + goChecks = { + build = fc.goBuild (common // { subPackages = [ "cmd/headscale" ]; }); + + # The pure unit subset. ./integration (Docker) and + # ./hscontrol/servertest (slow: 10s+ convergence plus race/stress/HA + # property tests — run by the servertest workflow instead) are dropped + # from the test set but kept in source so cmd/hi and friends still + # compile; TestPostgres* needs a server (the SQLite equivalents still + # run). CGO off matches the build. + gotest = fc.goTest (common // { + testExclude = [ "/integration" "/hscontrol/servertest" ]; + goSkip = [ "TestPostgres" ]; + testEnv = "export CGO_ENABLED=0"; + }); + + # Full-tree golangci-lint (golines, gofumpt, etc.); uses the overlay's + # golangci-lint built against the pinned Go. + golangci-lint = fc.goLint common; + + # nixpkgs-fmt + prettier, excluding generated output. goFmt = "off": + # Go formatting (golines, gofumpt) is enforced by the golangci-lint + # check, not treefmt. prettierExts matches the old prettier-lint glob + # (no json: testdata fixtures are hand-formatted). + formatting = fc.goFormat (common // { + goFmt = "off"; + prettier = true; + prettierExts = [ "ts" "js" "md" "yaml" "yml" "sass" "css" "scss" "html" ]; + # Mirror .prettierignore (docs/ are mkdocs-flavoured; gen/ generated). + fmtExclude = [ ./gen ./docs ]; + }); + }; in { # `nix develop` @@ -221,6 +279,9 @@ checks = { headscale = pkgs.testers.nixosTest (import ./nix/tests/headscale.nix); - }; + } + # The Go build/test checks are gated to Linux: parts of the tree are + # Linux-specific and the pure unit subset is validated by CI. + // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux goChecks; }); }