diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7a3fbe9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +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 + + 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-nodata diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1bfa3f8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,36 @@ +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: Clone data repository + run: git clone https://fmartingr:${{ secrets.FORGEJO_TOKEN }}@git.nakama.town/fmartingr/terraria-companion-data.git data + + - 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 + + - 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/.gitignore b/.gitignore index 8cf7f55..06d347b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,18 @@ server.log terraria-companion terraria-companion-* dist/ +build/ +# Data /data + +# Environment +.env + +# Coverage +coverage.html +coverage.out + +# AI tools +.claude/ +.cursor/ diff --git a/.goreleaser.yml b/.goreleaser.yml index 86ced31..831ab92 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -44,25 +44,31 @@ dockers_v2: - linux/arm64 checksum: - name_template: "checksums.txt" - -snapshot: - version_template: "{{ incpatch .Version }}-next" + name_template: checksums.txt changelog: sort: asc groups: - title: Features - regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' + regexp: "^.*feat.*$" order: 0 - - title: "Fixes" - regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' + - title: Bug Fixes + regexp: "^.*fix.*$" order: 1 + - title: Performance + regexp: "^.*perf.*$" + order: 2 + - title: Documentation + regexp: "^.*docs.*$" + order: 3 + - title: CI/CD + regexp: "^.*ci.*$" + order: 4 - title: Others order: 999 - filters: - exclude: - - "^chore\\(deps\\):" release: + gitea: + owner: fmartingr + name: terraria-companion prerelease: auto diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml deleted file mode 100644 index 05f8372..0000000 --- a/.woodpecker/ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -when: - event: - - push - - pull_request - branch: - - main - -steps: - format: - image: golang:1.24 - commands: - - test -z "$(gofmt -l .)" - - lint: - image: golangci/golangci-lint:latest - commands: - - golangci-lint run ./... - - build: - image: golang:1.24 - commands: - - go build -v ./... diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml deleted file mode 100644 index 395e9f5..0000000 --- a/.woodpecker/release.yml +++ /dev/null @@ -1,23 +0,0 @@ -when: - - event: tag - branch: main - -steps: - - name: Release - image: git.nakama.town/fmartingr/ci-images/ci-release:latest - environment: - GITEA_TOKEN: - from_secret: GITEA_TOKEN - DOCKER_HOST: unix:///var/run/docker.sock - volumes: - - "/var/run/docker.sock:/var/run/docker.sock" - commands: - # Clone data repository - - git clone https://fmartingr:$GITEA_TOKEN@git.nakama.town/fmartingr/terraria-companion-data.git data - - # Setup Docker buildx - - docker buildx create --use --name multiplatform --driver docker-container - - # Authenticate and release - - docker login -u fmartingr -p $GITEA_TOKEN git.nakama.town - - goreleaser release --clean diff --git a/Containerfile b/Containerfile index 61a7fb6..97af9fa 100644 --- a/Containerfile +++ b/Containerfile @@ -1,20 +1,23 @@ -FROM alpine:3.21 +# Build dependencies on native arch (no QEMU needed) +FROM --platform=$BUILDPLATFORM alpine:3.23 AS base +RUN apk add --no-cache ca-certificates tzdata +RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app +RUN mkdir /app && chown app:app /app + +# Runtime image +FROM scratch LABEL maintainer="Felipe Martin " LABEL org.opencontainers.image.source="https://git.nakama.town/fmartingr/terraria-companion" -RUN apk add --no-cache ca-certificates tzdata - -RUN addgroup -g 1000 app && \ - adduser -D -u 1000 -G app app +COPY --from=base /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=base /usr/share/zoneinfo /usr/share/zoneinfo +COPY --from=base /etc/passwd /etc/passwd +COPY --from=base /etc/group /etc/group +COPY --from=base /app /app +COPY terraria-companion /usr/local/bin/terraria-companion WORKDIR /app -ARG TARGETPLATFORM -COPY ${TARGETPLATFORM}/terraria-companion /usr/local/bin/terraria-companion -RUN chmod +x /usr/local/bin/terraria-companion && \ - mkdir -p /app/data /app/data/icons && \ - chown -R app:app /app - USER app EXPOSE 3000 diff --git a/Makefile b/Makefile index cc41a9a..dd45640 100644 --- a/Makefile +++ b/Makefile @@ -1,69 +1,92 @@ -BINARY_NAME := terraria-companion +PROJECT_NAME := terraria-companion +CGO_ENABLED ?= 0 PORT ?= 3000 VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") LDFLAGS := -X main.version=$(VERSION) +GOLANGCI_LINT_VERSION := v1.64.5 + +.DEFAULT_GOAL := help + +## help: Display this help message +.PHONY: help +help: + @echo "Available targets:" + @grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /' | sort + # ── Build ──────────────────────────────────────────────────────── + +## build: Build the production binary (embeds data assets) .PHONY: build -build: ## Build the production binary (embeds data assets) - go build -tags embed_data -ldflags "$(LDFLAGS)" -o $(BINARY_NAME) . +build: + CGO_ENABLED=$(CGO_ENABLED) go build -tags embed_data -ldflags "$(LDFLAGS)" -o $(PROJECT_NAME) . +## build-nodata: Build without embedded data (for CI/containers) .PHONY: build-nodata -build-nodata: ## Build without embedded data (for CI/containers) - go build -ldflags "$(LDFLAGS)" -o $(BINARY_NAME) . +build-nodata: + CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "$(LDFLAGS)" -o $(PROJECT_NAME) . +## build-snapshot: Build all platforms locally via goreleaser (snapshot) .PHONY: build-snapshot -build-snapshot: ## Build all platforms locally via goreleaser (snapshot) +build-snapshot: goreleaser build --snapshot --clean # ── Development ────────────────────────────────────────────────── + +## dev: Run in dev mode (serves frontend from disk, hot-reloads data) .PHONY: dev -dev: ## Run in dev mode (serves frontend from disk, hot-reloads data) +dev: DEV=1 go run . +## serve: Build and run the production server .PHONY: serve -serve: build ## Build and run the production server - ./$(BINARY_NAME) +serve: build + ./$(PROJECT_NAME) # ── Data Fetching ──────────────────────────────────────────────── + +## fetch: Fetch/resume data from Terraria wiki (Go fetcher) .PHONY: fetch -fetch: ## Fetch/resume data from Terraria wiki (Go fetcher) +fetch: go run . fetch +## fetch-reset: Reset fetch state and start fresh .PHONY: fetch-reset -fetch-reset: ## Reset fetch state and start fresh +fetch-reset: go run . fetch --reset +## fetch-only: Fetch a single phase (usage: make fetch-only PHASE=drops) .PHONY: fetch-only -fetch-only: ## Fetch a single phase (usage: make fetch-only PHASE=drops) +fetch-only: go run . fetch --only=$(PHASE) # ── Quality ────────────────────────────────────────────────────── -.PHONY: format -format: ## Format Go source files - gofmt -w . -.PHONY: lint -lint: ## Run linter +## format: Format code and tidy modules +.PHONY: format +format: + go fmt ./... + go mod tidy + +## ci-lint: Run linter (auto-installs golangci-lint for CI) +.PHONY: ci-lint +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 ./... -.PHONY: ci-lint -ci-lint: ## Run linter with colored output (for CI) - golangci-lint run --out-format colored-line-number ./... +## lint: Run linters +.PHONY: lint +lint: ci-lint # ── Cleanup ────────────────────────────────────────────────────── + +## clean: Remove build artifacts .PHONY: clean -clean: ## Remove build artifacts - rm -f $(BINARY_NAME) $(BINARY_NAME)-* +clean: + rm -f $(PROJECT_NAME) $(PROJECT_NAME)-* rm -rf dist/ +## clean-data: Remove fetched data (keeps icons as cache) .PHONY: clean-data -clean-data: ## Remove fetched data (keeps icons as cache) +clean-data: rm -rf data - -# ── Help ───────────────────────────────────────────────────────── -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' - -.DEFAULT_GOAL := help diff --git a/server.go b/server.go index e0dd87f..3f317a7 100644 --- a/server.go +++ b/server.go @@ -100,13 +100,13 @@ type StatusResponse struct { // ── DataStore ─────────────────────────────────────────────────── type DataStore struct { - mu sync.RWMutex - items []Item - itemsByName map[string]Item - recipesByResult map[string][]Recipe - recipesByIngredient map[string][]Recipe - dropsByItem map[string][]Drop - splashes []string + mu sync.RWMutex + items []Item + itemsByName map[string]Item + recipesByResult map[string][]Recipe + recipesByIngredient map[string][]Recipe + dropsByItem map[string][]Drop + splashes []string } func sanitizeImagefile(name string) string {