aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikolay Govorov <me@govorov.online>2026-08-27 22:10:27 +0100
committerNikolay Govorov <me@govorov.online>2026-08-27 22:10:27 +0100
commit579a4432e386a483f55a1ee0eaa8d7d0691f055b (patch)
tree44173bb59dddd93976029498c0fe25e02635a267
parentb4b8201aa94d94087e1519c8da60f52afd724047 (diff)
downloadtar
tar.gz
tar.bz2
tar.lz
tar.xz
tar.zst
zip
Migrate configuration from cgit to gilti
Diffstat
-rw-r--r--Dockerfile2+0 −2
-rw-r--r--README.md12+10 −2
-rw-r--r--cgit/Makefile4+0 −4
-rw-r--r--cgit/cgit.c540+106 −434
-rw-r--r--cgit/cgit.h9+0 −9
-rw-r--r--cgit/configfile.c94+0 −94
-rw-r--r--cgit/configfile.h14+0 −14
-rw-r--r--cgit/scan-tree.c65+1 −64
-rw-r--r--cgit/scan-tree.h1+0 −1
-rw-r--r--cgit/shared.c2+1 −1
-rw-r--r--cgit/ui-shared.c3+0 −3
-rw-r--r--charts/gilti/templates/configmap.yaml24+0 −24
-rw-r--r--charts/gilti/templates/deployment.yaml13+9 −4
-rw-r--r--config/cgitrc27+0 −27
-rw-r--r--crates/gilti/src/main.rs201+122 −79
-rwxr-xr-xscripts/entrypoint.sh5+1 −4
-rwxr-xr-xtests/chart.sh7+6 −1
-rwxr-xr-xtests/smoke.sh4+2 −2
18 files changed, 258 insertions, 769 deletions
diff --git a/Dockerfile b/Dockerfile
index 8c9c369..bd6957a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,7 +33,6 @@ RUN apk add --no-cache \
/var/lib/gilti/git /var/lib/gilti/git/repositories && \
install -d -m 0700 -o root -g root /var/lib/gilti/ssh && \
install -d -m 0755 -o root -g root /run/gilti && \
- install -d -m 0750 -o git -g git /run/gilti/http && \
install -d -m 0750 -o root -g git /run/gilti/ssh && \
install -d -m 0755 /etc/gilti && \
rm -rf /var/cache/apk/*
@@ -53,7 +52,6 @@ COPY --chown=root:root \
.container/binary-${TARGETARCH}/COPYING.cgit.txt \
.container/binary-${TARGETARCH}/COPYING.git.txt \
/usr/share/doc/gilti-cgit/
-COPY --chown=root:root config/cgitrc /etc/cgitrc
COPY --chown=root:root config/sshd_config /etc/ssh/sshd_config
COPY --chown=root:root --chmod=0755 scripts/entrypoint.sh /usr/local/bin/gilti-entrypoint
COPY --chown=root:root LICENSE README.md /usr/share/doc/gilti/
diff --git a/README.md b/README.md
index 50892bf..862664d 100644
--- a/README.md
+++ b/README.md
@@ -39,8 +39,16 @@ docker run --rm \
ghcr.io/dimidiumlabs/gilti:nightly
```
-Gilti snapshots this file at process startup; changing it takes effect after a
-restart. Repositories and the persistent SSH host key live on the state volume.
+The HTTP configuration is read from the environment at startup:
+
+- `GILTI_CGIT_ROOT_TITLE` (default: `Gilti`);
+- `GILTI_CGIT_ROOT_DESCRIPTION` (default: `A tiny Git server`);
+- `GILTI_CGIT_CLONE_PREFIX` (empty by default);
+- `GILTI_CGIT_CACHE` (default: `5`, maximum: `3600` seconds).
+
+Gilti snapshots the authorized keys file at process startup; changing it takes
+effect after a restart. Repositories and the persistent SSH host key live on
+the state volume.
## Helm
diff --git a/cgit/Makefile b/cgit/Makefile
index 5fafeb2..9249fea 100644
--- a/cgit/Makefile
+++ b/cgit/Makefile
@@ -8,11 +8,7 @@ include Makefile
CGIT_PREFIX = ../
CGIT_VERSION = v1.3.1
-CGIT_SCRIPT_NAME = gilti-cgit
-CGIT_CONFIG = /etc/cgitrc
-CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
-CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
CGIT_CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
CGIT_OBJS := $(patsubst %.c,%.o,$(wildcard $(CGIT_PREFIX)*.c))
diff --git a/cgit/cgit.c b/cgit/cgit.c
index 6719996..f04a962 100644
--- a/cgit/cgit.c
+++ b/cgit/cgit.c
@@ -15,254 +15,52 @@
#include "cgit.h"
#include "cmd.h"
-#include "configfile.h"
#include "html.h"
#include "ui-shared.h"
-#include "ui-stats.h"
#include "ui-blob.h"
#include "ui-summary.h"
#include "scan-tree.h"
const char *cgit_version = CGIT_VERSION;
-__attribute__((constructor))
-static void constructor_environment()
+static const char *config_value(const char *name)
{
- /* Do not look in /etc/ for gitconfig and gitattributes. */
- setenv("GIT_CONFIG_NOSYSTEM", "1", 1);
- setenv("GIT_ATTR_NOSYSTEM", "1", 1);
- unsetenv("HOME");
- unsetenv("XDG_CONFIG_HOME");
+ const char *value = getenv(name);
+
+ if (!value) {
+ fprintf(stderr, "gilti-cgit: missing required environment variable %s\n",
+ name);
+ exit(1);
+ }
+ return value;
}
-static void add_mimetype(const char *name, const char *value)
+static char *config_string(const char *name)
{
- struct string_list_item *item;
-
- item = string_list_insert(&ctx.cfg.mimetypes, name);
- item->util = xstrdup(value);
+ return xstrdup(config_value(name));
}
-void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *value)
+static char *config_optional_string(const char *name)
{
- const char *path;
- struct string_list_item *item;
-
- if (!strcmp(name, "name"))
- repo->name = strdup_first_line(value);
- else if (!strcmp(name, "clone-url"))
- repo->clone_url = strdup_first_line(value);
- else if (!strcmp(name, "desc"))
- repo->desc = strdup_first_line(value);
- else if (!strcmp(name, "owner"))
- repo->owner = strdup_first_line(value);
- else if (!strcmp(name, "homepage"))
- repo->homepage = strdup_first_line(value);
- else if (!strcmp(name, "defbranch"))
- repo->defbranch = strdup_first_line(value);
- else if (!strcmp(name, "extra-head-content"))
- repo->extra_head_content = strdup_first_line(value);
- else if (!strcmp(name, "snapshots"))
- repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
- else if (!strcmp(name, "enable-blame"))
- repo->enable_blame = atoi(value);
- else if (!strcmp(name, "enable-commit-graph"))
- repo->enable_commit_graph = atoi(value);
- else if (!strcmp(name, "enable-follow-links"))
- repo->enable_follow_links = atoi(value);
- else if (!strcmp(name, "enable-log-filecount"))
- repo->enable_log_filecount = atoi(value);
- else if (!strcmp(name, "enable-log-linecount"))
- repo->enable_log_linecount = atoi(value);
- else if (!strcmp(name, "enable-remote-branches"))
- repo->enable_remote_branches = atoi(value);
- else if (!strcmp(name, "enable-subject-links"))
- repo->enable_subject_links = atoi(value);
- else if (!strcmp(name, "enable-html-serving"))
- repo->enable_html_serving = atoi(value);
- else if (!strcmp(name, "branch-sort")) {
- if (!strcmp(value, "age"))
- repo->branch_sort = 1;
- if (!strcmp(value, "name"))
- repo->branch_sort = 0;
- } else if (!strcmp(name, "commit-sort")) {
- if (!strcmp(value, "date"))
- repo->commit_sort = 1;
- if (!strcmp(value, "topo"))
- repo->commit_sort = 2;
- } else if (!strcmp(name, "max-stats"))
- repo->max_stats = cgit_find_stats_period(value, NULL);
- else if (!strcmp(name, "module-link"))
- repo->module_link= strdup_first_line(value);
- else if (skip_prefix(name, "module-link.", &path)) {
- item = string_list_append(&repo->submodules, strdup_first_line(path));
- item->util = strdup_first_line(value);
- } else if (!strcmp(name, "section"))
- repo->section = strdup_first_line(value);
- else if (!strcmp(name, "snapshot-prefix"))
- repo->snapshot_prefix = strdup_first_line(value);
- else if (!strcmp(name, "readme") && value != NULL) {
- if (repo->readme.items == ctx.cfg.readme.items)
- memset(&repo->readme, 0, sizeof(repo->readme));
- string_list_append(&repo->readme, strdup_first_line(value));
- } else if (!strcmp(name, "logo") && value != NULL)
- repo->logo = strdup_first_line(value);
- else if (!strcmp(name, "logo-link") && value != NULL)
- repo->logo_link = strdup_first_line(value);
- else if (!strcmp(name, "hide"))
- repo->hide = atoi(value);
- else if (!strcmp(name, "ignore"))
- repo->ignore = atoi(value);
+ const char *value = config_value(name);
+
+ return *value ? xstrdup(value) : NULL;
}
-static void config_cb(const char *name, const char *value)
+static int config_integer(const char *name)
{
- const char *arg;
-
- if (!strcmp(name, "section"))
- ctx.cfg.section = strdup_first_line(value);
- else if (!strcmp(name, "repo.url"))
- ctx.repo = cgit_add_repo(value);
- else if (ctx.repo && !strcmp(name, "repo.path"))
- ctx.repo->path = trim_end(value, '/');
- else if (ctx.repo && skip_prefix(name, "repo.", &arg))
- cgit_repo_config(ctx.repo, arg, value);
- else if (!strcmp(name, "readme"))
- string_list_append(&ctx.cfg.readme, strdup_first_line(value));
- else if (!strcmp(name, "root-title"))
- ctx.cfg.root_title = strdup_first_line(value);
- else if (!strcmp(name, "root-desc"))
- ctx.cfg.root_desc = strdup_first_line(value);
- else if (!strcmp(name, "root-readme"))
- ctx.cfg.root_readme = strdup_first_line(value);
- else if (!strcmp(name, "css"))
- string_list_append(&ctx.cfg.css, strdup_first_line(value));
- else if (!strcmp(name, "js"))
- string_list_append(&ctx.cfg.js, strdup_first_line(value));
- else if (!strcmp(name, "favicon"))
- ctx.cfg.favicon = strdup_first_line(value);
- else if (!strcmp(name, "footer"))
- ctx.cfg.footer = strdup_first_line(value);
- else if (!strcmp(name, "head-include"))
- ctx.cfg.head_include = strdup_first_line(value);
- else if (!strcmp(name, "header"))
- ctx.cfg.header = strdup_first_line(value);
- else if (!strcmp(name, "logo"))
- ctx.cfg.logo = strdup_first_line(value);
- else if (!strcmp(name, "logo-link"))
- ctx.cfg.logo_link = strdup_first_line(value);
- else if (!strcmp(name, "module-link"))
- ctx.cfg.module_link = strdup_first_line(value);
- else if (!strcmp(name, "strict-export"))
- ctx.cfg.strict_export = strdup_first_line(value);
- else if (!strcmp(name, "virtual-root"))
- ctx.cfg.virtual_root = ensure_end(value, '/');
- else if (!strcmp(name, "noplainemail"))
- ctx.cfg.noplainemail = atoi(value);
- else if (!strcmp(name, "noheader"))
- ctx.cfg.noheader = atoi(value);
- else if (!strcmp(name, "snapshots"))
- ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
- else if (!strcmp(name, "enable-follow-links"))
- ctx.cfg.enable_follow_links = atoi(value);
- else if (!strcmp(name, "enable-http-clone"))
- ctx.cfg.enable_http_clone = atoi(value);
- else if (!strcmp(name, "enable-index-links"))
- ctx.cfg.enable_index_links = atoi(value);
- else if (!strcmp(name, "enable-index-owner"))
- ctx.cfg.enable_index_owner = atoi(value);
- else if (!strcmp(name, "enable-blame"))
- ctx.cfg.enable_blame = atoi(value);
- else if (!strcmp(name, "enable-commit-graph"))
- ctx.cfg.enable_commit_graph = atoi(value);
- else if (!strcmp(name, "enable-log-filecount"))
- ctx.cfg.enable_log_filecount = atoi(value);
- else if (!strcmp(name, "enable-log-linecount"))
- ctx.cfg.enable_log_linecount = atoi(value);
- else if (!strcmp(name, "enable-remote-branches"))
- ctx.cfg.enable_remote_branches = atoi(value);
- else if (!strcmp(name, "enable-subject-links"))
- ctx.cfg.enable_subject_links = atoi(value);
- else if (!strcmp(name, "enable-html-serving"))
- ctx.cfg.enable_html_serving = atoi(value);
- else if (!strcmp(name, "enable-tree-linenumbers"))
- ctx.cfg.enable_tree_linenumbers = atoi(value);
- else if (!strcmp(name, "enable-git-config"))
- ctx.cfg.enable_git_config = atoi(value);
- else if (!strcmp(name, "max-stats"))
- ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
- else if (!strcmp(name, "case-sensitive-sort"))
- ctx.cfg.case_sensitive_sort = atoi(value);
- else if (!strcmp(name, "embedded"))
- ctx.cfg.embedded = atoi(value);
- else if (!strcmp(name, "max-atom-items"))
- ctx.cfg.max_atom_items = atoi(value);
- else if (!strcmp(name, "max-message-length"))
- ctx.cfg.max_msg_len = atoi(value);
- else if (!strcmp(name, "max-repodesc-length"))
- ctx.cfg.max_repodesc_len = atoi(value);
- else if (!strcmp(name, "max-blob-size"))
- ctx.cfg.max_blob_size = atoi(value);
- else if (!strcmp(name, "max-repo-count")) {
- ctx.cfg.max_repo_count = atoi(value);
- if (ctx.cfg.max_repo_count <= 0)
- ctx.cfg.max_repo_count = INT_MAX;
- } else if (!strcmp(name, "max-commit-count"))
- ctx.cfg.max_commit_count = atoi(value);
- else if (!strcmp(name, "project-list"))
- ctx.cfg.project_list = strdup_first_line(expand_macros(value));
- else if (!strcmp(name, "scan-path"))
- if (ctx.cfg.project_list)
- scan_projects(expand_macros(value),
- ctx.cfg.project_list);
- else
- scan_tree(expand_macros(value));
- else if (!strcmp(name, "scan-hidden-path"))
- ctx.cfg.scan_hidden_path = atoi(value);
- else if (!strcmp(name, "section-from-path"))
- ctx.cfg.section_from_path = atoi(value);
- else if (!strcmp(name, "repository-sort"))
- ctx.cfg.repository_sort = strdup_first_line(value);
- else if (!strcmp(name, "section-sort"))
- ctx.cfg.section_sort = atoi(value);
- else if (!strcmp(name, "summary-log"))
- ctx.cfg.summary_log = atoi(value);
- else if (!strcmp(name, "summary-branches"))
- ctx.cfg.summary_branches = atoi(value);
- else if (!strcmp(name, "summary-tags"))
- ctx.cfg.summary_tags = atoi(value);
- else if (!strcmp(name, "side-by-side-diffs"))
- ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED;
- else if (!strcmp(name, "agefile"))
- ctx.cfg.agefile = strdup_first_line(value);
- else if (!strcmp(name, "mimetype-file"))
- ctx.cfg.mimetype_file = strdup_first_line(value);
- else if (!strcmp(name, "renamelimit"))
- ctx.cfg.renamelimit = atoi(value);
- else if (!strcmp(name, "remove-suffix"))
- ctx.cfg.remove_suffix = atoi(value);
- else if (!strcmp(name, "robots"))
- ctx.cfg.robots = strdup_first_line(value);
- else if (!strcmp(name, "clone-prefix"))
- ctx.cfg.clone_prefix = strdup_first_line(value);
- else if (!strcmp(name, "clone-url"))
- ctx.cfg.clone_url = strdup_first_line(value);
- else if (!strcmp(name, "local-time"))
- ctx.cfg.local_time = atoi(value);
- else if (!strcmp(name, "commit-sort")) {
- if (!strcmp(value, "date"))
- ctx.cfg.commit_sort = 1;
- if (!strcmp(value, "topo"))
- ctx.cfg.commit_sort = 2;
- } else if (!strcmp(name, "branch-sort")) {
- if (!strcmp(value, "age"))
- ctx.cfg.branch_sort = 1;
- if (!strcmp(value, "name"))
- ctx.cfg.branch_sort = 0;
- } else if (skip_prefix(name, "mimetype.", &arg))
- add_mimetype(arg, value);
- else if (!strcmp(name, "include"))
- parse_configfile(expand_macros(value), config_cb);
+ const char *value = config_value(name);
+ char *end;
+ long result;
+
+ errno = 0;
+ result = strtol(value, &end, 10);
+ if (errno || end == value || *end || result < INT_MIN || result > INT_MAX) {
+ fprintf(stderr, "gilti-cgit: environment variable %s must be an integer\n",
+ name);
+ exit(1);
+ }
+ return result;
}
static void querystring_cb(const char *name, const char *value)
@@ -324,64 +122,95 @@ static void querystring_cb(const char *name, const char *value)
static void prepare_context(void)
{
+ const char *value;
+
memset(&ctx, 0, sizeof(ctx));
- ctx.cfg.agefile = "info/web/last-modified";
- ctx.cfg.case_sensitive_sort = 1;
- ctx.cfg.branch_sort = 0;
- ctx.cfg.commit_sort = 0;
- ctx.cfg.logo = "/cgit.png";
- ctx.cfg.favicon = "/favicon.ico";
- ctx.cfg.local_time = 0;
- ctx.cfg.enable_http_clone = 1;
- ctx.cfg.enable_index_owner = 1;
- ctx.cfg.enable_tree_linenumbers = 1;
- ctx.cfg.enable_git_config = 0;
- ctx.cfg.max_repo_count = 50;
- ctx.cfg.max_commit_count = 50;
- ctx.cfg.max_lock_attempts = 5;
- ctx.cfg.max_msg_len = 80;
- ctx.cfg.max_repodesc_len = 80;
- ctx.cfg.max_blob_size = 0;
- ctx.cfg.max_stats = 0;
- ctx.cfg.project_list = NULL;
- ctx.cfg.renamelimit = -1;
- ctx.cfg.remove_suffix = 0;
- ctx.cfg.robots = "index, nofollow";
- ctx.cfg.root_title = "Git repository browser";
- ctx.cfg.root_desc = "a fast webinterface for the git dscm";
- ctx.cfg.scan_hidden_path = 0;
- ctx.cfg.script_name = CGIT_SCRIPT_NAME;
- ctx.cfg.section = "";
- ctx.cfg.repository_sort = "name";
- ctx.cfg.section_sort = 1;
- ctx.cfg.summary_branches = 10;
- ctx.cfg.summary_log = 10;
- ctx.cfg.summary_tags = 10;
- ctx.cfg.max_atom_items = 10;
- ctx.cfg.difftype = DIFF_UNIFIED;
- ctx.env.cgit_config = getenv("CGIT_CONFIG");
+ ctx.cfg.agefile = config_string("CGIT_AGEFILE");
+ ctx.cfg.branch_sort = config_integer("CGIT_BRANCH_SORT");
+ ctx.cfg.case_sensitive_sort = config_integer("CGIT_CASE_SENSITIVE_SORT");
+ ctx.cfg.clone_prefix = config_optional_string("CGIT_CLONE_PREFIX");
+ ctx.cfg.clone_url = config_optional_string("CGIT_CLONE_URL");
+ ctx.cfg.commit_sort = config_integer("CGIT_COMMIT_SORT");
+ ctx.cfg.difftype = config_integer("CGIT_DIFFTYPE");
+ ctx.cfg.embedded = config_integer("CGIT_EMBEDDED");
+ ctx.cfg.enable_blame = config_integer("CGIT_ENABLE_BLAME");
+ ctx.cfg.enable_commit_graph = config_integer("CGIT_ENABLE_COMMIT_GRAPH");
+ ctx.cfg.enable_follow_links = config_integer("CGIT_ENABLE_FOLLOW_LINKS");
+ ctx.cfg.enable_html_serving = config_integer("CGIT_ENABLE_HTML_SERVING");
+ ctx.cfg.enable_http_clone = config_integer("CGIT_ENABLE_HTTP_CLONE");
+ ctx.cfg.enable_index_links = config_integer("CGIT_ENABLE_INDEX_LINKS");
+ ctx.cfg.enable_index_owner = config_integer("CGIT_ENABLE_INDEX_OWNER");
+ ctx.cfg.enable_log_filecount = config_integer("CGIT_ENABLE_LOG_FILECOUNT");
+ ctx.cfg.enable_log_linecount = config_integer("CGIT_ENABLE_LOG_LINECOUNT");
+ ctx.cfg.enable_remote_branches = config_integer("CGIT_ENABLE_REMOTE_BRANCHES");
+ ctx.cfg.enable_subject_links = config_integer("CGIT_ENABLE_SUBJECT_LINKS");
+ ctx.cfg.enable_tree_linenumbers = config_integer("CGIT_ENABLE_TREE_LINENUMBERS");
+ ctx.cfg.favicon = config_string("CGIT_FAVICON");
+ ctx.cfg.footer = config_optional_string("CGIT_FOOTER");
+ ctx.cfg.head_include = config_optional_string("CGIT_HEAD_INCLUDE");
+ ctx.cfg.header = config_optional_string("CGIT_HEADER");
+ ctx.cfg.local_time = config_integer("CGIT_LOCAL_TIME");
+ ctx.cfg.logo = config_string("CGIT_LOGO");
+ ctx.cfg.logo_link = config_optional_string("CGIT_LOGO_LINK");
+ ctx.cfg.max_atom_items = config_integer("CGIT_MAX_ATOM_ITEMS");
+ ctx.cfg.max_blob_size = config_integer("CGIT_MAX_BLOB_SIZE");
+ ctx.cfg.max_commit_count = config_integer("CGIT_MAX_COMMIT_COUNT");
+ ctx.cfg.max_msg_len = config_integer("CGIT_MAX_MESSAGE_LENGTH");
+ ctx.cfg.max_repo_count = config_integer("CGIT_MAX_REPO_COUNT");
+ ctx.cfg.max_repodesc_len = config_integer("CGIT_MAX_REPODESC_LENGTH");
+ ctx.cfg.max_stats = config_integer("CGIT_MAX_STATS");
+ ctx.cfg.mimetype_file = config_optional_string("CGIT_MIMETYPE_FILE");
+ ctx.cfg.module_link = config_optional_string("CGIT_MODULE_LINK");
+ ctx.cfg.noheader = config_integer("CGIT_NOHEADER");
+ ctx.cfg.noplainemail = config_integer("CGIT_NOPLAINEMAIL");
+ ctx.cfg.remove_suffix = config_integer("CGIT_REMOVE_SUFFIX");
+ cgit_default_repo_desc = config_string("CGIT_REPO_DEFAULT_DESC");
+ ctx.cfg.renamelimit = config_integer("CGIT_RENAMELIMIT");
+ ctx.cfg.repository_sort = config_string("CGIT_REPOSITORY_SORT");
+ ctx.cfg.robots = config_string("CGIT_ROBOTS");
+ ctx.cfg.root_desc = config_string("CGIT_ROOT_DESC");
+ ctx.cfg.root_readme = config_optional_string("CGIT_ROOT_README");
+ ctx.cfg.root_title = config_string("CGIT_ROOT_TITLE");
+ ctx.cfg.scan_hidden_path = config_integer("CGIT_SCAN_HIDDEN_PATH");
+ ctx.cfg.script_name = config_string("SCRIPT_NAME");
+ ctx.cfg.section = config_string("CGIT_SECTION");
+ ctx.cfg.section_from_path = config_integer("CGIT_SECTION_FROM_PATH");
+ ctx.cfg.section_sort = config_integer("CGIT_SECTION_SORT");
+ ctx.cfg.snapshots = config_integer("CGIT_SNAPSHOTS");
+ ctx.cfg.strict_export = config_optional_string("CGIT_STRICT_EXPORT");
+ ctx.cfg.summary_branches = config_integer("CGIT_SUMMARY_BRANCHES");
+ ctx.cfg.summary_log = config_integer("CGIT_SUMMARY_LOG");
+ ctx.cfg.summary_tags = config_integer("CGIT_SUMMARY_TAGS");
+ ctx.cfg.virtual_root = ensure_end(config_value("CGIT_VIRTUAL_ROOT"), '/');
+ string_list_init_dup(&ctx.cfg.css);
+ string_list_init_dup(&ctx.cfg.js);
+ string_list_init_dup(&ctx.cfg.mimetypes);
+ string_list_init_dup(&ctx.cfg.readme);
+ value = config_value("CGIT_CSS");
+ if (*value)
+ string_list_append(&ctx.cfg.css, value);
+ value = config_value("CGIT_JS");
+ if (*value)
+ string_list_append(&ctx.cfg.js, value);
+ value = config_value("CGIT_README_0");
+ if (*value)
+ string_list_append(&ctx.cfg.readme, value);
+ value = config_value("CGIT_README_1");
+ if (*value)
+ string_list_append(&ctx.cfg.readme, value);
+
ctx.env.http_host = getenv("HTTP_HOST");
ctx.env.https = getenv("HTTPS");
- ctx.env.no_http = getenv("NO_HTTP");
ctx.env.path_info = getenv("PATH_INFO");
ctx.env.query_string = getenv("QUERY_STRING");
ctx.env.request_method = getenv("REQUEST_METHOD");
- ctx.env.script_name = getenv("SCRIPT_NAME");
ctx.env.server_name = getenv("SERVER_NAME");
ctx.env.server_port = getenv("SERVER_PORT");
ctx.page.mimetype = "text/html";
ctx.page.charset = PAGE_ENCODING;
- ctx.page.filename = NULL;
- ctx.page.size = 0;
ctx.page.modified = time(NULL);
- ctx.page.etag = NULL;
- string_list_init_dup(&ctx.cfg.mimetypes);
- if (ctx.env.script_name)
- ctx.cfg.script_name = xstrdup(ctx.env.script_name);
if (ctx.env.query_string)
ctx.qry.raw = xstrdup(ctx.env.query_string);
- if (!ctx.env.cgit_config)
- ctx.env.cgit_config = CGIT_CONFIG;
}
struct refmatch {
@@ -629,162 +458,13 @@ static void process_request(void)
cmd->fn();
}
-static int cmp_repos(const void *a, const void *b)
-{
- const struct cgit_repo *ra = a, *rb = b;
- return strcmp(ra->url, rb->url);
-}
-
-static char *build_snapshot_setting(int bitmap)
-{
- const struct cgit_snapshot_format *f;
- struct strbuf result = STRBUF_INIT;
-
- for (f = cgit_snapshot_formats; f->suffix; f++) {
- if (cgit_snapshot_format_bit(f) & bitmap) {
- if (result.len)
- strbuf_addch(&result, ' ');
- strbuf_addstr(&result, f->suffix);
- }
- }
- return strbuf_detach(&result, NULL);
-}
-
-static void print_repo(FILE *f, struct cgit_repo *repo)
-{
- struct string_list_item *item;
- fprintf(f, "repo.url=%s\n", repo->url);
- fprintf(f, "repo.name=%s\n", repo->name);
- fprintf(f, "repo.path=%s\n", repo->path);
- if (repo->owner)
- fprintf(f, "repo.owner=%s\n", repo->owner);
- if (repo->desc)
- fprintf(f, "repo.desc=%s\n", repo->desc);
- for_each_string_list_item(item, &repo->readme) {
- if (item->util)
- fprintf(f, "repo.readme=%s:%s\n", (char *)item->util, item->string);
- else
- fprintf(f, "repo.readme=%s\n", item->string);
- }
- if (repo->defbranch)
- fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
- if (repo->extra_head_content)
- fprintf(f, "repo.extra-head-content=%s\n", repo->extra_head_content);
- if (repo->module_link)
- fprintf(f, "repo.module-link=%s\n", repo->module_link);
- if (repo->section)
- fprintf(f, "repo.section=%s\n", repo->section);
- if (repo->homepage)
- fprintf(f, "repo.homepage=%s\n", repo->homepage);
- if (repo->clone_url)
- fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
- fprintf(f, "repo.enable-blame=%d\n",
- repo->enable_blame);
- fprintf(f, "repo.enable-commit-graph=%d\n",
- repo->enable_commit_graph);
- fprintf(f, "repo.enable-follow-links=%d\n",
- repo->enable_follow_links);
- fprintf(f, "repo.enable-log-filecount=%d\n",
- repo->enable_log_filecount);
- fprintf(f, "repo.enable-log-linecount=%d\n",
- repo->enable_log_linecount);
- if (repo->snapshots != ctx.cfg.snapshots) {
- char *tmp = build_snapshot_setting(repo->snapshots);
- fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
- free(tmp);
- }
- if (repo->snapshot_prefix)
- fprintf(f, "repo.snapshot-prefix=%s\n", repo->snapshot_prefix);
- if (repo->max_stats != ctx.cfg.max_stats)
- fprintf(f, "repo.max-stats=%s\n",
- cgit_find_stats_periodname(repo->max_stats));
- if (repo->logo)
- fprintf(f, "repo.logo=%s\n", repo->logo);
- if (repo->logo_link)
- fprintf(f, "repo.logo-link=%s\n", repo->logo_link);
- fprintf(f, "repo.enable-remote-branches=%d\n", repo->enable_remote_branches);
- fprintf(f, "repo.enable-subject-links=%d\n", repo->enable_subject_links);
- fprintf(f, "repo.enable-html-serving=%d\n", repo->enable_html_serving);
- if (repo->branch_sort == 1)
- fprintf(f, "repo.branch-sort=age\n");
- if (repo->commit_sort) {
- if (repo->commit_sort == 1)
- fprintf(f, "repo.commit-sort=date\n");
- else if (repo->commit_sort == 2)
- fprintf(f, "repo.commit-sort=topo\n");
- }
- fprintf(f, "repo.hide=%d\n", repo->hide);
- fprintf(f, "repo.ignore=%d\n", repo->ignore);
- fprintf(f, "\n");
-}
-
-static void print_repolist(FILE *f, struct cgit_repolist *list, int start)
-{
- int i;
-
- for (i = start; i < list->count; i++)
- print_repo(f, &list->repos[i]);
-}
-
-static void cgit_parse_args(int argc, const char **argv)
-{
- int i;
- const char *arg;
- int scan = 0;
-
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "--version")) {
- printf("CGit %s | https://git.zx2c4.com/cgit/\n", CGIT_VERSION);
- exit(0);
- }
- if (!strcmp(argv[i], "--nohttp")) {
- ctx.env.no_http = "1";
- } else if (skip_prefix(argv[i], "--query=", &arg)) {
- ctx.qry.raw = xstrdup(arg);
- } else if (skip_prefix(argv[i], "--repo=", &arg)) {
- ctx.qry.repo = xstrdup(arg);
- } else if (skip_prefix(argv[i], "--page=", &arg)) {
- ctx.qry.page = xstrdup(arg);
- } else if (skip_prefix(argv[i], "--head=", &arg)) {
- ctx.qry.head = xstrdup(arg);
- } else if (skip_prefix(argv[i], "--oid=", &arg)) {
- ctx.qry.oid = xstrdup(arg);
- ctx.qry.has_oid = 1;
- } else if (skip_prefix(argv[i], "--ofs=", &arg)) {
- ctx.qry.ofs = atoi(arg);
- } else if (skip_prefix(argv[i], "--scan-tree=", &arg) ||
- skip_prefix(argv[i], "--scan-path=", &arg)) {
- /*
- * HACK: The global snapshot bit mask defines the set
- * of allowed snapshot formats, but the config file
- * hasn't been parsed yet so the mask is currently 0.
- * By setting all bits high before scanning we make
- * sure that any in-repo cgitrc snapshot setting is
- * respected by scan_tree().
- *
- * NOTE: We assume that there aren't more than 8
- * different snapshot formats supported by cgit...
- */
- ctx.cfg.snapshots = 0xFF;
- scan++;
- scan_tree(arg);
- }
- }
- if (scan) {
- qsort(cgit_repolist.repos, cgit_repolist.count,
- sizeof(struct cgit_repo), cmp_repos);
- print_repolist(stdout, &cgit_repolist, 0);
- exit(0);
- }
-}
-
static NORETURN void cgit_die_routine(const char *msg, va_list params)
{
cgit_vprint_error_page(400, "Bad request", msg, params);
exit(0);
}
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
const char *path;
@@ -795,18 +475,10 @@ int cmd_main(int argc, const char **argv)
cgit_repolist.count = 0;
cgit_repolist.repos = NULL;
- cgit_parse_args(argc, argv);
- parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
+ scan_tree(config_value("CGIT_SCAN_PATH"));
ctx.repo = NULL;
http_parse_querystring(ctx.qry.raw, querystring_cb);
- /* If virtual-root isn't specified in cgitrc, lets pretend
- * that virtual-root equals SCRIPT_NAME, minus any possibly
- * trailing slashes.
- */
- if (!ctx.cfg.virtual_root && ctx.cfg.script_name)
- ctx.cfg.virtual_root = ensure_end(ctx.cfg.script_name, '/');
-
/* If no url parameter is specified on the querystring, use PATH_INFO
* as url. This allows cgit to work with virtual urls without the need
* for rewriterules in the webserver.
diff --git a/cgit/cgit.h b/cgit/cgit.h
index bed3f11..1102c87 100644
--- a/cgit/cgit.h
+++ b/cgit/cgit.h
@@ -59,7 +59,6 @@
#define BIT(x) (1U << (x))
-typedef void (*configfn)(const char *name, const char *value);
typedef void (*filepair_fn)(struct diff_filepair *pair);
typedef void (*linediff_fn)(char *line, int len);
@@ -191,7 +190,6 @@ struct cgit_config {
char *logo_link;
char *mimetype_file;
char *module_link;
- char *project_list;
struct string_list readme;
struct string_list css;
char *robots;
@@ -217,12 +215,10 @@ struct cgit_config {
int enable_subject_links;
int enable_html_serving;
int enable_tree_linenumbers;
- int enable_git_config;
int local_time;
int max_atom_items;
int max_repo_count;
int max_commit_count;
- int max_lock_attempts;
int max_msg_len;
int max_repodesc_len;
int max_blob_size;
@@ -258,14 +254,11 @@ struct cgit_page {
};
struct cgit_environment {
- const char *cgit_config;
const char *http_host;
const char *https;
- const char *no_http;
const char *path_info;
const char *query_string;
const char *request_method;
- const char *script_name;
const char *server_name;
const char *server_port;
};
@@ -295,8 +288,6 @@ extern const struct cgit_snapshot_format cgit_snapshot_formats[];
extern char *cgit_default_repo_desc;
extern struct cgit_repo *cgit_add_repo(const char *url);
extern struct cgit_repo *cgit_get_repoinfo(const char *url);
-extern void cgit_repo_config(struct cgit_repo *repo, const char *name,
- const char *value);
extern int chk_zero(int result, char *msg);
extern int chk_positive(int result, char *msg);
diff --git a/cgit/configfile.c b/cgit/configfile.c
deleted file mode 100644
--- a/cgit/configfile.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/* SPDX-FileCopyrightText: cgit Development Team <cgit@lists.zx2c4.com>
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-/* configfile.c: parsing of config files
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- * (see COPYING for full license text)
- */
-
-#include <git-compat-util.h>
-#include "configfile.h"
-
-static int next_char(FILE *f)
-{
- int c = fgetc(f);
- if (c == '\r') {
- c = fgetc(f);
- if (c != '\n') {
- ungetc(c, f);
- c = '\r';
- }
- }
- return c;
-}
-
-static void skip_line(FILE *f)
-{
- int c;
-
- while ((c = next_char(f)) && c != '\n' && c != EOF)
- ;
-}
-
-static int read_config_line(FILE *f, struct strbuf *name, struct strbuf *value)
-{
- int c = next_char(f);
-
- strbuf_reset(name);
- strbuf_reset(value);
-
- /* Skip comments and preceding spaces. */
- for(;;) {
- if (c == EOF)
- return 0;
- else if (c == '#' || c == ';')
- skip_line(f);
- else if (!isspace(c))
- break;
- c = next_char(f);
- }
-
- /* Read variable name. */
- while (c != '=') {
- if (c == '\n' || c == EOF)
- return 0;
- strbuf_addch(name, c);
- c = next_char(f);
- }
-
- /* Read variable value. */
- c = next_char(f);
- while (c != '\n' && c != EOF) {
- strbuf_addch(value, c);
- c = next_char(f);
- }
-
- return 1;
-}
-
-int parse_configfile(const char *filename, configfile_value_fn fn)
-{
- static int nesting;
- struct strbuf name = STRBUF_INIT;
- struct strbuf value = STRBUF_INIT;
- FILE *f;
-
- /* cancel deeply nested include-commands */
- if (nesting > 8)
- return -1;
- if (!(f = fopen(filename, "r")))
- return -1;
- nesting++;
- while (read_config_line(f, &name, &value))
- fn(name.buf, value.buf);
- nesting--;
- fclose(f);
- strbuf_release(&name);
- strbuf_release(&value);
- return 0;
-}
-
diff --git a/cgit/configfile.h b/cgit/configfile.h
deleted file mode 100644
--- a/cgit/configfile.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-FileCopyrightText: cgit Development Team <cgit@lists.zx2c4.com>
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#ifndef CONFIGFILE_H
-#define CONFIGFILE_H
-
-#include "cgit.h"
-
-typedef void (*configfile_value_fn)(const char *name, const char *value);
-
-extern int parse_configfile(const char *filename, configfile_value_fn fn);
-
-#endif /* CONFIGFILE_H */
diff --git a/cgit/scan-tree.c b/cgit/scan-tree.c
index 2cfef01..bc29daa 100644
--- a/cgit/scan-tree.c
+++ b/cgit/scan-tree.c
@@ -12,9 +12,7 @@
#include "cgit.h"
#include "scan-tree.h"
-#include "configfile.h"
#include "html.h"
-#include <config.h>
/* return 1 if path contains a objects/ directory and a HEAD file */
static int is_git_dir(const char *path)
@@ -50,32 +48,6 @@ out:
return result;
}
-static struct cgit_repo *repo;
-
-static void scan_tree_repo_config(const char *name, const char *value)
-{
- cgit_repo_config(repo, name, value);
-}
-
-static int gitconfig_config(const char *key, const char *value,
- const __attribute__((unused)) struct config_context *ctx, void *cb)
-{
- const char *name;
-
- if (!strcmp(key, "gitweb.owner"))
- cgit_repo_config(repo, "owner", value);
- else if (!strcmp(key, "gitweb.description"))
- cgit_repo_config(repo, "desc", value);
- else if (!strcmp(key, "gitweb.category"))
- cgit_repo_config(repo, "section", value);
- else if (!strcmp(key, "gitweb.homepage"))
- cgit_repo_config(repo, "homepage", value);
- else if (skip_prefix(key, "cgit.", &name))
- cgit_repo_config(repo, name, value);
-
- return 0;
-}
-
static char *xstrrchr(char *s, char *from, int c)
{
while (from >= s && *from != c)
@@ -87,6 +59,7 @@ static void add_repo(const char *base, struct strbuf *path)
{
struct stat st;
struct passwd *pwd;
+ struct cgit_repo *repo;
size_t pathlen;
struct strbuf rel = STRBUF_INIT;
char *p, *slash;
@@ -125,11 +98,6 @@ static void add_repo(const char *base, struct strbuf *path)
strbuf_setlen(&rel, rel.len - 1);
repo = cgit_add_repo(rel.buf);
- if (ctx.cfg.enable_git_config) {
- strbuf_addstr(path, "config");
- git_config_from_file(gitconfig_config, path->buf, NULL);
- strbuf_setlen(path, pathlen);
- }
if (ctx.cfg.remove_suffix) {
size_t urllen;
@@ -180,10 +148,6 @@ static void add_repo(const char *base, struct strbuf *path)
}
}
- strbuf_addstr(path, "cgitrc");
- if (!stat(path->buf, &st))
- parse_configfile(path->buf, &scan_tree_repo_config);
-
strbuf_release(&rel);
}
@@ -240,33 +204,6 @@ end:
closedir(dir);
}
-void scan_projects(const char *path, const char *projectsfile)
-{
- struct strbuf line = STRBUF_INIT;
- FILE *projects;
- int err;
-
- projects = fopen(projectsfile, "r");
- if (!projects) {
- fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
- projectsfile, strerror(errno), errno);
- return;
- }
- while (strbuf_getline(&line, projects) != EOF) {
- if (!line.len)
- continue;
- strbuf_insert(&line, 0, "/", 1);
- strbuf_insert(&line, 0, path, strlen(path));
- scan_path(path, line.buf);
- }
- if ((err = ferror(projects))) {
- fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
- projectsfile, strerror(err), err);
- }
- fclose(projects);
- strbuf_release(&line);
-}
-
void scan_tree(const char *path)
{
scan_path(path, path);
diff --git a/cgit/scan-tree.h b/cgit/scan-tree.h
index 539b4a8..626be7e 100644
--- a/cgit/scan-tree.h
+++ b/cgit/scan-tree.h
@@ -2,5 +2,4 @@
* SPDX-License-Identifier: GPL-2.0-only
*/
-extern void scan_projects(const char *path, const char *projectsfile);
extern void scan_tree(const char *path);
diff --git a/cgit/shared.c b/cgit/shared.c
index e786d17..fa6b10f 100644
--- a/cgit/shared.c
+++ b/cgit/shared.c
@@ -38,7 +38,7 @@ int chk_non_negative(int result, char *msg)
return result;
}
-char *cgit_default_repo_desc = "[no description]";
+char *cgit_default_repo_desc;
struct cgit_repo *cgit_add_repo(const char *url)
{
struct cgit_repo *ret;
diff --git a/cgit/ui-shared.c b/cgit/ui-shared.c
index d5e9a5f..96a8c3c 100644
--- a/cgit/ui-shared.c
+++ b/cgit/ui-shared.c
@@ -722,9 +722,6 @@ void cgit_print_age(time_t t, int tz, time_t max_relative)
void cgit_print_http_headers(void)
{
- if (ctx.env.no_http && !strcmp(ctx.env.no_http, "1"))
- return;
-
if (ctx.page.status)
htmlf("Status: %d %s\n", ctx.page.status, ctx.page.statusmsg);
if (ctx.page.mimetype && ctx.page.charset)
diff --git a/charts/gilti/templates/configmap.yaml b/charts/gilti/templates/configmap.yaml
index 1376076..7cb6f72 100644
--- a/charts/gilti/templates/configmap.yaml
+++ b/charts/gilti/templates/configmap.yaml
@@ -18,30 +18,6 @@ metadata:
labels:
{{- include "gilti.labels" . | nindent 4 }}
data:
- cgitrc: |
- root-title={{ .Values.cgit.rootTitle }}
- root-desc={{ .Values.cgit.rootDescription }}
- virtual-root=/
- css=/cgit.css
- logo=/cgit.png
- favicon=/favicon.ico
- # Gilti consumes this setting before invoking cgit.
- cache={{ .Values.cgit.cache }}
- enable-http-clone=0
- enable-index-owner=1
- enable-index-links=1
- enable-commit-graph=1
- enable-log-filecount=1
- enable-log-linecount=1
- enable-tree-linenumbers=1
- snapshots=
- remove-suffix=1
- readme=:README.md
- readme=:README
- {{- with .Values.cgit.clonePrefix }}
- clone-prefix={{ . }}
- {{- end }}
- scan-path=/var/lib/gilti/git/repositories
authorized_keys: |
{{- range .Values.ssh.authorizedKeys }}
{{ . }}
diff --git a/charts/gilti/templates/deployment.yaml b/charts/gilti/templates/deployment.yaml
index e71fb6a..12a39ce 100644
--- a/charts/gilti/templates/deployment.yaml
+++ b/charts/gilti/templates/deployment.yaml
@@ -47,6 +47,15 @@ spec:
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
+ env:
+ - name: GILTI_CGIT_ROOT_TITLE
+ value: {{ .Values.cgit.rootTitle | quote }}
+ - name: GILTI_CGIT_ROOT_DESCRIPTION
+ value: {{ .Values.cgit.rootDescription | quote }}
+ - name: GILTI_CGIT_CLONE_PREFIX
+ value: {{ .Values.cgit.clonePrefix | quote }}
+ - name: GILTI_CGIT_CACHE
+ value: {{ .Values.cgit.cache | quote }}
ports:
- name: http
containerPort: 8080
@@ -76,10 +85,6 @@ spec:
- name: state
mountPath: /var/lib/gilti
- name: config
- mountPath: /etc/cgitrc
- subPath: cgitrc
- readOnly: true
- - name: config
mountPath: /etc/gilti/authorized_keys
subPath: authorized_keys
readOnly: true
diff --git a/config/cgitrc b/config/cgitrc
deleted file mode 100644
--- a/config/cgitrc
+++ /dev/null
@@ -1,27 +0,0 @@
-# SPDX-FileCopyrightText: 2026 Nikolay Govorov
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-root-title=Gilti
-root-desc=A tiny Git server
-virtual-root=/
-
-css=/cgit.css
-logo=/cgit.png
-favicon=/favicon.ico
-
-# In-memory CGI response lifetime in seconds; zero disables the cache.
-cache=5
-
-enable-http-clone=0
-enable-index-owner=1
-enable-index-links=1
-enable-commit-graph=1
-enable-log-filecount=1
-enable-log-linecount=1
-enable-tree-linenumbers=1
-snapshots=
-
-remove-suffix=1
-readme=:README.md
-readme=:README
-scan-path=/var/lib/gilti/git/repositories
diff --git a/crates/gilti/src/main.rs b/crates/gilti/src/main.rs
index 1e323b5..67a3de9 100644
--- a/crates/gilti/src/main.rs
+++ b/crates/gilti/src/main.rs
@@ -5,108 +5,157 @@ mod cgi;
mod ui;
const DEFAULT_LISTEN_ADDR: &str = "0.0.0.0:8080";
+const DEFAULT_CACHE_SECONDS: &str = "5";
+const DEFAULT_ROOT_TITLE: &str = "Gilti";
+const DEFAULT_ROOT_DESCRIPTION: &str = "A tiny Git server";
const MAX_CACHE_SECONDS: u64 = 3600;
const CGIT: &str = "/usr/local/bin/gilti-cgit";
-const CGIT_CONFIG: &str = "/etc/cgitrc";
const GIT_HOME: &str = "/var/lib/gilti/git";
-const RUN_DIR: &str = "/run/gilti/http";
const CGIT_CSS: &str = "/usr/share/webapps/cgit/cgit.css";
const CGIT_JS: &str = "/usr/share/webapps/cgit/cgit.js";
const CGIT_LOGO: &str = "/usr/share/webapps/cgit/cgit.png";
const CGIT_FAVICON: &str = "/usr/share/webapps/cgit/favicon.ico";
+const CGIT_ENVIRONMENT: &[(&str, &str)] = &[
+ ("CGIT_AGEFILE", "info/web/last-modified"),
+ ("CGIT_BRANCH_SORT", "0"),
+ ("CGIT_CASE_SENSITIVE_SORT", "1"),
+ ("CGIT_CLONE_URL", ""),
+ ("CGIT_COMMIT_SORT", "0"),
+ ("CGIT_CSS", "/cgit.css"),
+ ("CGIT_DIFFTYPE", "0"),
+ ("CGIT_EMBEDDED", "0"),
+ ("CGIT_ENABLE_BLAME", "0"),
+ ("CGIT_ENABLE_COMMIT_GRAPH", "1"),
+ ("CGIT_ENABLE_FOLLOW_LINKS", "0"),
+ ("CGIT_ENABLE_HTML_SERVING", "0"),
+ ("CGIT_ENABLE_HTTP_CLONE", "0"),
+ ("CGIT_ENABLE_INDEX_LINKS", "1"),
+ ("CGIT_ENABLE_INDEX_OWNER", "1"),
+ ("CGIT_ENABLE_LOG_FILECOUNT", "1"),
+ ("CGIT_ENABLE_LOG_LINECOUNT", "1"),
+ ("CGIT_ENABLE_REMOTE_BRANCHES", "0"),
+ ("CGIT_ENABLE_SUBJECT_LINKS", "0"),
+ ("CGIT_ENABLE_TREE_LINENUMBERS", "1"),
+ ("CGIT_FAVICON", "/favicon.ico"),
+ ("CGIT_FOOTER", ""),
+ ("CGIT_HEADER", ""),
+ ("CGIT_HEAD_INCLUDE", ""),
+ ("CGIT_JS", ""),
+ ("CGIT_LOCAL_TIME", "0"),
+ ("CGIT_LOGO", "/cgit.png"),
+ ("CGIT_LOGO_LINK", ""),
+ ("CGIT_MAX_ATOM_ITEMS", "10"),
+ ("CGIT_MAX_BLOB_SIZE", "0"),
+ ("CGIT_MAX_COMMIT_COUNT", "50"),
+ ("CGIT_MAX_MESSAGE_LENGTH", "80"),
+ ("CGIT_MAX_REPO_COUNT", "50"),
+ ("CGIT_MAX_REPODESC_LENGTH", "80"),
+ ("CGIT_MAX_STATS", "0"),
+ ("CGIT_MIMETYPE_FILE", ""),
+ ("CGIT_MODULE_LINK", ""),
+ ("CGIT_NOHEADER", "0"),
+ ("CGIT_NOPLAINEMAIL", "0"),
+ ("CGIT_README_0", ":README.md"),
+ ("CGIT_README_1", ":README"),
+ ("CGIT_REMOVE_SUFFIX", "1"),
+ ("CGIT_REPO_DEFAULT_DESC", "[no description]"),
+ ("CGIT_RENAMELIMIT", "-1"),
+ ("CGIT_REPOSITORY_SORT", "name"),
+ ("CGIT_ROBOTS", "index, nofollow"),
+ ("CGIT_ROOT_README", ""),
+ ("CGIT_SCAN_HIDDEN_PATH", "0"),
+ ("CGIT_SCAN_PATH", "/var/lib/gilti/git/repositories"),
+ ("CGIT_SECTION", ""),
+ ("CGIT_SECTION_FROM_PATH", "0"),
+ ("CGIT_SECTION_SORT", "1"),
+ ("CGIT_SNAPSHOTS", "0"),
+ ("CGIT_STRICT_EXPORT", ""),
+ ("CGIT_SUMMARY_BRANCHES", "10"),
+ ("CGIT_SUMMARY_LOG", "10"),
+ ("CGIT_SUMMARY_TAGS", "10"),
+ ("CGIT_VIRTUAL_ROOT", "/"),
+ ("GIT_ATTR_NOSYSTEM", "1"),
+ ("GIT_CONFIG_NOSYSTEM", "1"),
+];
+
#[derive(Clone)]
struct AppState {
cgit: cgi::Cgi,
}
-struct CgitConfig {
- path: std::path::PathBuf,
+struct Config {
+ listen_addr: std::net::SocketAddr,
cache: std::time::Duration,
+ root_title: String,
+ root_description: String,
+ clone_prefix: String,
}
-impl CgitConfig {
- fn create() -> std::io::Result<Self> {
- let contents = std::fs::read_to_string(CGIT_CONFIG)?;
- let (contents, cache) = prepare_cgit_config(&contents)?;
- let path = std::path::PathBuf::from(format!("{RUN_DIR}/cgitrc.{}", std::process::id()));
- let mut options = std::fs::OpenOptions::new();
- options.write(true).create_new(true);
- std::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600);
- let mut file = options.open(&path)?;
- let config = Self { path, cache };
- std::io::Write::write_all(&mut file, contents.as_bytes())?;
- Ok(config)
+impl Config {
+ fn from_environment() -> std::io::Result<Self> {
+ let listen_addr = environment("GILTI_HTTP_ADDR", DEFAULT_LISTEN_ADDR)?
+ .parse()
+ .map_err(|_| invalid_config("GILTI_HTTP_ADDR must be a socket address"))?;
+ let cache = parse_cache(&environment("GILTI_CGIT_CACHE", DEFAULT_CACHE_SECONDS)?)?;
+ Ok(Self {
+ listen_addr,
+ cache,
+ root_title: environment("GILTI_CGIT_ROOT_TITLE", DEFAULT_ROOT_TITLE)?,
+ root_description: environment("GILTI_CGIT_ROOT_DESCRIPTION", DEFAULT_ROOT_DESCRIPTION)?,
+ clone_prefix: environment("GILTI_CGIT_CLONE_PREFIX", "")?,
+ })
}
}
-fn prepare_cgit_config(contents: &str) -> std::io::Result<(String, std::time::Duration)> {
- let mut output = String::with_capacity(contents.len());
- let mut cache = None;
-
- for line in contents.split_inclusive('\n') {
- let value = line
- .trim_end_matches(['\r', '\n'])
- .trim_start()
- .strip_prefix("cache=");
- let Some(value) = value else {
- output.push_str(line);
- continue;
- };
- if cache.is_some() {
- return Err(invalid_config("cache is configured more than once"));
+fn environment(name: &str, default: &str) -> std::io::Result<String> {
+ match std::env::var(name) {
+ Ok(value) => Ok(value),
+ Err(std::env::VarError::NotPresent) => Ok(default.to_owned()),
+ Err(std::env::VarError::NotUnicode(_)) => {
+ Err(invalid_config(format!("{name} must be valid UTF-8")))
}
- let seconds = value
- .trim()
- .parse::<u64>()
- .map_err(|_| invalid_config("cache must be an integer number of seconds"))?;
- if seconds > MAX_CACHE_SECONDS {
- return Err(invalid_config(format!(
- "cache must not exceed {MAX_CACHE_SECONDS} seconds"
- )));
- }
- cache = Some(std::time::Duration::from_secs(seconds));
}
+}
- Ok((output, cache.unwrap_or_default()))
+fn parse_cache(value: &str) -> std::io::Result<std::time::Duration> {
+ let seconds = value
+ .parse::<u64>()
+ .map_err(|_| invalid_config("GILTI_CGIT_CACHE must be an integer number of seconds"))?;
+ if seconds > MAX_CACHE_SECONDS {
+ return Err(invalid_config(format!(
+ "GILTI_CGIT_CACHE must not exceed {MAX_CACHE_SECONDS} seconds"
+ )));
+ }
+ Ok(std::time::Duration::from_secs(seconds))
}
fn invalid_config(message: impl Into<String>) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::InvalidData, message.into())
}
-impl Drop for CgitConfig {
- fn drop(&mut self) {
- match std::fs::remove_file(&self.path) {
- Err(error) if error.kind() != std::io::ErrorKind::NotFound => {
- eprintln!("gilti: cannot remove {}: {error}", self.path.display());
- }
- _ => {}
- }
- }
-}
-
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
- let listen_addr = std::env::var("GILTI_HTTP_ADDR")
- .unwrap_or_else(|_| DEFAULT_LISTEN_ADDR.to_owned())
- .parse::<std::net::SocketAddr>()?;
+ let config = Config::from_environment()?;
+ let listen_addr = config.listen_addr;
check_files()?;
- let cgit_config = CgitConfig::create()?;
if std::env::args().nth(1).as_deref() == Some("--check") {
return Ok(());
}
- let state = AppState {
- cgit: cgi::Cgi::new(CGIT, GIT_HOME, listen_addr)
- .cache(cgit_config.cache)
- .env("CGIT_CONFIG", cgit_config.path.as_os_str())
- .env("HOME", GIT_HOME)
- .env("PATH", "/usr/bin:/bin"),
- };
+ let mut cgit = cgi::Cgi::new(CGIT, GIT_HOME, config.listen_addr)
+ .cache(config.cache)
+ .env("CGIT_ROOT_TITLE", config.root_title)
+ .env("CGIT_ROOT_DESC", config.root_description)
+ .env("CGIT_CLONE_PREFIX", config.clone_prefix)
+ .env("PATH", "/usr/bin:/bin");
+ for (name, value) in CGIT_ENVIRONMENT {
+ cgit = cgit.env(*name, *value);
+ }
+ let state = AppState { cgit };
let app = axum::Router::new()
.route(
"/healthz",
@@ -151,7 +200,7 @@ fn check_files() -> std::io::Result<()> {
{
return Err(std::io::Error::other(format!("{CGIT} is not executable")));
}
- for path in [CGIT_CONFIG, CGIT_CSS, CGIT_JS, CGIT_LOGO, CGIT_FAVICON] {
+ for path in [CGIT_CSS, CGIT_JS, CGIT_LOGO, CGIT_FAVICON] {
if !std::fs::metadata(path)?.is_file() {
return Err(std::io::Error::other(format!(
"{path} is not a regular file"
@@ -264,20 +313,14 @@ fn plain_response(
#[cfg(test)]
mod tests {
#[test]
- fn extracts_cache_from_cgit_config() {
- let (contents, cache) =
- super::prepare_cgit_config("# comment\r\ncache=5\r\nroot-title=Gilti\r\n").unwrap();
- assert_eq!(contents, "# comment\r\nroot-title=Gilti\r\n");
- assert_eq!(cache, std::time::Duration::from_secs(5));
- }
-
- #[test]
- fn cache_is_optional_and_bounded() {
- let (_, cache) = super::prepare_cgit_config("root-title=Gilti\n").unwrap();
- assert!(cache.is_zero());
- assert!(super::prepare_cgit_config("cache=1\ncache=2\n").is_err());
- assert!(super::prepare_cgit_config("cache=3601\n").is_err());
- assert!(super::prepare_cgit_config("cache=forever\n").is_err());
+ fn cache_is_bounded() {
+ assert_eq!(
+ super::parse_cache("5").unwrap(),
+ std::time::Duration::from_secs(5)
+ );
+ assert!(super::parse_cache("0").unwrap().is_zero());
+ assert!(super::parse_cache("3601").is_err());
+ assert!(super::parse_cache("forever").is_err());
}
#[tokio::test]
diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh
index 3eaa8f9..6da7570 100755
--- a/scripts/entrypoint.sh
+++ b/scripts/entrypoint.sh
@@ -8,7 +8,6 @@ umask 077
state=/var/lib/gilti
run_dir=/run/gilti
-http_run_dir=$run_dir/http
ssh_run_dir=$run_dir/ssh
git_home=$state/git
@@ -30,10 +29,8 @@ prepare_runtime() {
install -d -m 0750 -o git -g git "$git_home" "$repositories"
install -d -m 0700 -o root -g root "$host_key_dir"
install -d -m 0755 -o root -g root "$run_dir"
- install -d -m 0750 -o git -g git "$http_run_dir"
install -d -m 0750 -o root -g git "$ssh_run_dir"
- rm -f "$ssh_run_dir/sshd.pid" "$http_run_dir"/cgitrc.* \
- "$authorized_keys" "$authorized_keys".*
+ rm -f "$ssh_run_dir/sshd.pid" "$authorized_keys" "$authorized_keys".*
}
prepare_authorized_keys() {
diff --git a/tests/chart.sh b/tests/chart.sh
index f0471d4..b5df06b 100755
--- a/tests/chart.sh
+++ b/tests/chart.sh
@@ -28,7 +28,12 @@ grep -q '^apiVersion: gateway.networking.k8s.io/v1$' "$rendered"
grep -q 'helm.sh/resource-policy: keep' "$rendered"
grep -q 'ssh-ed25519 AAAAcharttest gilti' "$rendered"
grep -q 'mountPath: /etc/gilti/authorized_keys' "$rendered"
-grep -q '^ cache=5$' "$rendered"
+grep -q 'name: GILTI_CGIT_CACHE' "$rendered"
+grep -q 'value: "5"' "$rendered"
+if grep -q 'cgitrc' "$rendered"; then
+ echo 'chart still provisions a cgit configuration file' >&2
+ exit 1
+fi
if grep -q '/var/cache/cgit' "$rendered"; then
echo 'chart still provisions the removed cgit disk cache' >&2
exit 1
diff --git a/tests/smoke.sh b/tests/smoke.sh
index f9ed0c7..0ed91da 100755
--- a/tests/smoke.sh
+++ b/tests/smoke.sh
@@ -86,8 +86,8 @@ if "$engine" exec "$name" test -e /var/cache/cgit; then
echo 'legacy cgit disk-cache directory exists' >&2
exit 1
fi
-if "$engine" exec "$name" sh -c 'grep -q "^cache=" /run/gilti/http/cgitrc.*'; then
- echo 'Gilti cache parameter leaked into the cgit configuration' >&2
+if "$engine" exec "$name" test -e /etc/cgitrc; then
+ echo 'legacy cgit configuration file is installed' >&2
exit 1
fi
sshd_config=$("$engine" exec "$name" /usr/sbin/sshd -T -f /etc/ssh/sshd_config \