Restructure: move proxmox/ into subfolder, add pihole/ section with config and CI #3

Merged
beatzaplenty merged 2 commits from worktree-debian-restructure into main 2026-07-23 02:33:54 +00:00
34 changed files with 2132 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
name: Secret Scan
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
secret-scan:
name: Scan for secrets and sensitive config
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for gitleaks git-log scan
- name: Install gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
gitleaks version
- name: Run secret scan
run: bash scripts/check-secrets.sh
+28
View File
@@ -0,0 +1,28 @@
name: Secret Scan
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
secret-scan:
name: Scan for secrets and sensitive config
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for gitleaks git-log scan
- name: Install gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
gitleaks version
- name: Run secret scan
run: bash scripts/check-secrets.sh
+41
View File
@@ -0,0 +1,41 @@
# Gitleaks configuration for debian-configuration repo.
# Extends the default ruleset with Pi-hole-specific secret patterns.
# https://github.com/gitleaks/gitleaks
title = "debian-configuration secret scan"
[extend]
useDefault = true
# ── Custom rules ───────────────────────────────────────────────────────────────
[[rules]]
id = "pihole-pwhash"
description = "Pi-hole password hash (pihole.toml webserver.api.pwhash)"
regex = '''pwhash\s*=\s*"[^"]{10,}"'''
tags = ["pihole", "password"]
[[rules]]
id = "pihole-totp-secret"
description = "Pi-hole 2FA TOTP secret"
regex = '''totp_secret\s*=\s*"[^"]{10,}"'''
tags = ["pihole", "2fa"]
[[rules]]
id = "pihole-app-pwhash"
description = "Pi-hole app password hash"
regex = '''app_pwhash\s*=\s*"[^"]{10,}"'''
tags = ["pihole", "password"]
# ── Allowlist ──────────────────────────────────────────────────────────────────
[allowlist]
description = "Known-safe patterns in this repo"
regexes = [
# TLS cert path reference — not the key itself
'''cert\s*=\s*"/etc/pihole/tls\.pem"''',
]
paths = [
# Example/template files are intentionally non-live
'''\.example$''',
]
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Apply a Pi-hole configuration directory to a destination Pi-hole instance.
#
# Usage: apply-config.sh <source-dir> <dest-host>
# source-dir Local directory containing config files (as written by pull-config.sh)
# dest-host SSH-reachable hostname or IP of the destination Pi-hole
#
# Example:
# ./apply-config.sh ./config root@pihole-new
# ./apply-config.sh /backup/pihole-20260723 root@192.168.2.101
#
# The destination Pi-hole must already have Pi-hole v6 installed.
# pihole-FTL is restarted at the end to apply the new config.
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <source-dir> <dest-host>" >&2
echo " source-dir Local directory with config files (from pull-config.sh)" >&2
echo " dest-host SSH target for the destination Pi-hole (e.g. root@pihole)" >&2
exit 1
}
[[ $# -eq 2 ]] || usage
SOURCE="$1"
DEST="$2"
[[ -d "${SOURCE}" ]] || { echo "Error: source directory '${SOURCE}' not found" >&2; exit 1; }
[[ -f "${SOURCE}/pihole.toml" ]] || { echo "Error: '${SOURCE}/pihole.toml' not found — is this a valid config dir?" >&2; exit 1; }
echo "Applying Pi-hole config from ${SOURCE}${DEST}"
# ── Verify destination is running Pi-hole ──────────────────────────────────────
ssh "${DEST}" "command -v pihole-FTL >/dev/null 2>&1 || { echo 'pihole-FTL not found on destination'; exit 1; }"
# ── pihole.toml ────────────────────────────────────────────────────────────────
echo " pihole.toml"
ssh "${DEST}" "cp /etc/pihole/pihole.toml /etc/pihole/pihole.toml.pre-apply 2>/dev/null || true"
scp "${SOURCE}/pihole.toml" "${DEST}:/etc/pihole/pihole.toml"
ssh "${DEST}" "chown pihole:pihole /etc/pihole/pihole.toml; chmod 640 /etc/pihole/pihole.toml"
# ── custom dnsmasq drop-ins ────────────────────────────────────────────────────
if [[ -d "${SOURCE}/dnsmasq.d" ]] && [[ -n "$(ls "${SOURCE}/dnsmasq.d/"*.conf 2>/dev/null)" ]]; then
echo " dnsmasq.d/ (custom drop-ins)"
for f in "${SOURCE}/dnsmasq.d/"*.conf; do
name="$(basename "$f")"
echo " ${name}"
scp "${f}" "${DEST}:/etc/dnsmasq.d/${name}"
done
fi
# ── Restart pihole-FTL ─────────────────────────────────────────────────────────
echo " Restarting pihole-FTL"
ssh "${DEST}" "systemctl restart pihole-FTL"
sleep 2
ssh "${DEST}" "systemctl is-active pihole-FTL"
echo "Done. Config applied to ${DEST}."
echo "Note: gravity (blocklists) is not transferred — run 'pihole updateGravity' on ${DEST} to rebuild."
@@ -0,0 +1,20 @@
# Detect iPXE clients (already running iPXE).
dhcp-match=set:ipxe,175
dhcp-userclass=set:ipxe,iPXE
# Detect UEFI x86_64 clients by client architecture.
dhcp-match=set:efi64,option:client-arch,7
dhcp-match=set:efi64,option:client-arch,9
# Boot file selection — more positive tags = higher priority.
# EFI iPXE (2 tags): already running iPXE on EFI, chain to HTTP menu.
dhcp-boot=tag:ipxe,tag:efi64,http://192.168.2.247/boot.ipxe
# BIOS iPXE (1 tag): already running iPXE, chain to HTTP menu.
dhcp-boot=tag:ipxe,http://192.168.2.247/boot.ipxe
# EFI non-iPXE (1 tag): send the EFI iPXE binary.
dhcp-boot=tag:efi64,ipxe.efi,,192.168.2.247
# BIOS/legacy fallback (0 tags): send the BIOS iPXE binary.
dhcp-boot=undionly.kpxe,,192.168.2.247
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Pull Pi-hole configuration from a running instance to a local directory.
# Sensitive fields (password hashes, TOTP secrets) are redacted automatically.
#
# Usage: pull-config.sh <source-host> <dest-dir>
# source-host SSH-reachable hostname or IP of the source Pi-hole
# dest-dir Local directory to write config into (created if absent)
#
# Example:
# ./pull-config.sh root@pihole ./config
# ./pull-config.sh root@192.168.2.253 /backup/pihole-$(date +%Y%m%d)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
usage() {
echo "Usage: $(basename "$0") <source-host> <dest-dir>" >&2
echo " source-host SSH target for the source Pi-hole (e.g. root@pihole)" >&2
echo " dest-dir Local directory to write config files into" >&2
exit 1
}
[[ $# -eq 2 ]] || usage
SOURCE="$1"
DEST="$2"
echo "Pulling Pi-hole config from ${SOURCE}${DEST}"
mkdir -p "${DEST}/dnsmasq.d"
# ── pihole.toml ────────────────────────────────────────────────────────────────
echo " pihole.toml"
ssh "${SOURCE}" "cat /etc/pihole/pihole.toml" > "${DEST}/pihole.toml"
"${SCRIPT_DIR}/sanitize-config.sh" "${DEST}/pihole.toml"
# ── custom dnsmasq drop-ins ────────────────────────────────────────────────────
# Pi-hole manages its own generated config; we only capture user-added files.
echo " dnsmasq.d/ (custom drop-ins)"
ssh "${SOURCE}" "ls /etc/dnsmasq.d/*.conf 2>/dev/null || true" | while read -r f; do
name="$(basename "$f")"
echo " ${name}"
ssh "${SOURCE}" "cat '${f}'" > "${DEST}/dnsmasq.d/${name}"
done
# ── DHCP static leases ─────────────────────────────────────────────────────────
echo " dhcp.leases"
ssh "${SOURCE}" "cat /etc/pihole/dhcp.leases 2>/dev/null || true" > "${DEST}/dhcp.leases"
echo "Done. Config written to ${DEST}/"
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Redact sensitive fields from a Pi-hole pihole.toml before committing.
# Called automatically by pull-config.sh; can also be run manually.
#
# Usage: sanitize-config.sh <pihole.toml>
# Edits the file in-place, replacing sensitive field values with "".
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <pihole.toml>" >&2
exit 1
}
[[ $# -eq 1 ]] || usage
FILE="$1"
[[ -f "$FILE" ]] || { echo "Error: file not found: $FILE" >&2; exit 1; }
REDACTED=0
redact_field() {
local field="$1"
# Match lines like: pwhash = "some-value" and blank the value
if grep -qE "^\s+${field}\s*=\s*\"[^\"]{1,}\"" "$FILE"; then
sed -i -E "s|^(\s+${field}\s*=\s*)\"[^\"]*\"|\1\"\"|" "$FILE"
echo " redacted: ${field}"
REDACTED=$((REDACTED + 1))
fi
}
echo "Sanitizing $(basename "$FILE")..."
redact_field "pwhash"
redact_field "app_pwhash"
redact_field "totp_secret"
if [[ $REDACTED -eq 0 ]]; then
echo " (nothing to redact)"
else
echo " ${REDACTED} field(s) redacted."
fi
View File
View File
View File
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Scan the repo for secrets and sensitive config values.
# Runs via CI (GitHub/Gitea Actions) and locally as a pre-commit check.
#
# Usage: scripts/check-secrets.sh [--staged-only]
# --staged-only Only check files staged for commit (for pre-commit hook use)
#
# Requires gitleaks on PATH, or falls back to Docker if available.
# Install gitleaks: https://github.com/gitleaks/gitleaks#installing
set -euo pipefail
REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)"
STAGED_ONLY=false
FAILURES=0
for arg in "$@"; do
[[ "$arg" == "--staged-only" ]] && STAGED_ONLY=true
done
cd "$REPO_ROOT"
# ── Resolve gitleaks binary ────────────────────────────────────────────────────
if command -v gitleaks &>/dev/null; then
GITLEAKS="gitleaks"
elif command -v docker &>/dev/null; then
GITLEAKS="docker run --rm -v ${REPO_ROOT}:/repo zricethezav/gitleaks:latest"
# Adjust paths for docker context
REPO_ROOT="/repo"
else
echo "ERROR: gitleaks not found. Install it or ensure Docker is available." >&2
echo " https://github.com/gitleaks/gitleaks#installing" >&2
exit 1
fi
echo "=== Secret scan ==="
if [[ "$STAGED_ONLY" == "true" ]]; then
# Pre-commit mode: scan only staged content
echo "Mode: staged files only"
if ! $GITLEAKS protect --staged --config="${REPO_ROOT}/.gitleaks.toml" --source="${REPO_ROOT}" 2>&1; then
FAILURES=$((FAILURES + 1))
fi
else
# CI mode: scan full git history
echo "Mode: full git history"
if ! $GITLEAKS detect --config="${REPO_ROOT}/.gitleaks.toml" --source="${REPO_ROOT}" 2>&1; then
FAILURES=$((FAILURES + 1))
fi
fi
# ── Pi-hole specific checks ────────────────────────────────────────────────────
echo ""
echo "=== Pi-hole config checks ==="
PIHOLE_TOML="${REPO_ROOT}/pihole/config/pihole.toml"
if [[ -f "$PIHOLE_TOML" ]]; then
# Check that known sensitive fields are empty
for field in pwhash totp_secret app_pwhash; do
value=$(grep -E "^\s+${field}\s*=" "$PIHOLE_TOML" | sed 's/.*=\s*"\(.*\)".*/\1/' | tr -d '[:space:]' || true)
if [[ -n "$value" && "$value" != '""' ]]; then
echo "FAIL: pihole.toml contains a non-empty '${field}' — run pihole/sanitize-config.sh before committing" >&2
FAILURES=$((FAILURES + 1))
else
echo " OK: ${field} is empty"
fi
done
else
echo " (pihole/config/pihole.toml not present, skipping Pi-hole checks)"
fi
# ── Summary ────────────────────────────────────────────────────────────────────
echo ""
if [[ $FAILURES -gt 0 ]]; then
echo "FAILED: ${FAILURES} issue(s) found. Fix before committing." >&2
exit 1
else
echo "All checks passed."
fi
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Install git hooks that run the secret scan before every commit.
# Run once after cloning: bash scripts/install-hooks.sh
set -euo pipefail
REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)"
HOOK="${REPO_ROOT}/.git/hooks/pre-commit"
cat > "$HOOK" << 'HOOK'
#!/usr/bin/env bash
exec "$(git rev-parse --show-toplevel)/scripts/check-secrets.sh" --staged-only
HOOK
chmod +x "$HOOK"
echo "Installed pre-commit hook → ${HOOK}"
echo "The secret scan will run automatically before every commit."