Kieran's opinionated (and probably slightly dumb) nix config

feat: add tangled sync service

dunkirk.sh c3548fd4 f5c5e91e

verified
Changed files
+239 -16
machines
terebithia
modules
home
system
nixos
services
secrets
+9
machines/terebithia/default.nix
···
file = ../../secrets/cloudflare.age;
owner = "caddy";
};
+
github-knot-sync = {
+
file = ../../secrets/github-knot-sync.age;
+
owner = "git";
+
};
};
environment.sessionVariables = {
···
hostname = "spindle.dunkirk.sh";
listenAddr = "127.0.0.1:6555";
};
+
};
+
+
atelier.services.knot-sync = {
+
enable = true;
+
secretsFile = config.age.secrets.github-knot-sync.path;
};
boot.loader.systemd-boot.enable = true;
+29 -16
modules/home/system/shell.nix
···
# Configuration variables - set these to your defaults
local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul"
local default_github_username="taciturnaxolotl"
+
local default_knot_host="knot.dunkirk.sh"
local extracted_github_username=""
# Check if current directory is a git repository
···
# Get the repository name from the current directory
local repo_name=$(basename "$(git rev-parse --show-toplevel)")
-
# Check if origin remote exists and points to ember
+
# Check if origin remote exists and points to knot
local origin_url=$(git remote get-url origin 2>/dev/null)
-
local origin_ember=false
+
local origin_knot=false
if [[ -n "$origin_url" ]]; then
# Try to extract GitHub username if origin is a GitHub URL
···
default_github_username=$extracted_github_username
fi
-
if [[ "$origin_url" == *"ember"* ]]; then
-
origin_ember=true
-
echo "✅ Origin remote exists and points to ember"
+
if [[ "$origin_url" == *"$default_knot_host"* || "$origin_url" == *"knot.dunkirk.sh"* ]]; then
+
origin_knot=true
+
echo "✅ Origin remote exists and points to knot"
else
-
echo "⚠️ Origin remote exists but doesn't point to ember"
+
echo "⚠️ Origin remote exists but doesn't point to knot"
fi
else
echo "⚠️ Origin remote doesn't exist"
···
fi
# Fix remotes if needed
-
if [[ "$origin_ember" = false || "$github_exists" = false ]]; then
-
echo "Setting up remotes..."
-
+
if [[ "$origin_knot" = false || "$github_exists" = false ]]; then
# Prompt for PLC identifier if needed
local plc_id=""
-
if [[ "$origin_ember" = false ]]; then
-
echo -n "Enter your PLC identifier [default: $default_plc_id]: "
-
read plc_input
-
plc_id=''${plc_input:-$default_plc_id}
+
local should_fix_origin=false
+
+
if [[ "$origin_knot" = false ]]; then
+
if [[ -n "$origin_url" ]]; then
+
echo -n "Migrate origin from $origin_url to knot.dunkirk.sh? [Y/n]: "
+
read fix_input
+
if [[ -z "$fix_input" || "$fix_input" =~ ^[Yy]$ ]]; then
+
should_fix_origin=true
+
fi
+
else
+
should_fix_origin=true
+
fi
+
+
if [[ "$should_fix_origin" = true ]]; then
+
echo -n "Enter your PLC identifier [default: $default_plc_id]: "
+
read plc_input
+
plc_id=''${plc_input:-$default_plc_id}
+
fi
fi
# Prompt for GitHub username with default from origin if available
···
fi
# Set up origin remote if needed
-
if [[ "$origin_ember" = false && -n "$plc_id" ]]; then
+
if [[ "$should_fix_origin" = true && -n "$plc_id" ]]; then
if git remote get-url origin &>/dev/null; then
git remote remove origin
fi
-
git remote add origin "git@ember:''${plc_id}/''${repo_name}"
-
echo "✅ Set up origin remote: git@ember:''${plc_id}/''${repo_name}"
+
git remote add origin "git@$default_knot_host:''${plc_id}/''${repo_name}"
+
echo "✅ Set up origin remote: git@$default_knot_host:''${plc_id}/''${repo_name}"
fi
# Set up GitHub remote if needed
+185
modules/nixos/services/knot-sync.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
+
let
+
cfg = config.atelier.services.knot-sync;
+
in
+
{
+
options.atelier.services.knot-sync = {
+
enable = lib.mkEnableOption "Knot to GitHub sync service";
+
+
repoDir = lib.mkOption {
+
type = lib.types.str;
+
default = "/home/git/did:plc:krxbvxvis5skq7jj6eot23ul";
+
description = "Directory containing git repositories";
+
};
+
+
githubUsername = lib.mkOption {
+
type = lib.types.str;
+
default = "taciturnaxolotl";
+
description = "GitHub username";
+
};
+
+
secretsFile = lib.mkOption {
+
type = lib.types.path;
+
description = "Path to secrets file containing GITHUB_TOKEN";
+
};
+
+
logFile = lib.mkOption {
+
type = lib.types.str;
+
default = "/home/git/knot-sync.log";
+
description = "Log file location";
+
};
+
+
interval = lib.mkOption {
+
type = lib.types.str;
+
default = "*/5 * * * *";
+
description = "Cron schedule for sync (default: every 5 minutes)";
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
systemd.services.knot-sync = {
+
description = "Sync Knot repositories to GitHub";
+
serviceConfig = {
+
Type = "oneshot";
+
User = "git";
+
EnvironmentFile = cfg.secretsFile;
+
ExecStart = pkgs.writeShellScript "knot-sync" ''
+
set -euo pipefail
+
+
# Variables
+
REPO_DIR="${cfg.repoDir}"
+
GITHUB_USERNAME="${cfg.githubUsername}"
+
LOG_FILE="${cfg.logFile}"
+
+
# Log function
+
log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"; }
+
+
# Create the post-receive hook template
+
cat <<'EOF' > /tmp/post-receive.template
+
#!${pkgs.bash}/bin/bash
+
# post-receive hook to sync to GitHub - AUTOGENERATED
+
+
# Load environment variables from secrets file
+
if [ -f "${cfg.secretsFile}" ]; then
+
source "${cfg.secretsFile}"
+
fi
+
+
# Variables
+
GITHUB_USERNAME="${cfg.githubUsername}"
+
LOG_FILE="${cfg.logFile}"
+
REPO_NAME=$(basename $(pwd))
+
+
# Log function
+
log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "''${LOG_FILE}"; }
+
+
# Check for nosync marker
+
if [ -f "$(pwd)/.nosync" ]; then
+
log "Skipping sync for $REPO_NAME (nosync marker present)"
+
exit 0
+
fi
+
+
# Function to sync to GitHub
+
sync_to_github() {
+
log "Syncing $REPO_NAME to GitHub"
+
expected_url="https://''${GITHUB_USERNAME}:''${GITHUB_TOKEN}@github.com/''${GITHUB_USERNAME}/''${REPO_NAME}.git"
+
current_url=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || echo "")
+
+
if [ -z "$current_url" ]; then
+
log "Adding origin remote"
+
${pkgs.git}/bin/git remote add origin "$expected_url"
+
elif [ "$current_url" != "$expected_url" ]; then
+
log "Updating origin remote URL"
+
${pkgs.git}/bin/git remote set-url origin "$expected_url"
+
fi
+
+
# Mirror push everything (refs, tags, branches)
+
if ${pkgs.git}/bin/git push --mirror origin 2>&1 | tee -a "''${LOG_FILE}"; then
+
log "Sync succeeded for $REPO_NAME"
+
return 0
+
else
+
log "Sync failed for $REPO_NAME"
+
return 1
+
fi
+
}
+
+
# Main
+
while read oldrev newrev refname; do
+
log "Received push for ref '$refname' (old revision: $oldrev, new revision: $newrev)"
+
sync_to_github
+
done
+
EOF
+
+
HOOK_TEMPLATE="/tmp/post-receive.template"
+
+
# Create the post-receive hook
+
create_hook() {
+
local new_repo_path="$1"
+
local hook_path="$new_repo_path/hooks/post-receive.d/forward"
+
local nosync_marker="$new_repo_path/.nosync"
+
+
# Skip if .nosync marker exists
+
if [ -f "$nosync_marker" ]; then
+
log "Skipping $new_repo_path (nosync marker present)"
+
return 0
+
fi
+
+
if [ -d "$new_repo_path" ] && [ ! -f "$hook_path" ]; then
+
# Check that it's a git repository, specifically a bare repo
+
if [ -f "$new_repo_path/config" ]; then
+
# Create hooks directory if it doesn't exist
+
mkdir -p "$(dirname "$hook_path")"
+
# Create hook from the template file, substituting variables.
+
if cat "$HOOK_TEMPLATE" > "$hook_path" && chmod +x "$hook_path"; then
+
log "Created hook for $new_repo_path"
+
# Check if repo has any commits before pushing
+
if (cd "$new_repo_path" && ${pkgs.git}/bin/git rev-parse HEAD >/dev/null 2>&1); then
+
# Auto push by simulating a post-receive hook trigger
+
log "Triggering initial push for $new_repo_path"
+
(cd "$new_repo_path" && \
+
echo "0000000000000000000000000000000000000000 $(${pkgs.git}/bin/git rev-parse HEAD) refs/heads/main" | \
+
"$hook_path")
+
fi
+
else
+
log "Hook creation failed for $new_repo_path"
+
fi
+
fi
+
fi
+
}
+
+
# Keep track of hooks created
+
hooks_created=0
+
+
# Find all directories that look like bare Git repos without a post-receive hook
+
${pkgs.findutils}/bin/find "$REPO_DIR" -mindepth 1 -maxdepth 1 -type d \! -name ".*" -print0 |
+
while IFS= read -r -d $'\0' repo_path; do
+
create_hook "$repo_path"
+
if [ $? -eq 0 ]; then
+
hooks_created=$((hooks_created + 1))
+
fi
+
done
+
+
# Only log completion if hooks were actually created
+
if [ $hooks_created -gt 0 ]; then
+
log "Sync job complete - Created $hooks_created hooks"
+
fi
+
'';
+
};
+
};
+
+
systemd.paths.knot-sync = {
+
description = "Watch for new Knot repositories";
+
wantedBy = [ "multi-user.target" ];
+
pathConfig = {
+
PathModified = cfg.repoDir;
+
Unit = "knot-sync.service";
+
MakeDirectory = true;
+
};
+
};
+
};
+
}
+13
secrets/github-knot-sync.age
···
+
age-encryption.org/v1
+
-> ssh-rsa DqcG0Q
+
eG7RPDx4xt/jE8lDjJ/whTT6ekS5ccZgLLzEqvJZ1bOsZm7ycfKRUjs5mZoNn8/Y
+
n6Rk6FyhTXSb0HrhCpSOSH23nCHwMFK/1H/pKdFpXTJAdx2LkX4VRI807/XjPHK4
+
Iyy0E1xL53RepvC6JPHmmW/Z7h/yamvP8MDE4Mds7oiBZYf/0Whdfw+PlKA5Xy7s
+
0xAQgQlODwSSwlNdtwgf0i1tcEkhDC1ts2o8/2NSoLj3s2348RAkv2n8yV0eu1bh
+
9UaeCOuyUxhCOYta7vWNp9xLy6MQuqptT2MNAjjYGHMZ0i5xH0lnvU1twqvN52Kf
+
wGuzKLw//qz13XWWgMaPbLz6L38j4gvl7eWd51vRQGX9Z9QDOHpu7iFcyY6bUeLh
+
3n9Quin11JrFl2Fx2qqDi8aILua9/Oq4bPchPURqbTUnZ+SR/aRFgN3aFVHmC0gL
+
l0BGhkCTFmb9+FrhILhmyKiln4dTaGigMhaSs9GuuXZ85FmCKjkp+6eRDDWP4WS9
+
+
--- T+md4gE0kZczI05P8evIAnS6wGnFPnMoTpaQM5DS1ao
+
mn̂�;�צ$`_�p�vR�1�7Fl�ç����� �+!����3�-�EYG��Q�*���������V�Զ����a�F��O����'}H��b���IHG�Z)t�����$�lXv�����F~sw%]G��H�s�LTv(�
+3
secrets/secrets.nix
···
"hn-alerts.age".publicKeys = [
kierank
];
+
"github-knot-sync.age".publicKeys = [
+
kierank
+
];
}