Nimbin[12]?Web & Tools / Git-Server / commits / d3e0233

Git-Server git · main

gitsync + this read-only git browser

git php bash apache self-hosted · first commit 2026-10-05 · last commit 2026-10-06 (3 days ago) · synced 3 days ago

PHP 77.1% CSS 11.4% Markdown 8.6% Makefile 2.9%
git clone https://git.christianimmanuel.de/web-tools/Git-Server.gitwget https://git.christianimmanuel.de/web-tools/Git-Server/archive/Git-Server.tar.gz

Fix backup root

Christian Immanuel · 2026-10-06 09:54 · d3e02334c732e7511da9ea693ca33013815f0f22

 gitsync       | 18 ++++++++++--------
 web/index.php |  2 +-
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/gitsync b/gitsync
index b611dab..e371f59 100755
--- a/gitsync
+++ b/gitsync
@@ -3,7 +3,7 @@
 # 100% Vibecode but tested.
 set -eu
 
-VERSION="1.11.0"
+VERSION="1.11.1"
 CONF="${GITSYNC_CONF:-$HOME/.config/gitsync/gitsync.conf}"
 STAGE="${GITSYNC_STAGE:-$HOME/.cache/gitsync}"
 DRYRUN=0 LOCAL=0 VERBOSE=0 FORCE=0 LIST=0 QUIET=0 ROOTNOW=0 SU_TRIED=0 SSH_KEY="" COLLECT_ROOT=""
@@ -231,13 +231,14 @@ sync_backup() { # conf dst desc tags name
     "$(find "$dst" -type f -printf '%s %p\n' | lang_stats)"
 }
 
-owned_by_root() { # path: root owned, not writable by group/others
-  local s; s=$(stat -c '%u %a' "$1") || return 1
-  [ "${s%% *}" = 0 ] && [ $(( 8#${s##* } & 8#022 )) = 0 ]
+owned_by_root() { # path: root owned, not writable by group/others (a dir may be, with the sticky bit)
+  local s m; s=$(stat -c '%u %a' "$1") || return 1; m=$(( 8#${s##* } ))
+  [ "${s%% *}" = 0 ] || return 1
+  [ $(( m & 8#022 )) = 0 ] || { [ -d "$1" ] && [ $(( m & 8#1000 )) != 0 ]; }
 }
 
 collect_root() { # USER: as root, collect [root:*]/[system:*] files of USER's backup.conf projects
-  local u="$1" uhome conf line path bconf name n=0
+  local u="$1" uhome conf line path bconf name p n=0
   [ "$(id -u)" = 0 ] || die "--collect-root must run as root"
   uhome=$(getent passwd "$u" | cut -d: -f6); [ -n "$uhome" ] || die "no such user: $u"
   conf="$uhome/.config/gitsync/gitsync.conf"; [ -f "$conf" ] || die "no config at $conf"
@@ -254,9 +255,10 @@ collect_root() { # USER: as root, collect [root:*]/[system:*] files of USER's ba
     [[ "$name" =~ ^[A-Za-z0-9_+-][A-Za-z0-9._+-]*$ ]] || { warn "bad name: $name"; continue; }
     bconf=$(readlink -f "$bconf" || true)
     # the file list must come from root, not from the user's config
-    if [ ! -f "$bconf" ] || ! owned_by_root "$bconf" || ! owned_by_root "$(dirname "$bconf")"; then
-      warn "$bconf: not owned by root (or writable by others), skipped"; continue
-    fi
+    if [ ! -f "$bconf" ]; then warn "$bconf: missing, skipped"; continue; fi
+    for p in "$bconf" "$(dirname "$bconf")"; do
+      owned_by_root "$p" || { warn "$name skipped: $p must be owned by root and not writable by others ($(stat -c '%U:%G %A' "$p"))"; continue 2; }
+    done
     mkdir -p "$CR_TMP/$name"; collect_backup "$bconf" "$CR_TMP/$name" rootsys; n=$((n + 1))
   done < "$conf"
   report_backup "root" ""
diff --git a/web/index.php b/web/index.php
index f6de421..ca5755c 100644
--- a/web/index.php
+++ b/web/index.php
@@ -1,7 +1,7 @@
 <?php
 declare(strict_types=1);
 /* git.christianimmanuel.de - read-only git browser. 100% Vibecode but tested. Works in lynx. */
-const VERSION = '1.11.0';
+const VERSION = '1.11.1';
 const SITE = 'Nimbin[12]?';
 const LEGAL = 'https://christianimmanuel.de';
 $ROOT   = __DIR__ . '/repos';