Sovereign Dev Stack
Gitea as a Tor Hidden Service
Gitea gives you self-hosted Git without relying on GitHub. Run it as a Tor hidden service and no external actor can reach your instance. The service binds to localhost and exposes itself only through the Tor network. This approach eliminates exposure to GitHub’s metadata collection while maintaining full control over your repositories.
The configuration is straightforward. Use the official Gitea image (v1.22.3 at time of writing), mount a data volume, and expose ports 3000 and 22. Set environment variables to disable telemetry and configure the root URL. The platform flag ensures compatibility with ARM64 hardware like Raspberry Pi 5 or AWS Graviton instances.
gitea:
image: gitea/gitea:v1.22.3
platform: linux/arm64
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__ROOT_URL=http://localhost:3002
- GITEA__server__SSH_PORT=2222
- GITEA__other__ENABLE_SWAGGER=false
- GITEA__metrics__ENABLED=false
volumes:
- /mnt/data/gitea:/data
ports:
- "3002:3000"
- "2222:22"
restart: always
labels:
- "com.centurylinklabs.watchtower.enable=true"
Tor handles the rest. Configure a hidden service in torrc to forward traffic from the onion address to Gitea’s local ports. After restarting Tor, fetch the onion hostname and use it as your remote URL. Note that Tor v0.4.8.10 or later is required for stable hidden service operation.
HiddenServiceDir /var/lib/tor/gitea/
HiddenServicePort 80 127.0.0.1:3002
HiddenServicePort 22 127.0.0.1:2222
Pushes go through Tor automatically when you use torsocks. This hides your IP from GitHub and any other observers watching package registries. Watch out: If you forget to prepend torsocks to git push commands, your real IP will be exposed. Always verify with torsocks --version before proceeding.
Routing Package Managers Through Tor
Package managers leak your IP and the packages you install. PyPI, npmjs, and Docker Hub log both. Routing these tools through Tor replaces your real IP with an exit node’s IP, breaking the fingerprint. This is particularly important for sovereign AI development where model weights and dependencies may reveal sensitive information.
Start with pip. Create a global pip.conf that proxies all requests through Tor’s SOCKS5 port. Trust the PyPI domains to avoid SSL errors. Verify the setup by installing a package and checking your exit IP. Gotcha: Some corporate networks block Tor exit nodes, causing pip install to fail silently. Test with torsocks curl https://api.ipify.org first.
mkdir -p ~/.pip
cat > ~/.pip/pip.conf << 'EOF'
[global]
proxy = socks5h://127.0.0.1:9050
trusted-host = pypi.org
pypi.python.org
files.pythonhosted.org
EOF
pip install requests
torsocks curl https://api.ipify.org
npm works similarly. Set global proxy settings to route all requests through Tor. Skip the config file if you prefer torsocks for individual installs. Warning: npm’s proxy configuration can break if you switch between Tor and non-Tor networks. Always run npm config delete proxy and npm config delete https-proxy when not using Tor.
npm config set proxy socks5://localhost:9050
npm config set https-proxy socks5://localhost:9050
npm config get proxy
torsocks npm install @11ty/eleventy
Docker pulls are slower over Tor, especially for large images like vLLM. Build a local registry to cache images once and avoid repeated Tor-routed downloads. Important: The official Docker registry (registry:2) doesn’t support authentication by default. For private images, use registry:2.8.1 with proper TLS configuration.
docker run -d \
-p 5000:5000 \
--name registry \
--restart always \
-v /mnt/docker-registry:/var/lib/registry \
registry:2.8.1
docker pull ghcr.io/remsky/kokoro-fastapi-gpu:latest
docker tag ghcr.io/remsky/kokoro-fastapi-gpu:latest localhost:5000/kokoro-tts:latest
docker push localhost:5000/kokoro-tts:latest
Update your docker-compose.yml to pull from the local registry instead of external sources. Pro tip: Add --insecure-registry localhost:5000 to your Docker daemon configuration (/etc/docker/daemon.json) to avoid TLS errors when using local registries.
Git Pseudonyms and Commit Privacy
Commit metadata reveals more than you think. Timestamps expose your timezone and work habits. Names and email addresses link commits to your identity. Fix both.
Set a global Git identity that doesn’t tie to your real name. Use a pseudonym for user.name and an onion-derived email for public repos. Note: Some Git hosts reject onion email addresses. Use a temporary email service like dev@sovereign.local for private repos.
git config --global user.name "sovereign-dev"
git config --global user.email "dev@sovereign.local"
git config --global user.email "dev@$(sudo cat /var/lib/tor/gitea/hostname)"
Optional: sign commits with an SSH key to prove authenticity without leaking identity. Watch out: SSH signing requires Git v2.34.0+ and OpenSSH v8.8+. Older versions will fail silently.
ssh-keygen -t ed25519 -C "sovereign-dev" -f ~/.ssh/gitea_signing -N ""
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/gitea_signing.pub
git config --global commit.gpgsign true
Freeze timestamps for existing commits or push them on a schedule to break the pattern. Gotcha: Git’s environment variables only affect new commits. Existing commits require git filter-branch as shown below.
GIT_AUTHOR_DATE="2026-01-01T12:00:00+00:00" \
GIT_COMMITTER_DATE="2026-01-01T12:00:00+00:00" \
git commit -m "update"
For daily pushes, use a cron job. Important: Cron jobs run with minimal environment variables. Always use absolute paths in scripts.
cat > /usr/local/bin/scheduled_push.sh << 'EOF'
#!/bin/bash
for repo in /mnt/projects/*/; do
cd "$repo" && git push origin main 2>/dev/null
done
EOF
chmod +x /usr/local/bin/scheduled_push.sh
crontab -e
Add this line to crontab (runs at 03:00 daily):
0 3 * * * /usr/local/bin/scheduled_push.sh
Rewrite history for existing repos to normalize timestamps. Warning: This is a destructive operation. Backup your repository first with git clone --mirror.
git filter-branch --env-filter '
export GIT_AUTHOR_DATE="2026-01-01T12:00:00+00:00"
export GIT_COMMITTER_DATE="2026-01-01T12:00:00+00:00"
' --tag-name-filter cat -- --branches --tags
What I Actually Use
- Gitea: self-hosted Git with SQLite, running on ARM64 (Raspberry Pi 5)
- Local Docker registry: caches large images to avoid slow Tor pulls (registry:2.8.1)
- torsocks: forces pip, npm, and git through Tor without per-command flags (v2.3.0)
- Commit signing: SSH-based with ed25519 keys for authenticity without identity leakage
Sovereign Dev Stack
Self-hosted Git & package management via Tor