From c1be7fd1b3566a49a11f1f09f25bedc01af3df1b Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Tue, 7 Apr 2026 18:08:25 +0200 Subject: [PATCH 01/14] ci: migrate from Woodpecker CI to Forgejo Actions Replace Woodpecker CI pipelines with GitHub/Forgejo Actions workflows using the ci-base:1.0.0 image. Simplify Makefile by removing unused variables and adopting a cleaner help target. Switch goreleaser from dockers_v2 to dockers + docker_manifests for per-arch image builds. Update Containerfile to Alpine 3.23 and fix nfpm description. --- .dockerignore | 7 ++ .github/workflows/ci.yml | 70 +++++++++++++++ .github/workflows/release.yml | 30 +++++++ .goreleaser.yml | 50 ++++++++--- .woodpecker/ci.yml | 51 ----------- .woodpecker/release.yml | 47 ---------- Containerfile | 26 ++---- Makefile | 162 ++++++++++++---------------------- docker-compose.yml | 10 +-- 9 files changed, 209 insertions(+), 244 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .woodpecker/ci.yml delete mode 100644 .woodpecker/release.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..87b66d5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +hako +hako.db +dist/ +build/ +devdata/ +.git/ +webapp/node_modules/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1af50d1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + format: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - run: make format + - run: git diff --exit-code + + goreleaser-lint: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + - uses: actions/goreleaser-action@v6.4.0 + with: + args: check + + lint: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - run: make ci-lint + + test: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - run: make test + + e2e: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - name: Install Playwright browsers + run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium + - run: make e2e + + build: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - run: make build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1f41606 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Release + +on: + push: + tags: ["v*"] + +jobs: + release: + runs-on: docker + container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Login to container registry + run: echo "${{ secrets.FORGEJO_TOKEN }}" | docker login git.nakama.town -u ${{ github.actor }} --password-stdin + + - name: Run GoReleaser + uses: actions/goreleaser-action@v6.4.0 + with: + args: release --clean + env: + GORELEASER_FORCE_TOKEN: gitea + GITEA_TOKEN: ${{ secrets.FORGEJO_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 692a8bd..7ed5652 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -52,20 +52,47 @@ archives: - goos: windows formats: ["zip"] -dockers_v2: - - images: - - "git.nakama.town/fmartingr/hako" - tags: - - "{{ .Version }}" +dockers: + - image_templates: + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-amd64" + use: buildx + build_flag_templates: + - "--platform=linux/amd64" + extra_files: [] dockerfile: Containerfile - platforms: - - linux/amd64 - - linux/arm64 - - linux/arm/v7 + - image_templates: + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-arm64" + use: buildx + build_flag_templates: + - "--platform=linux/arm64" + extra_files: [] + dockerfile: Containerfile + goarch: arm64 + - image_templates: + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-armv7" + use: buildx + build_flag_templates: + - "--platform=linux/arm/v7" + extra_files: [] + dockerfile: Containerfile + goarch: arm + goarm: "7" + +docker_manifests: + - name_template: "git.nakama.town/fmartingr/hako:{{ .Tag }}" + image_templates: + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-amd64" + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-arm64" + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-armv7" + - name_template: "git.nakama.town/fmartingr/hako:latest" + image_templates: + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-amd64" + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-arm64" + - "git.nakama.town/fmartingr/hako:{{ .Tag }}-armv7" nfpms: - maintainer: Felipe Martin - description: SMTP server to forward messages to shoutrrr endpoints + description: Self-hosted link archival service homepage: https://git.nakama.town/fmartingr/hako license: AGPL-3.0 formats: @@ -119,4 +146,7 @@ changelog: - "^chore\\(deps\\):" release: + gitea: + owner: fmartingr + name: hako prerelease: auto diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml deleted file mode 100644 index 1c04bd4..0000000 --- a/.woodpecker/ci.yml +++ /dev/null @@ -1,51 +0,0 @@ -when: - event: - - push - - pull_request - branch: - - main - -steps: - format: - image: golang:1.24 - commands: - - make format - - git diff --exit-code # Fail if files were changed - - lint-goreleaser: - image: goreleaser/goreleaser:latest - commands: - - goreleaser check - - lint: - image: golang:1.24 - commands: - - make ci-lint - - test: - image: golang:1.24 - commands: - - make test - - e2e: - image: golang:1.24 - environment: - TESTCONTAINERS_RYUK_DISABLED: true - DOCKER_HOST: unix:///var/run/docker.sock - volumes: - - /var/run/docker.sock:/var/run/docker.sock - commands: - # Install system dependencies - - apt-get update && apt-get install -y docker.io curl unzip - - # Install Bun for webapp build - - curl -fsSL https://bun.sh/install | bash - - export PATH="$HOME/.bun/bin:$PATH" - - # Install Playwright browsers - - go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium - - # Run e2e tests - - make e2e - when: - status: [success, failure] diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml deleted file mode 100644 index 96c9447..0000000 --- a/.woodpecker/release.yml +++ /dev/null @@ -1,47 +0,0 @@ -when: - - event: tag - branch: main - -steps: - - name: Release - image: ubuntu:24.04 - environment: - GITEA_TOKEN: - from_secret: GITEA_TOKEN - DOCKER_HOST: unix:///var/run/docker.sock - volumes: - - "/var/run/docker.sock:/var/run/docker.sock" - commands: - # Install system dependencies - - apt-get update - - apt-get install -y ca-certificates curl git make wget unzip upx-ucl - - # Install Docker from official repository (for buildx support) - - curl -fsSL https://get.docker.com | sh - - # Install Go 1.23 - - wget -q https://go.dev/dl/go1.23.5.linux-amd64.tar.gz - - tar -C /usr/local -xzf go1.23.5.linux-amd64.tar.gz - - rm go1.23.5.linux-amd64.tar.gz - - export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH" - - # Install GoReleaser via go install - - go install github.com/goreleaser/goreleaser/v2@latest - - # Install Bun (using official installer) - - curl -fsSL https://bun.sh/install | bash - - export PATH="$HOME/.bun/bin:$PATH" - - # Setup Docker buildx for multi-platform builds - - docker buildx create --use --name multiplatform --driver docker-container - - # Verify installations - - docker --version - - docker buildx version - - goreleaser --version - - bun --version - - upx --version - - # Authenticate and release - - docker login -u fmartingr -p $GITEA_TOKEN git.nakama.town - - goreleaser release --clean diff --git a/Containerfile b/Containerfile index 8276802..aa0e8e7 100644 --- a/Containerfile +++ b/Containerfile @@ -1,36 +1,20 @@ -# This file is used directly by the goreleaser build -# It is used to build the final container image -# To build the image locally use make build-docker - -# Runtime image -FROM alpine:3.20 +FROM alpine:3.23 LABEL maintainer="Felipe Martin " LABEL org.opencontainers.image.source="https://git.nakama.town/fmartingr/hako" -# Install runtime dependencies (yt-dlp and ffmpeg for video archival from Alpine community) RUN apk add --no-cache \ poppler-utils \ ca-certificates \ tzdata \ - && apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/v3.20/community yt-dlp ffmpeg + && apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/v3.23/community yt-dlp ffmpeg -# Create non-root user -RUN addgroup -g 1000 hako && \ - adduser -D -u 1000 -G hako hako +RUN addgroup -g 1000 hako && adduser -u 1000 -G hako -s /bin/sh -D hako WORKDIR /app +COPY hako /usr/local/bin/hako +RUN chown -R hako:hako /app -# Copy binary from goreleaser build context -# With dockers_v2, goreleaser provides the pre-built binary at $TARGETPLATFORM/hako -# The binary already has the webapp embedded via //go:embed -ARG TARGETPLATFORM -COPY ${TARGETPLATFORM}/hako /usr/local/bin/hako -RUN chmod +x /usr/local/bin/hako && \ - chown -R hako:hako /app - -# Switch to non-root user USER hako - EXPOSE 8080 ENTRYPOINT ["/usr/local/bin/hako"] diff --git a/Makefile b/Makefile index 78ace12..c82a536 100644 --- a/Makefile +++ b/Makefile @@ -1,156 +1,104 @@ PROJECT_NAME := hako - -SOURCE_FILES ?=./... - -TEST_OPTIONS ?= -v -failfast -race -bench=. -benchtime=100000x -cover -coverprofile=coverage.out -TEST_TIMEOUT ?=1m - -GOLANGCI_LINT_VERSION ?= v1.64.5 - -CLEAN_OPTIONS ?=-modcache -testcache - CGO_ENABLED := 0 -BUILDS_PATH := ./dist -FROM_MAKEFILE := y +DIST_PATH := ./dist +TEST_OPTIONS := -v -failfast -race -bench=. -benchtime=100000x -cover -coverprofile=coverage.out +TEST_TIMEOUT := 1m -CONTAINERFILE_NAME := Containerfile -CONTAINER_ALPINE_VERSION := 3.20 -CONTAINER_SOURCE_URL := "https://git.nakama.town/fmartingr/${PROJECT_NAME}" -CONTAINER_MAINTAINER := "Felipe Martin " -CONTAINER_BIN_NAME := ${PROJECT_NAME} +GOLANGCI_LINT_VERSION := v1.64.5 -BUILDX_PLATFORMS := linux/amd64,linux/arm64,linux/arm/v7 +.DEFAULT_GOAL := help -export PROJECT_NAME -export FROM_MAKEFILE - -export CGO_ENABLED - -export SOURCE_FILES -export TEST_OPTIONS -export TEST_TIMEOUT -export BUILDS_PATH - -export CONTAINERFILE_NAME -export CONTAINER_ALPINE_VERSION -export CONTAINER_SOURCE_URL -export CONTAINER_MAINTAINER -export CONTAINER_BIN_NAME - -export BUILDX_PLATFORMS - -.PHONY: all -all: help - -# this is godly -# https://news.ycombinator.com/item?id=11939200 +## help: Display this help message .PHONY: help -help: ### this screen. Keep it first target to be default -ifeq ($(UNAME), Linux) - @grep -P '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' -else - @# this is not tested, but prepared in advance for you, Mac drivers - @awk -F ':.*###' '$$0 ~ FS {printf "%15s%s\n", $$1 ":", $$2}' \ - $(MAKEFILE_LIST) | grep -v '@awk' | sort -endif +help: + @echo "Available targets:" + @grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /' | sort +## clean: Clean test cache, build files .PHONY: clean -clean: ### clean test cache, build files - $(info: Make: Clean) - @rm -rf ${BUILDS_PATH} +clean: + @rm -rf $(DIST_PATH) @rm -rf webapp/dist @rm -rf webapp/node_modules @rm -f webapp/bun.lockb - @go clean ${CLEAN_OPTIONS} - @find devdata -mindepth 1 ! -name config.yaml -delete 2>/dev/null || true - @-docker buildx rm ${PROJECT_NAME}_builder + @go clean -modcache -testcache +## clean-devdata: Clear devdata database and files (keeps config.yaml) .PHONY: clean-devdata -clean-devdata: ### Clear devdata database and files (keeps config.yaml) - $(info: Make: Cleaning devdata) +clean-devdata: @rm -rf devdata/data @rm -f devdata/hako.db devdata/hako.db-shm devdata/hako.db-wal +## build: Build project using goreleaser (snapshot) .PHONY: build -build: build-webapp ### builds the project for the setup os/arch combinations - $(info: Make: Build) - @goreleaser --clean --snapshot +build: build-webapp + goreleaser build --snapshot --clean +## build-webapp: Build the webapp .PHONY: build-webapp -build-webapp: ### Build the webapp - $(info: Make: Building webapp) +build-webapp: @which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1) @cd webapp && bun install && bun --bun node_modules/.bin/vite build +## build-docker: Build Docker image locally via goreleaser +.PHONY: build-docker +build-docker: build-webapp + goreleaser release --snapshot --clean + +## run: Build frontend and run the server +.PHONY: run +run: build-webapp run-server + +## run-server: Run the API server only (without building webapp) .PHONY: run-server -run-server: ### Runs the API server only (without building webapp) - $(info: Make: Running server) - CGO_ENABLED=${CGO_ENABLED} go run ./cmd/${PROJECT_NAME}/*.go server --config devdata/config.yaml - -.PHONY: run-quick -run-quick: build-webapp run-server ### Builds the webapp and runs the server +run-server: + CGO_ENABLED=$(CGO_ENABLED) go run ./cmd/$(PROJECT_NAME)/*.go server --config devdata/config.yaml +## run-webapp: Run the webapp in dev mode with hot-reload .PHONY: run-webapp -run-webapp: ### Runs the webapp in dev mode with hot-reload - $(info: Make: Running webapp dev server) +run-webapp: @which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1) @cd webapp && bun --bun node_modules/.bin/vite --mode development -.PHONY: run -run: ### Executes the project build locally - @make build - ${BUILDS_PATH}/${PROJECT_NAME} - -.PHONY: build-docker -build-docker: build-webapp ### Build Docker image locally using goreleaser snapshot - $(info: Make: Building Docker image) - @which goreleaser > /dev/null || (echo "Error: goreleaser is not installed. Install it from https://goreleaser.com" && exit 1) - @which docker > /dev/null || (echo "Error: docker is not installed" && exit 1) - @goreleaser --clean --snapshot --skip=publish - +## format: Format code and tidy modules .PHONY: format -format: ### Executes the formatting pipeline on the project - $(info: Make: Format) - @go fmt ./... - @go mod tidy +format: + go fmt ./... + go mod tidy +## ci-lint: Run golangci-lint (installs if missing) .PHONY: ci-lint -ci-lint: ### Check the project for errors - $(info: Make: Lint) - @go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION} - @golangci-lint run ./... +ci-lint: + @which golangci-lint > /dev/null 2>&1 || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) + golangci-lint run ./... +## lint: Run all linters .PHONY: lint -lint: lint-server lint-webapp ### Check the project for errors - $(info: Make: Lint) +lint: lint-server lint-webapp +## lint-server: Check the server for linting errors .PHONY: lint-server -lint-server: ### Check the server for linting errors - $(info: Make: Lint server) +lint-server: @golangci-lint run ./... +## lint-webapp: Check the webapp for linting errors .PHONY: lint-webapp -lint-webapp: ### Check the webapp for linting errors - $(info: Make: Lint webapp) +lint-webapp: @which bun > /dev/null || (echo "Error: Bun is not installed. Install it from https://bun.sh" && exit 1) @cd webapp && bun --bun node_modules/.bin/eslint . +## test: Run tests with coverage .PHONY: test -test: ### Runs the test suite - $(info: Make: Test) - CGO_ENABLED=1 go test ${TEST_OPTIONS} -timeout=${TEST_TIMEOUT} ${SOURCE_FILES} - @echo "Generating HTML coverage report..." - @go tool cover -html=coverage.out -o coverage.html - @echo "โœ… Coverage report generated: coverage.html" +test: + CGO_ENABLED=1 go test $(TEST_OPTIONS) -timeout=$(TEST_TIMEOUT) ./... +## e2e: Run end-to-end integration tests .PHONY: e2e -e2e: ### Run end-to-end integration tests - $(info: Make: E2E Tests) +e2e: @./scripts/e2e.sh +## e2e-install: Install Playwright browsers .PHONY: e2e-install -e2e-install: ### Install Playwright browsers - $(info: Make: Installing E2E dependencies) +e2e-install: @go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium diff --git a/docker-compose.yml b/docker-compose.yml index 7cb0e56..912e762 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,10 @@ services: hako: - # Note: Before running docker-compose, ensure the Linux binary is prepared: - # Run: make prepare-docker-compose - # Or: make build-docker (which builds via goreleaser) + # Note: Build the Docker image via goreleaser: + # Run: make build-docker build: context: . dockerfile: Containerfile - platforms: - - linux/amd64 - args: - TARGETPLATFORM: dist/hako_linux_amd64_v1 - platform: linux/amd64 container_name: hako restart: unless-stopped ports: -- 2.52.0 From bb660f3cd49ea630d612b50dcf37e0b8af2b5007 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Tue, 7 Apr 2026 18:09:57 +0200 Subject: [PATCH 02/14] fix: use master branch in CI workflow triggers --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1af50d1..ada078a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [master] pull_request: - branches: [main] + branches: [master] jobs: format: -- 2.52.0 From d4d2a5bed3257c00e4861ab83f3ab41469ab47af Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Tue, 7 Apr 2026 18:21:23 +0200 Subject: [PATCH 03/14] fix: add .gitkeep to webapp/dist for go:embed compatibility --- webapp/dist/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 webapp/dist/.gitkeep diff --git a/webapp/dist/.gitkeep b/webapp/dist/.gitkeep new file mode 100644 index 0000000..e69de29 -- 2.52.0 From 4fcf97c9fdca2b45220efaecef8d3ae2c91aebe7 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Tue, 7 Apr 2026 18:26:06 +0200 Subject: [PATCH 04/14] fix: use all:dist embed directive to include dot-files --- webapp/embed.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/embed.go b/webapp/embed.go index b13cf76..d2e69b9 100644 --- a/webapp/embed.go +++ b/webapp/embed.go @@ -7,7 +7,7 @@ import ( "os" ) -//go:embed dist +//go:embed all:dist var embeddedWebappFS embed.FS // GetFilesystem returns a HTTP filesystem with the embedded webapp files -- 2.52.0 From 92bf5c1297625452ca15e25979a7be01368c4247 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 8 Apr 2026 09:52:19 +0200 Subject: [PATCH 05/14] ci: increase test timeout to 5m for CI with race detector --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c82a536..1a62345 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CGO_ENABLED := 0 DIST_PATH := ./dist TEST_OPTIONS := -v -failfast -race -bench=. -benchtime=100000x -cover -coverprofile=coverage.out -TEST_TIMEOUT := 1m +TEST_TIMEOUT := 5m GOLANGCI_LINT_VERSION := v1.64.5 -- 2.52.0 From 64cffcb2e352324a06240d46021bc6affc90533c Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 8 Apr 2026 09:53:15 +0200 Subject: [PATCH 06/14] ci: use e2e/go.mod for e2e job Go version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ada078a..2f117af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version-file: go.mod + go-version-file: e2e/go.mod - name: Install Playwright browsers run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium - run: make e2e -- 2.52.0 From b6cd0ddbc6784eddbc320beee97365216e585655 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 8 Apr 2026 10:01:15 +0200 Subject: [PATCH 07/14] fix: add missing mattn/go-sqlite3 dependency to e2e module --- e2e/go.mod | 3 ++- e2e/go.sum | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/go.mod b/e2e/go.mod index 8d6b77a..1972dd1 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -6,6 +6,8 @@ replace github.com/fmartingr/hako => ../ require ( github.com/docker/go-connections v0.6.0 + github.com/google/uuid v1.6.0 + github.com/mattn/go-sqlite3 v1.14.41 github.com/playwright-community/playwright-go v0.5200.1 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go v0.40.0 @@ -34,7 +36,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index 7b98b1b..c2ceab7 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -68,6 +68,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA= +github.com/mattn/go-sqlite3 v1.14.41/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -- 2.52.0 From f47010cd0f8c3516cf5de3a82ad211c187380be6 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 8 Apr 2026 16:42:42 +0200 Subject: [PATCH 08/14] fix(e2e): make link creation reach test web server via shared network The test web server and Hako container previously ran on isolated Docker networks, so Hako could not fetch URLs like http://localhost:32865 (the host-mapped port) from inside its container. Most tests passed vacuously because they only checked DOM containers; the Archive tests failed because they tried to click an actual link item that was never created. Changes: - Add a shared Docker network so both containers can communicate by alias - Test web server registers as hako-testserver and returns that as its URL - Hako container joins the same network and can resolve the alias - CreateLink now navigates to /home before each call so it can be invoked multiple times in a row (the form redirects to /links after submission) - IsVisible uses .First() to avoid Playwright strict-mode errors when a selector matches multiple elements - Archive tests use .link-item instead of the never-matching a[href*='/links/'] selector - Bump Dockerfile.e2e Alpine to 3.23 to match the production Containerfile --- e2e/Dockerfile.e2e | 2 +- e2e/e2eutil/container.go | 13 ++++++++--- e2e/e2eutil/helper.go | 15 +++++++++++-- e2e/e2eutil/network.go | 34 +++++++++++++++++++++++++++++ e2e/e2eutil/testserver.go | 35 +++++++++++++++--------------- e2e/playwright/admin_rules_test.go | 2 +- e2e/playwright/archives_test.go | 12 +++++----- e2e/playwright/errors_test.go | 2 +- 8 files changed, 83 insertions(+), 32 deletions(-) create mode 100644 e2e/e2eutil/network.go diff --git a/e2e/Dockerfile.e2e b/e2e/Dockerfile.e2e index f8ad861..90fbc2d 100644 --- a/e2e/Dockerfile.e2e +++ b/e2e/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM alpine:3.20 +FROM alpine:3.23 RUN apk add --no-cache poppler-utils ca-certificates tzdata && \ addgroup -g 1000 hako && \ diff --git a/e2e/e2eutil/container.go b/e2e/e2eutil/container.go index 700821d..f5c8fc1 100644 --- a/e2e/e2eutil/container.go +++ b/e2e/e2eutil/container.go @@ -53,16 +53,23 @@ func StartHakoContainer(ctx context.Context, t *testing.T) (testcontainers.Conta os.Remove(binaryPath) }) - // Create container request + // Ensure shared network exists so Hako can reach the test web server by alias. + nw, err := getSharedNetwork(ctx) + if err != nil { + return nil, "", err + } + + // Create container request attached to the shared network. req := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ - Context: filepath.Join(projectRoot, "e2e"), - Dockerfile: "Dockerfile.e2e", + Context: filepath.Join(projectRoot, "e2e"), + Dockerfile: "Dockerfile.e2e", PrintBuildLog: true, }, ExposedPorts: []string{"8080/tcp"}, WaitingFor: wait.ForLog("Starting HTTP server"). WithStartupTimeout(30 * time.Second), + Networks: []string{nw.Name}, } // Start container diff --git a/e2e/e2eutil/helper.go b/e2e/e2eutil/helper.go index aef2371..23cea02 100644 --- a/e2e/e2eutil/helper.go +++ b/e2e/e2eutil/helper.go @@ -178,10 +178,12 @@ func (h *TestHelper) GetURL() string { return h.page.URL() } -// IsVisible checks if an element matching the selector is visible. +// IsVisible checks if any element matching the selector is visible. +// Uses .First() to avoid Playwright strict-mode errors when the selector +// resolves to multiple elements. func (h *TestHelper) IsVisible(selector string) bool { h.t.Helper() - visible, err := h.page.Locator(selector).IsVisible() + visible, err := h.page.Locator(selector).First().IsVisible() if err != nil { h.t.Logf("Error checking visibility of %s: %v", selector, err) return false @@ -305,9 +307,18 @@ func (h *TestHelper) Logout() error { } // CreateLink creates a new link via the Quick Add form. +// Navigates to /home first to ensure the form is available even when called +// repeatedly (after the first call the user is redirected to /links). func (h *TestHelper) CreateLink(url string) error { h.t.Helper() + // Ensure we're on the home page where the Quick Add form lives. + if !strings.HasSuffix(h.GetURL(), "/home") { + if err := h.Navigate("/home"); err != nil { + return fmt.Errorf("failed to navigate to home: %w", err) + } + } + // Wait for URL input on home page if err := h.WaitForSelector("input[name=\"url\"]", 5*time.Second); err != nil { return fmt.Errorf("URL input not found: %w", err) diff --git a/e2e/e2eutil/network.go b/e2e/e2eutil/network.go new file mode 100644 index 0000000..7200d65 --- /dev/null +++ b/e2e/e2eutil/network.go @@ -0,0 +1,34 @@ +package e2eutil + +import ( + "context" + "fmt" + "sync" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" +) + +// Shared network used by the test web server and Hako container so that +// Hako can fetch URLs from the test web server via Docker DNS. +var ( + sharedNetwork *testcontainers.DockerNetwork + sharedNetworkErr error + sharedNetworkOnce sync.Once +) + +// Network alias used by the test web server inside the shared network. +const testServerAlias = "hako-testserver" + +// getSharedNetwork lazily creates a shared Docker network for the test process. +// Both StartTestWebServer and StartHakoContainer attach to it so containers +// can communicate by alias. +func getSharedNetwork(ctx context.Context) (*testcontainers.DockerNetwork, error) { + sharedNetworkOnce.Do(func() { + sharedNetwork, sharedNetworkErr = network.New(ctx) + }) + if sharedNetworkErr != nil { + return nil, fmt.Errorf("failed to create shared network: %w", sharedNetworkErr) + } + return sharedNetwork, nil +} diff --git a/e2e/e2eutil/testserver.go b/e2e/e2eutil/testserver.go index c8d5d7a..11a78d9 100644 --- a/e2e/e2eutil/testserver.go +++ b/e2e/e2eutil/testserver.go @@ -7,13 +7,13 @@ import ( "testing" "time" - "github.com/docker/go-connections/nat" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) // StartTestWebServer starts an nginx container with test content for E2E testing. -// It builds the test web server image and returns the container instance and base URL. +// The container is attached to the shared network with the testServerAlias hostname, +// and the returned URL uses that alias so the Hako container can fetch from it. func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Container, string, error) { t.Helper() @@ -23,7 +23,13 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta return nil, "", fmt.Errorf("failed to get project root: %w", err) } - // Create container request + // Ensure shared network exists so Hako can reach this server by alias. + nw, err := getSharedNetwork(ctx) + if err != nil { + return nil, "", err + } + + // Create container request attached to the shared network. req := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join(projectRoot, "e2e", "testserver"), @@ -32,6 +38,10 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta }, ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), + Networks: []string{nw.Name}, + NetworkAliases: map[string][]string{ + nw.Name: {testServerAlias}, + }, } // Start container @@ -51,21 +61,10 @@ func StartTestWebServer(ctx context.Context, t *testing.T) (testcontainers.Conta } }) - // Get the mapped port - mappedPort, err := container.MappedPort(ctx, nat.Port("80/tcp")) - if err != nil { - return nil, "", fmt.Errorf("failed to get mapped port: %w", err) - } - - // Get the host - host, err := container.Host(ctx) - if err != nil { - return nil, "", fmt.Errorf("failed to get container host: %w", err) - } - - // Construct base URL - baseURL := fmt.Sprintf("http://%s:%s", host, mappedPort.Port()) - t.Logf("Test web server started at %s", baseURL) + // Use the network alias as the base URL so Hako can reach this server + // from inside its container via Docker DNS. + baseURL := fmt.Sprintf("http://%s", testServerAlias) + t.Logf("Test web server started at %s (network alias)", baseURL) return container, baseURL, nil } diff --git a/e2e/playwright/admin_rules_test.go b/e2e/playwright/admin_rules_test.go index 9074391..d613bdc 100644 --- a/e2e/playwright/admin_rules_test.go +++ b/e2e/playwright/admin_rules_test.go @@ -265,7 +265,7 @@ func TestRules_RuleApplication(t *testing.T) { helper.Screenshot("link-created-rule-test") // Navigate to link details to see which archiver was used - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") if err == nil { time.Sleep(1 * time.Second) helper.Screenshot("link-details-archiver-used") diff --git a/e2e/playwright/archives_test.go b/e2e/playwright/archives_test.go index 2199d01..a05e96e 100644 --- a/e2e/playwright/archives_test.go +++ b/e2e/playwright/archives_test.go @@ -53,7 +53,7 @@ func TestArchive_AutomaticCreation(t *testing.T) { helper.Screenshot("link-created") // Navigate to link details to see archive status - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") time.Sleep(1 * time.Second) helper.Screenshot("link-details-archive") @@ -105,7 +105,7 @@ func TestArchive_ViewHistory(t *testing.T) { time.Sleep(2 * time.Second) // Navigate to link details - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") time.Sleep(1 * time.Second) helper.Screenshot("archive-history") @@ -162,7 +162,7 @@ func TestArchive_StatusDisplay(t *testing.T) { time.Sleep(2 * time.Second) // Navigate to link details - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") time.Sleep(1 * time.Second) helper.Screenshot("archive-status") @@ -222,7 +222,7 @@ func TestArchive_Rearchive(t *testing.T) { time.Sleep(2 * time.Second) // Navigate to link details - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") time.Sleep(1 * time.Second) helper.Screenshot("before-rearchive") @@ -295,7 +295,7 @@ func TestArchive_ViewFiles(t *testing.T) { time.Sleep(2 * time.Second) // Navigate to link details - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") time.Sleep(1 * time.Second) @@ -356,7 +356,7 @@ func TestArchive_PreviewHTML(t *testing.T) { time.Sleep(2 * time.Second) // Navigate to link details - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") time.Sleep(1 * time.Second) diff --git a/e2e/playwright/errors_test.go b/e2e/playwright/errors_test.go index 661302b..1b90913 100644 --- a/e2e/playwright/errors_test.go +++ b/e2e/playwright/errors_test.go @@ -144,7 +144,7 @@ func TestError_ArchivalFailure(t *testing.T) { helper.Screenshot("archival-failure-link-created") // Navigate to link details - err = helper.Click("a[href*='/links/']") + err = helper.Click(".link-item") if err == nil { time.Sleep(5 * time.Second) helper.Screenshot("archival-failure-details") -- 2.52.0 From 53fcfe7267a4f713cb4470a3dda057bec145b01d Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 19 Aug 2026 14:45:50 +0200 Subject: [PATCH 09/14] ci: align Forgejo Actions with migrated repo conventions Bump ci-base to 1.1.0 and actions to v7, add Docker buildx setup for releases, restore dockers_v2 for multi-arch images, and set testcontainers env vars on the e2e job. Co-authored-by: Cursor --- .github/workflows/ci.yml | 37 +++++++++++++++------------- .github/workflows/release.yml | 9 ++++--- .goreleaser.yml | 46 ++++++++--------------------------- 3 files changed, 36 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f117af..061e850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,10 +9,10 @@ on: jobs: format: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version-file: go.mod - run: make format @@ -20,39 +20,42 @@ jobs: goreleaser-lint: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/goreleaser-action@v6.4.0 with: args: check lint: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version-file: go.mod - run: make ci-lint test: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version-file: go.mod - run: make test e2e: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 + env: + TESTCONTAINERS_RYUK_DISABLED: true + DOCKER_HOST: unix:///var/run/docker.sock steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version-file: e2e/go.mod - name: Install Playwright browsers @@ -61,10 +64,10 @@ jobs: build: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version-file: go.mod - run: make build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f41606..3b22d95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,17 +7,20 @@ on: jobs: release: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.0.0 + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Install Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: go.mod + - name: Setup Docker buildx + run: docker buildx create --use --name multiplatform --driver docker-container + - name: Login to container registry run: echo "${{ secrets.FORGEJO_TOKEN }}" | docker login git.nakama.town -u ${{ github.actor }} --password-stdin diff --git a/.goreleaser.yml b/.goreleaser.yml index 7ed5652..238bbfc 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -52,43 +52,17 @@ archives: - goos: windows formats: ["zip"] -dockers: - - image_templates: - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-amd64" - use: buildx - build_flag_templates: - - "--platform=linux/amd64" - extra_files: [] +dockers_v2: + - images: + - "git.nakama.town/fmartingr/hako" + tags: + - "{{ .Version }}" + - latest dockerfile: Containerfile - - image_templates: - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-arm64" - use: buildx - build_flag_templates: - - "--platform=linux/arm64" - extra_files: [] - dockerfile: Containerfile - goarch: arm64 - - image_templates: - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-armv7" - use: buildx - build_flag_templates: - - "--platform=linux/arm/v7" - extra_files: [] - dockerfile: Containerfile - goarch: arm - goarm: "7" - -docker_manifests: - - name_template: "git.nakama.town/fmartingr/hako:{{ .Tag }}" - image_templates: - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-amd64" - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-arm64" - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-armv7" - - name_template: "git.nakama.town/fmartingr/hako:latest" - image_templates: - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-amd64" - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-arm64" - - "git.nakama.town/fmartingr/hako:{{ .Tag }}-armv7" + platforms: + - linux/amd64 + - linux/arm64 + - linux/arm/v7 nfpms: - maintainer: Felipe Martin -- 2.52.0 From ffa604ee877b243788d504435b24f7170a5f1157 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 19 Aug 2026 15:05:13 +0200 Subject: [PATCH 10/14] fix(ci): mount Docker socket for e2e job on Forgejo Actions The e2e job runs testcontainers inside a ci-base container, which needs the host Docker socket mounted. Also preserve TESTCONTAINERS_RYUK_DISABLED from CI env and wait for archive UI before asserting in archive tests. Co-authored-by: Cursor --- .github/workflows/ci.yml | 6 +++++- e2e/playwright/archives_test.go | 15 ++++++++------- scripts/e2e.sh | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 061e850..5e52b30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,9 @@ jobs: e2e: runs-on: docker - container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 + container: + image: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 + options: --volume /var/run/docker.sock:/var/run/docker.sock env: TESTCONTAINERS_RYUK_DISABLED: true DOCKER_HOST: unix:///var/run/docker.sock @@ -58,6 +60,8 @@ jobs: - uses: actions/setup-go@v7 with: go-version-file: e2e/go.mod + - name: Verify Docker access + run: docker info - name: Install Playwright browsers run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium - run: make e2e diff --git a/e2e/playwright/archives_test.go b/e2e/playwright/archives_test.go index a05e96e..e06afff 100644 --- a/e2e/playwright/archives_test.go +++ b/e2e/playwright/archives_test.go @@ -55,11 +55,12 @@ func TestArchive_AutomaticCreation(t *testing.T) { // Navigate to link details to see archive status err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") - time.Sleep(1 * time.Second) helper.Screenshot("link-details-archive") - // Verify archive section exists - helper.AssertVisible(".archive-status, .archive-history, [class*='archive']", "Archive information should be visible") + // Wait for archive processing to surface in the UI + err = helper.WaitForSelector(".archive-status, [class*='archive']", 30*time.Second) + require.NoError(t, err, "Archive information should appear") + helper.AssertVisible(".archive-status, [class*='archive']", "Archive information should be visible") t.Log("Archive automatic creation test completed successfully") } @@ -107,13 +108,13 @@ func TestArchive_ViewHistory(t *testing.T) { // Navigate to link details err = helper.Click(".link-item") require.NoError(t, err, "Failed to navigate to link details") - time.Sleep(1 * time.Second) helper.Screenshot("archive-history") - // Verify archive history is visible - helper.AssertVisible(".archive-history, .archive-list, [class*='archive']", "Archive history should be visible") + // Wait for archive to appear in the UI + err = helper.WaitForSelector(".archive-status, [class*='archive']", 30*time.Second) + require.NoError(t, err, "Archive history should appear") + helper.AssertVisible(".archive-status, [class*='archive']", "Archive history should be visible") - // Wait for archive to process (with timeout) t.Log("Waiting for archive to complete...") time.Sleep(10 * time.Second) helper.Screenshot("archive-after-wait") diff --git a/scripts/e2e.sh b/scripts/e2e.sh index 770f21d..170d014 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -4,12 +4,12 @@ set -e echo "๐Ÿงช Running Hako E2E Tests" # Environment setup -export TESTCONTAINERS_RYUK_DISABLED=false +export TESTCONTAINERS_RYUK_DISABLED="${TESTCONTAINERS_RYUK_DISABLED:-false}" export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}" # Check Docker if ! docker info > /dev/null 2>&1; then - echo "โŒ Error: Docker is not running" + echo "โŒ Error: Docker is not available (check DOCKER_HOST and socket mount)" exit 1 fi -- 2.52.0 From 439a600721575c62b0722c23e95e15fa3cdfb9bd Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 19 Aug 2026 15:10:27 +0200 Subject: [PATCH 11/14] fix(ci): use Docker-in-Docker service for e2e testcontainers Mounting the host socket via container options is blocked by the runner, so run a dind service and point DOCKER_HOST at it instead. Co-authored-by: Cursor --- .github/workflows/ci.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e52b30..3627ee9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,19 +49,29 @@ jobs: e2e: runs-on: docker - container: - image: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 - options: --volume /var/run/docker.sock:/var/run/docker.sock + container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 + services: + dind: + image: docker:27-dind + env: + DOCKER_TLS_CERTDIR: "" + options: --privileged env: + DOCKER_HOST: tcp://dind:2375 TESTCONTAINERS_RYUK_DISABLED: true - DOCKER_HOST: unix:///var/run/docker.sock steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: e2e/go.mod - - name: Verify Docker access - run: docker info + - name: Wait for Docker + run: | + for i in $(seq 1 30); do + docker info >/dev/null 2>&1 && exit 0 + sleep 1 + done + echo "Docker daemon not ready" >&2 + exit 1 - name: Install Playwright browsers run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium - run: make e2e -- 2.52.0 From 68905fab6c21c81d9f7f1694011ae4d8e1c243a9 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 19 Aug 2026 15:16:15 +0200 Subject: [PATCH 12/14] fix(ci): install Playwright system deps before browser download The --with-deps flag fails in the DinD job container; install Ubuntu packages explicitly and download chromium from the e2e module context. Co-authored-by: Cursor --- .github/workflows/ci.yml | 13 +++++++++++-- scripts/e2e.sh | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3627ee9..8061306 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,14 +66,23 @@ jobs: go-version-file: e2e/go.mod - name: Wait for Docker run: | - for i in $(seq 1 30); do + for i in $(seq 1 60); do docker info >/dev/null 2>&1 && exit 0 sleep 1 done echo "Docker daemon not ready" >&2 exit 1 + - name: Install Playwright system dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends \ + libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \ + libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \ + libgbm1 libpango-1.0-0 libcairo2 libasound2t64 libatspi2.0-0 \ + fonts-liberation libappindicator3-1 xdg-utils - name: Install Playwright browsers - run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium + working-directory: e2e + run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install chromium - run: make e2e build: diff --git a/scripts/e2e.sh b/scripts/e2e.sh index 170d014..371c78c 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -16,7 +16,7 @@ fi # Install Playwright if needed if ! go run github.com/playwright-community/playwright-go/cmd/playwright@latest --help > /dev/null 2>&1; then echo "๐Ÿ“ฆ Installing Playwright browsers..." - go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium + (cd e2e && go run github.com/playwright-community/playwright-go/cmd/playwright@latest install chromium) fi # Build webapp (required for embed) -- 2.52.0 From 9b1ce9ddf7f4f157f3f578d3e610942a0c1f0c71 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 19 Aug 2026 15:23:04 +0200 Subject: [PATCH 13/14] fix(ci): simplify e2e job and default testcontainers for CI Drop DinD service (breaks apt/playwright in the job container) and the workflow env overrides that masked the e2e script config. Default Ryuk disabled in e2e.sh for CI friendliness. Co-authored-by: Cursor --- .github/workflows/ci.yml | 28 +--------------------------- scripts/e2e.sh | 2 +- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8061306..0f782b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,39 +50,13 @@ jobs: e2e: runs-on: docker container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 - services: - dind: - image: docker:27-dind - env: - DOCKER_TLS_CERTDIR: "" - options: --privileged - env: - DOCKER_HOST: tcp://dind:2375 - TESTCONTAINERS_RYUK_DISABLED: true steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: e2e/go.mod - - name: Wait for Docker - run: | - for i in $(seq 1 60); do - docker info >/dev/null 2>&1 && exit 0 - sleep 1 - done - echo "Docker daemon not ready" >&2 - exit 1 - - name: Install Playwright system dependencies - run: | - apt-get update - apt-get install -y --no-install-recommends \ - libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \ - libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \ - libgbm1 libpango-1.0-0 libcairo2 libasound2t64 libatspi2.0-0 \ - fonts-liberation libappindicator3-1 xdg-utils - name: Install Playwright browsers - working-directory: e2e - run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install chromium + run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium - run: make e2e build: diff --git a/scripts/e2e.sh b/scripts/e2e.sh index 371c78c..50c5d92 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -4,7 +4,7 @@ set -e echo "๐Ÿงช Running Hako E2E Tests" # Environment setup -export TESTCONTAINERS_RYUK_DISABLED="${TESTCONTAINERS_RYUK_DISABLED:-false}" +export TESTCONTAINERS_RYUK_DISABLED="${TESTCONTAINERS_RYUK_DISABLED:-true}" export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}" # Check Docker -- 2.52.0 From 5fbd6788a42d39f1cc428cd21a3f561afe9c8a57 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Wed, 19 Aug 2026 15:28:08 +0200 Subject: [PATCH 14/14] fix(ci): combine DinD with Playwright install without --with-deps DinD is required for testcontainers but breaks apt-based --with-deps; install chromium only after the daemon is ready. Co-authored-by: Cursor --- .github/workflows/ci.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f782b4..2707448 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,13 +50,30 @@ jobs: e2e: runs-on: docker container: git.nakama.town/fmartingr/ci-images/ci-base:1.1.0 + services: + dind: + image: docker:27-dind + env: + DOCKER_TLS_CERTDIR: "" + options: --privileged + env: + DOCKER_HOST: tcp://dind:2375 + TESTCONTAINERS_RYUK_DISABLED: true steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: e2e/go.mod + - name: Wait for Docker + run: | + for i in $(seq 1 60); do + docker info >/dev/null 2>&1 && exit 0 + sleep 1 + done + echo "Docker daemon not ready" >&2 + exit 1 - name: Install Playwright browsers - run: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium + run: go run github.com/playwright-community/playwright-go/cmd/playwright@v0.5200.1 install chromium - run: make e2e build: -- 2.52.0