Git Repository Operations

Standard operating procedures for git operations across the domus ecosystem.

Prerequisites

Credential Vault

SSH keys and GitHub tokens are stored in encrypted gocryptfs vaults.

# Check vault status
gcvault status

# Mount credentials vault (required for git push)
gcvault mount credentials
gcvault status output
VAULT                STATUS          SIZE       FILES
───────────────────────────────────────────────────────────
credentials          ✗ UNMOUNTED    -          -
work-sensitive       ✗ UNMOUNTED    -          -
network-configs      ✗ UNMOUNTED    -          -
personal             ✗ UNMOUNTED    -          -
Git push operations will fail with "Repository not found" if vault is unmounted.

SSH Agent

Ensure SSH agent has keys loaded:

# Check loaded keys
ssh-add -l

# If no keys, load from vault
ssh-add ~/.ssh/id_ed25519

For commands requiring explicit agent:

SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push

Repository Structure

Atelier Layout

Atelier Structure
Figure 1. Atelier Directory Structure

Remote Configuration

Most repos have multiple remotes for redundancy:

git remote -v
# origin    git@github.com:EvanusModestus/domus-infra-ops.git
# gitlab    git@gitlab.com:EvanusModestus/domus-infra-ops.git
# gitea     git@gitea:evanusmodestus/domus-infra-ops.git

Git Forges

Forge URL CLI Tool Primary Use

GitHub

github.com

gh

Public/Private repos

GitLab

gitlab.com

glab

Mirror/backup

Gitea

gitea-01.inside.domusdigitalis.dev

tea

Self-hosted backup

Gitea runs on the home enterprise network for air-gapped backups.

Daily Workflows

Single Repo Push

# Standard push
git push origin main

# With explicit SSH agent
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push origin main

# Push to all remotes
git remote | xargs -I{} git push {} main

Multi-Repo Push (domus-push-all)

Push all domus-* repos to all configured remotes:

# Push all repos
domus-push-all

# Check status first
domus-push-all --status

# Dry run
domus-push-all --dry-run

Quick Status Check

# All domus repos status
for repo in ~/atelier/_bibliotheca/domus-*/; do
  echo "=== $(basename $repo) ==="
  git -C "$repo" status --short
  git -C "$repo" log --oneline -1
done

Commit Patterns

Heredoc Commits (Preferred)

Multi-line commit messages with heredoc:

gach << 'EOF'
docs(secrets): Convert markdown to AsciiDoc format

- Converted 2025-SEC-001-Reference.md to .adoc
- Converted 2025-SEC-002-Complete-Architecture.md to .adoc
- Standardized on AsciiDoc across domus-secrets-ops
EOF

The gach function (git add + commit with heredoc):

# In ~/.zshrc
gach() {
    git add -A && git commit -m "$(cat)"
}

gacp() {
    git add -A && git commit -m "$(cat)" && git push
}

Conventional Commits

Prefix Usage Example

feat

New feature

feat(kvm): Add VM snapshot command

fix

Bug fix

fix(ssh): Handle connection timeout

docs

Documentation

docs(runbook): Add vault-ssh-ca troubleshooting

refactor

Code restructure

refactor(antora): Standardize attribute usage

chore

Maintenance

chore: Trigger rebuild for content updates

Scoped Commits

Use parentheses for scope:

docs(runbook): Add KVM network discovery
feat(kvm): Add VM interface listing
fix(ssh): Use correct cert path
refactor(antora): Convert hardcoded IPs to attributes

Creating New Repos

GitHub Private Repo

# Initialize locally
cd ~/atelier/_projects/personal/new-project
git init
git add -A
git commit -m "feat: Initial scaffold"

# Create on GitHub and push
gh repo create new-project --private --source=. --push --description "Project description"

If remote already exists:

# Add remote manually
git remote add origin git@github.com:EvanusModestus/new-project.git

# Create repo via gh (will fail to add remote but creates repo)
gh repo create new-project --private --description "Project description"

# Push
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push -u origin main

GitLab Mirror

# Create repo on GitLab
glab repo create new-project --private --description "Project description"

# Add remote
git remote add gitlab git@gitlab.com:EvanusModestus/new-project.git

# Push
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push gitlab main

Gitea (Self-Hosted)

Gitea runs at gitea-01.inside.domusdigitalis.dev for local backups.

# Create repo via tea CLI
tea repo create --name new-project --private

# Add Gitea remote (uses SSH config alias)
git remote add gitea git@gitea:evanusmodestus/new-project.git

# Push
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push gitea main
Gitea repos are accessible even when internet is down.

Full Multi-Remote Setup

Set up all three forges for a new repo:

# Initialize
cd ~/atelier/_projects/personal/new-project
git init && git add -A && git commit -m "feat: Initial commit"

# GitHub (primary)
gh repo create new-project --private --source=. --push

# GitLab (mirror)
glab repo create new-project --private
git remote add gitlab git@gitlab.com:EvanusModestus/new-project.git
git push gitlab main

# Gitea (local backup)
tea repo create --name new-project --private
git remote add gitea git@gitea:evanusmodestus/new-project.git
git push gitea main

Adding Remotes to Existing Repo

For an existing GitHub repo, add GitLab and Gitea:

# Get repo name from GitHub
REPO_NAME=$(gh repo view --json name -q '.name')
echo $REPO_NAME

# Create on GitLab and Gitea
glab repo create ${REPO_NAME} --private
tea repo create --name ${REPO_NAME} --private

# Add remotes
git remote add gitlab git@gitlab.com:EvanusModestus/${REPO_NAME}.git
git remote add gitea git@gitea:evanusmodestus/${REPO_NAME}.git

# Load SSH keys if needed
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh-add ~/.ssh/id_ed25519_gitlab
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh-add ~/.ssh/id_ed25519_gitea

# Push to all remotes
git remote | xargs -I {} git push {} main

Verify remotes:

git remote -v
# origin    git@github.com:EvanusModestus/new-project.git
# gitlab    git@gitlab.com:EvanusModestus/new-project.git
# gitea     git@gitea:evanusmodestus/new-project.git

Troubleshooting

"Repository not found"

ERROR: Repository not found.
fatal: Could not read from remote repository.

Causes:

  1. Vault unmounted - SSH keys not accessible

    gcvault mount credentials
  2. Repo doesn’t exist - Create it first

    gh repo create repo-name --private
  3. Wrong remote URL - Check configuration

    git remote -v
    git remote set-url origin git@github.com:EvanusModestus/repo-name.git

SSH Agent Issues

# Check agent
echo $SSH_AUTH_SOCK

# List keys
ssh-add -l

# If empty, start agent
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519

# Or use explicit socket
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push

ssh-add Hangs on Passphrase

If ssh-add hangs after entering passphrase (no response, no error):

# Use explicit SSH_AUTH_SOCK
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh-add ~/.ssh/id_ed25519_gitlab

# Verify key is valid first
ssh-keygen -y -f ~/.ssh/id_ed25519_gitlab
# Should prompt for passphrase and print public key

# Get passphrase from gopass
gopass show -c v3/domains/d000/identity/ssh/gitlab

Per-forge keys:

# GitHub
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh-add ~/.ssh/id_ed25519_github

# GitLab
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh-add ~/.ssh/id_ed25519_gitlab

# Gitea
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh-add ~/.ssh/id_ed25519_gitea

glab OAuth Token Expired

ERROR: oauth2: "invalid_grant" "The provided authorization grant is invalid, expired..."

Fix:

# Clear stale config
rm -rf ~/.config/glab-cli

# Re-authenticate
glab auth login

# Generate new PAT at:
# https://gitlab.com/-/user_settings/personal_access_tokens?scopes=api,write_repository

# Select SSH as default protocol
# Save PAT to gopass
gopass edit v3/domains/d000/cloud/gitlab/evanusmodestus/pat

Authentication Failure

# Test GitHub SSH
ssh -T git@github.com

# Test GitLab SSH
ssh -T git@gitlab.com

# Test Gitea SSH (uses SSH config alias)
ssh -T git@gitea

# If fails, check key is added
ssh-add -l

Gitea Connection Issues

If Gitea is unreachable:

# Check DNS resolution
host gitea-01.inside.domusdigitalis.dev

# Check connectivity
nc -zv gitea-01.inside.domusdigitalis.dev 22

# Check if on home network (VPN may be needed)
ip route | grep 10.50

Automation Scripts

domus-push-all

Location: ~/.local/bin/domus-push-all

#!/bin/bash
# Push all domus-* repos to all remotes

REPOS=(
  ~/atelier/_bibliotheca/domus-*
  ~/atelier/_projects/personal/netapi
  ~/atelier/_projects/personal/domus-cli
)

for repo in "${REPOS[@]}"; do
  [ -d "$repo/.git" ] || continue
  echo "=== $(basename $repo) ==="

  if [[ "$1" == "--status" ]]; then
    git -C "$repo" status --short
    continue
  fi

  for remote in $(git -C "$repo" remote); do
    if [[ "$1" == "--dry-run" ]]; then
      echo "Would push to $remote"
    else
      SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git -C "$repo" push "$remote" main
    fi
  done
done

domus-check (build verification)

# In ~/.zshrc
domus-check() {
  for repo in ~/atelier/_bibliotheca/domus-*/; do
    [ -f "$repo/Makefile" ] || continue
    echo "=== $(basename $repo) ==="
    make -C "$repo" 2>&1 | grep -E "FATAL|error|warning" || echo "✓ Clean"
  done
}

Quick Reference

Credentials & Agent

Task Command

Mount credentials

gcvault mount credentials

Check vault status

gcvault status

Push with agent

SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket git push

Test SSH auth

ssh -T git@github.com

Multi-Repo Operations

Task Command

Push all repos

domus-push-all

Check all repos

domus-push-all --status

Push to all remotes

git remote | xargs -I{} git push {} main

Creating Repos

Forge Command

GitHub

gh repo create name --private --source=. --push

GitLab

glab repo create name --private

Gitea

tea repo create --name name --private

Adding Remotes

Forge Command

GitHub

git remote add origin git@github.com:EvanusModestus/repo.git

GitLab

git remote add gitlab git@gitlab.com:EvanusModestus/repo.git

Gitea

git remote add gitea git@gitea:evanusmodestus/repo.git

Commits

Task Command

Heredoc commit

gach << 'EOF' …​ EOF

Heredoc + push

gacp << 'EOF' …​ EOF

Quick commit

git ac "message"

Quick commit + push

git acp "message"