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
4 changes: 2 additions & 2 deletions .github/TESTING_AUTOMATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Issue #48 is a perfect test case for the Ansible dependency updates:

### Method 1: Manually Trigger via GitHub Actions UI

1. Go to the [Actions tab](https://github.com/Stensel8/Scripts/actions)
1. Go to the [Actions tab](https://github.com/Thectic-NL/Scripts/actions)
2. Select "Auto-Update Dependencies" workflow
3. Click "Run workflow"
4. Enter issue number: `48`
Expand All @@ -29,7 +29,7 @@ Issue #48 is a perfect test case for the Ansible dependency updates:

The workflow automatically runs when dependency issues are opened or edited:

1. Go to [Issue #48](https://github.com/Stensel8/Scripts/issues/48)
1. Go to [Issue #48](https://github.com/Thectic-NL/Scripts/issues/48)
2. Click "Edit" on the issue
3. Add a space or make any minor edit to the description
4. Save the changes
Expand Down
12 changes: 12 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
self-hosted-runner: {}

paths:
.github/workflows/validate-scripts.yml:
ignore:
# validate-scripts.yml already runs `shellcheck -S warning` itself
# (see the "Run ShellCheck" step) as a deliberate choice to treat
# info/style findings as non-blocking. actionlint's own shellcheck
# integration doesn't pick up a repo .shellcheckrc, so without this
# it re-litigates that choice at error level for the same scripts.
- 'SC2086:info:'
- 'SC2129:style:'
74 changes: 74 additions & 0 deletions .github/scripts/check-renovate-patterns.py
Original file line number Diff line number Diff line change
@@ -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.")
88 changes: 88 additions & 0 deletions .github/workflows/config-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright (C) 2026 Sten Tijhuis
# SPDX-License-Identifier: MIT
name: Config validation

on:
push:
branches: [main]
paths:
- 'renovate.json'
- '.github/renovate.json'
- '.github/dependabot.yml'
- '.github/dependabot.yaml'
- '.github/scripts/check-renovate-patterns.py'
- '.github/workflows/**'
pull_request:
branches: [main]
paths:
- 'renovate.json'
- '.github/renovate.json'
- '.github/dependabot.yml'
- '.github/dependabot.yaml'
- '.github/scripts/check-renovate-patterns.py'
- '.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/*'

- name: Validate Renovate config
env:
NPM_CONFIG_LOGLEVEL: error
run: npx --yes --package renovate -- renovate-config-validator --strict

- name: Check Renovate file patterns
run: python3 .github/scripts/check-renovate-patterns.py renovate.json .github/renovate.json

- 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"

- 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
84 changes: 84 additions & 0 deletions .github/workflows/deploy-bunny.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright (C) 2026 Sten Tijhuis
# SPDX-License-Identifier: MIT
name: Deploy to Bunny.net

on:
push:
branches: ["main"]
paths:
- 'src/**'
- '.github/workflows/deploy-bunny.yml'
workflow_dispatch:

permissions: {}

concurrency:
group: "deploy"
cancel-in-progress: true

defaults:
run:
shell: bash

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
with:
fetch-depth: 0
persist-credentials: false

- 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/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" \
&& sudo dpkg -i "${{ runner.temp }}/hugo.deb"

- name: Build with Hugo
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
HUGO_ENVIRONMENT: production
TZ: Europe/Amsterdam
run: |
cd src && hugo \
--gc \
--minify \
--baseURL "https://scripts.thectic.nl/"

- name: Upload to Bunny Storage
env:
AWS_ACCESS_KEY_ID: ${{ secrets.BUNNY_STORAGE_ZONE }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BUNNY_ACCESS_KEY }}
AWS_DEFAULT_REGION: de
STORAGE_ZONE: ${{ secrets.BUNNY_STORAGE_ZONE }}
STORAGE_ENDPOINT: ${{ secrets.BUNNY_STORAGE_ENDPOINT }}
run: |
aws s3 sync src/public/ "s3://${STORAGE_ZONE}/" \
--endpoint-url "${STORAGE_ENDPOINT}" \
--delete \
--no-progress

- name: Wait for storage replication
run: sleep 15

- name: Purge Bunny Pull Zone cache
env:
PULL_ZONE_ID: ${{ secrets.BUNNY_PULL_ZONE_ID }}
API_KEY: ${{ secrets.BUNNY_API_KEY }}
run: |
curl -sS --fail-with-body -X POST \
"https://api.bunny.net/pullzone/${PULL_ZONE_ID}/purgeCache" \
-H "AccessKey: ${API_KEY}" \
-H "Content-Type: application/json"
34 changes: 34 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (C) 2026 Sten Tijhuis
# SPDX-License-Identifier: MIT
name: PR title

on:
pull_request:
types: [opened, edited, synchronize, reopened]

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
32 changes: 32 additions & 0 deletions .github/workflows/trivy-scan.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
.claude
src/public/
src/resources/
src/.hugo_build.lock
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Scripts

Installer scripts for tools and software I use regularly. Tailored to my preferences, but easy to adapt.
Installer scripts for tools and software used regularly. Tailored to THectic's preferences, but easy to adapt.

Also browsable as a site: [scripts.thectic.nl](https://scripts.thectic.nl) (`src/`).

## Usage

```bash
git clone --recurse-submodules https://github.com/Stensel8/scripts.git
git clone --recurse-submodules https://github.com/Thectic-NL/Scripts.git
cd scripts
```

Expand Down
Loading
Loading