| Type | Name |
|---|---|
| 📄 | .gitignore |
| 📄 | AGENTS.md |
| 📄 | README.md |
| 📄 | cmdkey |
| 📄 | config.example.json |
| 📄 | git |
| 📁 | lib/ |
| 📄 | package.json |
| 📁 | public/ |
| 📁 | routes/ |
| 📁 | scripts/ |
| 📄 | server.js |
| 📄 | setup.js |
| 📁 | views/ |
otgit
Self-hosted git host with a 1999 Microsoft-era UI. Reuses Octuna's CSS shell.
!otgit
What it does
- Host git repositories on your own server
- Browse repos, files, commits, and diffs in the browser
- Push and pull over HTTPS smart protocol with basic auth
- Render Markdown READMEs (safe subset, no raw HTML)
- Render unified diffs with red/green hunks like GitHub
- Per-user accounts, public/private repos, admin web UI
Why
Modern feature set, retro skin. Lightweight, low RAM, low code, secure.
No framework, no database, two npm dependencies maximum (currently zero
runtime deps beyond Node itself). Designed to be portable, easy to read,
and easy to host on any small VPS that has Node and the git binary.
Hard limits
- Server target under 60 MB resident, capped at 64 MB heap
- No source file over 400 lines
- Total CSS under 1000 lines
- Every tunable comes from
config.jsonviaconfig.get(). No magic
numbers in route files
Stack
- Raw Node
http(no Express, Koa, Fastify) gitbinary spawned for all repo operations- Bare repos at
data/repos/<owner>/<name>.git - JSON state with atomic writes (
users.json,repos.json) - scrypt password hashing, HMAC-signed session cookies
git http-backendCGI bridge for smart HTTP push/pull
Layout
server.js boot, route registration, listen
setup.js creates admin, generates secrets
config.json runtime tunables (generated by setup)
config.example.json
lib/
config.js layered config + setMany validate
store.js atomic JSON store
respond.js json/html/text + security headers + readJson
router.js :param pattern matcher
log.js leveled logger
validate.js name and path safety
auth.js scrypt + HMAC sessions
limit.js rate limit factory
data.js users.json + repos.json singletons
git.js spawn wrappers for every git op
http-backend.js smart HTTP CGI bridge
render.js templates, escape, sidebar, tree, blob, diff
render-md.js markdown safe subset
render-diff.js unified diff to HTML
routes/
static.js cached public assets
auth.js signup, login, logout, me, password
repo-write.js create, settings, rename, delete
smart-http.js info/refs, upload-pack, receive-pack
pages.js home, signup, new repo
admin.js admin settings page (full config control)
repo.js repo home, tree, blob, history, commit
user.js user profile
views/ HTML templates
public/ style.css, pages.css, app.js, logo.png
scripts/
smoke.js end-to-end smoke test
data/
users.json
repos.json
repos/<owner>/<name>.git/
Setup
git clone https://git.dek.cx/dek/octuna.git otgit
cd otgit
node setup.js
npm start
setup.js prompts for admin username, password, public URL, and port,
then writes config.json and creates the admin user.
Reverse proxy (nginx)
server {
listen 80;
server_name git.example.com;
client_max_body_size 500M;
location / {
proxy_pass http://127.0.0.1:3031;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 300s;
}
}
Then certbot --nginx -d git.example.com.
Pushing code
git remote add origin https://git.example.com/<user>/<repo>.git
git push -u origin main
Use the account password as the git basic auth password. Public repos
clone without auth.
Admin
The admin user (set by setup.js) sees an "Admin settings" link in
the sidebar. The page lets you edit any non-secret value in
config.json from the browser, with restart-required fields flagged.
v1 scope
In: signup, login, password change, repo CRUD, file tree, blob viewer
with line numbers, commit history, single-commit diff with hunks,
README render, push/pull, admin settings, per-repo settings.
Out (post-v1): issues, pull requests, stars, forks, webhooks, CI,
releases, wikis, organizations, SSH transport, federation.
License
MIT.