diff options
| author | Nikolay Govorov <me@govorov.online> | 2026-08-27 22:10:27 +0100 |
|---|---|---|
| committer | Nikolay Govorov <me@govorov.online> | 2026-08-27 22:10:27 +0100 |
| commit | 579a4432e386a483f55a1ee0eaa8d7d0691f055b (patch) | |
| tree | 44173bb59dddd93976029498c0fe25e02635a267 | |
| parent | b4b8201aa94d94087e1519c8da60f52afd724047 (diff) | |
| download | tar tar.gz tar.bz2 tar.lz tar.xz tar.zst zip | |
Migrate configuration from cgit to gilti
Diffstat
| -rw-r--r-- | Dockerfile | 2 | +0 −2 |
| -rw-r--r-- | README.md | 12 | +10 −2 |
| -rw-r--r-- | cgit/Makefile | 4 | +0 −4 |
| -rw-r--r-- | cgit/cgit.c | 540 | +106 −434 |
| -rw-r--r-- | cgit/cgit.h | 9 | +0 −9 |
| -rw-r--r-- | cgit/configfile.c | 94 | +0 −94 |
| -rw-r--r-- | cgit/configfile.h | 14 | +0 −14 |
| -rw-r--r-- | cgit/scan-tree.c | 65 | +1 −64 |
| -rw-r--r-- | cgit/scan-tree.h | 1 | +0 −1 |
| -rw-r--r-- | cgit/shared.c | 2 | +1 −1 |
| -rw-r--r-- | cgit/ui-shared.c | 3 | +0 −3 |
| -rw-r--r-- | charts/gilti/templates/configmap.yaml | 24 | +0 −24 |
| -rw-r--r-- | charts/gilti/templates/deployment.yaml | 13 | +9 −4 |
| -rw-r--r-- | config/cgitrc | 27 | +0 −27 |
| -rw-r--r-- | crates/gilti/src/main.rs | 201 | +122 −79 |
| -rwxr-xr-x | scripts/entrypoint.sh | 5 | +1 −4 |
| -rwxr-xr-x | tests/chart.sh | 7 | +6 −1 |
| -rwxr-xr-x | tests/smoke.sh | 4 | +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 \ |
