diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7c42757 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +# EditorConfig houdt de opmaak gelijk tussen editors en IDE's. +# VS Code, JetBrains en anderen lezen dit bestand vanzelf. +# Meer info: https://editorconfig.org +# +# Deze versie is organisatiebreed gelijk. Wijk hier niet per repository van af: +# vijf licht verschillende varianten leverden alleen ruis op, geen voordeel. +# +# De configbestanden die zelf geen comments kunnen dragen omdat het JSON is, +# staan hier genoemd zodat er ergens een aanwijzing is wat ze doen: +# +# .htmlhintrc - HTML-linting (HTMLHint). Structuur- en toegankelijkheidsregels. +# renovate.json - Renovate-bot. Automatische dependency-updates via pull requests. + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +# Trailing whitespace betekent iets in Markdown (regeleinde) +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[*.py] +indent_size = 4 + +[*.sh] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..45ff4d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# .gitattributes zorgt voor consistente regeleindes tussen Windows, Mac en Linux. +# Zonder dit kunnen regeleindes per ontwikkelaar of OS verschillen, wat leidt +# tot onnodige git-diffs en merge-conflicten. +# +# Deze versie is organisatiebreed gelijk. Wijk hier niet per repository van af: +# vier licht verschillende varianten leverden alleen ruis op, geen voordeel. + +# Standaard: forceer LF voor alle tekstbestanden +* text=auto eol=lf + +# Binaire bestanden: geen regeleindeconversie +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.avif binary +*.pdf binary +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary +*.zip binary diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f40285f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +## Summary + + + +## Type of change + + + +- [ ] `feat` — new page or feature +- [ ] `fix` — bug fix (broken link, incorrect command, layout issue) +- [ ] `content` — update or improve existing content +- [ ] `docs` — changes to CONTRIBUTING, README, or meta documentation +- [ ] `chore` — maintenance (dependencies, config, CI/CD) +- [ ] `refactor` — restructuring without content changes +- [ ] `style` — formatting, whitespace, typos +- [ ] `revert` — reverting a previous commit + +> [PR title and commit types must follow these standards — view the contributing guide](https://github.com/Thectic-NL/BypassNRO/blob/main/CONTRIBUTING.md#commit-messages) + +## Checklist + +- [ ] PR title follows the commit convention (e.g. `fix: correct nmcli command`) +- [ ] Both EN (`*.md`) and NL (`*.nl.md`) versions updated (if content changed) +- [ ] No broken internal links +- [ ] Tested locally with `cd src && hugo server` diff --git a/.github/scripts/check-renovate-patterns.py b/.github/scripts/check-renovate-patterns.py new file mode 100755 index 0000000..bb7c695 --- /dev/null +++ b/.github/scripts/check-renovate-patterns.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +"""Flag Renovate file patterns that look like a regex but are not delimited. + +managerFilePatterns and matchFileNames accept "RegEx (re2) and glob patterns". +A value counts as a regex only when it is wrapped in slashes; everything else +is read as a glob. So a pattern like + + "^\\.github/workflows/.*\\.ya?ml$" + +matches no file at all, and the custom manager around it never fires. Nothing +reports this: renovate-config-validator says the config is valid, because it +is -- it just silently does nothing. The only visible symptom is a dependency +that stops receiving updates, which is easy to miss for months. + +Usage: check-renovate-patterns.py [config.json ...] +Missing files are skipped, so the same call works in every repository. +""" +import json +import pathlib +import re +import sys + +# Constructs that carry meaning in a regex but not in a glob. +REGEXY = re.compile(r"^\^|\$$|\\\.|\.\*|\.\+|\(\?|\[\^|\\d|\\w|\\s|[)?]\|") + +# Renovate options whose values are matched as "regex or glob". +PATTERN_KEYS = { + "managerFilePatterns", + "matchFileNames", + "fileMatch", + "matchPackageNames", +} + +problems = [] + + +def walk(node, path, source): + if isinstance(node, dict): + for key, value in node.items(): + if key in PATTERN_KEYS and isinstance(value, list): + for index, pattern in enumerate(value): + if not isinstance(pattern, str): + continue + # A trailing "i" flag is allowed: /pattern/i + delimited = pattern.startswith("/") and pattern.rstrip("i").endswith("/") + if REGEXY.search(pattern) and not delimited: + problems.append((source, f"{path}.{key}[{index}]", pattern)) + walk(value, f"{path}.{key}", source) + elif isinstance(node, list): + for index, item in enumerate(node): + walk(item, f"{path}[{index}]", source) + + +files = [path for path in (pathlib.Path(a) for a in sys.argv[1:]) if path.is_file()] +if not files: + print("No Renovate config found to check.") + sys.exit(0) + +for config in files: + walk(json.loads(config.read_text()), "$", str(config)) + +if problems: + print("Renovate file patterns that look like a regex but are not wrapped in slashes.") + print("Renovate reads these as globs, so they match nothing and the rule never fires.\n") + for source, where, pattern in problems: + print(f"::error file={source}::{where}: {pattern!r} is read as a glob, not a regex") + print(f" {source} {where}") + print(f" found: {pattern!r}") + print(f" expect: '/{pattern}/'\n") + sys.exit(1) + +print(f"Checked {len(files)} Renovate config file(s): all file patterns are well formed.") diff --git a/.github/scripts/update-tool-checksums.sh b/.github/scripts/update-tool-checksums.sh new file mode 100755 index 0000000..9b4f15e --- /dev/null +++ b/.github/scripts/update-tool-checksums.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# +# Recalculate and optionally apply the SHA-256 checksums of the pinned CI tools. +# +# Renovate bumps the version numbers but cannot compute a checksum, so without +# this the pinned hash keeps pointing at the previous release and every bump +# fails the build with "computed checksum did NOT match". The workflow in +# .github/workflows/update-checksums.yml runs this on Renovate's own pull +# requests and commits the result back onto the branch. +# +# The hash is not simply taken from whatever the download happened to return. +# Each project publishes its own checksum file next to the release; the +# download is verified against that first, and only a verified hash is written +# into the repository. +# +# Usage: +# .github/scripts/update-tool-checksums.sh # show, then ask +# .github/scripts/update-tool-checksums.sh --apply # write without asking +# + +set -euo pipefail + +readonly RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' + +Write-Log() { + local level=$1; shift + local color=$NC + case $level in + INFO) color=$BLUE ;; + SUCCESS) color=$GREEN ;; + WARN) color=$YELLOW ;; + ERROR) color=$RED ;; + esac + if [[ $level == ERROR ]]; then + echo -e "${color}[$level]${NC} $*" >&2 + else + echo -e "${color}[$level]${NC} $*" + fi +} + +Stop-Script() { + Write-Log ERROR "$1" + exit 1 +} + +Show-Usage() { + cat <<'EOF' +Usage: update-tool-checksums.sh [--apply] + +Options: + --apply Write the checksums without prompting + -h, --help Show this help +EOF +} + +APPLY=false +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) APPLY=true; shift ;; + -h|--help) Show-Usage; exit 0 ;; + *) Write-Log ERROR "Unknown argument: $1"; Show-Usage; exit 1 ;; + esac +done + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +readonly REPO_ROOT +cd "$REPO_ROOT" + +readonly CONFIG_VALIDATION=".github/workflows/config-validation.yml" +readonly PR_CHECKS=".github/workflows/pr-checks.yml" + +# ── Reading and writing the pinned values ─────────────────────────────────── + +# Usage: Get-KeyValue -> value of `KEY: "value"` +Get-KeyValue() { + sed -n "s/^[[:space:]]*$2:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$1" | head -n1 +} + +# Usage: Set-KeyValue +Set-KeyValue() { + sed -i "s|^\([[:space:]]*$2:[[:space:]]*\"\)[^\"]*\"|\1$3\"|" "$1" +} + +# ── Fetching and verifying ────────────────────────────────────────────────── + +TEMP_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEMP_DIR"' EXIT + +# Usage: Get-VerifiedHash +# Downloads the artifact, checks it against the hash the project published, and +# echoes that hash. Refuses to return anything if the two disagree. +Get-VerifiedHash() { + local name=$1 url=$2 expected=$3 + local file="$TEMP_DIR/$name" + + [[ "$expected" =~ ^[a-f0-9]{64}$ ]] || Stop-Script "$name: no valid checksum published upstream (got: '$expected')" + + curl -sSL --fail-with-body --retry 5 --retry-delay 3 --retry-all-errors -o "$file" "$url" \ + || Stop-Script "$name: download failed ($url)" + + local actual + actual="$(sha256sum "$file" | awk '{print $1}')" + + if [[ "$actual" != "$expected" ]]; then + Stop-Script "$name: download does not match the published checksum. published=$expected downloaded=$actual" + fi + + echo "$actual" +} + +# Usage: Get-PublishedHash +# Pulls one line out of a checksums file and returns the hash on it. +Get-PublishedHash() { + curl -sSL --fail-with-body --retry 5 --retry-delay 3 --retry-all-errors "$1" \ + | grep -- "$2" | awk '{print $1}' | head -n1 +} + +# ── The tools ─────────────────────────────────────────────────────────────── + +ACTIONLINT_VERSION="$(Get-KeyValue "$CONFIG_VALIDATION" ACTIONLINT_VERSION)" +LYCHEE_VERSION="$(Get-KeyValue "$PR_CHECKS" LYCHEE_VERSION)" + +for pair in "actionlint:$ACTIONLINT_VERSION" "lychee:$LYCHEE_VERSION"; do + [[ -n "${pair#*:}" ]] || Stop-Script "Could not read the ${pair%%:*} version. Did the file layout change?" +done + +Write-Log INFO "Versions found in the repository:" +echo " actionlint: $ACTIONLINT_VERSION" +echo " lychee: $LYCHEE_VERSION" +echo + +Write-Log INFO "Downloading and verifying against the published checksums..." + +ACTIONLINT_SHA256="$(Get-VerifiedHash "actionlint.tar.gz" \ + "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \ + "$(Get-PublishedHash "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" "linux_amd64.tar.gz")")" +Write-Log SUCCESS "actionlint: $ACTIONLINT_SHA256" + +LYCHEE_SHA256="$(Get-VerifiedHash "lychee.tar.gz" \ + "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-x86_64-unknown-linux-gnu.tar.gz" \ + "$(Get-PublishedHash "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-x86_64-unknown-linux-gnu.tar.gz.sha256" "")")" +Write-Log SUCCESS "lychee: $LYCHEE_SHA256" + +echo +if [[ "$APPLY" != true ]]; then + read -rp "Write these checksums into the repository? [y/N] " response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + Write-Log INFO "No changes made" + exit 0 + fi +fi + +Set-KeyValue "$CONFIG_VALIDATION" ACTIONLINT_SHA256 "$ACTIONLINT_SHA256" +Set-KeyValue "$PR_CHECKS" LYCHEE_SHA256 "$LYCHEE_SHA256" + +Write-Log SUCCESS "Updated:" +echo " - $CONFIG_VALIDATION" +echo " - $PR_CHECKS" diff --git a/.github/workflows/config-validation.yml b/.github/workflows/config-validation.yml new file mode 100644 index 0000000..88e08ed --- /dev/null +++ b/.github/workflows/config-validation.yml @@ -0,0 +1,126 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +name: Config validation + +# De bot-configs zijn het enige deel van CI dat verder nergens door wordt +# geraakt: een kapotte renovate.json of dependabot.yml laat geen build falen, +# die houdt gewoon stilletjes op met zijn werk. Deze workflow merkt dat op. + +on: + push: + branches: [main] + paths: + - 'renovate.json' + - '.github/renovate.json' + - '.github/dependabot.yml' + - '.github/dependabot.yaml' + - '.github/scripts/check-renovate-patterns.py' + # Broader than the other repos: the actionlint job below covers every + # workflow, so every workflow change is relevant here. + - '.github/workflows/**' + pull_request: + branches: [main] + paths: + - 'renovate.json' + - '.github/renovate.json' + - '.github/dependabot.yml' + - '.github/dependabot.yaml' + - '.github/scripts/check-renovate-patterns.py' + # Broader than the other repos: the actionlint job below covers every + # workflow, so every workflow change is relevant here. + - '.github/workflows/**' + workflow_dispatch: + +permissions: {} + +jobs: + bot-configs: + name: Renovate and Dependabot config + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out source code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: 'lts/*' + + # Renovates eigen validator. --strict laat hem ook falen op warnings, + # bijvoorbeeld een optie die geldig maar verouderd is. Zonder argumenten + # zoekt hij de configbestanden zelf op en valideert hij ze als + # repository-config; geef je een pad mee, dan valideert hij ze als + # global config, en dat is een andere en zwakkere set regels. + # + # Bewust niet vastgezet. Dit is een linter op onze eigen config en geen + # onderdeel van wat we uitleveren, en juist de nieuwste release kent de + # nieuwste deprecations. Zijn eigen versie is geen pull request waard. + # + # NPM_CONFIG_LOGLEVEL: npm print "npm warn deprecated ..." voor packages + # diep in Renovates eigen dependency-boom. Die zeggen niets over de config + # die gevalideerd wordt, en ze lezen alsof dat wel zo is, is de fout die + # je vanzelf maakt als ze vlak boven de output van de validator staan. + - name: Validate Renovate config + env: + NPM_CONFIG_LOGLEVEL: error + run: npx --yes --package renovate -- renovate-config-validator --strict + + # De validator hierboven accepteert een correct gevormd patroon dat + # nergens op matcht; dit dekt het gat dat hij daarmee laat. + - name: Check Renovate file patterns + run: python3 .github/scripts/check-renovate-patterns.py renovate.json .github/renovate.json + + # GitHub valideert dependabot.yml pas als die op de default branch staat, + # en meldt het resultaat op een tabblad dat niemand opent. Dit haalt dat + # naar voren, naar de pull request. + - name: Validate Dependabot config + env: + # renovate: datasource=pypi depName=check-jsonschema + CHECK_JSONSCHEMA_VERSION: "0.38.0" + run: | + config="" + for candidate in .github/dependabot.yml .github/dependabot.yaml; do + if [ -f "$candidate" ]; then + config="$candidate" + break + fi + done + if [ -z "$config" ]; then + echo "No dependabot.yml in this repository; nothing to validate." + exit 0 + fi + pipx install "check-jsonschema==${CHECK_JSONSCHEMA_VERSION}" + check-jsonschema --builtin-schema vendor.dependabot "$config" + + # De workflowbestanden zijn ook config. De andere repositories draaien + # actionlint vanuit hun quality-workflow; deze had geen equivalent, dus + # het hoort hier. + # + # Als stap en niet als eigen job: GitHub rekent per job en rondt naar + # boven af op een hele minuut. actionlint is in vijf seconden klaar en + # heeft dezelfde checkout nodig als de stappen hierboven, dus een eigen + # job kostte een volle minuut extra voor niets. + # + # Vanaf hier draait elke stap op !cancelled(), zodat één rode controle de + # andere niet verbergt. De job faalt alsnog zodra er iets fout is. + - name: Install actionlint + if: ${{ !cancelled() }} + env: + # renovate: datasource=github-releases depName=rhysd/actionlint + ACTIONLINT_VERSION: "1.7.12" + ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8" + run: | + curl -sSL --fail-with-body -o actionlint.tar.gz \ + --retry 5 --retry-delay 3 --retry-all-errors \ + "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" + echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c - + tar -xzf actionlint.tar.gz actionlint + sudo install -m 0755 actionlint /usr/local/bin/actionlint + + - name: Run actionlint + if: ${{ !cancelled() }} + run: actionlint -color diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy-bunny.yml similarity index 72% rename from .github/workflows/deploy.yml rename to .github/workflows/deploy-bunny.yml index 3ad178b..8dab884 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy-bunny.yml @@ -1,17 +1,16 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT name: Deploy to Bunny.net on: push: branches: ["main"] paths: - - 'content/**' - - 'static/**' - - 'hugo.toml' - - 'go.mod' - - 'go.sum' - - '.github/workflows/deploy.yml' + - 'src/**' + - '.github/workflows/deploy-bunny.yml' workflow_dispatch: +# Geen token nodig; jobs die dat wel zijn, vragen er expliciet om. permissions: {} concurrency: @@ -24,9 +23,12 @@ defaults: jobs: build: + name: Build and deploy to Bunny Storage runs-on: ubuntu-latest permissions: contents: read + env: + HUGO_VERSION: 0.165.0 steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -34,25 +36,28 @@ jobs: fetch-depth: 0 persist-credentials: false + # go-version-file houdt de Go van de runner gelijk aan wat src/go.mod + # vraagt. Zonder dit installeert setup-go een oudere Go met + # GOTOOLCHAIN=local, en dan weigert `go` de hextra-module op te halen + # omdat go.mod een nieuwere Go eist dan er staat. - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: src/go.mod - name: Install Hugo run: | wget -O "${{ runner.temp }}/hugo.deb" \ - "https://github.com/gohugoio/hugo/releases/download/v0.165.0/hugo_extended_0.165.0_linux-amd64.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" \ && sudo dpkg -i "${{ runner.temp }}/hugo.deb" - - name: Download modules - run: hugo mod get -u - - name: Build with Hugo env: HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache HUGO_ENVIRONMENT: production TZ: Europe/Amsterdam run: | - hugo \ + cd src && hugo \ --gc \ --minify \ --baseURL "https://bypassnro.thectic.nl/" @@ -65,7 +70,7 @@ jobs: STORAGE_ZONE: ${{ secrets.BUNNY_STORAGE_ZONE }} STORAGE_ENDPOINT: ${{ secrets.BUNNY_STORAGE_ENDPOINT }} run: | - aws s3 sync public/ "s3://${STORAGE_ZONE}/" \ + aws s3 sync src/public/ "s3://${STORAGE_ZONE}/" \ --endpoint-url "${STORAGE_ENDPOINT}" \ --delete \ --no-progress diff --git a/.github/workflows/powershell.yml b/.github/workflows/powershell.yml index 85d511a..5cb3079 100644 --- a/.github/workflows/powershell.yml +++ b/.github/workflows/powershell.yml @@ -1,47 +1,64 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# -# https://github.com/microsoft/action-psscriptanalyzer -# For more information on PSScriptAnalyzer in general, see -# https://github.com/PowerShell/PSScriptAnalyzer - +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT name: PSScriptAnalyzer +# bypass.ps1 is de kern van deze repository, dus de PowerShell-lint houdt een +# eigen workflow -- de andere Thectic-repo's hebben geen PowerShell om naast te +# zetten. Instellingen staan in .github/PSScriptAnalyzerSettings.psd1. +# +# microsoft/psscriptanalyzer-action is niet door GitHub gecertificeerd en kent +# geen versietags; hij staat op een vastgezette commit-SHA. + on: push: - branches: [ "main", "hugo" ] + branches: [main] + paths: + - '**/*.ps1' + - '**/*.psm1' + - '**/*.psd1' + - '.github/workflows/powershell.yml' pull_request: - branches: [ "main", "hugo" ] + branches: [main] + paths: + - '**/*.ps1' + - '**/*.psm1' + - '**/*.psd1' + - '.github/workflows/powershell.yml' schedule: - cron: '43 19 * * 3' + workflow_dispatch: -permissions: - contents: read +permissions: {} + +concurrency: + group: powershell-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - build: - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + psscriptanalyzer: name: PSScriptAnalyzer runs-on: ubuntu-latest + permissions: + contents: read + # Voor de SARIF-upload naar het Security-tabblad. + security-events: write steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Check out source code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Run PSScriptAnalyzer - uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f + uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f # v1.1 with: - # Check https://github.com/microsoft/action-psscriptanalyzer for more info about the options. path: .\ recurse: true settings: .github/PSScriptAnalyzerSettings.psd1 output: results.sarif - # Upload the SARIF file generated in the previous step - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4 + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + if: ${{ !cancelled() }} with: sarif_file: results.sarif + category: psscriptanalyzer diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..43ab093 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,202 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +name: PR Checks + +on: + pull_request: + branches: [main] + +# Snel achter elkaar naar dezelfde pull request pushen startte evenveel volledige +# runs, en de eerste zijn dan al achterhaald. +concurrency: + group: pr-checks-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: {} + +jobs: + # Alle controles op een pull request, in een job. + # + # GitHub rekent per job en rondt elke job naar boven af op een hele minuut. + # Losse jobs voor markdownlint, de Hugo-build en de linkcheck kostten drie + # gefactureerde minuten voor werk dat samen in een minuut klaar is, plus een + # artefact met upload en download om de gebouwde site naar de linkcheck te + # krijgen. In een job leest lychee gewoon de map die de build ernaast zet. + # + # Elke stap draait op !cancelled(), zodat een rode markdownlint de Hugo-build + # niet verbergt. De job faalt alsnog zodra er iets fout is. + # + # De job draagt `pull-requests: write` omdat de laatste stap de checklist in + # de omschrijving bijwerkt. Alle actions staan op een vastgezette SHA. + pr-checks: + name: PR checks + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + env: + HUGO_VERSION: 0.165.0 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # fetch-depth: 0 voor Hugo's .GitInfo en .Lastmod. + fetch-depth: 0 + persist-credentials: false + + # ── 1. Markdown-opmaak ────────────────────────────────────────────────── + - name: Markdown lint + id: markdown + if: ${{ !cancelled() }} + uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0 + with: + globs: | + src/content/**/*.md + *.md + + # ── 2. Elk Engels document heeft een Nederlandse tegenhanger ──────────── + - name: Check every .md has a matching .nl.md + id: bilingual + if: ${{ !cancelled() }} + run: | + missing="" + while IFS= read -r en; do + base="${en%.md}" + nl="${base}.nl.md" + if [ ! -f "$nl" ]; then + missing="$missing\n $en → $nl missing" + fi + done < <(find src/content -name '*.md' ! -name '*.nl.md') + if [ -n "$missing" ]; then + echo -e "::error::Missing Dutch translation(s):$missing" + exit 1 + fi + echo "All content has EN + NL versions." + + # ── 3. Hugo bouwt zonder fouten ───────────────────────────────────────── + # + # go-version-file houdt de Go van de runner gelijk aan wat src/go.mod + # vraagt; anders weigert `go` de hextra-module op te halen. + - name: Setup Go + if: ${{ !cancelled() }} + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: src/go.mod + + - name: Install Hugo + if: ${{ !cancelled() }} + run: | + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" \ + && sudo dpkg -i "${{ runner.temp }}/hugo.deb" + + - name: Build + id: hugo + if: ${{ !cancelled() }} + env: + HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache + HUGO_ENVIRONMENT: production + TZ: Europe/Amsterdam + run: cd src && hugo --gc --minify --baseURL "http://localhost/" + + # ── 4. Kapotte interne links ──────────────────────────────────────────── + # + # Met de hand geïnstalleerd in plaats van via lycheeverse/lychee-action, + # dat zijn binary met een kale `curl -sfLO` ophaalt: geen retry, en geen + # controle op wat er terugkomt. Vastgezette versie, geverifieerde + # checksum, retry. De checksum wordt op Renovate-PR's herberekend door + # .github/workflows/update-checksums.yml. + - name: Install lychee + if: ${{ !cancelled() }} + env: + # extractVersion: lychee tagt zijn releases als "lychee-v0.24.2" en + # niet als "v0.24.2", dus het standaardpatroon leest de versie er niet + # uit. + # renovate: datasource=github-releases depName=lycheeverse/lychee extractVersion=^lychee-v(?.+)$ + LYCHEE_VERSION: "0.24.2" + # Uit de lychee-x86_64-unknown-linux-gnu.tar.gz.sha256 van de release zelf + LYCHEE_SHA256: "1f4e0ef7f6554a6ed33dd7ac144fb2e1bbed98598e7af973042fc5cd43951c9a" + run: | + curl -sSL --fail-with-body -o lychee.tar.gz \ + --retry 5 --retry-delay 3 --retry-all-errors \ + "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-x86_64-unknown-linux-gnu.tar.gz" + echo "${LYCHEE_SHA256} lychee.tar.gz" | sha256sum -c - + tar -xzf lychee.tar.gz lychee-x86_64-unknown-linux-gnu/lychee + sudo install -m 0755 lychee-x86_64-unknown-linux-gnu/lychee /usr/local/bin/lychee + lychee --version + + # lychee in offline modus: elke interne href en src moet uitkomen op een + # bestand dat de build daadwerkelijk heeft opgeleverd. + # + # --index-files: Hugo serveert elke pagina als /index.html, en + # zonder dit stopt lychee bij de map en zijn #fragments naar een andere + # pagina niet te controleren. + # + # De glob staat bewust tussen quotes: zonder quotes vult bash hem eerst + # in, en zonder globstar valt ** terug op een mapniveau. + - name: Check internal links + id: links + if: ${{ !cancelled() }} + run: | + lychee --offline --include-fragments --index-files index.html \ + --root-dir "${GITHUB_WORKSPACE}/src/public" "src/public/**/*.html" + + # ── 5. Checklist in de omschrijving bijwerken ─────────────────────────── + # + # Leest de uitkomst van de stappen hierboven in plaats van van losse jobs. + # Draait op !cancelled() en niet op success(), want juist bij een rode + # controle wil je de checklist bijgewerkt zien. + - name: Update PR checklist + if: ${{ !cancelled() }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RESULT_BILINGUAL: ${{ steps.bilingual.outcome }} + RESULT_HUGO: ${{ steps.hugo.outcome }} + RESULT_LINKS: ${{ steps.links.outcome }} + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + let body = pr.body || ''; + if (!body.trim()) return; + + const setCheck = (keyword, passed) => { + body = body.replace( + new RegExp(`- \\[[ xX]\\] (.*${keyword}.*)`, 'i'), + `- [${passed ? 'x' : ' '}] $1` + ); + }; + + // Dezelfde typelijst als pr-title.yml en CONTRIBUTING.md. Een scope + // en een `!` voor een breaking change zijn toegestaan: feat(nav)!: ... + const TITLE_RE = + /^(feat|fix|content|docs|chore|refactor|style|revert)(\([^)]+\))?!?: .+/; + + setCheck('PR title follows', TITLE_RE.test(pr.title)); + setCheck('Both EN', process.env.RESULT_BILINGUAL === 'success'); + setCheck('No broken', process.env.RESULT_LINKS === 'success'); + setCheck('Tested locally', process.env.RESULT_HUGO === 'success'); + + // De niet-gekozen types weghalen, maar alleen als er al een gekozen + // is. Zonder die voorwaarde stript de eerste run alle regels weg + // voordat de auteur er een heeft aangevinkt. + const TYPE_LINE = /^- \[([ xX])\] `\w+` —[^\n]*\n?/gm; + const ticked = [...body.matchAll(TYPE_LINE)] + .some(m => m[1].toLowerCase() === 'x'); + if (ticked) { + body = body.replace(/^- \[ \] `\w+` —[^\n]*\n?/gm, ''); + } + + body = body.replace(/\n{3,}/g, '\n\n'); + + if (body !== pr.body) { + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + body, + }); + } diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..e7840e9 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,45 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +name: PR title + +# De titel van de pull request is wat er op main terechtkomt zodra je squasht, +# dus dat is de plek waar Conventional Commits gecontroleerd moet worden en niet +# op de losse commits in de branch. +# +# Renovate levert zijn eigen titels al in dit formaat aan; dat is de +# semanticCommits-instelling in renovate.json. Deze controle dekt de rest. + +on: + pull_request: + # edited hoort erbij: zonder dat blijft de check rood staan nadat iemand de + # titel heeft verbeterd, want een titelwijziging is geen nieuwe push. + types: [opened, edited, synchronize, reopened] + +# Snel achter elkaar de omschrijving aanpassen startte evenveel runs. Alleen +# de laatste zegt nog iets, dus de rest mag weg. +concurrency: + group: pr-title-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: {} + +jobs: + pr-title: + name: Conventional commit title + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + content + docs + chore + refactor + style + revert diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 0000000..6422c97 --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,32 @@ +name: "Trivy filesystem scan" + +on: + schedule: + - cron: '0 2 * * 0' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +jobs: + trivy-scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run Trivy filesystem scan + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: fs + severity: CRITICAL,HIGH + format: sarif + output: trivy-results.sarif + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + if: always() + with: + sarif_file: trivy-results.sarif diff --git a/.github/workflows/update-checksums.yml b/.github/workflows/update-checksums.yml new file mode 100644 index 0000000..3017d10 --- /dev/null +++ b/.github/workflows/update-checksums.yml @@ -0,0 +1,85 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +name: Update tool SHA256 checksums + +# Renovate bumpt de vastgezette toolversies maar kan geen checksum berekenen, +# dus op eigen kracht landt elke bump met de hash van de vorige release er nog +# in, en stopt de build op "computed checksum did NOT match". Dit herberekent +# de hashes op Renovates pull requests en commit ze terug op de branch. +# +# Renovate moet die commits leren negeren, anders ziet hij de branch als door +# iemand anders gewijzigd en onderhoudt hij de pull request niet meer. Dat is +# de gitIgnoredAuthors-regel in renovate.json. + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main] + paths: + - '.github/workflows/config-validation.yml' + - '.github/workflows/pr-checks.yml' + - '.github/scripts/update-tool-checksums.sh' + +permissions: {} + +jobs: + update-checksums: + name: Recalculate SHA256 checksums + runs-on: ubuntu-latest + # Alleen Renovates eigen branches. Dit op een pull request van een mens + # draaien betekent commits pushen naar een branch waar iemand op dat moment + # aan werkt. + # + # De auteur van de pull request, niet github.actor. actor is degene die het + # meest recente event veroorzaakte, en dat is bij een synchronize degene die + # als laatste pushte; dat vergelijken met een botnaam is een controle die + # zizmor terecht spoofbaar noemt. De auteur ligt vast zodra de pull request + # geopend wordt en is niet naar een ander account te zetten. + # + # Also require the head repo to be this repo, not a fork. This job checks + # out github.head_ref and runs a script from it with a write token, so a + # PR from a fork naming its branch renovate/* would otherwise get its + # attacker-controlled script executed with push access. + if: >- + startsWith(github.head_ref, 'renovate/') && + github.event.pull_request.user.login == 'renovate[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: write + steps: + # persist-credentials: false, ook al pusht deze job. Anders staat het + # token de hele job in .git/config, ook terwijl het script hieronder + # release-tarballs van internet haalt. De push-stap krijgt het token in + # plaats daarvan expliciet mee, voor precies één commando. + # Pinned to the exact commit the pull_request event fired for, not the + # mutable branch name. head_ref is a moving target: a push to the + # renovate/* branch between the job's "if:" check above and this step + # would check out commits that check never evaluated. head.sha is fixed + # in the event payload, so it can't move underneath the job. + - name: Check out the pull request branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + # Het script controleert elke download tegen de checksum die het project + # naast de release publiceert voordat er iets wordt weggeschreven, dus een + # hash landt hier alleen als upstream er ook voor instaat. + - name: Recalculate and apply checksums + run: .github/scripts/update-tool-checksums.sh --apply + + - name: Commit updated checksums + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: ${{ github.head_ref }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add .github/workflows/config-validation.yml .github/workflows/pr-checks.yml + if git diff --staged --quiet; then + echo "Checksums are already up to date, nothing to commit." + else + git commit -m "chore: update tool SHA256 checksums" + git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ + "HEAD:refs/heads/${BRANCH}" + fi diff --git a/.gitignore b/.gitignore index e89bd9f..2acf1c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,15 @@ +# Hugo +/src/public/ +/src/resources/ +/src/_vendor/ +src/.hugo_build.lock + # Dependencies node_modules/ -# Hugo -public/ -.hugo_build.lock +# Tooling +/.claude +__pycache__/ # IDE .idea/ diff --git a/.lychee.toml b/.lychee.toml new file mode 100644 index 0000000..3cf93a0 --- /dev/null +++ b/.lychee.toml @@ -0,0 +1,10 @@ +# Lychee link checker configuration +# Used by the PR checks workflow (offline mode — internal links only) + +# Skip anchors that Hugo generates dynamically +include_fragments = true + +# Don't fail on these — they're valid Hugo internal paths +exclude = [ + "^http://localhost", +] diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..a4ab999 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,55 @@ +default: true + +# ── Disabled: not applicable to Hugo docs ────────────────────────────────── + +# Line length — docs have long tables, code examples, and URLs +MD013: false + +# Inline HTML — Hugo shortcodes ({{< callout >}}, {{% steps %}}) trigger this +MD033: false + +# First line must be H1 — files start with YAML front matter +MD041: false + +# Bare URLs — allowed in code blocks and examples +MD034: false + +# Multiple H1s — Hugo steps/details shortcodes contain headings +MD025: false + +# Blank lines around fences — common inside Hugo shortcode blocks +MD031: false + +# Blank lines around lists — pervasive in existing content +MD032: false + +# Multiple consecutive blank lines — style preference, not a bug +MD012: false + +# Blank line inside blockquote — Hugo callout/details shortcodes trigger this +MD028: false + +# Ordered list prefix style — 1/2/3 vs 1/1/1 is a style choice +MD029: false + +# Emphasis used instead of heading — intentional in existing docs +MD036: false + +# Code block language — desirable but too many pre-existing violations +MD040: false + +# Headings surrounded by blank lines — pre-existing violations in some docs +MD022: false + +# Heading levels increment by one — Hugo docs intentionally start at ### because +# h1/h2 are rendered by the theme template, not the content file +MD001: false + +# Duplicate headings — only flag duplicates within the same section, not across +# different top-level sections (e.g. two separate "### Install" blocks are fine) +MD024: + siblings_only: true + +# Table column style (pipe spacing/alignment) — overly pedantic, tables render +# correctly regardless of exact pipe spacing +MD060: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..279f399 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing + +Found a Windows build where this method behaves differently, or spotted a wrong +command or a broken link? Issues and pull requests are welcome. + +--- + +## Commit messages + +This project uses [Conventional Commits](https://www.conventionalcommits.org/). + +**Format:** +``` +: +``` + +**Types:** + +| Type | When to use | +|------|-------------| +| `feat` | New page or new feature | +| `fix` | Bug fix — broken link, wrong command, layout issue | +| `content` | Update or improve existing page content | +| `docs` | Changes to README, CONTRIBUTING, or other meta files | +| `chore` | Maintenance — dependencies, config, CI/CD, Hugo theme | +| `refactor` | Restructure without changing content | +| `style` | Formatting, whitespace, typo fixes | +| `revert` | Reverting a previous commit | + +**Examples:** +``` +feat: add autounattend.xml install path +fix: correct Sysprep flag in bypass.ps1 +content: update timeline with 25H2 retail status +chore: upgrade Hextra theme +``` + +**Rules:** +- Use lowercase for the type and description +- Keep the subject line under 72 characters +- No period at the end +- Use the imperative mood ("add", "fix", "update" — not "added", "fixed") + +--- + +## Pull requests + +- PR titles must follow the same commit convention above +- One logical change per PR +- Update both EN (`*.md`) and NL (`*.nl.md`) versions where applicable +- Test locally with `cd src && hugo server` before opening a PR + +--- + +## Project layout + +The Hugo site lives in `src/`: + +- `src/content/` — page content (`_index.md` EN, `_index.nl.md` NL) +- `src/static/` — files served as-is: `bypass.ps1`, `unattend.xml`, `robots.txt` +- `src/layouts/` — template overrides on top of the Hextra theme +- `src/hugo.toml` — site configuration + +The site is built and deployed to Bunny.net from `.github/workflows/deploy-bunny.yml` +on every push to `main` that touches `src/`. + +--- + +## Language + +This site is bilingual (EN + NL). When updating content, edit both +`src/content/.md` and `src/content/.nl.md`, and keep the structure +and headings in sync between the two files. diff --git a/SECURITY.md b/SECURITY.md index 7e56dcb..5149bda 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,17 +1,30 @@ -## Reporting a Vulnerability +# Beveiligingsbeleid -**Do not open a public GitHub issue for security vulnerabilities.** +## Ondersteunde versies -Report privately via [GitHub Security Advisories](https://github.com/Stensel8/bypassnro/security/advisories/new). +Alleen `main` wordt ondersteund. Er is geen releasehistorie om bij te houden; +wat op `main` staat is de actuele staat. -Include: -- Description of the vulnerability -- Steps to reproduce -- Potential impact -- Suggested fix (if any) +## Een kwetsbaarheid melden -You will receive a response within 7 days. If the report is accepted, a fix will be released as soon as possible and you will be credited in the release notes. +Vind je een beveiligingsprobleem, meld het dan privé in plaats van via een +openbaar issue. -### Out of scope +Gebruik daarvoor GitHubs [private vulnerability reporting](https://github.com/Thectic-NL/BypassNRO/security/advisories/new) +voor deze repository. Je krijgt binnen enkele werkdagen een eerste reactie. -By design, `unattend.xml` creates accounts without a password and auto-logs in once, and the one-liner downloads and runs a remote script. These are documented in the README, not vulnerabilities. +Kan dat niet, of gaat het om iets dat breder speelt dan deze repository: + +- **E-mail:** +- **PGP-sleutel:** + +De ondertekende, canonieke contactgegevens staan in +[security.txt](https://thectic.nl/.well-known/security.txt). + +Meld kwetsbaarheden in software van derden niet hier maar bij het project zelf. + +## Buiten scope + +`unattend.xml` maakt bewust accounts zonder wachtwoord aan en meldt eenmalig +automatisch aan, en de one-liner downloadt en draait een script van afstand. +Dit staat zo in de documentatie beschreven en is geen kwetsbaarheid. diff --git a/renovate.json b/renovate.json index 86e988f..e5f9b13 100644 --- a/renovate.json +++ b/renovate.json @@ -1,42 +1,90 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ], - "timezone": "Europe/Amsterdam", - "forkProcessing": "disabled", - "pinDigests": true, - "assigneesFromCodeOwners": true, - "reviewersFromCodeOwners": true, - "enabledManagers": [ - "github-actions", - "npm" - ], - "labels": [ - "dependencies" - ], - "packageRules": [ - { - "matchManagers": [ - "dockerfile", - "docker-compose" - ], - "groupName": "Docker images", - "addLabels": [ - "docker" - ] - }, - { - "matchManagers": [ - "github-actions" - ], - "groupName": "GitHub Actions", - "addLabels": [ - "github-actions" - ] - } - ], - "automerge": true, - "automergeType": "pr", - "semanticCommits": "enabled" -} +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "timezone": "Europe/Amsterdam", + "forkProcessing": "enabled", + "pinDigests": true, + "gitIgnoredAuthors": [ + "github-actions[bot]@users.noreply.github.com", + "41898282+github-actions[bot]@users.noreply.github.com" + ], + "enabledManagers": [ + "github-actions", + "gomod", + "custom.regex" + ], + "prHourlyLimit": 2, + "prConcurrentLimit": 5, + "labels": [ + "dependencies" + ], + "packageRules": [ + { + "matchManagers": [ + "github-actions" + ], + "groupName": "GitHub Actions", + "addLabels": [ + "github-actions" + ] + }, + { + "matchManagers": [ + "gomod" + ], + "groupName": "Go modules", + "addLabels": [ + "go" + ] + }, + { + "description": "Versions pinned by hand in the workflows. Not automerged: actionlint and lychee are pinned alongside a checksum that has to be updated in the same PR.", + "matchManagers": [ + "custom.regex" + ], + "groupName": "Build tooling versions", + "addLabels": [ + "build-tooling" + ], + "automerge": false + } + ], + "customManagers": [ + { + "customType": "regex", + "managerFilePatterns": [ + "/^\\.github/workflows/.*\\.ya?ml$/" + ], + "matchStrings": [ + "HUGO_VERSION:\\s*(?\\d+\\.\\d+\\.\\d+)" + ], + "depNameTemplate": "gohugoio/hugo", + "datasourceTemplate": "github-releases", + "versioningTemplate": "semver" + }, + { + "customType": "regex", + "description": "Tool versions pinned in workflows, annotated with a `# renovate:` comment on the line above", + "managerFilePatterns": [ + "/^\\.github/workflows/.*\\.ya?ml$/" + ], + "matchStrings": [ + "# renovate: datasource=(?[a-z-]+) depName=(?\\S+)(?: extractVersion=(?\\S+))?\\s+[A-Za-z_]+: \"(?[^\"]+)\"" + ], + "extractVersionTemplate": "^v?(?.*)$" + } + ], + "automerge": true, + "automergeType": "pr", + "semanticCommits": "enabled", + "assignees": [ + "Stensel8", + "AdiH1310" + ], + "reviewers": [ + "Stensel8", + "AdiH1310" + ] +} diff --git a/content/_index.md b/src/content/_index.md similarity index 100% rename from content/_index.md rename to src/content/_index.md diff --git a/src/content/_index.nl.md b/src/content/_index.nl.md new file mode 100644 index 0000000..63f43df --- /dev/null +++ b/src/content/_index.nl.md @@ -0,0 +1,74 @@ +--- +title: "" +toc: false +--- + +
+{{< hextra/hero-headline >}} + BypassNRO +{{< /hextra/hero-headline >}} +
+ +
+{{< hextra/hero-subtitle >}} + Windows OOBE omzeilen met Sysprep en unattend.xml; een betrouwbare methode die nog steeds werkt +{{< /hextra/hero-subtitle >}} +
+ +
+{{< hextra/hero-badge link="https://bypassnro.thectic.nl/bypass.ps1" >}} + Script downloaden + {{< icon name="download" attributes="height=20" >}} +{{< /hextra/hero-badge >}} +
+ +
+ +## Checksums + +**SHA256:** +- `bypass.ps1`: `bd418a953d1550bec7660f7de4508ad9b306666fc352f068368d331a1e074593` +- `unattend.xml`: `a7bdc3c7ee9046ddaf8caf9b198c915d7ad5b55814a5fccbcf07ca4480b8b877` + +Controleer met: `sha256sum bypass.ps1 unattend.xml` + +{{< callout type="info" >}} +**Waarom deze methode nog werkt:** Sinds maart 2025 heeft Microsoft het commando `oobe\bypassnro` uit Windows 11 (24H2/25H2) verwijderd. Het alternatief `start ms-cxh:localonly` werd geblokkeerd vanaf Insider-build 26220.6772 (6 oktober 2025). Of die blokkade ook de retail-tak 25H2 heeft bereikt, is niet opnieuw getest, dus controleer je eigen image voordat je erop vertrouwt. + +Deze aanpak met Sysprep en unattend.xml blijft werken omdat het onderdeel is van Windows' officiële enterprise-deploymenttools en niet eenvoudig te blokkeren is zonder enterprise-scenario's te breken. + +Zie de [GitHub-repository](https://github.com/Thectic-NL/BypassNRO) voor openstaande issues en updates. +{{< /callout >}} + +## Gebruik + +Druk tijdens Windows OOBE (Out of Box Experience) op **Shift+F10** en voer uit: + +### PowerShell +```powershell +iex(irm bypassnro.thectic.nl/bypass.ps1) +``` + +### Met parameters +```powershell +& ([scriptblock]::Create((irm bypassnro.thectic.nl/bypass.ps1))) -Force # bevestiging overslaan +& ([scriptblock]::Create((irm bypassnro.thectic.nl/bypass.ps1))) -NoReboot # afsluiten in plaats van herstarten +``` + +## Accounts + +De unattend.xml maakt `Admin` (Administrators) en `User` (Users) aan, beide **zonder wachtwoord**, en meldt `Admin` één keer automatisch aan. Stel direct na de eerste aanmelding een wachtwoord in. + +## Tijdlijn + +| Datum | Gebeurtenis | +|------|-------| +| Maart 2025 | Microsoft verwijdert `oobe\bypassnro` uit Windows 11 (24H2/25H2) | +| 6 oktober 2025 | Alternatief `start ms-cxh:localonly` geblokkeerd vanaf Insider-builds 26220.6772 / 26120.6772 | +| 1 september 2026 | Deze Sysprep-methode werkt nog steeds | + +## Opmerkingen + +Alleen de `oobeSystem`-pass wordt toegepast. `Sysprep /oobe` zonder `/generalize` draait `specialize` niet opnieuw, dus alles wat daar staat wordt genegeerd. Gebruik voor debloaten en tweaks [WinDeploy](https://github.com/Stensel8/WinDeploy) of [WinUtil](https://github.com/ChrisTitusTech/winutil). + +Problemen oplossen: Sysprep logt naar `C:\Windows\System32\Sysprep\Panther\setuperr.log`. diff --git a/go.mod b/src/go.mod similarity index 90% rename from go.mod rename to src/go.mod index b84e41e..f923b15 100644 --- a/go.mod +++ b/src/go.mod @@ -1,5 +1,5 @@ module github.com/Thectic-NL/BypassNRO -go 1.26.0 +go 1.26 require github.com/imfing/hextra v0.12.3 // indirect diff --git a/go.sum b/src/go.sum similarity index 100% rename from go.sum rename to src/go.sum diff --git a/hugo.toml b/src/hugo.toml similarity index 77% rename from hugo.toml rename to src/hugo.toml index 515a3ee..395f83b 100644 --- a/hugo.toml +++ b/src/hugo.toml @@ -1,7 +1,6 @@ baseURL = 'https://bypassnro.thectic.nl/' title = 'BypassNRO' defaultContentLanguage = 'en' -locale = 'en-US' enableRobotsTXT = true disableKinds = ['taxonomy', 'term', 'RSS'] disableHugoGeneratorInject = true @@ -11,6 +10,12 @@ disableHugoGeneratorInject = true label = 'English' weight = 1 title = 'BypassNRO' + locale = 'en-US' + [languages.nl] + label = 'Nederlands' + weight = 2 + title = 'BypassNRO' + locale = 'nl-NL' [menu] [[menu.main]] @@ -19,6 +24,16 @@ disableHugoGeneratorInject = true url = 'https://github.com/Thectic-NL/BypassNRO' [menu.main.params] icon = 'github' + [[menu.main]] + name = 'Language' + weight = 2 + [menu.main.params] + type = 'language-switch' + [[menu.main]] + name = 'Theme' + weight = 3 + [menu.main.params] + type = 'theme-toggle' [params] description = 'Windows OOBE bypass using Sysprep and unattend.xml; a reliable method that still works' diff --git a/layouts/_partials/custom/footer.html b/src/layouts/_partials/custom/footer.html similarity index 100% rename from layouts/_partials/custom/footer.html rename to src/layouts/_partials/custom/footer.html diff --git a/static/bypass.ps1 b/src/static/bypass.ps1 similarity index 96% rename from static/bypass.ps1 rename to src/static/bypass.ps1 index 1ec4298..ff0513a 100644 --- a/static/bypass.ps1 +++ b/src/static/bypass.ps1 @@ -29,13 +29,13 @@ Run Sysprep with /shutdown instead of /reboot. .EXAMPLE - & ([scriptblock]::Create((irm bypassnro.stensel.nl))) + & ([scriptblock]::Create((irm bypassnro.thectic.nl/bypass.ps1))) Run from an elevated prompt (Shift+F10 during OOBE gives you one). See the README for the shorter pipe-to-execute one-liner. .EXAMPLE - & ([scriptblock]::Create((irm bypassnro.stensel.nl))) -Force + & ([scriptblock]::Create((irm bypassnro.thectic.nl/bypass.ps1))) -Force Same, without the confirmation prompt. The short one-liner form cannot pass parameters, so use a script block when you need them. @@ -52,7 +52,7 @@ [CmdletBinding()] param( - [string]$UnattendUrl = 'https://raw.githubusercontent.com/Stensel8/bypassnro/main/unattend.xml', + [string]$UnattendUrl = 'https://bypassnro.thectic.nl/unattend.xml', [string]$Destination = 'C:\Windows\Panther\unattend.xml', [switch]$Force, [switch]$NoReboot diff --git a/static/robots.txt b/src/static/robots.txt similarity index 100% rename from static/robots.txt rename to src/static/robots.txt diff --git a/static/unattend.xml b/src/static/unattend.xml similarity index 95% rename from static/unattend.xml rename to src/static/unattend.xml index 56977e3..872fedf 100644 --- a/static/unattend.xml +++ b/src/static/unattend.xml @@ -1,6 +1,6 @@