{"id":49888908,"url":"https://github.com/oltdaniel/caddy-custom","last_synced_at":"2026-05-15T20:01:30.750Z","repository":{"id":357529158,"uuid":"1236908686","full_name":"oltdaniel/caddy-custom","owner":"oltdaniel","description":"Publish your custom caddy automatically as binary, container, apk, deb, ...","archived":false,"fork":false,"pushed_at":"2026-05-13T05:51:43.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-13T07:33:36.377Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oltdaniel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T17:31:21.000Z","updated_at":"2026-05-13T05:48:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oltdaniel/caddy-custom","commit_stats":null,"previous_names":["oltdaniel/caddy-custom"],"tags_count":2,"template":true,"template_full_name":null,"purl":"pkg:github/oltdaniel/caddy-custom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oltdaniel%2Fcaddy-custom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oltdaniel%2Fcaddy-custom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oltdaniel%2Fcaddy-custom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oltdaniel%2Fcaddy-custom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oltdaniel","download_url":"https://codeload.github.com/oltdaniel/caddy-custom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oltdaniel%2Fcaddy-custom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33077920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-15T20:01:28.698Z","updated_at":"2026-05-15T20:01:30.726Z","avatar_url":"https://github.com/oltdaniel.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Custom Caddy build \u0026 release\n\nBuilds [Caddy](https://caddyserver.com) with a curated plugin set, packages\nit as binary tarballs, `.deb`, `.apk`, and multi-arch Docker images, then\npublishes the result to either a [Forgejo](https://forgejo.org) instance\nor GitHub. Driven by a single config file: [build.yaml](build.yaml).\n\n## AI Disclaimer\n\nThis repository was bootstrapped and largely written with [Claude\nCode](https://claude.com/claude-code). Since my primary use case\nis automating custom Caddy packaging within my homelab, I'm\ncomfortable with AI-assisted code generation and the resulting\ncodebase size.\n\nHowever, all AI-generated output undergoes human review and validation\nthrough automated test suites or manual testing before merging.\n\n## Quick start\n\n```sh\n./build.sh tools # download yq, nfpm, and xcaddy into ./tools/\n./build.sh all # build every format enabled in build.yaml\n./release.sh # publish to the configured provider\n```\n\nIn CI, the workflows in [.forgejo/workflows](.forgejo/workflows) and\n[.github/workflows](.github/workflows) do exactly this on every push to\n`main`.\n\n## How versioning works\n\nThe published package version is computed at build time from\n`caddy.version` plus `caddy.version_suffix`:\n\n| `version_suffix` | Resolves to | Notes |\n|-|-|-|\n| `\"auto\"` (default)  | `\u003ccaddy.version\u003e.\u003cYYYYMMDD\u003e.\u003ccommit-count\u003e`  | Same commit produces the same version, so the already-published check skips rebuild. |\n| `\"\"` | `\u003ccaddy.version\u003e` | No suffix. |\n| `\"X\"` (anything else) | `\u003ccaddy.version\u003e.X` | Literal; digits/letters/dots only. |\n\nSo with `caddy.version: 2.11.2`, `version_suffix: auto`, and 42 commits\nin the repo today, you get `2.11.2.20260508.42`. Re-running the same\ncommit yields the same version, and the check job below short-circuits.\n\nThe nfpm `release` field (the `-1` in `caddy_\u003cver\u003e-1_amd64.deb`, `-r1`\nin the `.apk`) is hardcoded to `1`. All uniqueness lives in the version\nitself.\n\n\u003e If `auto` runs outside a git repo or against a shallow clone, the\n\u003e commit count falls back to `0` and rebuilds will collide. CI workflows\n\u003e clone with `fetch-depth: 0` to avoid this.\n\n## Providers\n\nThe release backend is selected by `release.provider` in `build.yaml`,\nwith the `RELEASE_PROVIDER` env var taking precedence. Each CI workflow\nsets the env explicitly so it stays self-contained.\n\nBoth providers create a Release tagged `v\u003cpkg_version\u003e` with binary,\ndeb, and apk attached as assets, so users get the same discovery\nsurface either way. The provider-specific extras differ:\n\n| Format | forgejo | github |\n|-|-|-|\n| binary | generic registry + release asset | release asset |\n| deb | debian registry + release asset  | release asset |\n| apk | alpine registry + release asset  | release asset |\n| docker | container registry | `ghcr.io` |\n| Release page | yes | yes |\n\nRe-uploading a same-named asset replaces the existing one on both\nproviders, so re-running a workflow on the same commit is a no-op.\n\n### Container image naming\n\nThe host and owner are auto-detected from the CI runner env —\n`FORGEJO_SERVER_URL` + `FORGEJO_REPOSITORY` for the forgejo backend\n(with `GITHUB_*` aliases as a fallback for older runners), and\n`GITHUB_REPOSITORY` for the github backend. Export manually for local\nruns — see [below](#running-releasesh-locally).\n\n| Backend | Image URL |\n|-|-|\n| forgejo | `\u003cforgejo-host\u003e/\u003cowner\u003e/\u003cdocker.image\u003e` |\n| github  | `ghcr.io/\u003cowner-lowercased\u003e/\u003crepo\u003e/\u003crelease.github.container_subpackage\u003e` if set, else `ghcr.io/\u003cowner-lowercased\u003e/\u003cdocker.image\u003e` |\n\nOn GitHub, `release.github.container_subpackage` is optional and names\na sub-package under the repo. The repo prefix is added automatically:\n\n| `release.github.container_subpackage` | Resulting URL |\n|-|-|\n| unset (defaults to `docker.image`) | `ghcr.io/oltdaniel/caddy-custom` |\n| `server` | `ghcr.io/oltdaniel/caddy-custom/server` |\n\nBoth forms are auto-linked to the repo (visibility on the repo page,\npermissions inheritance) — the default because `docker.image` matches\nthe repo name; the sub-package form because GitHub matches its leading\nsegment to a repo under the same owner. The choice is just:\n\n- Default: a top-level package named after `docker.image`.\n- Sub-package: set `container_subpackage` to anything else (e.g.\n  `server`) to publish under `ghcr.io/\u003cowner\u003e/\u003crepo\u003e/...`.\n\n### Token scopes\n\n| Provider | Env var (read by scripts) | CI secret name | Required scopes |\n|-|-|-|-|\n| forgejo | `FORGEJO_TOKEN` | `RELEASE_TOKEN` (Forgejo reserves the `FORGEJO_TOKEN` name) | `write:package` (package + container registries) **and** `write:repository` (releases + asset uploads) |\n| github | `GITHUB_TOKEN` | `GITHUB_TOKEN` (auto-injected) | `contents: write` (releases) **and** `packages: write` (ghcr) |\n\nFor just `release.sh check`, read scopes (`read:package` +\n`read:repository` on Forgejo; default `GITHUB_TOKEN` on GitHub) are\nenough — the check job runs without a write-scoped token if the\nregistry/repo is publicly readable.\n\nOn the GitHub workflow, `GITHUB_TOKEN` is auto-provided; the\n`permissions` block at the top of the workflow declares the two scopes.\nOn the Forgejo workflow, the token is supplied via the `RELEASE_TOKEN`\nsecret — Forgejo reserves `FORGEJO_TOKEN` for its own auto-injected\nrunner token, so user-defined secrets can't use that name. Each\nworkflow step maps `RELEASE_TOKEN` onto the `FORGEJO_TOKEN` env var\nthat the scripts and API calls read.\n\n## Installing \u0026 upgrading\n\nHow to consume the published artifacts, by format. Examples use the\nvalues currently in [build.yaml](build.yaml) (forgejo host\n`codeberg.org`, owner `oltdaniel`, package name `caddy-custom`).\nSubstitute `\u003cpkg_version\u003e` with a real version (e.g. `2.11.0.20260512.42`)\nand swap `amd64` / `x86_64` for your architecture as needed.\n\n### Binary tarball\n\nForgejo (generic registry):\n```sh\ncurl -fsSL -o caddy.tar.gz \\\n  https://codeberg.org/api/packages/oltdaniel/generic/caddy-custom/\u003cpkg_version\u003e/caddy_\u003cpkg_version\u003e_linux_amd64.tar.gz\ntar -xzf caddy.tar.gz caddy\nsudo install -m 755 caddy /usr/local/bin/\n```\n\nGitHub (release asset):\n```sh\ncurl -fsSL -o caddy.tar.gz \\\n  https://github.com/oltdaniel/caddy-custom/releases/download/v\u003cpkg_version\u003e/caddy_\u003cpkg_version\u003e_linux_amd64.tar.gz\ntar -xzf caddy.tar.gz caddy\nsudo install -m 755 caddy /usr/local/bin/\n```\n\nUpgrade: re-run the same commands with a newer `\u003cpkg_version\u003e`. There\nis no package manager — the binary is just overwritten.\n\n### Debian (`.deb`)\n\nForgejo (debian registry, integrates with `apt`):\n```sh\nsudo install -d -m 0755 /etc/apt/keyrings\ncurl -fsSL https://codeberg.org/api/packages/oltdaniel/debian/repository.key \\\n  | sudo gpg --dearmor -o /etc/apt/keyrings/caddy-custom.gpg\necho \"deb [signed-by=/etc/apt/keyrings/caddy-custom.gpg] https://codeberg.org/api/packages/oltdaniel/debian stable main\" \\\n  | sudo tee /etc/apt/sources.list.d/caddy-custom.list\nsudo apt update\nsudo apt install caddy-custom\n```\n\nUpgrade: `sudo apt update \u0026\u0026 sudo apt upgrade caddy-custom`.\n\nGitHub (release asset, manual install):\n```sh\ncurl -fsSL -o caddy.deb \\\n  https://github.com/oltdaniel/caddy-custom/releases/download/v\u003cpkg_version\u003e/caddy-custom_\u003cpkg_version\u003e-1_amd64.deb\nsudo apt install ./caddy.deb\n```\n\nUpgrade: download the new `.deb` and run `sudo apt install ./caddy.deb`\nagain — `apt` detects the higher version and upgrades in place.\n\n### Alpine (`.apk`)\n\nForgejo (alpine registry, integrates with `apk`):\n```sh\ncurl -fsSL -o /etc/apk/keys/caddy-custom.rsa.pub \\\n  https://codeberg.org/api/packages/oltdaniel/alpine/key\necho \"https://codeberg.org/api/packages/oltdaniel/alpine/v3/caddy-custom\" \\\n  | sudo tee -a /etc/apk/repositories\nsudo apk update\nsudo apk add caddy-custom\n```\n\nUpgrade: `sudo apk update \u0026\u0026 sudo apk upgrade caddy-custom`.\n\nGitHub (release asset, manual install):\n```sh\ncurl -fsSL -o caddy.apk \\\n  https://github.com/oltdaniel/caddy-custom/releases/download/v\u003cpkg_version\u003e/caddy-custom_\u003cpkg_version\u003e-r1_x86_64.apk\nsudo apk add --allow-untrusted caddy.apk\n```\n\nUpgrade: re-run the same commands with a newer `\u003cpkg_version\u003e`. The\n`--allow-untrusted` flag is required because the standalone `.apk`\nisn't signed against any key in `/etc/apk/keys/`.\n\n\u003e **OpenRC service.** The apk already ships `/etc/init.d/caddy`, so enable\n\u003e and start it directly:\n\u003e ```sh\n\u003e rc-update add caddy default\n\u003e rc-service caddy start\n\u003e ```\n\u003e Alpine community's [`caddy-openrc`](https://pkgs.alpinelinux.org/package/edge/community/x86_64/caddy-openrc)\n\u003e contains the same init script packaged standalone (no hard dependency on\n\u003e the upstream `caddy` binary). If you prefer to have it owned by the\n\u003e upstream package, install with `sudo apk add --force-overwrite caddy-openrc` —\n\u003e the file contents are byte-identical to ours, so apk only emits an\n\u003e overwrite warning.\n\n### Docker\n\nForgejo container registry:\n```sh\ndocker pull codeberg.org/oltdaniel/caddy-custom:latest\n```\n\nGitHub `ghcr.io`:\n```sh\ndocker pull ghcr.io/oltdaniel/caddy-custom:latest\n```\n\nBoth registries publish a multi-arch manifest, so `docker pull`\nauto-selects the right platform. Upgrade is another `docker pull`\n(plus a container restart). Pin to `:\u003cpkg_version\u003e` instead of\n`:latest` if you want reproducible deployments.\n\n## CI flow\n\nBoth workflows follow the same two-job shape:\n\n1. **`check`** — runs `./release.sh check`, which queries the provider\n for the current `pkg_version` and outputs `published=true|false`.\n Only needs `bash`, `curl`, `yq`.\n2. **`build-and-publish`** — gated on `published != 'true'`. Runs\n `./build.sh all` then `./release.sh`. The whole job is skipped (no\n runner allocated) when the version already exists.\n\nThe result: pushing the same commit twice does no work the second time;\npushing a new commit always builds because the commit count differs.\n\nA separate `auto-update` workflow runs on a monthly schedule. It calls\n[`check-updates.sh`](check-updates.sh) to detect new upstream releases\n(Caddy, xcaddy, plugins, yq, nfpm) and new content in the tracked\n`dist/` sources, then either opens a rolling PR or commits directly to\n`main` depending on `auto_update.mode`.\n\n\u003e On **GitHub**, `auto_update.mode: pr` additionally requires the\n\u003e repository setting *Settings → Actions → General → Workflow\n\u003e permissions → Allow GitHub Actions to create and approve pull\n\u003e requests* to be enabled. Without it, the workflow fails at PR-open\n\u003e time with a 403 even when the token scopes are correct. On\n\u003e **Forgejo**, the token scopes listed above (`write:repository`) are\n\u003e sufficient — no extra repo-level toggle.\n\n## Reproducibility and pinning\n\nAll external dependencies are pinned to achieve reproducible builds.\n\n**Caddy.** Pinned by release tag. Set `caddy.version` to an explicit\nsemver (e.g., `\"2.11.2\"`), not `\"latest\"`. Resolved via `go.sum` during\nthe xcaddy build, so integrity is cryptographically verified by Go.\n\n**Plugins.** Each plugin in `xcaddy.plugins` *must* include a version\ntag (preferred: `@vX.Y.Z`; fallback: `@\u003ccommit-sha\u003e` for projects\nwithout release tags). Bare module paths are rejected at build time.\nExample:\n\n```yaml\nplugins:\n  - github.com/caddy-dns/cloudflare@v0.2.4\n  - github.com/some-plugin/name@abc123def456\n```\n\n**Build tools (yq, nfpm, xcaddy).** All three pinned by version +\nper-architecture SHA256 in [lib/tools.sh](lib/tools.sh). Downloads are\nverified against the hash before use. [`check-updates.sh`](check-updates.sh)\nproposes new versions with their hashes together, keeping the pins in\nsync. xcaddy's hash is computed locally from the downloaded tarball\n(upstream publishes SHA-512 only); yq and nfpm use the SHA-256\n`checksums.txt` from their releases. The Go toolchain is still required\nat build time because xcaddy invokes `go build` to produce the Caddy\nbinary — it is no longer needed to install xcaddy itself.\n\n**`dist/` content.** Files imported from\n[caddyserver/dist](https://github.com/caddyserver/dist) and\n[alpinelinux/aports](https://github.com/alpinelinux/aports) are pinned\nby commit SHA in `build.yaml` under `dist.*`. The update checker fetches\neach tracked file from upstream, diffs it against the local copy, and\non `--apply` rewrites the changed file and bumps the pin.\n\n**Container image.** The base image (`docker.base_image`) is\nintentionally *tag*-pinned but *not* digest-pinned. The Dockerfile runs\n`apk add` from alpine's repos, which floats independently of the base\ndigest — a digest pin would be misleading. Container freshness comes\nfrom the monthly rebuild, not from pinning. The Caddy binary *inside*\nthe container is pinned and SHA256-verified as described above.\n\n## Configuration surface\n\nAll configuration lives in [build.yaml](build.yaml). The most common\nknobs:\n\n- `caddy.version` — upstream Caddy version. Either `\"latest\"` (resolves\n  to the current `caddyserver/caddy` release tag at build time) or an\n  explicit semver like `\"2.11.2\"`. Pre-release tags (`\"2.12.0-beta1\"`)\n  are allowed; branches, commit SHAs, and `\"nightly\"` are rejected\n  because the resulting `pkg_version` has to be deb/apk/Docker-safe.\n- `xcaddy.plugins` — `--with` modules. Versions and replacements\n  supported per [xcaddy](https://github.com/caddyserver/xcaddy)'s syntax.\n- `architectures[]` — comment out rows you don't need; each row produces\n  one binary, optionally one deb, one apk, and one Docker platform.\n- `formats.*` — toggle binary / deb / apk / docker on or off. The same\n  flag gates both the build (`build.sh all`) and the publish\n  (`release.sh` with no targets); a format that's `false` is skipped\n  end-to-end.\n- `release.provider` — `forgejo` or `github`.\n- `auto_update.mode` — `pr` (rolling PR) or `auto` (commit to `main`).\n\n## Local development\n\n```sh\n./build.sh tools # download yq/nfpm/xcaddy (SHA256-verified)\n./build.sh binary # per-arch binaries into ./out/binaries/\n./build.sh deb apk # nfpm packages into ./out/packages/\n./build.sh docker # local Docker image\n./build.sh clean # rm -rf ./out/\n```\n\n`build.sh` and `release.sh` are intentionally independent. There is no\ncombined entry point — CI workflows orchestrate the two-step flow\nexplicitly.\n\n### Running release.sh locally\n\nIn CI, the runner auto-populates the host + `owner/repo` env vars, so\n`release.sh` knows where to publish without any config:\n\n- Forgejo Actions (v7+ runner) sets [`FORGEJO_SERVER_URL` and\n  `FORGEJO_REPOSITORY`][forgejo-env], plus the `GITHUB_*` aliases for\n  GitHub Actions compatibility. The forgejo backend prefers the\n  `FORGEJO_*` names and falls back to `GITHUB_*`.\n- GitHub Actions sets `GITHUB_REPOSITORY`. (The host is always\n  `api.github.com`, so no server-URL var is needed.)\n\n[forgejo-env]: https://forgejo.org/docs/next/user/actions/reference/#env-1\n\nOutside CI those env vars are unset, so `release.sh` will refuse to run\nunless you export them yourself:\n\n```sh\n# Forgejo (e.g. publishing to Codeberg)\nFORGEJO_SERVER_URL=https://codeberg.org \\\nFORGEJO_REPOSITORY=oltdaniel/caddy-custom \\\nFORGEJO_TOKEN=... \\\n  ./release.sh check                        # is this pkg_version published?\n\nFORGEJO_SERVER_URL=https://codeberg.org \\\nFORGEJO_REPOSITORY=oltdaniel/caddy-custom \\\nFORGEJO_TOKEN=... \\\n  ./release.sh binary deb                   # publish only those targets\n\n# GitHub (no server-URL var — host is api.github.com)\nGITHUB_REPOSITORY=oltdaniel/caddy-custom \\\nGITHUB_TOKEN=... RELEASE_PROVIDER=github \\\n  ./release.sh check\n```\n\nFor just `release.sh check` against a public repo/registry, the token\ncan be omitted (read-only HTTP is unauthenticated). The host and\n`owner/repo` vars are still required so the script knows what to query.\n\n## Tests\n\nThe scripts in [tests/](tests/) exercise the produced artifacts on a\nhost with the matching runtime available:\n\n| Script | What it checks |\n|-|-|\n| `tests/binary.sh` | Tarball extracts, binary runs, `caddy version` includes the pinned plugins. |\n| `tests/deb.sh` | `.deb` installs into a Debian container, service starts, removes cleanly. |\n| `tests/apk.sh` | `.apk` installs into an Alpine container, OpenRC init script works. |\n| `tests/docker.sh` | Image starts, default Caddyfile resolves, welcome page is served. |\n\nThese are run by hand or in CI before publishing — they are not part of\nthe default `build.sh all` target.\n\n## Requirements\n\n- `bash`, `curl`, `git`, `gettext` (for `envsubst`)\n- Go toolchain (xcaddy invokes it to compile the Caddy binary)\n- Docker + buildx (only for the `docker` target)\n\n`yq`, `nfpm`, and `xcaddy` are bootstrapped automatically into `./tools/`\nfrom their GitHub release tarballs, verified against the SHA256 pins in\n[lib/tools.sh](lib/tools.sh).\n\n## Layout\n\n```\nbuild.sh entry point for building artifacts\nrelease.sh entry point for publishing — dispatches on provider\ncheck-updates.sh detects pin drift for Caddy, xcaddy, plugins, tools, dist files\nbuild.yaml configuration\nlib/\n  common.sh shared helpers (cfg/yq, pkg_version, tool bootstrap)\n  tools.sh pinned versions + SHA256 for yq, nfpm\n  build-binary.sh xcaddy invocation\n  build-packages.sh  nfpm deb/apk packaging\n  build-docker.sh buildx multi-arch image\n  release-forgejo.sh forgejo backend (registries + releases)\n  release-github.sh  github backend (releases + ghcr.io)\ntemplates/ nfpm + Dockerfile templates (envsubst-rendered)\ndist/ static payload synced from upstream Caddy/Alpine repos\ntests/ artifact-level smoke tests\n```\n\n## License\n\nThe build scripts, workflows, tests, templates, and configuration in\nthis repository are licensed under the [Apache License 2.0](LICENSE).\n\nFiles under [dist/](dist/) are synced verbatim from upstream and remain\nunder their original licenses:\n\n| Path | Upstream | License |\n|-|-|-|\n| `dist/caddy-dist/` | [caddyserver/dist](https://github.com/caddyserver/dist) | Apache-2.0 |\n| `dist/aports/` | [alpinelinux/aports](https://github.com/alpinelinux/aports) (`community/caddy`) | MIT (per the aports repository) |\n\nCaddy itself is built from\n[caddyserver/caddy](https://github.com/caddyserver/caddy) (Apache-2.0)\nvia xcaddy; the resulting binary is redistributed under that license.\nPlugin licenses depend on each module in `xcaddy.plugins` — check the\nrespective project for terms.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foltdaniel%2Fcaddy-custom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foltdaniel%2Fcaddy-custom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foltdaniel%2Fcaddy-custom/lists"}