1ff4be5cac4c2843c2d5014fbed58988772b1d1d
raw

lilgit

A read-only HTTP(S) frontend for git, built on Lilush runtime.

Features:

Everything is served straight from the git object database, i.e. from bare repositories on disk.

Pushes to served repositories appear immediately — the git core's mtime caches invalidate themselves.

No support for git push over HTTP, push over ssh to the bare repo instead.

URL scheme

PathBehavior
/repo index
/{repo}redirect to the default-branch tree
/{repo}/tree/{ref}/{path}/directory listing, README rendered below
/{repo}/blob/{ref}/{path}markdown/djot as HTML; source as text; images/binaries raw
/{repo}/raw/{ref}/{path}raw bytes, extension-guessed MIME
/{repo}/refsbranches and tags
/{repo}/log/{ref}commit log (first-parent, paged via ?after=<sha>)
/{repo}/commit/{sha}commit view: metadata, message, changed files

Every route of a repository listed in auth.private answers 401 until credentials are presented; see "Private repositories" below.

Configuration

Read from LILGIT_CONFIG_FILE (default /etc/lilgit/config.json); a missing file means pure defaults. CLI flags (--ip, --port, --dir, --title) override everything.

{
    "ip": "127.0.0.1",
    "port": 8091,
    "site_title": "my code",
    "repos_dir": "/srv/git",
    "repos": [
        {
            "name": "lilush",
            "path": "/srv/git/lilush.git",
            "description": "Linux Shell & Lua Framework",
            "default_branch": "master"
        }
    ],
    "ssl": { "default": { "cert": "...", "key": "..." } }
}

Repositories are discovered in repos_dir at startup (dirs with HEAD or .git/HEAD).

A top-level directory that is not itself a repository is treated as a category and scanned one level deep: /srv/git/repo.git serves as repo, /srv/git/tools/lilgit.git serves as tools/lilgit, so repositories with the same basename can live in different categories.

A repository directory is never scanned for nested repositories, and discovery never goes deeper than one category level. Explicit repos entries (plain or category-qualified names) win on name collision.

The ssl table enables built-in TLS (SNI-capable); omit it when running behind a reverse proxy.

Private repositories

Repositories named in auth.private are served only to visitors holding HTTP Basic credentials from auth.users — browsing pages, raw blobs and the smart-HTTP clone endpoints alike — and are left out of the repo index until the visitor authenticates.

Any valid user unlocks any private repo; there is no per-repo access list. An empty private list (the default) means auth is off and every discovered repository is public.

{
    "auth": {
        "realm": "lilgit",
        "users": { "alice": "hunter2", "bob": "s3cret" },
        "private": ["work/secret", "scratch"]
    }
}

Names in private are registry names, exactly as they appear in the index and in URLs (repo or category/repo). A name matching no repository is logged as a warning at startup, not an error.

Caveats worth knowing before relying on this:

  • Passwords are stored in the clear, so the config file belongs to root at mode 0600.

  • Basic credentials travel in the clear as well: serve over TLS (the ssl table) or behind a TLS-terminating proxy.

  • A private repository answers 401, not 404, so its existence remains inferable from its URL. Only its contents, description and index entry are hidden.

  • There is no rate limiting or lockout on failed attempts.

  • Responses for private repositories are marked private rather than public in Cache-Control, so shared caches will not store them.

Tests

Run from the pack root:

lilush tests/run.lua
LILGIT_E2E=1 lilush tests/run.lua

The e2e spec forks a real server and drives it with the git and curl binaries.

Roadmap

Performance sweep:

Deferred features: