···
9
+
cfg = config.atelier.services.knot-sync;
12
+
options.atelier.services.knot-sync = {
13
+
enable = lib.mkEnableOption "Knot to GitHub sync service";
15
+
repoDir = lib.mkOption {
16
+
type = lib.types.str;
17
+
default = "/home/git/did:plc:krxbvxvis5skq7jj6eot23ul";
18
+
description = "Directory containing git repositories";
21
+
githubUsername = lib.mkOption {
22
+
type = lib.types.str;
23
+
default = "taciturnaxolotl";
24
+
description = "GitHub username";
27
+
secretsFile = lib.mkOption {
28
+
type = lib.types.path;
29
+
description = "Path to secrets file containing GITHUB_TOKEN";
32
+
logFile = lib.mkOption {
33
+
type = lib.types.str;
34
+
default = "/home/git/knot-sync.log";
35
+
description = "Log file location";
38
+
interval = lib.mkOption {
39
+
type = lib.types.str;
40
+
default = "*/5 * * * *";
41
+
description = "Cron schedule for sync (default: every 5 minutes)";
45
+
config = lib.mkIf cfg.enable {
46
+
systemd.services.knot-sync = {
47
+
description = "Sync Knot repositories to GitHub";
51
+
EnvironmentFile = cfg.secretsFile;
52
+
ExecStart = pkgs.writeShellScript "knot-sync" ''
56
+
REPO_DIR="${cfg.repoDir}"
57
+
GITHUB_USERNAME="${cfg.githubUsername}"
58
+
LOG_FILE="${cfg.logFile}"
61
+
log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"; }
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
68
+
# Load environment variables from secrets file
69
+
if [ -f "${cfg.secretsFile}" ]; then
70
+
source "${cfg.secretsFile}"
74
+
GITHUB_USERNAME="${cfg.githubUsername}"
75
+
LOG_FILE="${cfg.logFile}"
76
+
REPO_NAME=$(basename $(pwd))
79
+
log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "''${LOG_FILE}"; }
81
+
# Check for nosync marker
82
+
if [ -f "$(pwd)/.nosync" ]; then
83
+
log "Skipping sync for $REPO_NAME (nosync marker present)"
87
+
# Function to 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 "")
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"
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"
106
+
log "Sync failed for $REPO_NAME"
112
+
while read oldrev newrev refname; do
113
+
log "Received push for ref '$refname' (old revision: $oldrev, new revision: $newrev)"
118
+
HOOK_TEMPLATE="/tmp/post-receive.template"
120
+
# Create the post-receive 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"
126
+
# Skip if .nosync marker exists
127
+
if [ -f "$nosync_marker" ]; then
128
+
log "Skipping $new_repo_path (nosync marker present)"
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" | \
149
+
log "Hook creation failed for $new_repo_path"
155
+
# Keep track of hooks created
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))
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"
175
+
systemd.paths.knot-sync = {
176
+
description = "Watch for new Knot repositories";
177
+
wantedBy = [ "multi-user.target" ];
179
+
PathModified = cfg.repoDir;
180
+
Unit = "knot-sync.service";
181
+
MakeDirectory = true;