mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-15 19:51:33 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3830af5392 | ||
|
|
9e9664013b | ||
|
|
3e377986bc | ||
|
|
c85c7327bf | ||
|
|
886ee2ddae | ||
|
|
ac751e23b5 | ||
|
|
ebe7d07b3b | ||
|
|
4f14c345a6 | ||
|
|
7e293d6ef9 | ||
|
|
f7067b939b | ||
|
|
6bf4200f26 | ||
|
|
e9a5a901ec | ||
|
|
d3c8db6229 | ||
|
|
8f811147d6 | ||
|
|
0d33c64372 | ||
|
|
9b25ccdc35 | ||
|
|
18012d46e8 | ||
|
|
750f76aa9d | ||
|
|
6019a64c41 | ||
|
|
890029a98d | ||
|
|
cf26a89026 | ||
|
|
b75c37f651 | ||
|
|
c84e773df1 | ||
|
|
07133df6d2 | ||
|
|
1f1927d95d | ||
|
|
7334b8f637 | ||
|
|
96f210d253 | ||
|
|
a4bfa82388 | ||
|
|
338957eb3f | ||
|
|
26d9d784ba | ||
|
|
f163b9a42a | ||
|
|
04729c1f48 | ||
|
|
121f557048 | ||
|
|
c5df116673 | ||
|
|
71fd1c39df | ||
|
|
d97f5ac056 | ||
|
|
79407ba406 | ||
|
|
937aadc5d3 | ||
|
|
24dbab6813 | ||
|
|
30b6db2758 | ||
|
|
7655400560 | ||
|
|
32ccb0920f | ||
|
|
c88d04c71b | ||
|
|
86845f2505 | ||
|
|
7f112a761c | ||
|
|
a9cf69c04a | ||
|
|
132d093021 | ||
|
|
62917824fa | ||
|
|
62ef4a5366 | ||
|
|
8fa986015d | ||
|
|
8d574e5e7d | ||
|
|
91f05e210e | ||
|
|
bc70cba215 | ||
|
|
8b049da3c7 | ||
|
|
325d6bedda | ||
|
|
fc9e6fcf7b |
@@ -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 \
|
||||
|
||||
16
Dockerfile
16
Dockerfile
@@ -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"]
|
||||
|
||||
11
README.md
11
README.md
@@ -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)
|
||||
|
||||
@@ -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
4
conf/config.docker.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[server]
|
||||
pages = "tcp/:3000"
|
||||
caddy = "tcp/:3001"
|
||||
metrics = "tcp/:3002"
|
||||
@@ -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
24
flake.lock
generated
@@ -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"
|
||||
}
|
||||
|
||||
23
flake.nix
23
flake.nix
@@ -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
44
go.mod
@@ -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
96
go.sum
@@ -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
207
gomod2nix.toml
Normal 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="
|
||||
@@ -14,5 +14,7 @@
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"automerge": false
|
||||
}
|
||||
},
|
||||
"semanticCommits": "disabled",
|
||||
"commitMessagePrefix": "[Renovate]"
|
||||
}
|
||||
|
||||
12
src/audit.go
12
src/audit.go
@@ -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,
|
||||
|
||||
179
src/auth.go
179
src/auth.go
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
|
||||
25
src/fetch.go
25
src/fetch.go
@@ -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
|
||||
}
|
||||
|
||||
35
src/main.go
35
src/main.go
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
127
src/observe.go
127
src/observe.go
@@ -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
|
||||
|
||||
79
src/pages.go
79
src/pages.go
@@ -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
55
src/pages_test.go
Normal 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")
|
||||
}
|
||||
@@ -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] {
|
||||
|
||||
105
src/schema.pb.go
105
src/schema.pb.go
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//go:build !unix
|
||||
|
||||
package git_pages
|
||||
|
||||
func OnReload(handler func()) {
|
||||
// not implemented
|
||||
}
|
||||
|
||||
func WaitForInterrupt() {
|
||||
for {
|
||||
// Ctrl+C not supported
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user