diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b69e058..aa5bb3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,106 +1,21 @@ name: CI on: - push: - branches: [main] pull_request: branches: [main] -env: - REGISTRY: ghcr.io - HARBOR_REGISTRY: registry.goharbor.io/bupd - IMAGE_NAME: ${{ github.repository }} - jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - - - name: Verify formatting - run: test -z "$(gofmt -l .)" - - - name: Lint - run: go vet ./... - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v ./... - - docker: + test: runs-on: ubuntu-latest - needs: build - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + container: + image: golang:1.25-bookworm permissions: contents: read - packages: write - id-token: write steps: - uses: actions/checkout@v4 - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Install cosign - uses: sigstore/cosign-installer@v3 - - - name: Install crane - uses: imjasonh/setup-crane@v0.4 - - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Harbor - uses: docker/login-action@v3 - with: - registry: registry.goharbor.io - username: ${{ secrets.HARBOR_USERNAME }} - password: ${{ secrets.HARBOR_PASSWORD }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=latest - type=sha,prefix= - - - name: Build and push - id: build-and-push - uses: docker/build-push-action@v6 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - sbom: true - provenance: mode=max - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Sign GHCR image - run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} - - - name: Copy image to Harbor - run: crane copy ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} ${{ env.HARBOR_REGISTRY }}/shitpost:latest + - name: Install Task + run: go install github.com/go-task/task/v3/cmd/task@v3.45.4 - - name: Sign Harbor image - run: cosign sign --yes ${{ env.HARBOR_REGISTRY }}/shitpost:latest + - name: Run task CI + run: task ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d279df1..55cf775 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,14 +7,16 @@ on: env: REGISTRY: ghcr.io - HARBOR_REGISTRY: registry.goharbor.io/bupd IMAGE_NAME: ${{ github.repository }} jobs: - goreleaser: + binaries: runs-on: ubuntu-latest + outputs: + hashes: ${{ steps.hash.outputs.hashes }} permissions: contents: write + id-token: write steps: - uses: actions/checkout@v4 with: @@ -28,7 +30,11 @@ jobs: - name: Install syft uses: anchore/sbom-action/download-syft@v0 + - name: Install cosign + uses: sigstore/cosign-installer@v3 + - name: Run GoReleaser + id: run-goreleaser uses: goreleaser/goreleaser-action@v6 with: version: latest @@ -36,9 +42,36 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - docker: + - name: Generate SLSA subjects + id: hash + env: + ARTIFACTS: ${{ steps.run-goreleaser.outputs.artifacts }} + run: | + set -euo pipefail + hashes=$(echo "$ARTIFACTS" | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0) + if test "$hashes" = ""; then + checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select(.type=="Checksum") | .path') + hashes=$(base64 -w0 "$checksum_file") + fi + echo "hashes=$hashes" >> "$GITHUB_OUTPUT" + + binary-provenance: + needs: binaries + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 + with: + base64-subjects: ${{ needs.binaries.outputs.hashes }} + upload-assets: true + + image: runs-on: ubuntu-latest - needs: goreleaser + needs: binaries + outputs: + digest: ${{ steps.build-and-push.outputs.digest }} + image: ${{ steps.image.outputs.image }} permissions: contents: read packages: write @@ -55,9 +88,6 @@ jobs: - name: Install cosign uses: sigstore/cosign-installer@v3 - - name: Install crane - uses: imjasonh/setup-crane@v0.4 - - name: Login to GHCR uses: docker/login-action@v3 with: @@ -65,13 +95,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Harbor - uses: docker/login-action@v3 - with: - registry: registry.goharbor.io - username: ${{ secrets.HARBOR_USERNAME }} - password: ${{ secrets.HARBOR_PASSWORD }} - - name: Extract metadata id: meta uses: docker/metadata-action@v5 @@ -83,7 +106,7 @@ jobs: type=semver,pattern={{major}} type=raw,value=latest - - name: Build and push + - name: Build and push GHCR image id: build-and-push uses: docker/build-push-action@v6 with: @@ -97,20 +120,23 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + - name: Output image name + id: image + run: echo "image=${REGISTRY}/${IMAGE_NAME}" >> "$GITHUB_OUTPUT" + - name: Sign GHCR image run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} - - name: Get version tag - id: version - run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - - name: Copy image to Harbor - run: crane copy ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} ${{ env.HARBOR_REGISTRY }}/shitpost:${{ steps.version.outputs.tag }} - - - name: Copy latest to Harbor - run: crane copy ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} ${{ env.HARBOR_REGISTRY }}/shitpost:latest - - - name: Sign Harbor images - run: | - cosign sign --yes ${{ env.HARBOR_REGISTRY }}/shitpost:${{ steps.version.outputs.tag }} - cosign sign --yes ${{ env.HARBOR_REGISTRY }}/shitpost:latest + image-provenance: + needs: image + permissions: + actions: read + id-token: write + packages: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 + with: + image: ${{ needs.image.outputs.image }} + digest: ${{ needs.image.outputs.digest }} + registry-username: ${{ github.actor }} + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 548fae4..83985b0 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -26,15 +26,25 @@ builds: archives: - id: default - format: tar.gz + formats: [tar.gz] name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" format_overrides: - goos: windows - format: zip + formats: [zip] checksum: name_template: "checksums.txt" +signs: + - cmd: cosign + signature: "${artifact}.sigstore.json" + args: + - sign-blob + - "--bundle=${signature}" + - "${artifact}" + - --yes + artifacts: checksum + sboms: - artifacts: archive cmd: syft diff --git a/README.md b/README.md index c8d15bb..5ab8198 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ A lightweight, self-hosted alternative to Postiz, Buffer, and Hootsuite for deve - Supports text, images, videos, and documents - Alt-text support for accessible image posts - Self-hosted and privacy-focused -- Multi-arch Docker images (amd64, arm64) +- Multi-arch GHCR Docker images (amd64, arm64) - Lightweight Go binary with minimal dependencies ## Supported Platforms @@ -54,10 +54,6 @@ A lightweight, self-hosted alternative to Postiz, Buffer, and Hootsuite for deve Pre-built multi-arch images (amd64, arm64) are available: ```sh -# Primary (Harbor) -docker pull registry.goharbor.io/bupd/shitpost:latest - -# Alternative (GitHub Container Registry) docker pull ghcr.io/bupd/shitpost:latest ``` @@ -87,7 +83,7 @@ For production, use a versioned tag (e.g., `v1.0.0`) to avoid unexpected updates docker run -d --name shitpost \ --env-file .env \ -v ./downloads:/app/downloads \ - registry.goharbor.io/bupd/shitpost:latest + ghcr.io/bupd/shitpost:latest ``` 3. Check logs: @@ -209,6 +205,8 @@ Built as a lightweight wrapper around [humanwhocodes/crosspost](https://github.c - No third-party analytics or tracking - Media files stored locally (configure volume mounts) - Consider implementing allowlists for public-facing bots +- See [docs/release-security-plan.md](docs/release-security-plan.md) for the OWASP-based release hardening plan. +- See [docs/slsa.md](docs/slsa.md) for the release provenance target and verification commands. ## Troubleshooting diff --git a/Taskfile.yml b/Taskfile.yml index 9e8fb92..ba80130 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -2,7 +2,7 @@ version: "3" vars: ENV_FILE: '{{default ".env" .ENV_FILE}}' - IMAGE: '{{default "registry.goharbor.io/bupd/shitpost" .IMAGE}}' + IMAGE: '{{default "ghcr.io/bupd/shitpost" .IMAGE}}' TAG: '{{default "dev" .TAG}}' CROSSPOST_REPO: '{{default "https://github.com/bupd/crosspost.git" .CROSSPOST_REPO}}' CROSSPOST_REF: '{{default "main" .CROSSPOST_REF}}' @@ -92,6 +92,19 @@ tasks: cmds: - gofmt -w main.go + fmt:check: + desc: Check Go formatting + cmds: + - test -z "$(gofmt -l .)" + + ci: + desc: Run CI checks without modifying files + cmds: + - task: fmt:check + - task: lint + - task: test + - task: build + validate: desc: Format, lint, test, and build cmds: diff --git a/docker-compose.yml b/docker-compose.yml index 4b15012..3373751 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: shitpost-bot: - image: registry.goharbor.io/bupd/shitpost:latest + image: ghcr.io/bupd/shitpost:latest build: context: . args: diff --git a/docs/release-security-plan.md b/docs/release-security-plan.md new file mode 100644 index 0000000..cfc7065 --- /dev/null +++ b/docs/release-security-plan.md @@ -0,0 +1,151 @@ +# Release security plan + +This plan turns OWASP release and supply-chain guidance into concrete work for `shitpost`. + +The goal is not to claim security by adding badges. The goal is to make every release traceable, reviewable, reproducible enough to verify, and difficult to tamper with between source and published artifact. + +## OWASP baseline + +The relevant OWASP guidance comes from: + +- [OWASP CI/CD Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/CI_CD_Security_Cheat_Sheet.html) +- [OWASP Software Supply Chain Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Software_Supply_Chain_Security_Cheat_Sheet.html) +- [OWASP Software Component Verification Standard](https://owasp.org/www-project-software-component-verification-standard/) +- [OWASP SAMM](https://owasp.org/www-project-samm/) + +For this repo, those sources reduce to these release requirements: + +- Protect source control with reviewed pull requests, protected branches, protected release tags, MFA, and no bypassable direct production release path. +- Run builds only on hosted, isolated, ephemeral CI workers for release artifacts. +- Grant CI jobs the least permissions they need, and separate build, signing, publishing, and deployment authority where practical. +- Keep secrets out of source, logs, images, binaries, and release artifacts. +- Pin dependencies and third-party CI actions; review and update them through an explicit process. +- Generate SBOMs and monitor dependencies, base images, build tools, and release artifacts for vulnerabilities. +- Sign artifacts and container images; publish checksums and provenance bound to artifact digests. +- Verify provenance and signatures before consumers deploy or mirror release artifacts. +- Keep release logs and audit events useful for incident response without leaking credentials. +- Document a release runbook and recovery path for compromised credentials, compromised dependencies, or bad releases. + +## Current state + +Already present: + +- GitHub Actions runs Go formatting, vet, build, and tests on pull requests through `task ci`. +- Release archives are built by GoReleaser on version tags. +- GoReleaser emits checksums and SBOMs for archives. +- Docker Buildx emits SBOM and provenance metadata for container images. +- GHCR images are signed with keyless `cosign`. +- SLSA generic provenance is configured for release archives. +- SLSA container provenance is configured for GHCR images. +- `docs/slsa.md` documents the SLSA Build L3 target and verification commands. + +Known gaps: + +- GitHub branch and tag protection are not documented as required release controls. +- Third-party GitHub Actions are version-tagged but not pinned to immutable SHAs, except the SLSA reusable workflows which must remain semver-tagged for verifier compatibility. +- No repo-local Dependabot configuration is present for Go modules, GitHub Actions, or Docker base images. +- No explicit secret scanning gate is present in CI. +- No vulnerability gate runs `govulncheck`, OSV, Trivy, Grype, or equivalent scanners. +- No release smoke job verifies published signatures and SLSA provenance after release. +- No incident response checklist exists for bad releases or leaked publishing credentials. + +## Target release model + +Release artifacts: + +- GitHub release archives produced by GoReleaser. +- GHCR container image published by digest and tag. + +Release authority: + +- Source changes land through reviewed pull requests to `main`. +- Version tags are protected and created only from reviewed commits on `main`. +- CI uses minimal `GITHUB_TOKEN` permissions per job. +- Releases are triggered only by pushed `v*` tags. + +Consumer verification: + +- Archives are verified with `slsa-verifier verify-artifact` and GoReleaser checksums. +- GHCR images are verified with `slsa-verifier verify-image` and `cosign verify`. + +Pull request test model: + +- `.github/workflows/ci.yml` runs on pull requests to `main`. +- The test job runs in the `golang:1.25-bookworm` container. +- The job installs `task` and runs `task ci`. +- `task ci` checks formatting, vets code, runs tests, and builds the bot without modifying files. + +## Implementation plan + +### Phase 1: lock the release path + +Outcome: only reviewed source can become a release. + +- Enable GitHub branch protection for `main`. +- Require pull request review before merge. +- Require status checks: formatting, vet, build, tests, workflow lint. +- Disable direct pushes to `main` except maintainers under emergency policy. +- Protect `v*` tags so release tags cannot be moved or created from unreviewed commits. +- Require maintainer MFA for repository and registry access. +- Document who can create releases and who can rotate release credentials. + +### Phase 2: harden dependencies and CI inputs + +Outcome: dependencies and CI extensions are visible, reviewed, and updateable. + +- Add Dependabot for Go modules, GitHub Actions, Docker base images, and the Hugo UI if package manifests are active. +- Pin third-party GitHub Actions by immutable SHA where compatible. +- Keep SLSA reusable workflows pinned to `@vX.Y.Z` because `slsa-verifier` expects that form. +- Add a lightweight policy for approving new GitHub Actions and external services. +- Add `govulncheck` for Go dependency and standard-library vulnerability checks. +- Add container image scanning for the final image digest. +- Add secret scanning in CI with a tool such as `gitleaks`. + +### Phase 3: make artifact integrity enforceable + +Outcome: every published artifact can be verified by digest, signature, and provenance. + +- Keep GoReleaser checksums attached to every release. +- Keep SBOM generation for release archives and container images. +- Keep SLSA provenance for archives and GHCR images. +- Add a post-release verification job that downloads the just-published assets and runs `slsa-verifier`. +- Add a post-release `cosign verify` check for the GHCR image. + +### Phase 4: add release operations and recovery + +Outcome: a bad or compromised release has a practiced response path. + +- Write a release runbook covering tag creation, release monitoring, verification, and rollback. +- Write a credential rotation checklist for `GITHUB_TOKEN`, Telegram bot token, and publishing tokens. +- Document how to revoke or supersede a bad release. +- Add release audit checks: tag actor, workflow run URL, image digest, checksums, provenance URL, SBOM URL. +- Ensure logs do not print secrets or full authorization URLs. + +## Definition of done + +The release process is acceptable when a maintainer can answer yes to all of these: + +- Did the release tag come from a reviewed commit on `main`? +- Did CI run on GitHub-hosted runners with minimal job permissions? +- Were tests, vulnerability checks, secret scanning, and image scanning completed? +- Are release archives checksummed, SBOMed, signed or attested, and covered by SLSA provenance? +- Is the GHCR image signed, SBOMed, and covered by SLSA provenance? +- Can a consumer verify the archive and image without trusting local build output? +- Is there an incident path for revoking, replacing, or warning about a bad release? + +## Near-term work items + +Do these next, in order: + +1. Add Dependabot coverage for Go modules, GitHub Actions, and Docker. +2. Add `govulncheck` and secret scanning to CI. +3. Add final container image scanning after Buildx produces a digest. +4. Pin non-SLSA third-party Actions by SHA. +5. Add a release verification job using `slsa-verifier` and `cosign verify`. +6. Document GitHub branch and tag protection settings in a release runbook. + +## Non-goals for now + +- Replacing GitHub Actions with self-hosted build infrastructure. +- Requiring fully hermetic builds. +- Blocking all releases on zero low-severity findings. diff --git a/docs/slsa.md b/docs/slsa.md new file mode 100644 index 0000000..bbc57e2 --- /dev/null +++ b/docs/slsa.md @@ -0,0 +1,68 @@ +# SLSA Build L3 target + +`shitpost` targets SLSA Build L3 for release artifacts produced by GitHub Actions. + +SLSA compliance is artifact-specific. A pipeline is not simply "SLSA compliant"; each released artifact needs provenance that consumers can verify against expected source, workflow, and builder identities. + +The broader release-security plan is in [release-security-plan.md](release-security-plan.md). Start there for OWASP release hardening work; this document covers the SLSA-specific part. + +## What Build L3 means + +SLSA Build L3 requires: + +- A consistent, documented build process. +- Provenance for the released artifact, bound to the artifact digest. +- Signed provenance generated by a hosted build platform. +- Hardened build isolation so build steps cannot forge provenance signing material or influence other builds. +- A distribution and verification path for consumers. + +## Current implementation + +Release archives: + +- Built by GoReleaser in `.github/workflows/release.yml`. +- Checksummed by GoReleaser in `checksums.txt`. +- Attested by `slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0`. +- Provenance is uploaded to the GitHub release. + +GHCR container image: + +- Built on GitHub-hosted runners by Docker Buildx in `.github/workflows/release.yml`. +- Published to `ghcr.io/bupd/shitpost` by digest. +- Signed keylessly with `cosign`. +- Attested by `slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0`. + +## Verify release archives + +Install `slsa-verifier`, download a release asset plus its `.intoto.jsonl` provenance, then run: + +```sh +slsa-verifier verify-artifact ./shitpost__linux_amd64.tar.gz \ + --provenance-path ./multiple.intoto.jsonl \ + --source-uri github.com/bupd/shitpost \ + --source-tag +``` + +## Verify GHCR container provenance + +Install `slsa-verifier`, then run: + +```sh +slsa-verifier verify-image ghcr.io/bupd/shitpost: \ + --source-uri github.com/bupd/shitpost \ + --source-tag +``` + +Also verify the image signature: + +```sh +cosign verify ghcr.io/bupd/shitpost: \ + --certificate-identity-regexp '^https://github.com/bupd/shitpost/.github/workflows/release.yml@refs/tags/v[0-9]+\\.[0-9]+\\.[0-9]+$' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + +## Remaining gaps + +- Pin third-party Actions by immutable SHA where compatible. The SLSA reusable workflows must remain semver-tagged for verifier compatibility. +- Add branch/tag protection rules in GitHub settings so releases only come from reviewed code and protected tags. +- Add a release smoke test that verifies a freshly published artifact and image with `slsa-verifier`. diff --git a/ui/content/getting-started/_index.md b/ui/content/getting-started/_index.md index d77f8cc..cd0c6c1 100644 --- a/ui/content/getting-started/_index.md +++ b/ui/content/getting-started/_index.md @@ -20,7 +20,7 @@ curl -o .env https://raw.githubusercontent.com/bupd/shitpost/main/.env.example docker run -d --name shitpost \ --env-file .env \ -v ./downloads:/app/downloads \ - registry.goharbor.io/bupd/shitpost:latest + ghcr.io/bupd/shitpost:latest ``` ## What it does diff --git a/ui/content/getting-started/architecture.md b/ui/content/getting-started/architecture.md index ce0238a..c811a37 100644 --- a/ui/content/getting-started/architecture.md +++ b/ui/content/getting-started/architecture.md @@ -48,9 +48,9 @@ The final container starts `./bot` from `/app` and stores downloaded media in `/ ## CI and releases -Pull requests and pushes run formatting, vet, build, and tests. Pushes to `main` also build multi-arch images for `linux/amd64` and `linux/arm64`, sign them, publish to GHCR, and copy `latest` to Harbor. +Pull requests run `task ci` inside a Go container to check formatting, vet, tests, and builds. -Tags beginning with `v` run GoReleaser, publish release archives, build semver image tags, copy them to Harbor, and sign the images. +Tags beginning with `v` run GoReleaser, publish release archives, build GHCR semver image tags, sign the GHCR image, and attach provenance. ## Security model diff --git a/ui/content/getting-started/deployment.md b/ui/content/getting-started/deployment.md index 244753f..6c86a37 100644 --- a/ui/content/getting-started/deployment.md +++ b/ui/content/getting-started/deployment.md @@ -21,7 +21,7 @@ docker run -d --name shitpost \ --restart unless-stopped \ --env-file /opt/shitpost/.env \ -v /opt/shitpost/downloads:/app/downloads \ - registry.goharbor.io/bupd/shitpost:latest + ghcr.io/bupd/shitpost:latest ``` Check logs: @@ -47,7 +47,6 @@ The Compose service is named `shitpost-bot` and the container is named `shitpost Published images are available from: ```sh -docker pull registry.goharbor.io/bupd/shitpost:latest docker pull ghcr.io/bupd/shitpost:latest ``` diff --git a/ui/content/getting-started/installation.md b/ui/content/getting-started/installation.md index 512c55d..a2b3a72 100644 --- a/ui/content/getting-started/installation.md +++ b/ui/content/getting-started/installation.md @@ -26,7 +26,7 @@ Edit `.env`, then run: docker run -d --name shitpost \ --env-file .env \ -v ./downloads:/app/downloads \ - registry.goharbor.io/bupd/shitpost:latest + ghcr.io/bupd/shitpost:latest ``` Follow logs: diff --git a/ui/content/getting-started/troubleshooting.md b/ui/content/getting-started/troubleshooting.md index f72c29d..f67235f 100644 --- a/ui/content/getting-started/troubleshooting.md +++ b/ui/content/getting-started/troubleshooting.md @@ -76,5 +76,5 @@ docker rm -f shitpost docker run -d --name shitpost \ --env-file .env \ -v ./downloads:/app/downloads \ - registry.goharbor.io/bupd/shitpost:latest + ghcr.io/bupd/shitpost:latest ``` diff --git a/ui/hugo.toml b/ui/hugo.toml index d9e1ff3..2e49e3e 100644 --- a/ui/hugo.toml +++ b/ui/hugo.toml @@ -6,7 +6,7 @@ disableKinds = ["taxonomy", "term"] [params] description = "Telegram-powered social media crossposting for self-hosters." repo = "https://github.com/bupd/shitpost" -image = "registry.goharbor.io/bupd/shitpost:latest" +image = "ghcr.io/bupd/shitpost:latest" [markup] [markup.highlight]