← All 19 apps

CandyServe

CandyServe

Self-hostable Git server over SSH + Git + HTTP

port of charmbracelet/soft-serve gitserversshself-hosted

CandyServe code coverage

Self-hostable Git server with SSH (authorized keys), Git (git://), and HTTP endpoints. Users, repos, access control, and optional LFS — all driven by a config file.

Install

composer require sugarcraft/candy-serve

Quickstart

use SugarCraft\Serve\{AccessControl, Config, Repo, User};
use SugarCraft\Serve\Git\GitDaemon;

$config = Config::fromDefaults();        // CANDY_SERVE_* env vars, else defaults
                                         // (Config::load($yamlPath) reads a config file)

// Describe a repository and create it as a bare git repo on disk
$repo = Repo::new('my-project', $config->reposPath() . '/my-project.git')
    ->withDescription('Greeting service')
    ->withPublic(true)
    ->addCollaborator('alice')
    ->init();

// Ask the permission matrix
$acl = AccessControl::getInstance();
$acl->canWrite(User::new('alice'), $repo);   // true  (collaborator)
$acl->canWrite(User::new('bob'), $repo);     // false
$acl->canRead(null, $repo);                  // true  (public repo, anonymous read)

// Serve it over git:// (blocks; serveAsync() runs on a ReactPHP loop instead)
$daemon = new GitDaemon($config);
$daemon->registerRepo($repo);
$daemon->serve();

What's in the box

SSH front endSSHServer authenticates users by public key and dispatches git upload-pack / receive-pack and the browse commands for each connection it is handed.
Git daemonGitDaemon serves the git:// protocol, blocking with serve() or on a ReactPHP loop with serveAsync().
HTTP smart protocolHttpSmartProtocol\Server handles smart-HTTP clone/fetch/push, with Basic auth when required.
Access controlAccessControl answers read / write / admin for a user (or anonymous) against a repo's visibility and collaborators.
LFS supportGit LFS over HTTP through LFSHandler and a pluggable storage backend.
Config fileYAML server configuration via Config::load(), or env + defaults via Config::fromDefaults().

Source & demos

Try the quickstart →

API

ClassMethodDescription
Configstatic fromDefaults() / static load(path)Configuration from env + defaults, or from a YAML file
ConfigreposPath() / sshPath() / dbPath() / lfsPath()Data directories
Repostatic new(name, path) / withDescription() / withPublic() / withPrivate() / withVisibility() / withAllowPush() / addCollaborator()Immutable repository description
Repoinit() / path() / branches()Create the bare repository; inspect it
Userstatic new(username) / withAdmin() / withAuthorizedKeys() / addAuthorizedKey()Immutable user account with SSH keys
AccessControlstatic getInstance() / canRead() / canWrite() / canAdmin()Permission checks
SSHServer__construct(Config) / registerUser() / registerRepo() / handleConnection(stream, username, command, publicKey)SSH command dispatcher
GitDaemon__construct(Config) / registerRepo() / serve(pidFile) / serveAsync(loop, pidFile) / shutdown()git:// protocol daemon
HttpSmartProtocol\Server__construct(Config) / registerRepo() / registerUser() / handleRequest(…)Smart-HTTP endpoint

Demos.

VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.

Config inspection

Config inspection

Resolved server config — name, host, ports, repo dir.
Repo metadata

Repo metadata

Per-repo settings — owner, ACLs, LFS, hooks.