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

feat: add tangled sync service

dunkirk.sh c3548fd4 f5c5e91e

verified
+239 -16
+9
machines/terebithia/default.nix
··· 115 115 file = ../../secrets/cloudflare.age; 116 116 owner = "caddy"; 117 117 }; 118 + github-knot-sync = { 119 + file = ../../secrets/github-knot-sync.age; 120 + owner = "git"; 121 + }; 118 122 }; 119 123 120 124 environment.sessionVariables = { ··· 266 270 hostname = "spindle.dunkirk.sh"; 267 271 listenAddr = "127.0.0.1:6555"; 268 272 }; 273 + }; 274 + 275 + atelier.services.knot-sync = { 276 + enable = true; 277 + secretsFile = config.age.secrets.github-knot-sync.path; 269 278 }; 270 279 271 280 boot.loader.systemd-boot.enable = true;
+29 -16
modules/home/system/shell.nix
··· 169 169 # Configuration variables - set these to your defaults 170 170 local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul" 171 171 local default_github_username="taciturnaxolotl" 172 + local default_knot_host="knot.dunkirk.sh" 172 173 local extracted_github_username="" 173 174 174 175 # Check if current directory is a git repository ··· 180 181 # Get the repository name from the current directory 181 182 local repo_name=$(basename "$(git rev-parse --show-toplevel)") 182 183 183 - # Check if origin remote exists and points to ember 184 + # Check if origin remote exists and points to knot 184 185 local origin_url=$(git remote get-url origin 2>/dev/null) 185 - local origin_ember=false 186 + local origin_knot=false 186 187 187 188 if [[ -n "$origin_url" ]]; then 188 189 # Try to extract GitHub username if origin is a GitHub URL ··· 192 193 default_github_username=$extracted_github_username 193 194 fi 194 195 195 - if [[ "$origin_url" == *"ember"* ]]; then 196 - origin_ember=true 197 - echo "✅ Origin remote exists and points to ember" 196 + if [[ "$origin_url" == *"$default_knot_host"* || "$origin_url" == *"knot.dunkirk.sh"* ]]; then 197 + origin_knot=true 198 + echo "✅ Origin remote exists and points to knot" 198 199 else 199 - echo "⚠️ Origin remote exists but doesn't point to ember" 200 + echo "⚠️ Origin remote exists but doesn't point to knot" 200 201 fi 201 202 else 202 203 echo "⚠️ Origin remote doesn't exist" ··· 212 213 fi 213 214 214 215 # Fix remotes if needed 215 - if [[ "$origin_ember" = false || "$github_exists" = false ]]; then 216 - echo "Setting up remotes..." 217 - 216 + if [[ "$origin_knot" = false || "$github_exists" = false ]]; then 218 217 # Prompt for PLC identifier if needed 219 218 local plc_id="" 220 - if [[ "$origin_ember" = false ]]; then 221 - echo -n "Enter your PLC identifier [default: $default_plc_id]: " 222 - read plc_input 223 - plc_id=''${plc_input:-$default_plc_id} 219 + local should_fix_origin=false 220 + 221 + if [[ "$origin_knot" = false ]]; then 222 + if [[ -n "$origin_url" ]]; then 223 + echo -n "Migrate origin from $origin_url to knot.dunkirk.sh? [Y/n]: " 224 + read fix_input 225 + if [[ -z "$fix_input" || "$fix_input" =~ ^[Yy]$ ]]; then 226 + should_fix_origin=true 227 + fi 228 + else 229 + should_fix_origin=true 230 + fi 231 + 232 + if [[ "$should_fix_origin" = true ]]; then 233 + echo -n "Enter your PLC identifier [default: $default_plc_id]: " 234 + read plc_input 235 + plc_id=''${plc_input:-$default_plc_id} 236 + fi 224 237 fi 225 238 226 239 # Prompt for GitHub username with default from origin if available ··· 232 245 fi 233 246 234 247 # Set up origin remote if needed 235 - if [[ "$origin_ember" = false && -n "$plc_id" ]]; then 248 + if [[ "$should_fix_origin" = true && -n "$plc_id" ]]; then 236 249 if git remote get-url origin &>/dev/null; then 237 250 git remote remove origin 238 251 fi 239 - git remote add origin "git@ember:''${plc_id}/''${repo_name}" 240 - echo "✅ Set up origin remote: git@ember:''${plc_id}/''${repo_name}" 252 + git remote add origin "git@$default_knot_host:''${plc_id}/''${repo_name}" 253 + echo "✅ Set up origin remote: git@$default_knot_host:''${plc_id}/''${repo_name}" 241 254 fi 242 255 243 256 # Set up GitHub remote if needed
+185
modules/nixos/services/knot-sync.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + 8 + let 9 + cfg = config.atelier.services.knot-sync; 10 + in 11 + { 12 + options.atelier.services.knot-sync = { 13 + enable = lib.mkEnableOption "Knot to GitHub sync service"; 14 + 15 + repoDir = lib.mkOption { 16 + type = lib.types.str; 17 + default = "/home/git/did:plc:krxbvxvis5skq7jj6eot23ul"; 18 + description = "Directory containing git repositories"; 19 + }; 20 + 21 + githubUsername = lib.mkOption { 22 + type = lib.types.str; 23 + default = "taciturnaxolotl"; 24 + description = "GitHub username"; 25 + }; 26 + 27 + secretsFile = lib.mkOption { 28 + type = lib.types.path; 29 + description = "Path to secrets file containing GITHUB_TOKEN"; 30 + }; 31 + 32 + logFile = lib.mkOption { 33 + type = lib.types.str; 34 + default = "/home/git/knot-sync.log"; 35 + description = "Log file location"; 36 + }; 37 + 38 + interval = lib.mkOption { 39 + type = lib.types.str; 40 + default = "*/5 * * * *"; 41 + description = "Cron schedule for sync (default: every 5 minutes)"; 42 + }; 43 + }; 44 + 45 + config = lib.mkIf cfg.enable { 46 + systemd.services.knot-sync = { 47 + description = "Sync Knot repositories to GitHub"; 48 + serviceConfig = { 49 + Type = "oneshot"; 50 + User = "git"; 51 + EnvironmentFile = cfg.secretsFile; 52 + ExecStart = pkgs.writeShellScript "knot-sync" '' 53 + set -euo pipefail 54 + 55 + # Variables 56 + REPO_DIR="${cfg.repoDir}" 57 + GITHUB_USERNAME="${cfg.githubUsername}" 58 + LOG_FILE="${cfg.logFile}" 59 + 60 + # Log function 61 + log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"; } 62 + 63 + # Create the post-receive hook template 64 + cat <<'EOF' > /tmp/post-receive.template 65 + #!${pkgs.bash}/bin/bash 66 + # post-receive hook to sync to GitHub - AUTOGENERATED 67 + 68 + # Load environment variables from secrets file 69 + if [ -f "${cfg.secretsFile}" ]; then 70 + source "${cfg.secretsFile}" 71 + fi 72 + 73 + # Variables 74 + GITHUB_USERNAME="${cfg.githubUsername}" 75 + LOG_FILE="${cfg.logFile}" 76 + REPO_NAME=$(basename $(pwd)) 77 + 78 + # Log function 79 + log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "''${LOG_FILE}"; } 80 + 81 + # Check for nosync marker 82 + if [ -f "$(pwd)/.nosync" ]; then 83 + log "Skipping sync for $REPO_NAME (nosync marker present)" 84 + exit 0 85 + fi 86 + 87 + # Function to sync to GitHub 88 + sync_to_github() { 89 + log "Syncing $REPO_NAME to GitHub" 90 + expected_url="https://''${GITHUB_USERNAME}:''${GITHUB_TOKEN}@github.com/''${GITHUB_USERNAME}/''${REPO_NAME}.git" 91 + current_url=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || echo "") 92 + 93 + if [ -z "$current_url" ]; then 94 + log "Adding origin remote" 95 + ${pkgs.git}/bin/git remote add origin "$expected_url" 96 + elif [ "$current_url" != "$expected_url" ]; then 97 + log "Updating origin remote URL" 98 + ${pkgs.git}/bin/git remote set-url origin "$expected_url" 99 + fi 100 + 101 + # Mirror push everything (refs, tags, branches) 102 + if ${pkgs.git}/bin/git push --mirror origin 2>&1 | tee -a "''${LOG_FILE}"; then 103 + log "Sync succeeded for $REPO_NAME" 104 + return 0 105 + else 106 + log "Sync failed for $REPO_NAME" 107 + return 1 108 + fi 109 + } 110 + 111 + # Main 112 + while read oldrev newrev refname; do 113 + log "Received push for ref '$refname' (old revision: $oldrev, new revision: $newrev)" 114 + sync_to_github 115 + done 116 + EOF 117 + 118 + HOOK_TEMPLATE="/tmp/post-receive.template" 119 + 120 + # Create the post-receive hook 121 + create_hook() { 122 + local new_repo_path="$1" 123 + local hook_path="$new_repo_path/hooks/post-receive.d/forward" 124 + local nosync_marker="$new_repo_path/.nosync" 125 + 126 + # Skip if .nosync marker exists 127 + if [ -f "$nosync_marker" ]; then 128 + log "Skipping $new_repo_path (nosync marker present)" 129 + return 0 130 + fi 131 + 132 + if [ -d "$new_repo_path" ] && [ ! -f "$hook_path" ]; then 133 + # Check that it's a git repository, specifically a bare repo 134 + if [ -f "$new_repo_path/config" ]; then 135 + # Create hooks directory if it doesn't exist 136 + mkdir -p "$(dirname "$hook_path")" 137 + # Create hook from the template file, substituting variables. 138 + if cat "$HOOK_TEMPLATE" > "$hook_path" && chmod +x "$hook_path"; then 139 + log "Created hook for $new_repo_path" 140 + # Check if repo has any commits before pushing 141 + if (cd "$new_repo_path" && ${pkgs.git}/bin/git rev-parse HEAD >/dev/null 2>&1); then 142 + # Auto push by simulating a post-receive hook trigger 143 + log "Triggering initial push for $new_repo_path" 144 + (cd "$new_repo_path" && \ 145 + echo "0000000000000000000000000000000000000000 $(${pkgs.git}/bin/git rev-parse HEAD) refs/heads/main" | \ 146 + "$hook_path") 147 + fi 148 + else 149 + log "Hook creation failed for $new_repo_path" 150 + fi 151 + fi 152 + fi 153 + } 154 + 155 + # Keep track of hooks created 156 + hooks_created=0 157 + 158 + # Find all directories that look like bare Git repos without a post-receive hook 159 + ${pkgs.findutils}/bin/find "$REPO_DIR" -mindepth 1 -maxdepth 1 -type d \! -name ".*" -print0 | 160 + while IFS= read -r -d $'\0' repo_path; do 161 + create_hook "$repo_path" 162 + if [ $? -eq 0 ]; then 163 + hooks_created=$((hooks_created + 1)) 164 + fi 165 + done 166 + 167 + # Only log completion if hooks were actually created 168 + if [ $hooks_created -gt 0 ]; then 169 + log "Sync job complete - Created $hooks_created hooks" 170 + fi 171 + ''; 172 + }; 173 + }; 174 + 175 + systemd.paths.knot-sync = { 176 + description = "Watch for new Knot repositories"; 177 + wantedBy = [ "multi-user.target" ]; 178 + pathConfig = { 179 + PathModified = cfg.repoDir; 180 + Unit = "knot-sync.service"; 181 + MakeDirectory = true; 182 + }; 183 + }; 184 + }; 185 + }
+13
secrets/github-knot-sync.age
··· 1 + age-encryption.org/v1 2 + -> ssh-rsa DqcG0Q 3 + eG7RPDx4xt/jE8lDjJ/whTT6ekS5ccZgLLzEqvJZ1bOsZm7ycfKRUjs5mZoNn8/Y 4 + n6Rk6FyhTXSb0HrhCpSOSH23nCHwMFK/1H/pKdFpXTJAdx2LkX4VRI807/XjPHK4 5 + Iyy0E1xL53RepvC6JPHmmW/Z7h/yamvP8MDE4Mds7oiBZYf/0Whdfw+PlKA5Xy7s 6 + 0xAQgQlODwSSwlNdtwgf0i1tcEkhDC1ts2o8/2NSoLj3s2348RAkv2n8yV0eu1bh 7 + 9UaeCOuyUxhCOYta7vWNp9xLy6MQuqptT2MNAjjYGHMZ0i5xH0lnvU1twqvN52Kf 8 + wGuzKLw//qz13XWWgMaPbLz6L38j4gvl7eWd51vRQGX9Z9QDOHpu7iFcyY6bUeLh 9 + 3n9Quin11JrFl2Fx2qqDi8aILua9/Oq4bPchPURqbTUnZ+SR/aRFgN3aFVHmC0gL 10 + l0BGhkCTFmb9+FrhILhmyKiln4dTaGigMhaSs9GuuXZ85FmCKjkp+6eRDDWP4WS9 11 + 12 + --- T+md4gE0kZczI05P8evIAnS6wGnFPnMoTpaQM5DS1ao 13 + 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
··· 32 32 "hn-alerts.age".publicKeys = [ 33 33 kierank 34 34 ]; 35 + "github-knot-sync.age".publicKeys = [ 36 + kierank 37 + ]; 35 38 }