56 Commits

Author SHA1 Message Date
woodpecker-bot
3830af5392 [Renovate] Update all dependencies 2026-03-07 03:32:16 +00:00
miyuko
9e9664013b Record the authorized forge user's name in the audit log. 2026-03-03 03:21:40 +00:00
miyuko
3e377986bc Accept forge authorization for deleting a site. 2026-03-03 01:29:27 +00:00
miyuko
c85c7327bf Reword the code comment regarding the webhook delivery timer. 2026-03-03 01:29:03 +00:00
woodpecker-bot
886ee2ddae [Renovate] Update all dependencies 2026-02-28 00:29:38 +00:00
woodpecker-bot
ac751e23b5 [Renovate] Update module golang.org/x/net to v0.51.0 [SECURITY] 2026-02-28 00:29:01 +00:00
woodpecker-bot
ebe7d07b3b [Renovate] Update all dependencies 2026-02-21 23:56:24 +00:00
woodpecker-bot
4f14c345a6 [Renovate] Update all dependencies 2026-02-14 00:15:28 +00:00
miyuko
7e293d6ef9 Normalize archive member names. 2026-02-10 15:34:13 +00:00
woodpecker-bot
f7067b939b [Renovate] Update module github.com/go-git/go-billy/v6 to v6.0.0-20260207062542-7cf3dc9049c3 2026-02-08 00:35:21 +00:00
woodpecker-bot
6bf4200f26 [Renovate] Update all dependencies 2026-02-07 00:15:58 +00:00
Catherine
e9a5a901ec Improve panic messages in ApplyTarPatch. 2026-02-03 09:51:22 +00:00
woodpecker-bot
d3c8db6229 [Renovate] Update all dependencies 2026-01-24 00:18:54 +00:00
Catherine
8f811147d6 Enable Sentry telemetry buffer by default.
No observed issues on Grebedoc for a month, so it should be stable now.
2026-01-19 02:41:15 +00:00
Catherine
0d33c64372 [breaking-change] Only allow a single [[wildcard]].index-repo.
The git-pages webhook security model depends on there being
a 1:1 mapping between site URLs and repositories; being able to
specify multiple of them breaks this model, as anyone could switch
the published site from one to the other if both repositories exist.
2026-01-19 02:25:01 +00:00
Catherine
9b25ccdc35 CI: update to Go 1.25.6.
To address CVE-2025-61728.
2026-01-17 00:29:38 +00:00
woodpecker-bot
18012d46e8 [Renovate] Update all dependencies 2026-01-17 00:22:21 +00:00
woodpecker-bot
750f76aa9d [Renovate] Update https://code.forgejo.org/actions/checkout action to v6.0.2 2026-01-11 00:27:31 +00:00
woodpecker-bot
6019a64c41 [Renovate] Update all dependencies 2026-01-10 00:20:46 +00:00
woodpecker-bot
890029a98d [Renovate] Update all dependencies 2026-01-03 00:11:55 +00:00
bin
cf26a89026 nix: use gomod2nix. 2025-12-29 04:37:41 -05:00
woodpecker-bot
b75c37f651 [Renovate] Update module github.com/go-git/go-git/v6 to v6.0.0-20251224103503-78aff6aa5ea9 2025-12-28 23:28:36 +00:00
Catherine
c84e773df1 Update module codeberg.org/git-pages/go-headers to v1.1.1. 2025-12-28 23:22:46 +00:00
Catherine
07133df6d2 Fix the Docker build script. 2025-12-24 14:44:23 +00:00
Catherine
1f1927d95d Log Accept: value for HEAD/GET requests.
Instead of `Content-Type:` which is essentially never relevant.
2025-12-24 14:28:16 +00:00
David Leadbeater
7334b8f637 Add a Vary header when content negotiation happens
Without this, if a cache first sees a compressed version of the request,
it will return that for potentially any future requests, even if they
don't request compression.
2025-12-24 14:36:23 +11:00
Catherine
96f210d253 Clear git metadata from PATCH'd manifests. 2025-12-24 02:18:09 +00:00
Catherine
a4bfa82388 Configure Renovate commit messages. 2025-12-24 00:19:20 +00:00
Catherine
338957eb3f Switch CI to self-hosted runners. 2025-12-23 17:37:16 +00:00
Catherine
26d9d784ba Configure Renovate commit messages. 2025-12-23 12:52:33 +00:00
woodpecker-bot
f163b9a42a chore(deps): update module github.com/maypok86/otter/v2 to v2.3.0 2025-12-23 12:48:37 +00:00
David Leadbeater
04729c1f48 Ensure leading directories always exist in manifest
When extracting from an archive it is possible the leading directories
are not part of the archive. Add them to the manifest as otherwise the
behaviour of "index.html" varies depending how the archive was created.
2025-12-23 13:40:05 +01:00
miyuko
121f557048 Fix go vet and staticcheck not performing any work in CI. 2025-12-22 16:10:55 +00:00
miyuko
c5df116673 Scrub the Forge-Authorization header from Sentry events. 2025-12-22 14:35:02 +00:00
woodpecker-bot
71fd1c39df chore(deps): update all dependencies 2025-12-22 00:47:56 +00:00
Catherine
d97f5ac056 Fix manifest StoredSize field being always zero. 2025-12-16 20:05:35 +00:00
Catherine
79407ba406 Fix timeout bug introduced in commit 9c6f735d.
This bug would cause POST hooks triggered for large repositories to
silently fail.

We need the update context to have the principal (which is tied to
the HTTP request), but not the cancellation (which is also tied to
the HTTP request and is triggered once the request is done either way).
2025-12-16 14:43:36 +00:00
David Leadbeater
937aadc5d3 Allow setting custom Cache-Control headers via _headers
Before this change Cache-Control header would always be overridden, this
change allows custom Cache-Control, provided Cache-Control is added to
the header allow list.
2025-12-15 21:02:25 +11:00
Catherine
24dbab6813 Begin paths with / in problem report.
Otherwise you get reports like:

    (archive)
    : directory shadows redirect "/ /foo 301"; remove the directory or use a 301! forced redirect instead
2025-12-14 19:47:28 +00:00
Catherine
30b6db2758 Limit amount of data fetched from git repository.
Like limiting the size of an archive, it is a supplementary check meant
to limit resource consumption prior to the final check done in
`StoreManifest()`.
2025-12-14 19:42:25 +00:00
Catherine
7655400560 Limit original size of the contents of a site manifest.
The limit is applied to the original size and not compressed size for
predictability and fairness.
2025-12-14 19:30:45 +00:00
woodpecker-bot
32ccb0920f chore(deps): update all dependencies 2025-12-13 05:26:59 +00:00
Catherine
c88d04c71b Add a relaxed-idna feature to allow some uses of _ in hostnames.
This is added to aid migration from Codeberg Pages v2. Forgejo allows
both `_` and `-` in usernames, and it is necessary to be able to accept
host names like `user_name.codeberg.page` under a wildcard domain.
(It is not possible to get a TLS certificate for a host name like this,
so only a wildcard certificate will be able to cover it.)
2025-12-12 02:27:22 +00:00
David Leadbeater
86845f2505 Check for overflow when calculating size of zip 2025-12-12 01:24:24 +00:00
Catherine
7f112a761c Simplify signal handling code.
This does not require `//go:build`.
2025-12-11 10:09:50 +00:00
David Leadbeater
a9cf69c04a Ensure the branch parameter really is a branch
Currently you can specify "Branch: HEAD" or "Branch: refs/tags/v1" and
go-git will resolve it to the relevant ref. Given the HTTP header is
called Branch this is confusing.
2025-12-11 17:18:19 +11:00
Catherine
132d093021 Implement -audit-rollback.
This feature is useful if you need to restore data after an accidental
overwrite or compromise.
2025-12-11 03:12:57 +00:00
David Leadbeater
62917824fa Support zstd inside zip files.
Given this is already depending on zstd I don't see a reason not to.

Can be tested with libarchive via: `bsdtar -a --options zip:compression=zstd -cf file.zip files...`

Reviewed-on: https://codeberg.org/git-pages/git-pages/pulls/91
Co-authored-by: David Leadbeater <dgl@dgl.cx>
Co-committed-by: David Leadbeater <dgl@dgl.cx>
2025-12-09 06:16:30 +01:00
Catherine
62ef4a5366 Make project name validation more consistent and stricter.
Previously, you could issue e.g. a `GET /%2e%2e/%2e%2e` and it would
get interpreted as a parent directory path segment in the handler.
This didn't result in a path traversal vulnerability when passed to
the S3 backend because of a `path.Clean()` call indirectly done by
`makeWebRoot()`, but it's prudent to not take chances.
2025-12-07 20:24:50 +00:00
Catherine
8fa986015d Process IDNA host names. 2025-12-07 19:28:05 +00:00
Catherine
8d574e5e7d Stabilize the audit feature. 2025-12-07 14:31:48 +00:00
miyuko
91f05e210e [breaking-change] Remove the log-level config option.
This reverts commit 351d0a0c85.

This option does not have any effect at the moment and may potentially
confuse users. It can be easily reintroduced later (by reverting this
commit) once we start logging at any level other than `info`.
2025-12-07 13:12:45 +00:00
miyuko
bc70cba215 Apply the log-level config option to the syslog log sink. 2025-12-07 13:03:14 +00:00
Catherine
8b049da3c7 Treat allowed-repository-url-prefixes = [] the same as unspecified.
Previously, this would disallow all git clones except for those via
wildcard domains. This is highly unintuitive. It also meant that
disabling this function via environment variable was not possible.
2025-12-07 12:55:41 +00:00
miyuko
325d6bedda [breaking-change] Change the format of the SYSLOG_ADDR env variable. 2025-12-07 09:52:15 +00:00
Catherine
fc9e6fcf7b [breaking-change] Listen only on localhost by default.
It is expected that in most deployments, a reverse proxy server like
Caddy or Nginx will be connecting to Caddy; listening on any address
by default is a privacy and security concern.
2025-12-07 07:17:54 +00:00
29 changed files with 951 additions and 323 deletions

View File

@@ -10,16 +10,16 @@ env:
jobs:
check:
runs-on: codeberg-small-lazy
runs-on: debian-trixie
container:
image: docker.io/library/node:24-trixie-slim@sha256:fcdfd7bcd8f641c8c76a8950343c73912d68ba341e8dd1074e663b784d3e76f4
image: docker.io/library/node:24-trixie-slim@sha256:4fc981bf8dfc5e36e15e0cb73c5761a14cabff0932dcad1cf26cd3c3425db5d4
steps:
- name: Check out source code
uses: https://code.forgejo.org/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: https://code.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up toolchain
uses: https://code.forgejo.org/actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
uses: https://code.forgejo.org/actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: '>=1.25.0'
go-version: '>=1.25.6'
- name: Install dependencies
run: |
apt-get -y update
@@ -28,25 +28,28 @@ jobs:
- name: Build service
run: |
go build
- name: Run tests
run: |
go test ./...
- name: Run static analysis
run: |
go vet
staticcheck
go vet ./...
staticcheck ./...
release:
# IMPORTANT: This workflow step will not work without the Releases unit enabled!
if: ${{ forge.ref == 'refs/heads/main' || startsWith(forge.event.ref, 'refs/tags/v') }}
needs: [check]
runs-on: codeberg-medium-lazy
runs-on: debian-trixie
container:
image: docker.io/library/node:24-trixie-slim@sha256:fcdfd7bcd8f641c8c76a8950343c73912d68ba341e8dd1074e663b784d3e76f4
image: docker.io/library/node:24-trixie-slim@sha256:4fc981bf8dfc5e36e15e0cb73c5761a14cabff0932dcad1cf26cd3c3425db5d4
steps:
- name: Check out source code
uses: https://code.forgejo.org/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: https://code.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up toolchain
uses: https://code.forgejo.org/actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
uses: https://code.forgejo.org/actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: '>=1.25.0'
go-version: '>=1.25.6'
- name: Install dependencies
run: |
apt-get -y update
@@ -61,7 +64,7 @@ jobs:
build linux arm64
build darwin arm64
- name: Create release
uses: https://code.forgejo.org/actions/forgejo-release@fc0488c944626f9265d87fbc4dd6c08f78014c63 # v2.7.3
uses: https://code.forgejo.org/actions/forgejo-release@e7b60f9ae8d4bbf3ed4cc178e4656ce40eb67256 # v2.11.2
with:
tag: ${{ startsWith(forge.event.ref, 'refs/tags/v') && forge.ref_name || 'latest' }}
release-dir: assets
@@ -72,16 +75,16 @@ jobs:
package:
if: ${{ forge.ref == 'refs/heads/main' || startsWith(forge.event.ref, 'refs/tags/v') }}
needs: [check]
runs-on: codeberg-medium-lazy
runs-on: debian-trixie
container:
image: docker.io/library/node:24-trixie-slim@sha256:fcdfd7bcd8f641c8c76a8950343c73912d68ba341e8dd1074e663b784d3e76f4
image: docker.io/library/node:24-trixie-slim@sha256:4fc981bf8dfc5e36e15e0cb73c5761a14cabff0932dcad1cf26cd3c3425db5d4
steps:
- name: Install dependencies
run: |
apt-get -y update
apt-get -y install buildah ca-certificates
apt-get -y install ca-certificates buildah qemu-user-binfmt
- name: Check out source code
uses: https://code.forgejo.org/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: https://code.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Authenticate with Docker
run: |
buildah login --authfile=/tmp/authfile-${FORGE}.json \

View File

@@ -1,9 +1,9 @@
# Install CA certificates.
FROM docker.io/library/alpine:latest@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 AS ca-certificates-builder
FROM docker.io/library/alpine:3 AS ca-certificates-builder
RUN apk --no-cache add ca-certificates
# Build supervisor.
FROM docker.io/library/golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS supervisor-builder
FROM docker.io/library/golang:1.26-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS supervisor-builder
RUN apk --no-cache add git
WORKDIR /build
RUN git clone https://github.com/ochinchina/supervisord . && \
@@ -11,12 +11,12 @@ RUN git clone https://github.com/ochinchina/supervisord . && \
RUN GOBIN=/usr/bin go install -ldflags "-s -w"
# Build Caddy with S3 storage backend.
FROM docker.io/library/caddy:2.10.2-builder@sha256:fe404674d209455fdef351db5437758ee0e70a6b59abe770663c09cfa05dbddf AS caddy-builder
FROM docker.io/library/caddy:2.11.2-builder@sha256:d4f984844fc3b867ac88fd814285a38eaaf5b3ecadb9ca1b3b0397182ef60cfe AS caddy-builder
RUN xcaddy build ${CADDY_VERSION} \
--with=github.com/ss098/certmagic-s3@v0.0.0-20250922022452-8af482af5f39
# Build git-pages.
FROM docker.io/library/golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS git-pages-builder
FROM docker.io/library/golang:1.26-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS git-pages-builder
RUN apk --no-cache add git
WORKDIR /build
COPY go.mod go.sum ./
@@ -26,7 +26,7 @@ COPY src/ ./src/
RUN go build -ldflags "-s -w" -o git-pages .
# Compose git-pages and Caddy.
FROM docker.io/library/busybox:1.37.0-musl@sha256:ef13e7482851632be3faf5bd1d28d4727c0810901d564b35416f309975a12a30
FROM docker.io/library/busybox:1.37.0-musl@sha256:19b646668802469d968a05342a601e78da4322a414a7c09b1c9ee25165042138
COPY --from=ca-certificates-builder /etc/ssl/cert.pem /etc/ssl/cert.pem
COPY --from=supervisor-builder /usr/bin/supervisord /bin/supervisord
COPY --from=caddy-builder /usr/bin/caddy /bin/caddy
@@ -36,7 +36,7 @@ WORKDIR /app
RUN mkdir /app/data
COPY conf/supervisord.conf /app/supervisord.conf
COPY conf/Caddyfile /app/Caddyfile
COPY conf/config.example.toml /app/config.toml
COPY conf/config.docker.toml /app/config.toml
# Caddy ports:
EXPOSE 80/tcp 443/tcp 443/udp
@@ -46,8 +46,8 @@ EXPOSE 3000/tcp 3001/tcp 3002/tcp
# While the default command is to run git-pages standalone, the intended configuration
# is to use it with Caddy and store both site data and credentials to an S3-compatible
# object store.
# * In a standalone configuration, the default, git-caddy listens on port 3000 (http).
# * In a combined configuration, supervisord launches both git-caddy and Caddy, and
# * In a standalone configuration, the default, git-pages listens on port 3000 (http).
# * In a combined configuration, supervisord launches both git-pages and Caddy, and
# Caddy listens on ports 80 (http) and 443 (https).
CMD ["git-pages"]
# CMD ["supervisord"]

View File

@@ -117,7 +117,7 @@ The authorization flow for content updates (`PUT`, `PATCH`, `DELETE`, `POST` req
4. **Wildcard Match (content):** If the method is `POST`, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and (for `PUT` requests) the body contains a repository URL, and the requested clone URL is a *matching* clone URL, the request is authorized.
- **Index repository:** If the request URL is `scheme://<user>.<host>/`, a *matching* clone URL is computed by templating `[[wildcard]].clone-url` with `<user>` and `<project>`, where `<project>` is computed by templating each element of `[[wildcard]].index-repos` with `<user>`, and `[[wildcard]]` is the section where the match occurred.
- **Project repository:** If the request URL is `scheme://<user>.<host>/<project>/`, a *matching* clone URL is computed by templating `[[wildcard]].clone-url` with `<user>` and `<project>`, and `[[wildcard]]` is the section where the match occurred.
5. **Forge Authorization:** If the method is `PUT` or `PATCH`, and the body contains an archive, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and `[[wildcard]].authorization` is non-empty, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at the *matching* clone URL (as defined above) as determined by an API call to the forge, the request is authorized. (This enables publishing a site for a private repository.)
5. **Forge Authorization:** If the method is `PUT` or `PATCH` or `DELETE`, and (unless the method is `DELETE`) the body contains an archive, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and `[[wildcard]].authorization` is non-empty, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at the *matching* clone URL (as defined above) as determined by an API call to the forge, the request is authorized. (This enables publishing a site for a private repository.)
5. **Default Deny:** Otherwise, the request is not authorized.
The authorization flow for metadata retrieval (`GET` requests with site paths starting with `.git-pages/`) in the following order, with the first of multiple applicable rule taking precedence:
@@ -137,10 +137,11 @@ _git-pages_ has robust observability features built in:
* If `SENTRY_DSN` environment variable is set, panics are reported to Sentry.
* If `SENTRY_DSN` and `SENTRY_LOGS=1` environment variables are set, logs are uploaded to Sentry.
* If `SENTRY_DSN` and `SENTRY_TRACING=1` environment variables are set, traces are uploaded to Sentry.
* Optional syslog integration allows transmitting application logs to a syslog daemon. When present, the `SYSLOG_ADDR` environment variable enables the integration, and the variable's value is used to configure the absolute path to a Unix socket (usually located at `/dev/log` on Unix systems) or a network address of one of the following formats:
* for TLS over TCP: `tcp+tls://host:port`;
* for plain TCP: `tcp://host:post`;
* for UDP: `udp://host:port`.
* Optional syslog integration allows transmitting application logs to a syslog daemon. When present, the `SYSLOG_ADDR` environment variable enables the integration, and the value is used to configure the syslog destination. The value must follow the format `family/address` and is usually one of the following:
* a Unix datagram socket: `unixgram//dev/log`;
* TLS over TCP: `tcp+tls/host:port`;
* plain TCP: `tcp/host:post`;
* UDP: `udp/host:port`.
Architecture (v2)

View File

@@ -25,11 +25,5 @@ https://, http:// {
on_demand
}
# initial PUT/POST for a new domain has to happen over HTTP
@upgrade `method('GET') && protocol('http')`
redir @upgrade https://{host}{uri} 301
reverse_proxy http://localhost:3000
header Alt-Svc `h3=":443"; persist=1, h2=":443"; persist=1`
encode
}

4
conf/config.docker.toml Normal file
View File

@@ -0,0 +1,4 @@
[server]
pages = "tcp/:3000"
caddy = "tcp/:3001"
metrics = "tcp/:3002"

View File

@@ -2,18 +2,17 @@
# as the intrinsic default value.
log-format = "text"
log-level = "info"
[server]
# Use "-" to disable the handler.
pages = "tcp/:3000"
caddy = "tcp/:3001"
metrics = "tcp/:3002"
pages = "tcp/localhost:3000"
caddy = "tcp/localhost:3001"
metrics = "tcp/localhost:3002"
[[wildcard]] # non-default section
domain = "codeberg.page"
clone-url = "https://codeberg.org/<user>/<project>.git"
index-repos = ["<user>.codeberg.page", "pages"]
index-repo = "pages"
index-repo-branch = "main"
authorization = "forgejo"
@@ -51,7 +50,7 @@ max-symlink-depth = 16
update-timeout = "60s"
max-heap-size-ratio = 0.5 # * RAM_size
forbidden-domains = []
# allowed-repository-url-prefixes = <nil>
allowed-repository-url-prefixes = []
allowed-custom-headers = ["X-Clacks-Overhead"]
[audit]

24
flake.lock generated
View File

@@ -18,6 +18,29 @@
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1763982521,
"narHash": "sha256-ur4QIAHwgFc0vXiaxn5No/FuZicxBr2p0gmT54xZkUQ=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "02e63a239d6eabd595db56852535992c898eba72",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1757882181,
@@ -52,6 +75,7 @@
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs"
}

View File

@@ -3,6 +3,12 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
nix-filter.url = "github:numtide/nix-filter";
gomod2nix = {
url = "github:nix-community/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils";
};
};
outputs =
@@ -11,13 +17,20 @@
nixpkgs,
flake-utils,
nix-filter,
}:
...
}@inputs:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
pkgs = import nixpkgs {
inherit system;
git-pages = pkgs.buildGo125Module {
overlays = [
inputs.gomod2nix.overlays.default
];
};
git-pages = pkgs.buildGoApplication {
pname = "git-pages";
version = "0";
@@ -43,7 +56,8 @@
"-s -w"
];
vendorHash = "sha256-CIEDUWnd5Sth3yYNtw+w1ucYqLCacO34G+EDXVe4+6o=";
go = pkgs.go_1_25;
modules = ./gomod2nix.toml;
};
in
{
@@ -56,6 +70,7 @@
packages = with pkgs; [
caddy
gomod2nix
];
};

44
go.mod
View File

@@ -3,29 +3,30 @@ module codeberg.org/git-pages/git-pages
go 1.25.0
require (
codeberg.org/git-pages/go-headers v1.1.0
codeberg.org/git-pages/go-slog-syslog v0.0.0-20251122144254-06c45d430fb9
codeberg.org/git-pages/go-headers v1.1.1
codeberg.org/git-pages/go-slog-syslog v0.0.0-20251207093707-892f654e80b7
github.com/KimMachineGun/automemlimit v0.7.5
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/creasty/defaults v1.8.0
github.com/dghubble/trie v0.1.0
github.com/fatih/color v1.18.0
github.com/getsentry/sentry-go v0.40.0
github.com/getsentry/sentry-go/slog v0.40.0
github.com/go-git/go-billy/v6 v6.0.0-20251206100608-d4862421331a
github.com/go-git/go-git/v6 v6.0.0-20251206100705-e633db5b9a34
github.com/getsentry/sentry-go v0.43.0
github.com/getsentry/sentry-go/slog v0.43.0
github.com/go-git/go-billy/v6 v6.0.0-20260226131633-45bd0956d66f
github.com/go-git/go-git/v6 v6.0.0-20260305211659-2083cf940afa
github.com/jpillora/backoff v1.0.0
github.com/kankanreno/go-snowflake v1.2.0
github.com/klauspost/compress v1.18.2
github.com/maypok86/otter/v2 v2.2.1
github.com/minio/minio-go/v7 v7.0.97
github.com/klauspost/compress v1.18.4
github.com/maypok86/otter/v2 v2.3.0
github.com/minio/minio-go/v7 v7.0.99
github.com/pelletier/go-toml/v2 v2.2.4
github.com/pquerna/cachecontrol v0.2.0
github.com/prometheus/client_golang v1.23.2
github.com/samber/slog-multi v1.6.0
github.com/samber/slog-multi v1.7.1
github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37
github.com/valyala/fasttemplate v1.2.2
google.golang.org/protobuf v1.36.10
golang.org/x/net v0.51.0
google.golang.org/protobuf v1.36.11
)
require (
@@ -33,41 +34,44 @@ require (
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/kevinburke/ssh_config v1.5.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/leodido/go-syslog/v4 v4.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/crc64nvme v1.1.0 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/slog-common v0.19.0 // indirect
github.com/samber/slog-common v0.20.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/tinylib/msgp v1.3.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/tj/assert v0.0.3 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

96
go.sum
View File

@@ -1,7 +1,7 @@
codeberg.org/git-pages/go-headers v1.1.0 h1:rk7/SOSsn+XuL7PUQZFYUaWKHEaj6K8mXmUV9rF2VxE=
codeberg.org/git-pages/go-headers v1.1.0/go.mod h1:N4gwH0U3YPwmuyxqH7xBA8j44fTPX+vOEP7ejJVBPts=
codeberg.org/git-pages/go-slog-syslog v0.0.0-20251122144254-06c45d430fb9 h1:xfPDg8ThBt3+t+C+pvM3bEH4ePUzP5t5kY2v19TqgKc=
codeberg.org/git-pages/go-slog-syslog v0.0.0-20251122144254-06c45d430fb9/go.mod h1:8NPSXbYcVb71qqNM5cIgn1/uQgMisLbu2dVD1BNxsUw=
codeberg.org/git-pages/go-headers v1.1.1 h1:fpIBELKo66Z2k+gCeYl5mCEXVQ99Lmx1iup1nbo2shE=
codeberg.org/git-pages/go-headers v1.1.1/go.mod h1:N4gwH0U3YPwmuyxqH7xBA8j44fTPX+vOEP7ejJVBPts=
codeberg.org/git-pages/go-slog-syslog v0.0.0-20251207093707-892f654e80b7 h1:+rkrAxhNZo/eKEcKOqVOsF6ohAPv5amz0JLburOeRjs=
codeberg.org/git-pages/go-slog-syslog v0.0.0-20251207093707-892f654e80b7/go.mod h1:8NPSXbYcVb71qqNM5cIgn1/uQgMisLbu2dVD1BNxsUw=
github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk=
github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -18,8 +18,8 @@ github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXye
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
@@ -31,28 +31,26 @@ github.com/dghubble/trie v0.1.0 h1:kJnjBLFFElBwS60N4tkPvnLhnpcDxbBjIulgI8CpNGM=
github.com/dghubble/trie v0.1.0/go.mod h1:sOmnzfBNH7H92ow2292dDFWNsVQuh/izuD7otCYb1ak=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/getsentry/sentry-go/slog v0.40.0 h1:uR2EPL9w6uHw3XB983IAqzqM9mP+fjJpNY9kfob3/Z8=
github.com/getsentry/sentry-go/slog v0.40.0/go.mod h1:ArRaP+0rsbnJGyvZwYDo/vDQT/YBbOQeOlO+DGW+F9s=
github.com/getsentry/sentry-go v0.43.0 h1:XbXLpFicpo8HmBDaInk7dum18G9KSLcjZiyUKS+hLW4=
github.com/getsentry/sentry-go v0.43.0/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
github.com/getsentry/sentry-go/slog v0.43.0 h1:BYGiM4VFu4//S0vrTSf52MmZSmjhOikHIkBeZZw9P4Q=
github.com/getsentry/sentry-go/slog v0.43.0/go.mod h1:EAq/2dhW43dV7fwy4OjTWSsvhZjTM9jjsck0kYt9MYE=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
github.com/go-git/go-billy/v6 v6.0.0-20251206100608-d4862421331a h1:8JM2eaLX/ObLssDAowWTqw53RIKrMKC9n6QUGq9hA8g=
github.com/go-git/go-billy/v6 v6.0.0-20251206100608-d4862421331a/go.mod h1:0NjwVNrwtVFZBReAp5OoGklGJIgJFEbVyHneAr4lc8k=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251203093322-2d981fbae6b7 h1:f8lec5CHzeDgHKzEBZKD6MwAUeaYDfIT+aCL9bU/TqY=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251203093322-2d981fbae6b7/go.mod h1:LzlZlYf8eQeXZKsd2azifbQGsaiTkcjI5WxzH1Wiyhg=
github.com/go-git/go-git/v6 v6.0.0-20251206100705-e633db5b9a34 h1:zvQHay88dsz9zO+61k0CmmFo3VAcTBtGlxTwDbnHG0w=
github.com/go-git/go-git/v6 v6.0.0-20251206100705-e633db5b9a34/go.mod h1:djt5SZ0fMrkORuVAxrZlwtRMw+hnqfZZVqWFH/uQAMI=
github.com/go-git/go-billy/v6 v6.0.0-20260226131633-45bd0956d66f h1:Uvbx7nITO3Sd1GdXarX0TbyYmOaSNIJP0mm4LocEyyA=
github.com/go-git/go-billy/v6 v6.0.0-20260226131633-45bd0956d66f/go.mod h1:ZW9JC5gionMP1kv5uiaOaV23q0FFmNrVOV8VW+y/acc=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67 h1:3hutPZF+/FBjR/9MdsLJ7e1mlt9pwHgwxMW7CrbmWII=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67/go.mod h1:xKt0pNHST9tYHvbiLxSY27CQWFwgIxBJuDrOE0JvbZw=
github.com/go-git/go-git/v6 v6.0.0-20260305211659-2083cf940afa h1:fIbZ264qSeJ+GRz+5nq6SFonkCanp/6CRXhYutq8GlE=
github.com/go-git/go-git/v6 v6.0.0-20260305211659-2083cf940afa/go.mod h1:V/qoTD4qCYizR+fKFA9++d2APoE8Yheci7dXALaSeuI=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
@@ -65,10 +63,10 @@ github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2E
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/kankanreno/go-snowflake v1.2.0 h1:Zx2SctsH5pivIj9vyhwyDyQS23jcDJx4iT49Bjv81kk=
github.com/kankanreno/go-snowflake v1.2.0/go.mod h1:6CZ+10PeVsFXKZUTYyJzPiRIjn1IXbInaWLCX/LDJ0g=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kevinburke/ssh_config v1.5.0 h1:3cPZmE54xb5j3G5xQCjSvokqNwU2uW+3ry1+PRLSPpA=
github.com/kevinburke/ssh_config v1.5.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -90,14 +88,14 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI=
github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=
github.com/minio/crc64nvme v1.1.0 h1:e/tAguZ+4cw32D+IO/8GSf5UVr9y+3eJcxZI2WOO/7Q=
github.com/minio/crc64nvme v1.1.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs8w=
github.com/maypok86/otter/v2 v2.3.0/go.mod h1:XgIdlpmL6jYz882/CAx1E4C1ukfgDKSaw4mWq59+7l8=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.97 h1:lqhREPyfgHTB/ciX8k2r8k0D93WaFqxbJX36UZq5occ=
github.com/minio/minio-go/v7 v7.0.97/go.mod h1:re5VXuo0pwEtoNLsNuSr0RrLfT/MBtohwdaSmPPSRSk=
github.com/minio/minio-go/v7 v7.0.99 h1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=
github.com/minio/minio-go/v7 v7.0.99/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
@@ -124,16 +122,16 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/samber/slog-multi v1.6.0 h1:i1uBY+aaln6ljwdf7Nrt4Sys8Kk6htuYuXDHWJsHtZg=
github.com/samber/slog-multi v1.6.0/go.mod h1:qTqzmKdPpT0h4PFsTN5rYRgLwom1v+fNGuIrl1Xnnts=
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE=
github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -141,8 +139,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 h1:K11tjwz8zTTSZkz4TUjfLN+y8uJWP38BbyPqZ2yB/Yk=
@@ -155,20 +153,22 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

207
gomod2nix.toml Normal file
View File

@@ -0,0 +1,207 @@
schema = 3
[mod]
[mod."codeberg.org/git-pages/go-headers"]
version = "v1.1.1"
hash = "sha256-qgL7l1FHXxcBWhBnBLEI0yENd6P+frvwlKxEAXLA3VY="
[mod."codeberg.org/git-pages/go-slog-syslog"]
version = "v0.0.0-20251207093707-892f654e80b7"
hash = "sha256-ye+DBIyxqTEOViYRrQPWyGJCaLmyKSDwH5btlqDPizM="
[mod."github.com/KimMachineGun/automemlimit"]
version = "v0.7.5"
hash = "sha256-lH/ip9j2hbYUc2W/XIYve/5TScQPZtEZe3hu76CY//k="
[mod."github.com/Microsoft/go-winio"]
version = "v0.6.2"
hash = "sha256-tVNWDUMILZbJvarcl/E7tpSnkn7urqgSHa2Eaka5vSU="
[mod."github.com/ProtonMail/go-crypto"]
version = "v1.3.0"
hash = "sha256-TUG+C4MyeWglOmiwiW2/NUVurFHXLgEPRd3X9uQ1NGI="
[mod."github.com/beorn7/perks"]
version = "v1.0.1"
hash = "sha256-h75GUqfwJKngCJQVE5Ao5wnO3cfKD9lSIteoLp/3xJ4="
[mod."github.com/c2h5oh/datasize"]
version = "v0.0.0-20231215233829-aa82cc1e6500"
hash = "sha256-8MqL7xCvE6fIjanz2jwkaLP1OE5kLu62TOcQx452DHQ="
[mod."github.com/cespare/xxhash/v2"]
version = "v2.3.0"
hash = "sha256-7hRlwSR+fos1kx4VZmJ/7snR7zHh8ZFKX+qqqqGcQpY="
[mod."github.com/cloudflare/circl"]
version = "v1.6.3"
hash = "sha256-XZm4EastgX67Dgm5BpOEW/PY4aLcHM/O8+Xbz26PuTY="
[mod."github.com/creasty/defaults"]
version = "v1.8.0"
hash = "sha256-I1LE1cfOhMS5JxB7+fWTKieefw2Gge1UhIZh+A6pa6s="
[mod."github.com/cyphar/filepath-securejoin"]
version = "v0.6.1"
hash = "sha256-obqip8c1c9mjXFznyXF8aDnpcMw7ttzv+e28anCa/v0="
[mod."github.com/davecgh/go-spew"]
version = "v1.1.1"
hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
[mod."github.com/dghubble/trie"]
version = "v0.1.0"
hash = "sha256-hVh7uYylpMCCSPcxl70hJTmzSwaA1MxBmJFBO5Xdncc="
[mod."github.com/dustin/go-humanize"]
version = "v1.0.1"
hash = "sha256-yuvxYYngpfVkUg9yAmG99IUVmADTQA0tMbBXe0Fq0Mc="
[mod."github.com/emirpasic/gods"]
version = "v1.18.1"
hash = "sha256-hGDKddjLj+5dn2woHtXKUdd49/3xdsqnhx7VEdCu1m4="
[mod."github.com/fatih/color"]
version = "v1.18.0"
hash = "sha256-pP5y72FSbi4j/BjyVq/XbAOFjzNjMxZt2R/lFFxGWvY="
[mod."github.com/getsentry/sentry-go"]
version = "v0.43.0"
hash = "sha256-Wu1inIhjuAw6wKburwqIlNxC0I4akunHGh/8DOqo3xg="
[mod."github.com/getsentry/sentry-go/slog"]
version = "v0.43.0"
hash = "sha256-FJMx2E8anKtHknn867gCkYPjitZb9Okqp2uZ+dV7JqA="
[mod."github.com/go-git/gcfg/v2"]
version = "v2.0.2"
hash = "sha256-icqMDeC/tEg/3979EuEN67Ml5KjdDA0R3QvR6iLLrSI="
[mod."github.com/go-git/go-billy/v6"]
version = "v6.0.0-20260226131633-45bd0956d66f"
hash = "sha256-s+dthtn+JewJ58R5VbvWaEoYLozDt5YpkHyXcN0xMvQ="
[mod."github.com/go-git/go-git/v6"]
version = "v6.0.0-20260305211659-2083cf940afa"
hash = "sha256-aUUgVODQVanWVQ44tcrOKIvtzJPlZKFNPPvEdAxdPxw="
[mod."github.com/go-ini/ini"]
version = "v1.67.0"
hash = "sha256-V10ahGNGT+NLRdKUyRg1dos5RxLBXBk1xutcnquc/+4="
[mod."github.com/golang/groupcache"]
version = "v0.0.0-20241129210726-2c02b8208cf8"
hash = "sha256-AdLZ3dJLe/yduoNvZiXugZxNfmwJjNQyQGsIdzYzH74="
[mod."github.com/google/uuid"]
version = "v1.6.0"
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
[mod."github.com/jpillora/backoff"]
version = "v1.0.0"
hash = "sha256-uxHg68NN8hrwPCrPfLYYprZHf7dMyEoPoF46JFx0IHU="
[mod."github.com/kankanreno/go-snowflake"]
version = "v1.2.0"
hash = "sha256-713xGEqjwaUGIu2EHII5sldWmcquFpxZmte/7R/O6LA="
[mod."github.com/kevinburke/ssh_config"]
version = "v1.5.0"
hash = "sha256-4SijlenzNuWb5CavWrky8qoQj+6fKCJgOiQANzN5TUE="
[mod."github.com/klauspost/compress"]
version = "v1.18.4"
hash = "sha256-swwNE6xKz4ZAOUHPWFlHYiqFeZLRZuuKYhLQ34aYnAU="
[mod."github.com/klauspost/cpuid/v2"]
version = "v2.3.0"
hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc="
[mod."github.com/klauspost/crc32"]
version = "v1.3.0"
hash = "sha256-RsS/MDJbVzVB+i74whqABgwZJWMw+AutF6HhJBVgbag="
[mod."github.com/leodido/go-syslog/v4"]
version = "v4.3.0"
hash = "sha256-fCJ2rgrrPR/Ey/PoAsJhd8Sl8mblAnnMAmBuoWFBTgg="
[mod."github.com/mattn/go-colorable"]
version = "v0.1.13"
hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.20"
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
[mod."github.com/maypok86/otter/v2"]
version = "v2.3.0"
hash = "sha256-ELzmi/s2WqDeUmzSGnfx+ys2Hs28XHqF7vlEzyRotIA="
[mod."github.com/minio/crc64nvme"]
version = "v1.1.1"
hash = "sha256-RVVi/gWPBEQqcW4n+KIKxlA3uY5+77e2rhkVk8fFNUo="
[mod."github.com/minio/md5-simd"]
version = "v1.1.2"
hash = "sha256-vykcXvy2VBBAXnJott/XsGTT0gk2UL36JzZKfJ1KAUY="
[mod."github.com/minio/minio-go/v7"]
version = "v7.0.99"
hash = "sha256-Q2VISIvHDggBzidGWzgHbVUZrDCsSIGBPcWfMJcC39w="
[mod."github.com/munnerz/goautoneg"]
version = "v0.0.0-20191010083416-a7dc8b61c822"
hash = "sha256-79URDDFenmGc9JZu+5AXHToMrtTREHb3BC84b/gym9Q="
[mod."github.com/pbnjay/memory"]
version = "v0.0.0-20210728143218-7b4eea64cf58"
hash = "sha256-QI+F1oPLOOtwNp8+m45OOoSfYFs3QVjGzE0rFdpF/IA="
[mod."github.com/pelletier/go-toml/v2"]
version = "v2.2.4"
hash = "sha256-8qQIPldbsS5RO8v/FW/se3ZsAyvLzexiivzJCbGRg2Q="
[mod."github.com/philhofer/fwd"]
version = "v1.2.0"
hash = "sha256-cGx2/0QQay46MYGZuamFmU0TzNaFyaO+J7Ddzlr/3dI="
[mod."github.com/pjbgf/sha1cd"]
version = "v0.5.0"
hash = "sha256-11XBkhdciQGsQ7jEMZ6PgphRKjruTSc7ZxfOwDuPCr8="
[mod."github.com/pkg/errors"]
version = "v0.9.1"
hash = "sha256-mNfQtcrQmu3sNg/7IwiieKWOgFQOVVe2yXgKBpe/wZw="
[mod."github.com/pmezard/go-difflib"]
version = "v1.0.0"
hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA="
[mod."github.com/pquerna/cachecontrol"]
version = "v0.2.0"
hash = "sha256-tuTERCFfwmqPepw/rs5cyv9fArCD30BqgjZqwMV+vzQ="
[mod."github.com/prometheus/client_golang"]
version = "v1.23.2"
hash = "sha256-3GD4fBFa1tJu8MS4TNP6r2re2eViUE+kWUaieIOQXCg="
[mod."github.com/prometheus/client_model"]
version = "v0.6.2"
hash = "sha256-q6Fh6v8iNJN9ypD47LjWmx66YITa3FyRjZMRsuRTFeQ="
[mod."github.com/prometheus/common"]
version = "v0.66.1"
hash = "sha256-bqHPaV9IV70itx63wqwgy2PtxMN0sn5ThVxDmiD7+Tk="
[mod."github.com/prometheus/procfs"]
version = "v0.16.1"
hash = "sha256-OBCvKlLW2obct35p0L9Q+1ZrxZjpTmbgHMP2rng9hpo="
[mod."github.com/rs/xid"]
version = "v1.6.0"
hash = "sha256-rJB7h3KuH1DPp5n4dY3MiGnV1Y96A10lf5OUl+MLkzU="
[mod."github.com/samber/lo"]
version = "v1.52.0"
hash = "sha256-xgMsPJv3rydHH10NZU8wz/DhK2VbbR8ymivOg1ChTp0="
[mod."github.com/samber/slog-common"]
version = "v0.20.0"
hash = "sha256-aWcvt9XNyKaolLhvthcXeFDl0t6uo7Vdo8WzCducf1E="
[mod."github.com/samber/slog-multi"]
version = "v1.7.1"
hash = "sha256-wHXt2lwFfjm1p7jnZi44SlHtjdk531BGz2O9pfiylxo="
[mod."github.com/sergi/go-diff"]
version = "v1.4.0"
hash = "sha256-rs9NKpv/qcQEMRg7CmxGdP4HGuFdBxlpWf9LbA9wS4k="
[mod."github.com/stretchr/testify"]
version = "v1.11.1"
hash = "sha256-sWfjkuKJyDllDEtnM8sb/pdLzPQmUYWYtmeWz/5suUc="
[mod."github.com/tinylib/msgp"]
version = "v1.6.1"
hash = "sha256-R2LutHQFZ7HAqeyzHqzMeyAJHxcYc+n1x7ysyrXefmQ="
[mod."github.com/tj/assert"]
version = "v0.0.3"
hash = "sha256-4xhmZcHpUafabaXejE9ucVnGxG/txomvKzBg6cbkusg="
[mod."github.com/tj/go-redirects"]
version = "v0.0.0-20200911105812-fd1ba1020b37"
hash = "sha256-GpYpxdT4F7PkwGXLo7cYVcIRJrzd1sKHtFDH+bRb6Tk="
[mod."github.com/valyala/bytebufferpool"]
version = "v1.0.0"
hash = "sha256-I9FPZ3kCNRB+o0dpMwBnwZ35Fj9+ThvITn8a3Jr8mAY="
[mod."github.com/valyala/fasttemplate"]
version = "v1.2.2"
hash = "sha256-gp+lNXE8zjO+qJDM/YbS6V43HFsYP6PKn4ux1qa5lZ0="
[mod."go.yaml.in/yaml/v2"]
version = "v2.4.2"
hash = "sha256-oC8RWdf1zbMYCtmR0ATy/kCkhIwPR9UqFZSMOKLVF/A="
[mod."go.yaml.in/yaml/v3"]
version = "v3.0.4"
hash = "sha256-NkGFiDPoCxbr3LFsI6OCygjjkY0rdmg5ggvVVwpyDQ4="
[mod."golang.org/x/crypto"]
version = "v0.48.0"
hash = "sha256-uBIGGSGmWWklRxX6XTOqUECzz165UFY9Y99Ka3pLKAw="
[mod."golang.org/x/net"]
version = "v0.51.0"
hash = "sha256-bLDpVRTPWM7IowHw1jdr9EPCRQNAVFsPwz69olySah4="
[mod."golang.org/x/sys"]
version = "v0.41.0"
hash = "sha256-owjs3/IzAKfFlIz1U1fiHSfl2+bTUhaXTyWEjL5SWHk="
[mod."golang.org/x/text"]
version = "v0.34.0"
hash = "sha256-wGKd1JkeiFROibvo2kkAuQ7JajSIfV4utGaoGbTQhQM="
[mod."google.golang.org/protobuf"]
version = "v1.36.11"
hash = "sha256-7W+6jntfI/awWL3JP6yQedxqP5S9o3XvPgJ2XxxsIeE="
[mod."gopkg.in/yaml.v3"]
version = "v3.0.1"
hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU="

View File

@@ -14,5 +14,7 @@
"lockFileMaintenance": {
"enabled": true,
"automerge": false
}
},
"semanticCommits": "disabled",
"commitMessagePrefix": "[Renovate]"
}

View File

@@ -102,6 +102,12 @@ func (record *AuditRecord) DescribePrincipal() string {
if record.Principal.GetIpAddress() != "" {
items = append(items, record.Principal.GetIpAddress())
}
if record.Principal.GetForgeUser() != nil {
items = append(items, fmt.Sprintf("%s/%s(%d)",
record.Principal.GetForgeUser().GetOrigin(),
record.Principal.GetForgeUser().GetHandle(),
record.Principal.GetForgeUser().GetId()))
}
if record.Principal.GetCliAdmin() {
items = append(items, "<cli-admin>")
}
@@ -265,11 +271,7 @@ type auditedBackend struct {
var _ Backend = (*auditedBackend)(nil)
func NewAuditedBackend(backend Backend) Backend {
if config.Feature("audit") {
return &auditedBackend{backend}
} else {
return backend
}
return &auditedBackend{backend}
}
// This function does not retry appending audit records; as such, if it returns an error,

View File

@@ -12,6 +12,8 @@ import (
"slices"
"strings"
"time"
"golang.org/x/net/idna"
)
type AuthError struct {
@@ -42,25 +44,48 @@ func authorizeInsecure(r *http.Request) *Authorization {
return nil
}
var idnaProfile = idna.New(idna.MapForLookup(), idna.BidiRule())
func GetHost(r *http.Request) (string, error) {
// FIXME: handle IDNA
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
// dirty but the go stdlib doesn't have a "split port if present" function
host = r.Host
}
if strings.HasPrefix(host, ".") {
// this also rejects invalid characters and labels
host, err = idnaProfile.ToASCII(host)
if err != nil {
if config.Feature("relaxed-idna") {
// unfortunately, the go IDNA library has some significant issues around its
// Unicode TR46 implementation: https://github.com/golang/go/issues/76804
// we would like to allow *just* the _ here, but adding `idna.StrictDomainName(false)`
// would also accept domains like `*.foo.bar` which should clearly be disallowed.
// as a workaround, accept a domain name if it is valid with all `_` characters
// replaced with an alphanumeric character (we use `a`); this allows e.g. `foo_bar.xxx`
// and `foo__bar.xxx`, as well as `_foo.xxx` and `foo_.xxx`. labels starting with
// an underscore are explicitly rejected below.
_, err = idnaProfile.ToASCII(strings.ReplaceAll(host, "_", "a"))
}
if err != nil {
return "", AuthError{http.StatusBadRequest,
fmt.Sprintf("malformed host name %q", host)}
}
}
if strings.HasPrefix(host, ".") || strings.HasPrefix(host, "_") {
return "", AuthError{http.StatusBadRequest,
fmt.Sprintf("host name %q is reserved", host)}
fmt.Sprintf("reserved host name %q", host)}
}
host = strings.TrimSuffix(host, ".")
return host, nil
}
func IsValidProjectName(name string) bool {
return !strings.HasPrefix(name, ".") && !strings.Contains(name, "%")
}
func GetProjectName(r *http.Request) (string, error) {
// path must be either `/` or `/foo/` (`/foo` is accepted as an alias)
path := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/")
if path == ".index" || strings.HasPrefix(path, ".index/") {
if !IsValidProjectName(path) {
return "", AuthError{http.StatusBadRequest,
fmt.Sprintf("directory name %q is reserved", ".index")}
} else if strings.Contains(path, "/") {
@@ -81,6 +106,8 @@ type Authorization struct {
repoURLs []string
// Only the exact branch is allowed.
branch string
// The authorized forge user.
forgeUser *ForgeUser
}
func authorizeDNSChallenge(r *http.Request) (*Authorization, error) {
@@ -240,8 +267,8 @@ func authorizeWildcardMatchSite(r *http.Request, pattern *WildcardPattern) (*Aut
}
if userName, found := pattern.Matches(host); found {
repoURLs, branch := pattern.ApplyTemplate(userName, projectName)
return &Authorization{repoURLs, branch}, nil
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
return &Authorization{repoURLs: []string{repoURL}, branch: branch}, nil
} else {
return nil, AuthError{
http.StatusUnauthorized,
@@ -365,9 +392,6 @@ func AuthorizeMetadataRetrieval(r *http.Request) (*Authorization, error) {
return nil, joinErrors(causes...)
}
// Returns `repoURLs, err` where if `err == nil` then the request is authorized to clone from
// any repository URL included in `repoURLs` (by case-insensitive comparison), or any URL at all
// if `repoURLs == nil`.
func AuthorizeUpdateFromRepository(r *http.Request) (*Authorization, error) {
causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}}
@@ -436,7 +460,7 @@ func AuthorizeUpdateFromRepository(r *http.Request) (*Authorization, error) {
}
func checkAllowedURLPrefix(repoURL string) error {
if config.Limits.AllowedRepositoryURLPrefixes != nil {
if len(config.Limits.AllowedRepositoryURLPrefixes) > 0 {
allowedPrefix := false
repoURL = strings.ToLower(repoURL)
for _, allowedRepoURLPrefix := range config.Limits.AllowedRepositoryURLPrefixes {
@@ -584,6 +608,59 @@ func checkGogsRepositoryPushPermission(baseURL *url.URL, authorization string) e
return nil
}
// Gogs, Gitea, and Forgejo all support the same API here.
func fetchGogsAuthorizedUser(baseURL *url.URL, authorization string) (*ForgeUser, error) {
request, err := http.NewRequest("GET", baseURL.JoinPath("/api/v1/user").String(), nil)
if err != nil {
panic(err) // misconfiguration
}
request.Header.Set("Accept", "application/json")
request.Header.Set("Authorization", authorization)
httpClient := http.Client{Timeout: 5 * time.Second}
response, err := httpClient.Do(request)
if err != nil {
return nil, AuthError{
http.StatusServiceUnavailable,
fmt.Sprintf("cannot fetch authorized forge user: %s", err),
}
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, AuthError{
http.StatusServiceUnavailable,
fmt.Sprintf(
"cannot fetch authorized forge user: GET %s returned %s",
request.URL,
response.Status,
),
}
}
decoder := json.NewDecoder(response.Body)
var userInfo struct {
ID int64
Login string
}
if err = decoder.Decode(&userInfo); err != nil {
return nil, errors.Join(AuthError{
http.StatusServiceUnavailable,
fmt.Sprintf(
"cannot fetch authorized forge user: GET %s returned malformed JSON",
request.URL,
),
}, err)
}
origin := request.URL.Hostname()
return &ForgeUser{
Origin: &origin,
Id: &userInfo.ID,
Handle: &userInfo.Login,
}, nil
}
func authorizeForgeWithToken(r *http.Request) (*Authorization, error) {
authorization := r.Header.Get("Forge-Authorization")
if authorization == "" {
@@ -607,25 +684,32 @@ func authorizeForgeWithToken(r *http.Request) (*Authorization, error) {
}
if userName, found := pattern.Matches(host); found {
repoURLs, branch := pattern.ApplyTemplate(userName, projectName)
for _, repoURL := range repoURLs {
parsedRepoURL, err := url.Parse(repoURL)
if err != nil {
panic(err) // misconfiguration
}
if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil {
errs = append(errs, err)
continue
}
// This will actually be ignored by the caller of AuthorizeUpdateFromArchive,
// but we return this information as it makes sense to do contextually here.
return &Authorization{
[]string{repoURL},
branch,
}, nil
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
parsedRepoURL, err := url.Parse(repoURL)
if err != nil {
panic(err) // misconfiguration
}
if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil {
errs = append(errs, err)
continue
}
authorizedUser, err := fetchGogsAuthorizedUser(parsedRepoURL, authorization)
if err != nil {
errs = append(errs, err)
continue
}
return &Authorization{
// This will actually be ignored by the callers of AuthorizeUpdateFromArchive and
// AuthorizeDeletion, but we return this information as it makes sense to do
// contextually here.
repoURLs: []string{repoURL},
branch: branch,
forgeUser: authorizedUser,
}, nil
}
}
@@ -658,7 +742,7 @@ func AuthorizeUpdateFromArchive(r *http.Request) (*Authorization, error) {
return auth, nil
}
if config.Limits.AllowedRepositoryURLPrefixes != nil {
if len(config.Limits.AllowedRepositoryURLPrefixes) > 0 {
causes = append(causes, AuthError{http.StatusUnauthorized, "DNS challenge not allowed"})
} else {
// DNS challenge gives absolute authority.
@@ -676,6 +760,41 @@ func AuthorizeUpdateFromArchive(r *http.Request) (*Authorization, error) {
return nil, joinErrors(causes...)
}
func AuthorizeDeletion(r *http.Request) (*Authorization, error) {
causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}}
if err := CheckForbiddenDomain(r); err != nil {
return nil, err
}
auth := authorizeInsecure(r)
if auth != nil {
return auth, nil
}
auth, err := authorizeDNSChallenge(r)
if err != nil && IsUnauthorized(err) {
causes = append(causes, err)
} else if err != nil { // bad request
return nil, err
} else {
logc.Printf(r.Context(), "auth: DNS challenge: allow *\n")
return auth, nil
}
auth, err = authorizeForgeWithToken(r)
if err != nil && IsUnauthorized(err) {
causes = append(causes, err)
} else if err != nil { // bad request
return nil, err
} else {
logc.Printf(r.Context(), "auth: forge token: allow\n")
return auth, nil
}
return nil, joinErrors(causes...)
}
func CheckForbiddenDomain(r *http.Request) error {
host, err := GetHost(r)
if err != nil {

View File

@@ -63,7 +63,6 @@ type Config struct {
Insecure bool `toml:"-" env:"insecure"`
Features []string `toml:"features"`
LogFormat string `toml:"log-format" default:"text"`
LogLevel string `toml:"log-level" default:"info"`
Server ServerConfig `toml:"server"`
Wildcard []WildcardConfig `toml:"wildcard"`
Fallback FallbackConfig `toml:"fallback"`
@@ -74,17 +73,17 @@ type Config struct {
}
type ServerConfig struct {
Pages string `toml:"pages" default:"tcp/:3000"`
Caddy string `toml:"caddy" default:"tcp/:3001"`
Metrics string `toml:"metrics" default:"tcp/:3002"`
Pages string `toml:"pages" default:"tcp/localhost:3000"`
Caddy string `toml:"caddy" default:"tcp/localhost:3001"`
Metrics string `toml:"metrics" default:"tcp/localhost:3002"`
}
type WildcardConfig struct {
Domain string `toml:"domain"`
CloneURL string `toml:"clone-url"` // URL template, not an exact URL
IndexRepos []string `toml:"index-repos" default:"[]"`
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
Authorization string `toml:"authorization"`
Domain string `toml:"domain"`
CloneURL string `toml:"clone-url"` // URL template, not an exact URL
IndexRepo string `toml:"index-repo" default:"pages"`
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
Authorization string `toml:"authorization"`
}
type FallbackConfig struct {
@@ -140,7 +139,7 @@ type LimitsConfig struct {
// List of domains unconditionally forbidden for uploads.
ForbiddenDomains []string `toml:"forbidden-domains" default:"[]"`
// List of allowed repository URL prefixes. Setting this option prohibits uploading archives.
AllowedRepositoryURLPrefixes []string `toml:"allowed-repository-url-prefixes"`
AllowedRepositoryURLPrefixes []string `toml:"allowed-repository-url-prefixes" default:"[]"`
// List of allowed custom headers. Header name must be in the MIME canonical form,
// e.g. `Foo-Bar`. Setting this option permits including this custom header in `_headers`,
// unless it is fundamentally unsafe.

View File

@@ -9,7 +9,9 @@ import (
"errors"
"fmt"
"io"
"math"
"os"
"path"
"strings"
"github.com/c2h5oh/datasize"
@@ -60,6 +62,16 @@ func (err UnresolvedRefError) Error() string {
return fmt.Sprintf("%d unresolved blob references", len(err.missing))
}
func normalizeArchiveMemberName(fileName string) string {
// Strip the leading slash and any extraneous path segments.
fileName = path.Clean(fileName)
fileName = strings.TrimPrefix(fileName, "/")
if fileName == "." {
fileName = ""
}
return fileName
}
// Returns a map of git hash to entry. If `manifest` is nil, returns an empty map.
func indexManifestByGitHash(manifest *Manifest) map[string]*Entry {
index := map[string]*Entry{}
@@ -109,15 +121,10 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return nil, err
}
// For some reason, GNU tar includes any leading `.` path segments in archive filenames,
// unless there is a `..` path segment anywhere in the input filenames.
fileName := header.Name
for {
if strippedName, found := strings.CutPrefix(fileName, "./"); found {
fileName = strippedName
} else {
break
}
fileName := normalizeArchiveMemberName(header.Name)
if fileName == "" {
// This must be the root directory. It will be filled in by EnsureLeadingDirectories.
continue
}
switch header.Typeflag {
@@ -144,6 +151,9 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return nil, UnresolvedRefError{missing}
}
// Ensure parent directories exist for all entries.
EnsureLeadingDirectories(manifest)
logc.Printf(ctx,
"reuse: %s recycled, %s transferred\n",
datasize.ByteSize(dataBytesRecycled).HR(),
@@ -153,6 +163,9 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return manifest, nil
}
// Used for zstd decompression inside zip files, it is recommended to share this.
var zstdDecomp = zstd.ZipDecompressor()
func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*Manifest, error) {
data, err := io.ReadAll(reader)
if err != nil {
@@ -164,9 +177,18 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return nil, err
}
// Support zstd compression inside zip files.
archive.RegisterDecompressor(zstd.ZipMethodWinZip, zstdDecomp)
archive.RegisterDecompressor(zstd.ZipMethodPKWare, zstdDecomp)
// Detect and defuse zipbombs.
var totalSize uint64
for _, file := range archive.File {
if totalSize+file.UncompressedSize64 < totalSize {
// Would overflow
totalSize = math.MaxUint64
break
}
totalSize += file.UncompressedSize64
}
if totalSize > config.Limits.MaxSiteSize.Bytes() {
@@ -184,8 +206,9 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
missing := []string{}
manifest := NewManifest()
for _, file := range archive.File {
normalizedName := normalizeArchiveMemberName(file.Name)
if strings.HasSuffix(file.Name, "/") {
AddDirectory(manifest, file.Name)
AddDirectory(manifest, normalizedName)
} else {
fileReader, err := file.Open()
if err != nil {
@@ -200,10 +223,10 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
if file.Mode()&os.ModeSymlink != 0 {
entry := addSymlinkOrBlobReference(
manifest, file.Name, string(fileData), index, &missing)
manifest, normalizedName, string(fileData), index, &missing)
dataBytesRecycled += entry.GetOriginalSize()
} else {
AddFile(manifest, file.Name, fileData)
AddFile(manifest, normalizedName, fileData)
dataBytesTransferred += int64(len(fileData))
}
}
@@ -213,6 +236,9 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return nil, UnresolvedRefError{missing}
}
// Ensure parent directories exist for all entries.
EnsureLeadingDirectories(manifest)
logc.Printf(ctx,
"reuse: %s recycled, %s transferred\n",
datasize.ByteSize(dataBytesRecycled).HR(),

View File

@@ -23,6 +23,8 @@ import (
"google.golang.org/protobuf/proto"
)
var ErrRepositoryTooLarge = errors.New("repository too large")
func FetchRepository(
ctx context.Context, repoURL string, branch string, oldManifest *Manifest,
) (
@@ -57,7 +59,7 @@ func FetchRepository(
repo, err = git.CloneContext(ctx, storer, nil, &git.CloneOptions{
Bare: true,
URL: repoURL,
ReferenceName: plumbing.ReferenceName(branch),
ReferenceName: plumbing.NewBranchReferenceName(branch),
SingleBranch: true,
Depth: 1,
Tags: git.NoTags,
@@ -152,9 +154,10 @@ func FetchRepository(
// This will only succeed if a `blob:none` filter isn't supported and we got a full
// clone despite asking for a partial clone.
for hash, manifestEntry := range blobsNeeded {
if err := readGitBlob(repo, hash, manifestEntry); err == nil {
dataBytesTransferred += manifestEntry.GetOriginalSize()
if err := readGitBlob(repo, hash, manifestEntry, &dataBytesTransferred); err == nil {
delete(blobsNeeded, hash)
} else if errors.Is(err, ErrRepositoryTooLarge) {
return nil, err
}
}
@@ -193,10 +196,9 @@ func FetchRepository(
// All remaining blobs should now be available.
for hash, manifestEntry := range blobsNeeded {
if err := readGitBlob(repo, hash, manifestEntry); err != nil {
if err := readGitBlob(repo, hash, manifestEntry, &dataBytesTransferred); err != nil {
return nil, err
}
dataBytesTransferred += manifestEntry.GetOriginalSize()
delete(blobsNeeded, hash)
}
}
@@ -210,7 +212,9 @@ func FetchRepository(
return manifest, nil
}
func readGitBlob(repo *git.Repository, hash plumbing.Hash, entry *Entry) error {
func readGitBlob(
repo *git.Repository, hash plumbing.Hash, entry *Entry, bytesTransferred *int64,
) error {
blob, err := repo.BlobObject(hash)
if err != nil {
return fmt.Errorf("git blob %s: %w", hash, err)
@@ -239,5 +243,14 @@ func readGitBlob(repo *git.Repository, hash plumbing.Hash, entry *Entry) error {
entry.Transform = Transform_Identity.Enum()
entry.OriginalSize = proto.Int64(blob.Size)
entry.CompressedSize = proto.Int64(blob.Size)
*bytesTransferred += blob.Size
if uint64(*bytesTransferred) > config.Limits.MaxSiteSize.Bytes() {
return fmt.Errorf("%w: fetch exceeds %s limit",
ErrRepositoryTooLarge,
config.Limits.MaxSiteSize.HR(),
)
}
return nil
}

View File

@@ -14,6 +14,7 @@ import (
"net/http/httputil"
"net/url"
"os"
"path"
"runtime/debug"
"strings"
"time"
@@ -217,6 +218,8 @@ func Main() {
"display audit log")
auditRead := flag.String("audit-read", "",
"extract contents of audit record `id` to files '<id>-*'")
auditRollback := flag.String("audit-rollback", "",
"restore site from contents of audit record `id`")
auditServer := flag.String("audit-server", "",
"listen for notifications on `endpoint` and spawn a process for each audit event")
runMigration := flag.String("run-migration", "",
@@ -237,6 +240,7 @@ func Main() {
*unfreezeDomain != "",
*auditLog,
*auditRead != "",
*auditRollback != "",
*auditServer != "",
*runMigration != "",
*traceGarbage,
@@ -248,7 +252,8 @@ func Main() {
if cliOperations > 1 {
logc.Fatalln(ctx, "-list-blobs, -list-manifests, -get-blob, -get-manifest, -get-archive, "+
"-update-site, -freeze-domain, -unfreeze-domain, -audit-log, -audit-read, "+
"-audit-server, -run-migration, and -trace-garbage are mutually exclusive")
"-audit-rollback, -audit-server, -run-migration, and -trace-garbage are "+
"mutually exclusive")
}
if *configTomlPath != "" && *noConfig {
@@ -483,6 +488,34 @@ func Main() {
logc.Fatalln(ctx, err)
}
case *auditRollback != "":
ctx = WithPrincipal(ctx)
GetPrincipal(ctx).CliAdmin = proto.Bool(true)
id, err := ParseAuditID(*auditRollback)
if err != nil {
logc.Fatalln(ctx, err)
}
record, err := backend.QueryAuditLog(ctx, id)
if err != nil {
logc.Fatalln(ctx, err)
}
if record.GetManifest() == nil || record.GetDomain() == "" || record.GetProject() == "" {
logc.Fatalln(ctx, "no manifest in audit record")
}
webRoot := path.Join(record.GetDomain(), record.GetProject())
err = backend.StageManifest(ctx, record.GetManifest())
if err != nil {
logc.Fatalln(ctx, err)
}
err = backend.CommitManifest(ctx, webRoot, record.GetManifest(), ModifyManifestOptions{})
if err != nil {
logc.Fatalln(ctx, err)
}
case *auditServer != "":
if flag.NArg() < 1 {
logc.Fatalln(ctx, "handler path not provided")

View File

@@ -144,11 +144,28 @@ func AddProblem(manifest *Manifest, pathName, format string, args ...any) error
return fmt.Errorf("%s: %s", pathName, cause)
}
// EnsureLeadingDirectories adds directory entries for any parent directories
// that are implicitly referenced by files in the manifest but don't have
// explicit directory entries. (This can be the case if an archive is created
// via globs rather than including a whole directory.)
func EnsureLeadingDirectories(manifest *Manifest) {
for name := range manifest.Contents {
for dir := path.Dir(name); dir != "." && dir != ""; dir = path.Dir(dir) {
if dir == "/" {
panic("malformed manifest (paths must not be rooted in /)")
}
if _, exists := manifest.Contents[dir]; !exists {
AddDirectory(manifest, dir)
}
}
}
}
func GetProblemReport(manifest *Manifest) []string {
var report []string
for _, problem := range manifest.Problems {
report = append(report,
fmt.Sprintf("%s: %s", problem.GetPath(), problem.GetCause()))
fmt.Sprintf("/%s: %s", problem.GetPath(), problem.GetCause()))
}
return report
}
@@ -283,6 +300,7 @@ func PrepareManifest(ctx context.Context, manifest *Manifest) error {
return nil
}
var ErrSiteTooLarge = errors.New("site too large")
var ErrManifestTooLarge = errors.New("manifest too large")
// Uploads inline file data over certain size to the storage backend. Returns a copy of
@@ -325,13 +343,22 @@ func StoreManifest(
}
}
// Compute the deduplicated storage size.
var blobSizes = make(map[string]int64)
for _, entry := range manifest.Contents {
// Compute the total and deduplicated storage size.
totalSize := int64(0)
blobSizes := map[string]int64{}
for _, entry := range extManifest.Contents {
totalSize += entry.GetOriginalSize()
if entry.GetType() == Type_ExternalFile {
blobSizes[string(entry.Data)] = entry.GetCompressedSize()
}
}
if uint64(totalSize) > config.Limits.MaxSiteSize.Bytes() {
return nil, fmt.Errorf("%w: contents size %s exceeds %s limit",
ErrSiteTooLarge,
datasize.ByteSize(totalSize).HR(),
config.Limits.MaxSiteSize.HR(),
)
}
for _, blobSize := range blobSizes {
*extManifest.StoredSize += blobSize
}

View File

@@ -13,7 +13,6 @@ import (
"os"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
@@ -52,6 +51,59 @@ func hasSentry() bool {
return os.Getenv("SENTRY_DSN") != ""
}
func chainSentryMiddleware(
middleware ...func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event,
) func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
return func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
for idx := 0; idx < len(middleware) && event != nil; idx++ {
event = middleware[idx](event, hint)
}
return event
}
}
// sensitiveHTTPHeaders extends the list of sensitive headers defined in the Sentry Go SDK with our
// own application-specific header field names.
var sensitiveHTTPHeaders = map[string]struct{}{
"Forge-Authorization": {},
}
// scrubSentryEvent removes sensitive HTTP header fields from the Sentry event.
func scrubSentryEvent(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
if event.Request != nil && event.Request.Headers != nil {
for key := range event.Request.Headers {
if _, ok := sensitiveHTTPHeaders[key]; ok {
delete(event.Request.Headers, key)
}
}
}
return event
}
// sampleSentryEvent returns a function that discards a Sentry event according to the sample rate,
// unless the associated HTTP request triggers a mutation or it took too long to produce a response,
// in which case the event is never discarded.
func sampleSentryEvent(sampleRate float64) func(*sentry.Event, *sentry.EventHint) *sentry.Event {
return func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
newSampleRate := sampleRate
if event.Request != nil {
switch event.Request.Method {
case "PUT", "POST", "DELETE":
newSampleRate = 1
}
}
duration := event.Timestamp.Sub(event.StartTime)
threshold := time.Duration(config.Observability.SlowResponseThreshold)
if duration >= threshold {
newSampleRate = 1
}
if rand.Float64() < newSampleRate {
return event
}
return nil
}
}
func InitObservability() {
debug.SetPanicOnFault(true)
@@ -62,29 +114,15 @@ func InitObservability() {
logHandlers := []slog.Handler{}
logLevel := slog.LevelInfo
switch strings.ToLower(config.LogLevel) {
case "debug":
logLevel = slog.LevelDebug
case "info":
logLevel = slog.LevelInfo
case "warn":
logLevel = slog.LevelWarn
case "error":
logLevel = slog.LevelError
default:
log.Println("unknown log level", config.LogLevel)
}
switch config.LogFormat {
case "none":
// nothing to do
case "text":
logHandlers = append(logHandlers,
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}))
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}))
case "json":
logHandlers = append(logHandlers,
slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}))
slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{}))
default:
log.Println("unknown log format", config.LogFormat)
}
@@ -113,39 +151,23 @@ func InitObservability() {
enableTracing = value
}
options := sentry.ClientOptions{}
options.DisableTelemetryBuffer = !config.Feature("sentry-telemetry-buffer")
options.Environment = environment
options.EnableLogs = enableLogs
options.EnableTracing = enableTracing
options.TracesSampleRate = 1
tracesSampleRate := 1.00
switch environment {
case "development", "staging":
default:
options.BeforeSendTransaction = func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
sampleRate := 0.05
if trace, ok := event.Contexts["trace"]; ok {
if data, ok := trace["data"].(map[string]any); ok {
if method, ok := data["http.request.method"].(string); ok {
switch method {
case "PUT", "DELETE", "POST":
sampleRate = 1
default:
duration := event.Timestamp.Sub(event.StartTime)
threshold := time.Duration(config.Observability.SlowResponseThreshold)
if duration >= threshold {
sampleRate = 1
}
}
}
}
}
if rand.Float64() < sampleRate {
return event
}
return nil
}
tracesSampleRate = 0.05
}
options := sentry.ClientOptions{}
options.Environment = environment
options.EnableLogs = enableLogs
options.EnableTracing = enableTracing
options.TracesSampleRate = 1 // use our own custom sampling logic
options.BeforeSend = scrubSentryEvent
options.BeforeSendTransaction = chainSentryMiddleware(
sampleSentryEvent(tracesSampleRate),
scrubSentryEvent,
)
if err := sentry.Init(options); err != nil {
log.Fatalf("sentry: %s\n", err)
}
@@ -153,7 +175,6 @@ func InitObservability() {
if enableLogs {
logHandlers = append(logHandlers, sentryslog.Option{
AddSource: true,
LogLevel: levelsFromMinimum(logLevel),
}.NewSentryHandler(context.Background()))
}
}
@@ -161,18 +182,6 @@ func InitObservability() {
slog.SetDefault(slog.New(slogmulti.Fanout(logHandlers...)))
}
// From sentryslog, because for some reason they don't make it public.
func levelsFromMinimum(minLevel slog.Level) []slog.Level {
allLevels := []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError, sentryslog.LevelFatal}
var result []slog.Level
for _, level := range allLevels {
if level >= minLevel {
result = append(result, level)
}
}
return result
}
func FiniObservability() {
var wg sync.WaitGroup
timeout := 2 * time.Second

View File

@@ -132,18 +132,20 @@ func getPage(w http.ResponseWriter, r *http.Request) error {
err = nil
sitePath = strings.TrimPrefix(r.URL.Path, "/")
if projectName, projectPath, hasProjectSlash := strings.Cut(sitePath, "/"); projectName != "" {
var projectManifest *Manifest
var projectMetadata ManifestMetadata
projectManifest, projectMetadata, err = backend.GetManifest(
r.Context(), makeWebRoot(host, projectName),
GetManifestOptions{BypassCache: bypassCache},
)
if err == nil {
if !hasProjectSlash {
writeRedirect(w, http.StatusFound, r.URL.Path+"/")
return nil
if IsValidProjectName(projectName) {
var projectManifest *Manifest
var projectMetadata ManifestMetadata
projectManifest, projectMetadata, err = backend.GetManifest(
r.Context(), makeWebRoot(host, projectName),
GetManifestOptions{BypassCache: bypassCache},
)
if err == nil {
if !hasProjectSlash {
writeRedirect(w, http.StatusFound, r.URL.Path+"/")
return nil
}
sitePath, manifest, metadata = projectPath, projectManifest, projectMetadata
}
sitePath, manifest, metadata = projectPath, projectManifest, projectMetadata
}
}
if manifest == nil && (err == nil || errors.Is(err, ErrObjectNotFound)) {
@@ -214,6 +216,7 @@ func getPage(w http.ResponseWriter, r *http.Request) error {
// we only offer `/.git-pages/archive.tar` and not the `.tar.gz`/`.tar.zst` variants
// because HTTP can already request compression using the `Content-Encoding` mechanism
acceptedEncodings := ParseAcceptEncodingHeader(r.Header.Get("Accept-Encoding"))
w.Header().Add("Vary", "Accept-Encoding")
negotiated := acceptedEncodings.Negotiate("zstd", "gzip", "identity")
if negotiated != "" {
w.Header().Set("Content-Encoding", negotiated)
@@ -325,6 +328,7 @@ func getPage(w http.ResponseWriter, r *http.Request) error {
var offeredEncodings []string
acceptedEncodings := ParseAcceptEncodingHeader(r.Header.Get("Accept-Encoding"))
w.Header().Add("Vary", "Accept-Encoding")
negotiatedEncoding := true
switch entry.GetTransform() {
case Transform_Identity:
@@ -415,13 +419,15 @@ func getPage(w http.ResponseWriter, r *http.Request) error {
io.Copy(w, reader)
}
} else {
// consider content fresh for 60 seconds (the same as the freshness interval of
// manifests in the S3 backend), and use stale content anyway as long as it's not
// older than a hour; while it is cheap to handle If-Modified-Since queries
// server-side, on the client `max-age=0, must-revalidate` causes every resource
// to block the page load every time
w.Header().Set("Cache-Control", "max-age=60, stale-while-revalidate=3600")
// see https://web.dev/articles/stale-while-revalidate for details
if _, hasCacheControl := w.Header()["Cache-Control"]; !hasCacheControl {
// consider content fresh for 60 seconds (the same as the freshness interval of
// manifests in the S3 backend), and use stale content anyway as long as it's not
// older than a hour; while it is cheap to handle If-Modified-Since queries
// server-side, on the client `max-age=0, must-revalidate` causes every resource
// to block the page load every time
w.Header().Set("Cache-Control", "max-age=60, stale-while-revalidate=3600")
// see https://web.dev/articles/stale-while-revalidate for details
}
// http.ServeContent handles conditional requests and range requests
http.ServeContent(w, r, entryPath, mtime, reader)
@@ -493,9 +499,10 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
result = UpdateFromRepository(ctx, webRoot, repoURL, branch)
default:
_, err := AuthorizeUpdateFromArchive(r)
if err != nil {
if auth, err := AuthorizeUpdateFromArchive(r); err != nil {
return err
} else if auth.forgeUser != nil {
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
}
if checkDryRun(w, r) {
@@ -525,8 +532,10 @@ func patchPage(w http.ResponseWriter, r *http.Request) error {
return err
}
if _, err = AuthorizeUpdateFromArchive(r); err != nil {
if auth, err := AuthorizeUpdateFromArchive(r); err != nil {
return err
} else if auth.forgeUser != nil {
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
}
if checkDryRun(w, r) {
@@ -597,12 +606,16 @@ func reportUpdateResult(w http.ResponseWriter, r *http.Request, result UpdateRes
switch result.outcome {
case UpdateError:
if errors.Is(result.err, ErrManifestTooLarge) {
w.WriteHeader(http.StatusRequestEntityTooLarge)
if errors.Is(result.err, ErrSiteTooLarge) {
w.WriteHeader(http.StatusUnprocessableEntity)
} else if errors.Is(result.err, ErrManifestTooLarge) {
w.WriteHeader(http.StatusUnprocessableEntity)
} else if errors.Is(result.err, errArchiveFormat) {
w.WriteHeader(http.StatusUnsupportedMediaType)
} else if errors.Is(result.err, ErrArchiveTooLarge) {
w.WriteHeader(http.StatusRequestEntityTooLarge)
} else if errors.Is(result.err, ErrRepositoryTooLarge) {
w.WriteHeader(http.StatusUnprocessableEntity)
} else if errors.Is(result.err, ErrMalformedPatch) {
w.WriteHeader(http.StatusUnprocessableEntity)
} else if errors.Is(result.err, ErrPreconditionFailed) {
@@ -651,9 +664,10 @@ func deletePage(w http.ResponseWriter, r *http.Request) error {
return err
}
_, err = AuthorizeUpdateFromRepository(r)
if err != nil {
if auth, err := AuthorizeDeletion(r); err != nil {
return err
} else if auth.forgeUser != nil {
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
}
if checkDryRun(w, r) {
@@ -671,7 +685,9 @@ func deletePage(w http.ResponseWriter, r *http.Request) error {
}
func postPage(w http.ResponseWriter, r *http.Request) error {
// Start a timer for the request timeout immediately.
// The HTTP requests for webhook delivery usually have a short timeout. We start the timer
// before doing any time-consuming work so that it's closely aligned to the client's timeout and
// we can respond before the webhook delivery is considered failed.
requestTimeout := 3 * time.Second
requestTimer := time.NewTimer(requestTimeout)
@@ -762,7 +778,7 @@ func postPage(w http.ResponseWriter, r *http.Request) error {
result := UpdateFromRepository(ctx, webRoot, repoURL, auth.branch)
resultChan <- result
observeSiteUpdate("webhook", &result)
}(r.Context())
}(context.WithoutCancel(r.Context()))
var result UpdateResult
select {
@@ -810,7 +826,14 @@ func ServePages(w http.ResponseWriter, r *http.Request) {
// any intentional deviation is an opportunity to miss an issue that will affect our
// visitors but not our health checks.
if r.Header.Get("Health-Check") == "" {
logc.Println(r.Context(), "pages:", r.Method, r.Host, r.URL, r.Header.Get("Content-Type"))
var mediaType string
switch r.Method {
case "HEAD", "GET":
mediaType = r.Header.Get("Accept")
default:
mediaType = r.Header.Get("Content-Type")
}
logc.Println(r.Context(), "pages:", r.Method, r.Host, r.URL, mediaType)
if region := os.Getenv("FLY_REGION"); region != "" {
machine_id := os.Getenv("FLY_MACHINE_ID")
w.Header().Add("Server", fmt.Sprintf("git-pages (fly.io; %s; %s)", region, machine_id))

55
src/pages_test.go Normal file
View File

@@ -0,0 +1,55 @@
package git_pages
import (
"net/http"
"strings"
"testing"
)
func checkHost(t *testing.T, host string, expectOk string, expectErr string) {
host, err := GetHost(&http.Request{Host: host})
if expectErr != "" {
if err == nil || !strings.HasPrefix(err.Error(), expectErr) {
t.Errorf("%s: expect err %s, got err %s", host, expectErr, err)
}
}
if expectOk != "" {
if err != nil {
t.Errorf("%s: expect ok %s, got err %s", host, expectOk, err)
} else if host != expectOk {
t.Errorf("%s: expect ok %s, got ok %s", host, expectOk, host)
}
}
}
func TestHelloName(t *testing.T) {
config = &Config{Features: []string{}}
checkHost(t, "foo.bar", "foo.bar", "")
checkHost(t, "foo-baz.bar", "foo-baz.bar", "")
checkHost(t, "foo--baz.bar", "foo--baz.bar", "")
checkHost(t, "foo.bar.", "foo.bar", "")
checkHost(t, ".foo.bar", "", "reserved host name")
checkHost(t, "..foo.bar", "", "reserved host name")
checkHost(t, "ß.bar", "xn--zca.bar", "")
checkHost(t, "xn--zca.bar", "xn--zca.bar", "")
checkHost(t, "foo-.bar", "", "malformed host name")
checkHost(t, "-foo.bar", "", "malformed host name")
checkHost(t, "foo_.bar", "", "malformed host name")
checkHost(t, "_foo.bar", "", "malformed host name")
checkHost(t, "foo_baz.bar", "", "malformed host name")
checkHost(t, "foo__baz.bar", "", "malformed host name")
checkHost(t, "*.foo.bar", "", "malformed host name")
config = &Config{Features: []string{"relaxed-idna"}}
checkHost(t, "foo-.bar", "", "malformed host name")
checkHost(t, "-foo.bar", "", "malformed host name")
checkHost(t, "foo_.bar", "foo_.bar", "")
checkHost(t, "_foo.bar", "", "reserved host name")
checkHost(t, "foo_baz.bar", "foo_baz.bar", "")
checkHost(t, "foo__baz.bar", "foo__baz.bar", "")
checkHost(t, "*.foo.bar", "", "malformed host name")
}

View File

@@ -48,9 +48,9 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
iter := root
for _, segment := range segments[:len(segments)-1] {
if iter.children == nil {
panic("malformed manifest")
panic("malformed manifest (not a directory)")
} else if _, exists := iter.children[segment]; !exists {
panic("malformed manifest")
panic("malformed manifest (node does not exist)")
} else {
iter = iter.children[segment]
}
@@ -70,7 +70,7 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
return err
}
segments := strings.Split(strings.TrimRight(header.Name, "/"), "/")
segments := strings.Split(normalizeArchiveMemberName(header.Name), "/")
fileName := segments[len(segments)-1]
node := root
for index, segment := range segments[:len(segments)-1] {

View File

@@ -750,6 +750,7 @@ type Principal struct {
state protoimpl.MessageState `protogen:"open.v1"`
IpAddress *string `protobuf:"bytes,1,opt,name=ip_address,json=ipAddress" json:"ip_address,omitempty"`
CliAdmin *bool `protobuf:"varint,2,opt,name=cli_admin,json=cliAdmin" json:"cli_admin,omitempty"`
ForgeUser *ForgeUser `protobuf:"bytes,3,opt,name=forge_user,json=forgeUser" json:"forge_user,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -798,6 +799,73 @@ func (x *Principal) GetCliAdmin() bool {
return false
}
func (x *Principal) GetForgeUser() *ForgeUser {
if x != nil {
return x.ForgeUser
}
return nil
}
type ForgeUser struct {
state protoimpl.MessageState `protogen:"open.v1"`
Origin *string `protobuf:"bytes,1,opt,name=origin" json:"origin,omitempty"`
Id *int64 `protobuf:"varint,2,opt,name=id" json:"id,omitempty"`
Handle *string `protobuf:"bytes,3,opt,name=handle" json:"handle,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ForgeUser) Reset() {
*x = ForgeUser{}
mi := &file_schema_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ForgeUser) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForgeUser) ProtoMessage() {}
func (x *ForgeUser) ProtoReflect() protoreflect.Message {
mi := &file_schema_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForgeUser.ProtoReflect.Descriptor instead.
func (*ForgeUser) Descriptor() ([]byte, []int) {
return file_schema_proto_rawDescGZIP(), []int{8}
}
func (x *ForgeUser) GetOrigin() string {
if x != nil && x.Origin != nil {
return *x.Origin
}
return ""
}
func (x *ForgeUser) GetId() int64 {
if x != nil && x.Id != nil {
return *x.Id
}
return 0
}
func (x *ForgeUser) GetHandle() string {
if x != nil && x.Handle != nil {
return *x.Handle
}
return ""
}
var File_schema_proto protoreflect.FileDescriptor
const file_schema_proto_rawDesc = "" +
@@ -853,11 +921,18 @@ const file_schema_proto_rawDesc = "" +
"\x06domain\x18\n" +
" \x01(\tR\x06domain\x12\x18\n" +
"\aproject\x18\v \x01(\tR\aproject\x12%\n" +
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"G\n" +
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"r\n" +
"\tPrincipal\x12\x1d\n" +
"\n" +
"ip_address\x18\x01 \x01(\tR\tipAddress\x12\x1b\n" +
"\tcli_admin\x18\x02 \x01(\bR\bcliAdmin*V\n" +
"\tcli_admin\x18\x02 \x01(\bR\bcliAdmin\x12)\n" +
"\n" +
"forge_user\x18\x03 \x01(\v2\n" +
".ForgeUserR\tforgeUser\"K\n" +
"\tForgeUser\x12\x16\n" +
"\x06origin\x18\x01 \x01(\tR\x06origin\x12\x0e\n" +
"\x02id\x18\x02 \x01(\x03R\x02id\x12\x16\n" +
"\x06handle\x18\x03 \x01(\tR\x06handle*V\n" +
"\x04Type\x12\x10\n" +
"\fInvalidEntry\x10\x00\x12\r\n" +
"\tDirectory\x10\x01\x12\x0e\n" +
@@ -889,7 +964,7 @@ func file_schema_proto_rawDescGZIP() []byte {
}
var file_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_schema_proto_goTypes = []any{
(Type)(0), // 0: Type
(Transform)(0), // 1: Transform
@@ -902,27 +977,29 @@ var file_schema_proto_goTypes = []any{
(*Manifest)(nil), // 8: Manifest
(*AuditRecord)(nil), // 9: AuditRecord
(*Principal)(nil), // 10: Principal
nil, // 11: Manifest.ContentsEntry
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
(*ForgeUser)(nil), // 11: ForgeUser
nil, // 12: Manifest.ContentsEntry
(*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
}
var file_schema_proto_depIdxs = []int32{
0, // 0: Entry.type:type_name -> Type
1, // 1: Entry.transform:type_name -> Transform
5, // 2: HeaderRule.header_map:type_name -> Header
11, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
12, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
4, // 4: Manifest.redirects:type_name -> RedirectRule
6, // 5: Manifest.headers:type_name -> HeaderRule
7, // 6: Manifest.problems:type_name -> Problem
12, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
13, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
2, // 8: AuditRecord.event:type_name -> AuditEvent
10, // 9: AuditRecord.principal:type_name -> Principal
8, // 10: AuditRecord.manifest:type_name -> Manifest
3, // 11: Manifest.ContentsEntry.value:type_name -> Entry
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
11, // 11: Principal.forge_user:type_name -> ForgeUser
3, // 12: Manifest.ContentsEntry.value:type_name -> Entry
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_schema_proto_init() }
@@ -936,7 +1013,7 @@ func file_schema_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_schema_proto_rawDesc), len(file_schema_proto_rawDesc)),
NumEnums: 3,
NumMessages: 9,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -132,4 +132,11 @@ message AuditRecord {
message Principal {
string ip_address = 1;
bool cli_admin = 2;
ForgeUser forge_user = 3;
}
message ForgeUser {
string origin = 1;
int64 id = 2;
string handle = 3;
}

View File

@@ -1,4 +1,6 @@
//go:build unix
// See https://pkg.go.dev/os/signal#hdr-Windows for a description of what this module
// will do on Windows (tl;dr nothing calls the reload handler, the interrupt handler works
// more or less how you'd expect).
package git_pages
@@ -21,7 +23,7 @@ func OnReload(handler func()) {
func WaitForInterrupt() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, syscall.SIGINT)
signal.Notify(sigint, syscall.SIGINT, syscall.SIGTERM)
<-sigint
signal.Stop(sigint)
}

View File

@@ -1,13 +0,0 @@
//go:build !unix
package git_pages
func OnReload(handler func()) {
// not implemented
}
func WaitForInterrupt() {
for {
// Ctrl+C not supported
}
}

View File

@@ -182,6 +182,9 @@ func PartialUpdateFromArchive(
// `*Manifest` objects, which should never be mutated.
newManifest := &Manifest{}
proto.Merge(newManifest, oldManifest)
newManifest.RepoUrl = nil
newManifest.Branch = nil
newManifest.Commit = nil
if err := ApplyTarPatch(newManifest, reader, parents); err != nil {
return nil, err
} else {

View File

@@ -11,7 +11,7 @@ import (
type WildcardPattern struct {
Domain []string
CloneURL *fasttemplate.Template
IndexRepos []*fasttemplate.Template
IndexRepo *fasttemplate.Template
IndexBranch string
Authorization bool
}
@@ -49,27 +49,24 @@ func (pattern *WildcardPattern) Matches(host string) (string, bool) {
return subdomain, true
}
func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) ([]string, string) {
var repoURLs []string
func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) (string, string) {
var repoURL string
var branch string
repoURLTemplate := pattern.CloneURL
if projectName == ".index" {
for _, indexRepoTemplate := range pattern.IndexRepos {
indexRepo := indexRepoTemplate.ExecuteString(map[string]any{"user": userName})
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
"user": userName,
"project": indexRepo,
}))
}
repoURL = repoURLTemplate.ExecuteString(map[string]any{
"user": userName,
"project": pattern.IndexRepo.ExecuteString(map[string]any{"user": userName}),
})
branch = pattern.IndexBranch
} else {
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
repoURL = repoURLTemplate.ExecuteString(map[string]any{
"user": userName,
"project": projectName,
}))
})
branch = "pages"
}
return repoURLs, branch
return repoURL, branch
}
func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
@@ -80,14 +77,10 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
return nil, fmt.Errorf("wildcard pattern: clone URL: %w", err)
}
var indexRepoTemplates []*fasttemplate.Template
var indexRepoBranch string = config.IndexRepoBranch
for _, indexRepo := range config.IndexRepos {
indexRepoTemplate, err := fasttemplate.NewTemplate(indexRepo, "<", ">")
if err != nil {
return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
}
indexRepoTemplates = append(indexRepoTemplates, indexRepoTemplate)
indexRepoTemplate, err := fasttemplate.NewTemplate(config.IndexRepo, "<", ">")
if err != nil {
return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
}
authorization := false
@@ -107,7 +100,7 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
wildcardPatterns = append(wildcardPatterns, &WildcardPattern{
Domain: strings.Split(config.Domain, "."),
CloneURL: cloneURLTemplate,
IndexRepos: indexRepoTemplates,
IndexRepo: indexRepoTemplate,
IndexBranch: indexRepoBranch,
Authorization: authorization,
})