aboutsummaryrefslogtreecommitdiff
path: root/cmd/cgitd/main.go
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2026-04-14 17:09:12 +0700
committerShulhan <ms@kilabit.info>2026-04-14 21:17:11 +0700
commit3cf0fd6460e828b2d39afe5b5673c4d0d2fdf24a (patch)
tree7411fdcf10ef91bff027254acaed9ed3276745a0 /cmd/cgitd/main.go
parent829eb0711305e8946fa2f4a1c57c43354f35e208 (diff)
downloadcgit-main.tar.xz
cmd/cgitd: HTTP server for cgit CGIHEADmain
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).
Diffstat (limited to 'cmd/cgitd/main.go')
-rw-r--r--cmd/cgitd/main.go200
1 files changed, 200 insertions, 0 deletions
diff --git a/cmd/cgitd/main.go b/cmd/cgitd/main.go
new file mode 100644
index 0000000..9abced6
--- /dev/null
+++ b/cmd/cgitd/main.go
@@ -0,0 +1,200 @@
+// Copyright (C) 2026 cgit Development Team <cgit@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:10010".
+//
+// 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 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.
+//
+// # Environment passed as arguments to cgit
+//
+// The following environment variables is paseed to cgit as arguments.
+//
+// CGIT_CACHE_ROOT - The directory for storing cgit caches, passed in
+// "--cache" argument.
+// 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:10010`
+ 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 throught systemd socket`, logp)
+ err = httpd.Serve(listener)
+ }
+ if err != nil {
+ log.Fatalf(`%s: %s`, logp, err)
+ }
+}