diff --git a/.gitignore b/.gitignore index 296fef3..b8bc73f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ data # Dev TLS certs (self-signed for local testing) .dev-certs + +# CI artifact (kaniko reads .tags for image tag list) +.tags diff --git a/.woodpecker/push.yml b/.woodpecker/push.yml index 59b8f36..f7c588e 100644 --- a/.woodpecker/push.yml +++ b/.woodpecker/push.yml @@ -3,14 +3,34 @@ when: branch: main steps: - - name: docker-build-push + - name: release + image: alpine + commands: + - apk add --no-cache git + - ./scripts/release.sh + environment: + GITEA_REPO_TOKEN: + from_secret: gitea_repo_token + + - name: docker image: woodpeckerci/plugin-kaniko + depends_on: [release] settings: repo: ${CI_REPO_OWNER}/${CI_REPO_NAME} registry: git.keligrubb.com - tags: latest,${CI_COMMIT_SHA:0:7} username: ${CI_REPO_OWNER} password: from_secret: gitea_registry_token single-snapshot: true cleanup: true + + - name: helm + image: alpine/helm + depends_on: [release] + environment: + GITEA_REGISTRY_TOKEN: + from_secret: gitea_registry_token + commands: + - apk add --no-cache curl + - helm package helm/kestrelos + - curl -sf -u $CI_REPO_OWNER:$GITEA_REGISTRY_TOKEN -X POST --upload-file kestrelos-*.tgz https://git.keligrubb.com/api/packages/$CI_REPO_OWNER/helm/api/charts diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6361e43 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +All notable changes to this project will be documented in this file. diff --git a/README.md b/README.md index 87632bc..47785bd 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,30 @@ docker run -p 3000:3000 kestrelos:latest ## Kubernetes (Helm) +**From Gitea registry:** ```bash -helm install kestrelos ./helm/kestrelos --set image.repository=your-registry/kestrelos --set image.tag=latest +helm repo add keligrubb --username YOUR_USER --password YOUR_TOKEN https://git.keligrubb.com/api/packages/keligrubb/helm +helm repo update +helm install kestrelos keligrubb/kestrelos +``` + +**From source:** +```bash +helm install kestrelos ./helm/kestrelos ``` Health: `GET /health` (overview), `GET /health/live` (liveness), `GET /health/ready` (readiness). Probes are configured in the Helm chart. Optional: enable Ingress in `helm/kestrelos/values.yaml`. +## Releases + +Merges to `main` trigger a semver release. Use one of these prefixes in your PR title to set the version bump: + +- `major:` – breaking changes +- `minor:` – new features +- `patch:` – bug fixes, docs (default if no prefix) + +Example: `minor: Add map layer toggle` + ## Security - Device data is validated server-side; only valid entries are returned. diff --git a/helm/kestrelos/values.yaml b/helm/kestrelos/values.yaml index 84574ed..8b481b3 100644 --- a/helm/kestrelos/values.yaml +++ b/helm/kestrelos/values.yaml @@ -1,7 +1,7 @@ replicaCount: 1 image: - repository: kestrelos + repository: git.keligrubb.com/keligrubb/kestrelos tag: latest pullPolicy: IfNotPresent diff --git a/package-lock.json b/package-lock.json index 484dbe9..497cd9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "ws": "^8.18.0" }, "devDependencies": { + "@iconify-json/tabler": "^1.2.26", "@nuxt/eslint": "^1.15.0", "@nuxt/test-utils": "^4.0.0", "@playwright/test": "^1.58.2", @@ -1433,6 +1434,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify-json/tabler": { + "version": "1.2.26", + "resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.26.tgz", + "integrity": "sha512-92G+ZD70AZgeJf07JfQzH+isnai6DwPcMBuF/qL1F+xAxdXCJzGd3w2RmsRvOmB+w1ImmWEEDms50QivQIjd6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify/collections": { "version": "1.0.649", "resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.649.tgz", diff --git a/package.json b/package.json index 83fb6b7..4bd2de9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "kestrelos", + "version": "0.1.0", "private": true, "type": "module", "scripts": { @@ -32,6 +33,7 @@ "ws": "^8.18.0" }, "devDependencies": { + "@iconify-json/tabler": "^1.2.26", "@nuxt/eslint": "^1.15.0", "@nuxt/test-utils": "^4.0.0", "@playwright/test": "^1.58.2", diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..0cff822 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,56 @@ +#!/bin/sh +set -e + +# version +msg="${CI_COMMIT_MESSAGE:-}" +bump=patch +echo "$msg" | grep -qi minor: && bump=minor +echo "$msg" | grep -qi major: && bump=major +cur=$(awk '/"version"/ { match($0, /[0-9]+\.[0-9]+\.[0-9]+/); print substr($0, RSTART, RLENGTH); exit }' package.json) +major=$(echo "$cur" | cut -d. -f1); minor=$(echo "$cur" | cut -d. -f2); patch=$(echo "$cur" | cut -d. -f3) +case "$bump" in major) major=$((major+1)); minor=0; patch=0 ;; minor) minor=$((minor+1)); patch=0 ;; patch) patch=$((patch+1)) ;; esac +newVersion="$major.$minor.$patch" +[ -z "$cur" ] && { echo "error: could not read version from package.json"; exit 1; } + +# changelog entry (strip prefix from first line) +changelogEntry=$(echo "$msg" | head -1 | awk '{sub(/^[mM]ajor:[ \t]*/,""); sub(/^[mM]inor:[ \t]*/,""); sub(/^[pP]atch:[ \t]*/,""); print}') +[ -z "$changelogEntry" ] && changelogEntry="Release v$newVersion" + +# bump files +awk -v v="$newVersion" '/"version"/ { sub(/[0-9]+\.[0-9]+\.[0-9]+/, v) } { print }' package.json > package.json.tmp && mv package.json.tmp package.json +awk -v v="$newVersion" '/^version:/ { $0 = "version: " v }; /^appVersion:/ { $0 = "appVersion: \"" v "\"" }; { print }' helm/kestrelos/Chart.yaml > helm/kestrelos/Chart.yaml.tmp && mv helm/kestrelos/Chart.yaml.tmp helm/kestrelos/Chart.yaml +awk -v v="$newVersion" '/^ tag:/ { $0 = " tag: " v }; { print }' helm/kestrelos/values.yaml > helm/kestrelos/values.yaml.tmp && mv helm/kestrelos/values.yaml.tmp helm/kestrelos/values.yaml + +# changelog +new="## [$newVersion] - $(date +%Y-%m-%d) +### Changed +- $changelogEntry + +" +{ [ ! -f CHANGELOG.md ] && printf '# Changelog\n\n'; printf '%s' "$new"; [ -f CHANGELOG.md ] && cat CHANGELOG.md; } > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md + +# git +git config user.email "ci@kestrelos" && git config user.name "CI" +git add package.json helm/kestrelos/Chart.yaml helm/kestrelos/values.yaml CHANGELOG.md +git commit -m "release v$newVersion [skip ci]" +url="https://${CI_REPO_OWNER}:${GITEA_REPO_TOKEN}@${CI_FORGE_URL#https://}/${CI_REPO_OWNER}/${CI_REPO_NAME}.git" +git tag "v$newVersion" +# artifact for kaniko (tag list) +printf '%s\n%s\n' "$newVersion" "latest" > .tags +retry() { n=0; while ! "$@"; do n=$((n+1)); [ $n -ge 3 ] && return 1; sleep 2; done; } +retry git push "$url" HEAD:main "v$newVersion" + +# gitea release +body="## Changelog +### Changed +- $changelogEntry + +## Installation +- [Docker image](${CI_FORGE_URL}/${CI_REPO_OWNER}/-/packages/container/${CI_REPO_NAME}) +- [Helm chart](${CI_FORGE_URL}/${CI_REPO_OWNER}/-/packages/helm)" +release_url="${CI_FORGE_URL}/api/v1/repos/${CI_REPO_OWNER}/${CI_REPO_NAME}/releases" +echo "$body" | awk -v tag="v$newVersion" 'BEGIN{printf "{\"tag_name\":\"" tag "\",\"name\":\"" tag "\",\"body\":\""} { gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); if (NR>1) printf "\\n"; printf "%s", $0 } END{printf "\"}\n"}' > /tmp/release.json +wget -q -O /dev/null --post-file=/tmp/release.json \ + --header="Authorization: token ${GITEA_REPO_TOKEN}" \ + --header="Content-Type: application/json" \ + "$release_url"