[PATCH] cmd/cgitd: HTTP server for cgit CGI

Shulhan ms at kilabit.info
Fri Apr 17 19:43:38 UTC 2026


The cmd/cgitd provides a quick alternatives to setup and run cgit
without installing another second application (usually a web server with
their own configuration).

To build and run cgitd you needs Go compiler.

Its only needs five environment variables, two requireds, and three
optionals.
Required variables are,

- CGITD_CGIT_BIN: path to cgit.cgi
- CGITD_CGIT_STATIC_DIR: path to directory that contains cgit.css and
  cgit.js

The other three optional variables are,

- CGITD_LISTEN: address and port to serve HTTP connections, default to
  "127.0.0.1:19418" if it not set.
- CGITD_REPO_PREFIXES: comma separated prefix that used in repo.url.
- CGITD_STATIC_DIR: path to directory that contains favicon and logo.
- CGIT_CONFIG: path to cgitrc

Once sets, you can build and run cgitd using the following commands,

	$ go build ./cmd/cgitd
	$ ./cgitd

Signed-off-by: Shulhan <ms at kilabit.info>
---
This patch is leaning more toward sharing rather than expecting it to be
merged to upstream.

When the first time I try to setup cgit, I am stuck on "should I install
this another package (Apache/lighttpd/...)? Is there any other way I can
see how it works before deploying it on server?"

The original idea is to be able running cgit on new cloned/modified cgit
source code,

	$ CGIT_CONFIG=${PWD}/cgitrc.test go run ./cmd/cgitd

and then update cgitrc.test to see how it works using my local
repositories.

Then, I think it can be helpful to others, not only for quick testing
during cgit development itself, but can be used on live deployment too.

PS: Any Arch Linux users who wants to try or like this patch can install
the prebuild package from https://build.kilabit.info/aur.
You can see whats included in the package here:
https://git.kilabit.info/aur-cgit-git and
https://git.kilabit.info/cgit 

 .gitignore        |   1 +
 Makefile          |  13 +++-
 README            |  33 ++++++++
 cmd/cgitd/main.go | 192 ++++++++++++++++++++++++++++++++++++++++++++++
 go.mod            |  11 +++
 go.sum            |   4 +
 6 files changed, 252 insertions(+), 2 deletions(-)
 create mode 100644 cmd/cgitd/main.go
 create mode 100644 go.mod
 create mode 100644 go.sum

diff --git a/.gitignore b/.gitignore
index 661df34..0d05b2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 # Files I don't care to see in git-status/commit
 /cgit
+/cgitd
 cgit.conf
 CGIT-CFLAGS
 VERSION
diff --git a/Makefile b/Makefile
index 16bfc55..888a5c4 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,7 @@ CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
 CGIT_CONFIG = /etc/cgitrc
 CACHE_ROOT = /var/cache/cgit
 prefix = /usr/local
+bindir = $(prefix)/bin
 libdir = $(prefix)/lib
 filterdir = $(libdir)/cgit/filters
 docdir = $(prefix)/share/doc/cgit
@@ -23,6 +24,7 @@ MAN_TXT  = $(MAN5_TXT)
 DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
 DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
 DOC_PDF  = $(patsubst %.txt,%.pdf,$(MAN_TXT))
+GO := $(shell which go 2>/dev/null)
 
 ASCIIDOC = asciidoc
 ASCIIDOC_EXTRA =
@@ -68,13 +70,17 @@ ifndef V
 	export V
 endif
 
+
 .SUFFIXES:
 
-all:: cgit
+all:: cgit cgitd
 
 cgit:
 	$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1
 
+cgitd: cmd/cgitd/main.go
+	if [ -n "$(GO)" ]; then $(GO) build ./cmd/cgitd; fi
+
 sparse:
 	$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk NO_CURL=1 cgit-sparse
 
@@ -85,6 +91,8 @@ test:
 install: all
 	$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
 	$(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(bindir)
+	if [ -n "$(GO)" ]; then $(INSTALL) -m 0755 cgitd $(DESTDIR)$(bindir)/cgitd; fi
 	$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
 	$(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
 	$(INSTALL) -m 0644 cgit.js $(DESTDIR)$(CGIT_DATA_PATH)/cgit.js
@@ -115,6 +123,7 @@ endef
 
 uninstall:
 	rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
+	rm -f $(DESTDIR)$(bindir)/cgitd
 	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
 	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.js
 	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
@@ -157,7 +166,7 @@ $(DOC_PDF): %.pdf : %.txt
 	a2x -f pdf cgitrc.5.txt
 
 clean: clean-doc
-	$(RM) cgit VERSION CGIT-CFLAGS *.o tags
+	$(RM) cgit cgitd VERSION CGIT-CFLAGS *.o tags
 	$(RM) -r .deps
 
 cleanall: clean
diff --git a/README b/README
index 7a6b4a4..24cf52e 100644
--- a/README
+++ b/README
@@ -51,6 +51,7 @@ Dependencies
 * libzip
 * libcrypto (OpenSSL)
 * libssl (OpenSSL)
+* optional: Go compiler, to build cgitd
 * optional: luajit or lua, most reliably used when pkg-config is available
 
 Apache configuration
@@ -67,6 +68,38 @@ like this:
     </Directory>
 
 
+Running with cgitd
+-------------------
+
+The cmd/cgitd provides a quick alternatives to setup and run cgit
+without installing another second application (usually a web server with
+their own configuration).
+
+To build and run cgitd you needs the Go compiler.
+
+Cgitd needs five environment variables, two are requireds, and three are
+optionals.
+Required variables are,
+
+- CGITD_CGIT_BIN: path to cgit.cgi
+- CGITD_CGIT_STATIC_DIR: path to directory that contains cgit.css and
+  cgit.js
+
+The three optional variables are,
+
+- CGITD_LISTEN: address and port to serve HTTP connections, default to
+  "127.0.0.1:19418".
+- CGITD_REPO_PREFIXES: comma separated prefix that used in repo.url.
+- CGITD_STATIC_DIR: path to directory that contains favicon and logo.
+  If set, the content of this directory will be served under "/static/"
+  path, so make sure to add that prefix to favicon= and logo= in cgitrc.
+- CGIT_CONFIG: path to cgitrc
+
+Once sets, you can build and run cgitd using the following commands,
+
+	$ go build ./cmd/cgitd
+	$ ./cgitd
+
 Runtime configuration
 ---------------------
 
diff --git a/cmd/cgitd/main.go b/cmd/cgitd/main.go
new file mode 100644
index 0000000..56b1b28
--- /dev/null
+++ b/cmd/cgitd/main.go
@@ -0,0 +1,192 @@
+// Copyright (C) 2026 cgit Development Team <cgit at lists.zx2c4.com>
+// Licensed under GNU General Public License v2
+// (see COPYING for full license text)
+
+// Program cgitd is the HTTP server for cgit CGI.
+// This program can be built into single binary, provides as an alternatives
+// to common setup that require second application (web server with their own
+// configuration).
+//
+// cgitd application can be customized through environment variables or by
+// modifying this code directly before build.
+//
+// # Environment variables
+//
+// The following environment variables affect on cgitd only.
+//
+// CGITD_CGIT_BIN - The full path to cgit.cgi.
+// It should be in the same directory that contains the "filters" scripts.
+// This environment is required, default to "/usr/lib/cgit/cgit.cgi".
+//
+// CGITD_CGIT_STATIC_DIR - The full path to directory that contains cgit
+// resources (cgit.css, cgit.js, cgit.png).
+// This environment is required, default to "/usr/share/webapps/cgit/".
+//
+// CGITD_LISTEN - IP address and port to accept HTTP connections.
+// This environment is required, default to "127.0.0.1:19418".
+//
+// CGITD_REPO_PREFIXES - Comma separated repo URL prefixes that will be
+// handled by cgit.
+// This environment is optional, no default value.
+//
+// CGITD_STATIC_DIR - The full path to serve static files, like favicon and
+// logo.
+// If set, the content of this directory will be served under "/static/" path,
+// so make sure to add that prefix to favicon= and logo= in cgitrc.
+// This environment is optional, no default value.
+//
+// # Environment passed to cgit
+//
+// The following environment variables is passed to cgit process.
+//
+// CGIT_CONFIG - The full path to cgitrc file.
+// This environment is optional.
+package main
+
+import (
+	"log"
+	"net"
+	"net/http"
+	"net/http/cgi"
+	"os"
+	"strings"
+
+	"git.sr.ht/~shulhan/pakakeh.go/lib/systemd"
+)
+
+// List of known environment variables handled by cgitd.
+const (
+	envCgitBin       = `CGITD_CGIT_BIN`
+	envCgitStaticDir = `CGITD_CGIT_STATIC_DIR`
+	envListen        = `CGITD_LISTEN`
+	envRepoPrefixes  = `CGITD_REPO_PREFIXES`
+	envStaticDir     = `CGITD_STATIC_DIR`
+)
+
+const (
+	defCgitBin       = `/usr/lib/cgit/cgit.cgi`
+	defCgitStaticDir = `/usr/share/webapps/cgit/`
+	defListen        = `127.0.0.1:19418`
+	defStaticPrefix  = `/static`
+)
+
+// List of known environment variables passed to cgit process.
+var listCgitEnv = []string{
+	`CGIT_CONFIG`,
+}
+
+type config struct {
+	cgitBin       string // Path to cgit.cgi.
+	cgitStaticDir string // Path to cgit static resources.
+	listen        string // Address for accepting connection.
+	staticDir     string // Path to non-cgit resources like favicon and logo.
+
+	cgitEnvs     []string // List of environment variables for cgit.
+	repoPrefixes []string // List of repo URL prefixes.
+}
+
+func initConfig() (cfg config) {
+	cfg.cgitBin = os.Getenv(envCgitBin)
+	if cfg.cgitBin == "" {
+		cfg.cgitBin = defCgitBin
+	}
+
+	cfg.cgitStaticDir = os.Getenv(envCgitStaticDir)
+	if cfg.cgitStaticDir == "" {
+		cfg.cgitStaticDir = defCgitStaticDir
+	}
+
+	cfg.listen = os.Getenv(envListen)
+	if cfg.listen == "" {
+		cfg.listen = defListen
+	}
+
+	repoPrefixes := os.Getenv(envRepoPrefixes)
+	for _, item := range strings.Split(repoPrefixes, ",") {
+		item = strings.TrimSpace(item)
+		if item != "" {
+			cfg.repoPrefixes = append(cfg.repoPrefixes, item)
+		}
+	}
+
+	cfg.staticDir = os.Getenv(envStaticDir)
+
+	for _, key := range listCgitEnv {
+		val := os.Getenv(key)
+		if val != "" {
+			cfg.cgitEnvs = append(cfg.cgitEnvs, key+`=`+val)
+		}
+	}
+
+	return cfg
+}
+
+func main() {
+	logp := `cgitd`
+	cfg := initConfig()
+
+	log.Printf(`%s: %s=%s`, logp, envCgitBin, cfg.cgitBin)
+	log.Printf(`%s: %s=%s`, logp, envCgitStaticDir, cfg.cgitStaticDir)
+	log.Printf(`%s: %s=%s`, logp, envListen, cfg.listen)
+	log.Printf(`%s: %s=%s`, logp, envRepoPrefixes, cfg.repoPrefixes)
+	log.Printf(`%s: %s=%s`, logp, envStaticDir, cfg.staticDir)
+	log.Printf(`%s: cgitEnvs=%s`, logp, cfg.cgitEnvs)
+
+	rootfs, err := os.OpenRoot(cfg.cgitStaticDir)
+	if err != nil {
+		log.Fatalf(`%s: %s`, logp, err)
+	}
+	cgitStaticFS := http.FileServerFS(rootfs.FS())
+
+	mux := http.NewServeMux()
+	mux.Handle(`GET /cgit.css`, cgitStaticFS)
+	mux.Handle(`GET /cgit.js`, cgitStaticFS)
+	mux.Handle(`GET /cgit.png`, cgitStaticFS)
+	mux.Handle(`GET /robots.txt`, cgitStaticFS)
+
+	if cfg.staticDir != "" {
+		rootfs, err = os.OpenRoot(cfg.staticDir)
+		if err != nil {
+			log.Fatalf(`%s: %s`, logp, err)
+		}
+		staticFS := http.FileServerFS(rootfs.FS())
+		mux.Handle(`GET `+defStaticPrefix+`/`,
+			http.StripPrefix(defStaticPrefix, staticFS))
+	}
+
+	cgitHandler := &cgi.Handler{
+		Path: cfg.cgitBin,
+		Env:  cfg.cgitEnvs,
+	}
+	for _, prefix := range cfg.repoPrefixes {
+		mux.Handle(`GET `+prefix, cgitHandler)
+	}
+	mux.Handle(`GET /`, cgitHandler)
+
+	httpd := http.Server{
+		Addr:    cfg.listen,
+		Handler: mux,
+	}
+
+	// Allow activation through systemd socket.
+	var listener net.Listener
+	listeners, err := systemd.Listeners(true)
+	if err != nil {
+		log.Fatalf(`%s: %s`, logp, err)
+	}
+	for _, l := range listeners {
+		if l.Addr().String() == cfg.listen {
+			listener = l
+		}
+	}
+	if listener == nil {
+		log.Printf(`%s: listening on address %s`, logp, cfg.listen)
+		err = httpd.ListenAndServe()
+	} else {
+		log.Printf(`%s: activated through systemd socket`, logp)
+		err = httpd.Serve(listener)
+	}
+	if err != nil {
+		log.Fatalf(`%s: %s`, logp, err)
+	}
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6807df0
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+// Copyright (C) 2026 cgit Development Team <cgit at lists.zx2c4.com>
+// Licensed under GNU General Public License v2
+// (see COPYING for full license text)
+
+module git.zx2c4.com/cgit
+
+go 1.26.0
+
+require git.sr.ht/~shulhan/pakakeh.go v0.62.0
+
+require golang.org/x/sys v0.42.0 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..c6b687e
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+git.sr.ht/~shulhan/pakakeh.go v0.62.0 h1:rAX0NpfxGbRfe6YhG7I9mmETmcafAreXUZqwPWNDvw4=
+git.sr.ht/~shulhan/pakakeh.go v0.62.0/go.mod h1:M8FG29UN+TMqRsOvKFTjsBuZDiGeBzYlAFxHjBM03Rs=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=

base-commit: 829eb0711305e8946fa2f4a1c57c43354f35e208
-- 
2.54.0.rc2.6.g1194a4dfc5.dirty



More information about the CGit mailing list