Nimbin[12]?Web & Tools / Git-Server / README.md

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
README.md 8.3 KB · 168 lines raw

gitsync + git browser

Self-hosted, read-only git browser for git.christianimmanuel.de. Repos are grouped in categories, browsable like GitHub/GitLab, and cloneable via HTTPS. Nothing is pushed to the server with git; gitsync on your laptop publishes the projects you list.

100% Vibecode but tested.

Parts

FileWhat
gitsynclaptop script: stages listed projects, rsyncs them to the server
gitsync.confexample config with categories
web/index.php, web/style.cssthe browser (PHP, no database)
apache/*.confvhosts: -common.conf holds everything (browser, clones, private auth), the :80 and -le-ssl vhosts just include it
Makefileinstall / uninstall / config / deploy / clean

Server (Debian)

sudo apt install apache2 git libapache2-mod-php rsync
sudo a2enmod rewrite cgi env
sudo mkdir -p /var/www/html/git.christianimmanuel.de/repos
sudo chown -R debian:www-data /var/www/html/git.christianimmanuel.de

From the laptop: make deploy, then on the server:

sudo mv /tmp/git.christianimmanuel.de*.conf /etc/apache2/sites-available/
sudo a2ensite git.christianimmanuel.de && sudo systemctl reload apache2
sudo certbot --apache -d git.christianimmanuel.de     # first time only; afterwards keep our -le-ssl.conf
sudo a2ensite git.christianimmanuel.de-le-ssl && sudo systemctl reload apache2

Both vhosts only Include git.christianimmanuel.de-common.conf; edit that one file for changes.

The repos/ directory is only reachable through index.php and git-http-backend. Only git-upload-pack is exposed, so clones work and pushes do not.

Laptop

su -c 'make install'   # /usr/bin/gitsync
make config         # ~/.config/gitsync/gitsync.conf (only if missing)
make config-update  # overwrite it with the shipped config (.bak is kept)
gitsync             # publish

Config format:

HOST=debian@162.19.227.194
REMOTE_DIR=/var/www/html/git.christianimmanuel.de

[SDL & Graphics]
~/Git/sdl_3d | 3D rendering experiments | sdl, 3d, c

Automatic daily sync

make ssh-key        # creates ~/.ssh/gitsync_ed25519 and prints the server-side commands (user git-local)
make cron           # crontab entry: 12:00 daily, only if the server answers a ping
make uncron         # remove it
su -c 'make cron-daily'  # alternative without a user crontab: /etc/cron.daily/gitsync (edit USER= in cron/gitsync first)

Set HOST=git-local@… and SSH_KEY=~/.ssh/gitsync_ed25519 in the config (default). The upload runs with BatchMode, so it never hangs on a password prompt.

Options: -n dry run, -q quiet, -l stage only, -c list config, -f ignore secret scan, -v verbose, -V version.

Secret scan

Before staging, every project is checked for private keys, AWS/GitHub/GitLab/Slack/OpenAI tokens, api_key = "…" / password: '…' style lines with a quoted literal value containing a digit (placeholders like "your_api_key" are ignored), and files like .env, id_rsa, *.pem. Git projects: tracked files and the whole history are scanned (untracked/ignored files are never published anyway). Plain directories: exactly the staged files are scanned, so ignored files don't trigger it. Files named example, sample, template or public are exempt from the filename check. A hit skips that project and prints where it was found. If it is a false positive, publish with gitsync -f. If it is real: rotate the key, then remove it from history:

pip install git-filter-repo
git filter-repo --invert-paths --path path/to/client.key --path path/to/server.key
git push --force origin --all   # if the project has a remote

gitsync checks the whole history because a bare clone ships every commit, not just HEAD. Staging dir is ~/.cache/gitsync (make clean removes it).

Private projects

Prefix a config line with ! to publish it under /private instead of the public tree:

[Private]
!~/Git/notes | My private notes | notes

Everything below /private (pages, raw files, tarballs, git clone) is protected by HTTP Basic auth in Apache. Private projects are not listed on the public pages and are not secret-scanned. Create users on the server:

sudo apt install apache2-utils
sudo htpasswd -cB /var/www/html/git.christianimmanuel.de/.htpasswd NAME    # -c only for the first user

Fetch with wget --user=NAME --ask-password URL or git clone https://NAME@git.christianimmanuel.de/private/cat/name.git. Apache enforces this in -common.conf; index.php additionally checks the login itself against .htpasswd (bcrypt), so pages, raw files and tarballs stay closed even with an outdated vhost.

lfs-backup config

Publish exactly the files an lfs-backup config lists, always private:

[Private]
!lfs-backup < /etc/pkgusr/backup.conf | My LFS system configuration | lfs, config

Secrets, encrypted. With SECRETS_PASS=~/.config/gitsync/secrets.pass in the config, secret files go up encrypted (openssl, AES-256, PBKDF2) as secrets-user.tar.enc (your files) and secrets-root.tar.enc (root/system files). The server never sees them in plain; the web view shows them as binary files.

(umask 077; openssl rand -base64 32 > ~/.config/gitsync/secrets.pass)   # one line; keep a copy elsewhere!

Restore on another system:

wget --user=NAME --ask-password https://git.christianimmanuel.de/private/private/lfs-backup/raw/secrets-root.tar.enc
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in secrets-root.tar.enc | tar -xv   # asks for the passphrase

Files only root can read ([root:*], /etc/rc.d/rc.iptables, …) are collected as root:

Scripts collection

SCRIPTS=~/Bash-Public in the config publishes a directory of standalone scripts at /scripts. Every subdirectory is a group, every file a script. Lines starting with ### at the top of a file are shown as description (###### lines are ignored). Each script gets a wget … && chmod 740 line, a source view and a raw URL (/scripts/raw/<group>/<file>). Same excludes, .gitignore and secret scan as plain directories.

Browser features

Categories, cards with description, tags, language bar and last change. Search by name/description/tag. File tree, file view, Markdown README (rendered server side), raw files, commit log with diffs, commit activity graph (per repo and site wide), git clone and wget tarball commands, dark mode. Everything is plain HTML, so it works in lynx; JavaScript only adds syntax highlighting and copy buttons.

Make targets

install, uninstall, config, config-update, ssh-key, cron, uncron, deploy-web, deploy-apache, deploy, clean