···169169 # Configuration variables - set these to your defaults
170170 local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul"
171171 local default_github_username="taciturnaxolotl"
172172+ local default_knot_host="knot.dunkirk.sh"
172173 local extracted_github_username=""
173174174175 # Check if current directory is a git repository
···180181 # Get the repository name from the current directory
181182 local repo_name=$(basename "$(git rev-parse --show-toplevel)")
182183183183- # Check if origin remote exists and points to ember
184184+ # Check if origin remote exists and points to knot
184185 local origin_url=$(git remote get-url origin 2>/dev/null)
185185- local origin_ember=false
186186+ local origin_knot=false
186187187188 if [[ -n "$origin_url" ]]; then
188189 # Try to extract GitHub username if origin is a GitHub URL
···192193 default_github_username=$extracted_github_username
193194 fi
194195195195- if [[ "$origin_url" == *"ember"* ]]; then
196196- origin_ember=true
197197- echo "✅ Origin remote exists and points to ember"
196196+ if [[ "$origin_url" == *"$default_knot_host"* || "$origin_url" == *"knot.dunkirk.sh"* ]]; then
197197+ origin_knot=true
198198+ echo "✅ Origin remote exists and points to knot"
198199 else
199199- echo "⚠️ Origin remote exists but doesn't point to ember"
200200+ echo "⚠️ Origin remote exists but doesn't point to knot"
200201 fi
201202 else
202203 echo "⚠️ Origin remote doesn't exist"
···212213 fi
213214214215 # Fix remotes if needed
215215- if [[ "$origin_ember" = false || "$github_exists" = false ]]; then
216216- echo "Setting up remotes..."
217217-216216+ if [[ "$origin_knot" = false || "$github_exists" = false ]]; then
218217 # Prompt for PLC identifier if needed
219218 local plc_id=""
220220- if [[ "$origin_ember" = false ]]; then
221221- echo -n "Enter your PLC identifier [default: $default_plc_id]: "
222222- read plc_input
223223- plc_id=''${plc_input:-$default_plc_id}
219219+ local should_fix_origin=false
220220+221221+ if [[ "$origin_knot" = false ]]; then
222222+ if [[ -n "$origin_url" ]]; then
223223+ echo -n "Migrate origin from $origin_url to knot.dunkirk.sh? [Y/n]: "
224224+ read fix_input
225225+ if [[ -z "$fix_input" || "$fix_input" =~ ^[Yy]$ ]]; then
226226+ should_fix_origin=true
227227+ fi
228228+ else
229229+ should_fix_origin=true
230230+ fi
231231+232232+ if [[ "$should_fix_origin" = true ]]; then
233233+ echo -n "Enter your PLC identifier [default: $default_plc_id]: "
234234+ read plc_input
235235+ plc_id=''${plc_input:-$default_plc_id}
236236+ fi
224237 fi
225238226239 # Prompt for GitHub username with default from origin if available
···232245 fi
233246234247 # Set up origin remote if needed
235235- if [[ "$origin_ember" = false && -n "$plc_id" ]]; then
248248+ if [[ "$should_fix_origin" = true && -n "$plc_id" ]]; then
236249 if git remote get-url origin &>/dev/null; then
237250 git remote remove origin
238251 fi
239239- git remote add origin "git@ember:''${plc_id}/''${repo_name}"
240240- echo "✅ Set up origin remote: git@ember:''${plc_id}/''${repo_name}"
252252+ git remote add origin "git@$default_knot_host:''${plc_id}/''${repo_name}"
253253+ echo "✅ Set up origin remote: git@$default_knot_host:''${plc_id}/''${repo_name}"
241254 fi
242255243256 # Set up GitHub remote if needed
+185
modules/nixos/services/knot-sync.nix
···11+{
22+ config,
33+ lib,
44+ pkgs,
55+ ...
66+}:
77+88+let
99+ cfg = config.atelier.services.knot-sync;
1010+in
1111+{
1212+ options.atelier.services.knot-sync = {
1313+ enable = lib.mkEnableOption "Knot to GitHub sync service";
1414+1515+ repoDir = lib.mkOption {
1616+ type = lib.types.str;
1717+ default = "/home/git/did:plc:krxbvxvis5skq7jj6eot23ul";
1818+ description = "Directory containing git repositories";
1919+ };
2020+2121+ githubUsername = lib.mkOption {
2222+ type = lib.types.str;
2323+ default = "taciturnaxolotl";
2424+ description = "GitHub username";
2525+ };
2626+2727+ secretsFile = lib.mkOption {
2828+ type = lib.types.path;
2929+ description = "Path to secrets file containing GITHUB_TOKEN";
3030+ };
3131+3232+ logFile = lib.mkOption {
3333+ type = lib.types.str;
3434+ default = "/home/git/knot-sync.log";
3535+ description = "Log file location";
3636+ };
3737+3838+ interval = lib.mkOption {
3939+ type = lib.types.str;
4040+ default = "*/5 * * * *";
4141+ description = "Cron schedule for sync (default: every 5 minutes)";
4242+ };
4343+ };
4444+4545+ config = lib.mkIf cfg.enable {
4646+ systemd.services.knot-sync = {
4747+ description = "Sync Knot repositories to GitHub";
4848+ serviceConfig = {
4949+ Type = "oneshot";
5050+ User = "git";
5151+ EnvironmentFile = cfg.secretsFile;
5252+ ExecStart = pkgs.writeShellScript "knot-sync" ''
5353+ set -euo pipefail
5454+5555+ # Variables
5656+ REPO_DIR="${cfg.repoDir}"
5757+ GITHUB_USERNAME="${cfg.githubUsername}"
5858+ LOG_FILE="${cfg.logFile}"
5959+6060+ # Log function
6161+ log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"; }
6262+6363+ # Create the post-receive hook template
6464+ cat <<'EOF' > /tmp/post-receive.template
6565+ #!${pkgs.bash}/bin/bash
6666+ # post-receive hook to sync to GitHub - AUTOGENERATED
6767+6868+ # Load environment variables from secrets file
6969+ if [ -f "${cfg.secretsFile}" ]; then
7070+ source "${cfg.secretsFile}"
7171+ fi
7272+7373+ # Variables
7474+ GITHUB_USERNAME="${cfg.githubUsername}"
7575+ LOG_FILE="${cfg.logFile}"
7676+ REPO_NAME=$(basename $(pwd))
7777+7878+ # Log function
7979+ log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "''${LOG_FILE}"; }
8080+8181+ # Check for nosync marker
8282+ if [ -f "$(pwd)/.nosync" ]; then
8383+ log "Skipping sync for $REPO_NAME (nosync marker present)"
8484+ exit 0
8585+ fi
8686+8787+ # Function to sync to GitHub
8888+ sync_to_github() {
8989+ log "Syncing $REPO_NAME to GitHub"
9090+ expected_url="https://''${GITHUB_USERNAME}:''${GITHUB_TOKEN}@github.com/''${GITHUB_USERNAME}/''${REPO_NAME}.git"
9191+ current_url=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || echo "")
9292+9393+ if [ -z "$current_url" ]; then
9494+ log "Adding origin remote"
9595+ ${pkgs.git}/bin/git remote add origin "$expected_url"
9696+ elif [ "$current_url" != "$expected_url" ]; then
9797+ log "Updating origin remote URL"
9898+ ${pkgs.git}/bin/git remote set-url origin "$expected_url"
9999+ fi
100100+101101+ # Mirror push everything (refs, tags, branches)
102102+ if ${pkgs.git}/bin/git push --mirror origin 2>&1 | tee -a "''${LOG_FILE}"; then
103103+ log "Sync succeeded for $REPO_NAME"
104104+ return 0
105105+ else
106106+ log "Sync failed for $REPO_NAME"
107107+ return 1
108108+ fi
109109+ }
110110+111111+ # Main
112112+ while read oldrev newrev refname; do
113113+ log "Received push for ref '$refname' (old revision: $oldrev, new revision: $newrev)"
114114+ sync_to_github
115115+ done
116116+ EOF
117117+118118+ HOOK_TEMPLATE="/tmp/post-receive.template"
119119+120120+ # Create the post-receive hook
121121+ create_hook() {
122122+ local new_repo_path="$1"
123123+ local hook_path="$new_repo_path/hooks/post-receive.d/forward"
124124+ local nosync_marker="$new_repo_path/.nosync"
125125+126126+ # Skip if .nosync marker exists
127127+ if [ -f "$nosync_marker" ]; then
128128+ log "Skipping $new_repo_path (nosync marker present)"
129129+ return 0
130130+ fi
131131+132132+ if [ -d "$new_repo_path" ] && [ ! -f "$hook_path" ]; then
133133+ # Check that it's a git repository, specifically a bare repo
134134+ if [ -f "$new_repo_path/config" ]; then
135135+ # Create hooks directory if it doesn't exist
136136+ mkdir -p "$(dirname "$hook_path")"
137137+ # Create hook from the template file, substituting variables.
138138+ if cat "$HOOK_TEMPLATE" > "$hook_path" && chmod +x "$hook_path"; then
139139+ log "Created hook for $new_repo_path"
140140+ # Check if repo has any commits before pushing
141141+ if (cd "$new_repo_path" && ${pkgs.git}/bin/git rev-parse HEAD >/dev/null 2>&1); then
142142+ # Auto push by simulating a post-receive hook trigger
143143+ log "Triggering initial push for $new_repo_path"
144144+ (cd "$new_repo_path" && \
145145+ echo "0000000000000000000000000000000000000000 $(${pkgs.git}/bin/git rev-parse HEAD) refs/heads/main" | \
146146+ "$hook_path")
147147+ fi
148148+ else
149149+ log "Hook creation failed for $new_repo_path"
150150+ fi
151151+ fi
152152+ fi
153153+ }
154154+155155+ # Keep track of hooks created
156156+ hooks_created=0
157157+158158+ # Find all directories that look like bare Git repos without a post-receive hook
159159+ ${pkgs.findutils}/bin/find "$REPO_DIR" -mindepth 1 -maxdepth 1 -type d \! -name ".*" -print0 |
160160+ while IFS= read -r -d $'\0' repo_path; do
161161+ create_hook "$repo_path"
162162+ if [ $? -eq 0 ]; then
163163+ hooks_created=$((hooks_created + 1))
164164+ fi
165165+ done
166166+167167+ # Only log completion if hooks were actually created
168168+ if [ $hooks_created -gt 0 ]; then
169169+ log "Sync job complete - Created $hooks_created hooks"
170170+ fi
171171+ '';
172172+ };
173173+ };
174174+175175+ systemd.paths.knot-sync = {
176176+ description = "Watch for new Knot repositories";
177177+ wantedBy = [ "multi-user.target" ];
178178+ pathConfig = {
179179+ PathModified = cfg.repoDir;
180180+ Unit = "knot-sync.service";
181181+ MakeDirectory = true;
182182+ };
183183+ };
184184+ };
185185+}