michal/tit

NameTypeObject
.github tree dbc662bf79b9
.gitignore blob 734f4fd4c644
AGENTS.md blob c417d7187849
CONTRIBUTING.md blob ff9633249768
Cargo.lock blob ecfc7f5cc875
Cargo.toml blob d345a7602e0a
LICENSE blob fe02c78abb6a
PLAN.md blob 86d622876ca9
README.md blob 01bda513ad51
assets tree 0694594f1556
config.example.toml blob 2134e947e98d
deny.toml blob edb632aa6a40
release tree e80b2aee44e2
rust-toolchain.toml blob 80e6a12026ae
scripts tree 16a84e03e337
src tree 8a02fe964ef3
templates tree 0de0c6a54066
tests tree 1484a2c6af79

README.md

tit

tit is a small, self-hosted collaborative development environment for Git. One executable provides:

  • a server-rendered Web UI;
  • public HTTP clone and authenticated SSH clone and push;
  • accounts that use SSH keys;
  • repositories, issues, pull requests, reviews, and a protected default branch;
  • repository and issue RSS feeds;
  • repository name and source search;
  • public user profiles;
  • backup, restore, diagnostics, audit, and repair commands.

The server does not require Git, OpenSSH, JavaScript, or an external database at run time. SQLite stores application data. Bare repository directories store Git objects and refs.

Build

Install the Rust toolchain that rust-toolchain.toml specifies. Build the release executable:

cargo build --locked --release

The executable is target/release/tit.

Configure

Create a private instance directory and copy the example configuration:

install -d -m 700 /srv/tit
install -m 600 config.example.toml /srv/tit/config.toml

Set public_url, the HTTP listener, and the SSH listener and advertised hostname in /srv/tit/config.toml. Use HTTPS for a public instance. The included files in release/examples/ contain service definitions and a Caddy example for Linux, macOS, FreeBSD, OpenBSD, and NetBSD.

Create the first administrator:

tit --config /srv/tit/config.toml setup admin alice "SSH_PUBLIC_KEY"

The command prints one recovery credential. Store it offline.

Start the HTTP and SSH servers:

tit --config /srv/tit/config.toml serve

The server creates and preserves an Ed25519 SSH host key in the instance directory. It holds an instance lock until shutdown. Send SIGINT or SIGTERM for a controlled shutdown.

Use GET /healthz as a readiness check. Use GET /metrics for the fixed, unlabelled process counters.

Accounts and login

Create a one-time signup code while the server runs:

tit --config /srv/tit/config.toml invite-code

Open /signup and submit the code with an SSH public key. The Web UI displays the new recovery credential one time.

Web login uses SSH approval. Start login in the browser, then run the displayed auth SSH command. The fallback flow signs a short-lived challenge with ssh-keygen -Y sign. Account recovery replaces the account keys, revokes sessions and feed tokens, and issues a new recovery credential.

Each account has a public /<username> profile. The account can publish a plain-text bio and contact email. The profile lists only public repositories. The account page lists active and revoked SSH keys. A fresh SSH auth approval is required to add or revoke a key.

Repositories

An authenticated account can create a repository through SSH:

ssh -p 2222 tit.example repo create project

List all SSH commands:

ssh -p 2222 tit.example help

An invalid SSH command returns a nonzero status and directs the user to help.

Clone a public repository through HTTP or SSH:

git clone https://tit.example/alice/project.git
git clone ssh://tit@tit.example:2222/alice/project.git

HTTP Git access is read-only. SSH permits repository readers to clone and writers, maintainers, and owners to push. The SSH login name does not select the account; the SSH key does.

An administrator can import an existing bare repository while the server is stopped:

tit --config /srv/tit/config.toml admin repository import \
  alice project /absolute/path/project.git

Owners and maintainers can change a repository description, visibility, and collaborators on the repository settings page. They can select an existing branch as the default branch and archive the repository. The owner can rename or unarchive the repository. A rename does not move the bare repository directory.

The ref policy is fixed. tit rejects force updates, prevents deletion of the default branch, and permits only owners and maintainers to update it.

Use the offline administrator commands to import or rename repositories and to administer accounts. Run the applicable command with --help for its exact arguments:

tit admin repository --help
tit admin account --help

Web UI

Anonymous users can browse recently updated public repositories and public profiles. Authenticated users get an account overview and their repositories.

A repository page provides refs, commits, trees, blobs, blame, archives, source search, issues, pull requests, watch state, and RSS. The recent commit list shows ten commits and links to the complete commit view. Forms and navigation operate without JavaScript.

Issue and pull-request comments use bounded Markdown. Raw HTML and unsafe links do not render.

Authenticated accounts can open /activity to see recent events from watched repositories. A commit page can download a .patch file. Each pull-request revision has an immutable .patch download. These files work with git apply.

The SSH interface can list and create issues and pull requests. It can also comment on, close, and reopen issues, and close and reopen pull requests. Commands support human output and versioned JSON output. Run ssh -p 2222 tit.example help for the exact syntax.

Backup and restore

Create a backup while the server is stopped or active:

tit --config /srv/tit/config.toml backup /var/backups/tit-backup.tar

An online backup uses the private control socket and pauses Git ref mutations while it copies consistent state. A backup contains credentials. Store it as a secret.

Restore to a new, empty instance directory:

install -d -m 700 /srv/tit-restored
tit restore /var/backups/tit-backup.tar /srv/tit-restored
tit --config /srv/tit-restored/config.toml doctor

Restore does not activate the new instance. Stop the old server before you start the restored instance.

Diagnostics

Check an instance without changing it:

tit --config /srv/tit/config.toml doctor
tit --config /srv/tit/config.toml doctor \
  --backup /var/backups/tit-backup.tar

Inspect records or write deterministic JSON Lines:

tit --config /srv/tit/config.toml inspect account alice
tit --config /srv/tit/config.toml inspect repository alice project
tit --config /srv/tit/config.toml dump >tit-dump.jsonl

The dump can contain credential and token hashes. Store it as a secret.

Run a repair command only after doctor reports the applicable problem and only while the server is stopped:

tit --config /srv/tit/config.toml repair intents
tit --config /srv/tit/config.toml repair quarantine

Run maintenance while the server is stopped. This command removes expired or revoked workflow records and old audit events, then compacts SQLite. The default retention period is 365 days:

tit --config /srv/tit/config.toml maintenance
tit --config /srv/tit/config.toml maintenance --retention-days 90

Upgrade

Make and verify a backup before an upgrade. Stop the server, replace the executable, and start the server. tit applies forward-only SQLite migrations during startup. Run doctor after startup.

To go back to an older version, restore the pre-upgrade backup into a new instance directory. Do not run an older executable against a newer database.

Release packages

Build a release archive for the current host:

cargo build --locked --release
./scripts/package-release target/release/tit dist

Verify an archive and its checksum:

./scripts/verify-release-artifact \
  dist/tit-VERSION-TARGET.tar.gz \
  dist/tit-VERSION-TARGET.tar.gz.sha256

The package contains the executable, example service files, shell completions, the manual page, the example configuration, this README, and the license.

Development

Read CONTRIBUTING.md before you change code. Run the quality gate directly:

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --locked --all-targets --all-features
cargo deny check advisories licenses sources
cargo build --locked --release

The ignored workload tests are explicit performance checks:

cargo test --locked --release --test git_reads \
  measures_bounded_search_without_an_index -- --ignored --nocapture
cargo test --locked --release --test metadata_search \
  measures_bounded_repository_name_search_without_an_index \
  -- --ignored --nocapture
cargo test --locked --release --test sqlite_workload \
  -- --ignored --nocapture

tit is licensed under the MIT License. See LICENSE.