From 91e3bbc9d949e6dabcb464ff9686638fdf649027 Mon Sep 17 00:00:00 2001 From: Stavros Date: Tue, 20 May 2025 16:39:27 +0300 Subject: [PATCH] refactor: store version in constants --- .github/workflows/nightly.yml | 68 +++++++++++++++++++++----------- .github/workflows/release.yml | 61 +++++++++++++++++----------- Dockerfile | 9 ++++- air.toml | 2 +- cmd/root.go | 4 +- cmd/version.go | 24 +++++++++++ frontend/bun.lockb | Bin 160619 -> 160590 bytes internal/assets/assets.go | 5 --- internal/constants/constants.go | 5 +++ 9 files changed, 122 insertions(+), 56 deletions(-) create mode 100644 cmd/version.go diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 352f70d..2df55a4 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -22,9 +22,31 @@ jobs: prerelease: true tag_name: nightly - binary-build: + generate-metadata: runs-on: ubuntu-latest needs: create-release + outputs: + VERSION: ${{ steps.metadata.outputs.VERSION }} + COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }} + BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: nightly + + - name: Generate metadata + id: metadata + run: | + echo "VERSION=nightly" >> "$GITHUB_OUTPUT" + echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT" + + binary-build: + runs-on: ubuntu-latest + needs: + - create-release + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -46,11 +68,7 @@ jobs: - name: Install backend dependencies run: | - go mod tidy - - - name: Set version - run: | - echo nightly > internal/assets/version + go mod download - name: Build frontend run: | @@ -60,7 +78,7 @@ jobs: - name: Build run: | cp -r frontend/dist internal/assets/dist - CGO_ENABLED=0 go build -ldflags "-s -w" -o tinyauth-amd64 + go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 - name: Upload artifact uses: actions/upload-artifact@v4 @@ -70,7 +88,9 @@ jobs: binary-build-arm: runs-on: ubuntu-24.04-arm - needs: create-release + needs: + - create-release + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -92,11 +112,7 @@ jobs: - name: Install backend dependencies run: | - go mod tidy - - - name: Set version - run: | - echo nightly > internal/assets/version + go mod download - name: Build frontend run: | @@ -106,7 +122,7 @@ jobs: - name: Build run: | cp -r frontend/dist internal/assets/dist - CGO_ENABLED=0 go build -ldflags "-s -w" -o tinyauth-arm64 + go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 - name: Upload artifact uses: actions/upload-artifact@v4 @@ -116,7 +132,9 @@ jobs: image-build: runs-on: ubuntu-latest - needs: create-release + needs: + - create-release + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -139,19 +157,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Set version - run: | - echo nightly > internal/assets/version - - name: Build and push uses: docker/build-push-action@v6 id: build with: - context: . platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} tags: ghcr.io/${{ github.repository_owner }}/tinyauth outputs: type=image,push-by-digest=true,name-canonical=true,push=true + build-args: | + VERSION=${{ needs.generate-metadata.outputs.VERSION }} + COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} + BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} - name: Export digest run: | @@ -169,7 +186,9 @@ jobs: image-build-arm: runs-on: ubuntu-24.04-arm - needs: create-release + needs: + - create-release + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -200,11 +219,14 @@ jobs: uses: docker/build-push-action@v6 id: build with: - context: . platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} tags: ghcr.io/${{ github.repository_owner }}/tinyauth outputs: type=image,push-by-digest=true,name-canonical=true,push=true + build-args: | + VERSION=${{ needs.generate-metadata.outputs.VERSION }} + COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} + BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} - name: Export digest run: | @@ -225,7 +247,6 @@ jobs: needs: - image-build - image-build-arm - - create-release steps: - name: Download digests uses: actions/download-artifact@v4 @@ -263,7 +284,6 @@ jobs: needs: - binary-build - binary-build-arm - - create-release steps: - uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6302ee2..e233e45 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,29 @@ on: - "v*" jobs: + generate-metadata: + runs-on: ubuntu-latest + outputs: + VERSION: ${{ steps.metadata.outputs.VERSION }} + COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }} + BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: nightly + + - name: Generate metadata + id: metadata + run: | + echo "VERSION=nightly" >> "$GITHUB_OUTPUT" + echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT" + binary-build: runs-on: ubuntu-latest + needs: + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -27,11 +48,7 @@ jobs: - name: Install backend dependencies run: | - go mod tidy - - - name: Set version - run: | - echo ${{ github.ref_name }} > internal/assets/version + go mod download - name: Build frontend run: | @@ -41,7 +58,7 @@ jobs: - name: Build run: | cp -r frontend/dist internal/assets/dist - CGO_ENABLED=0 go build -ldflags "-s -w" -o tinyauth-amd64 + go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 - name: Upload artifact uses: actions/upload-artifact@v4 @@ -51,6 +68,8 @@ jobs: binary-build-arm: runs-on: ubuntu-24.04-arm + needs: + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -70,11 +89,7 @@ jobs: - name: Install backend dependencies run: | - go mod tidy - - - name: Set version - run: | - echo ${{ github.ref_name }} > internal/assets/version + go mod download - name: Build frontend run: | @@ -84,7 +99,7 @@ jobs: - name: Build run: | cp -r frontend/dist internal/assets/dist - CGO_ENABLED=0 go build -ldflags "-s -w" -o tinyauth-arm64 + go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 - name: Upload artifact uses: actions/upload-artifact@v4 @@ -94,6 +109,8 @@ jobs: image-build: runs-on: ubuntu-latest + needs: + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -114,19 +131,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Set version - run: | - echo ${{ github.ref_name }} > internal/assets/version - - name: Build and push uses: docker/build-push-action@v6 id: build with: - context: . platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} tags: ghcr.io/${{ github.repository_owner }}/tinyauth outputs: type=image,push-by-digest=true,name-canonical=true,push=true + build-args: | + VERSION=${{ needs.generate-metadata.outputs.VERSION }} + COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} + BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} - name: Export digest run: | @@ -144,6 +160,8 @@ jobs: image-build-arm: runs-on: ubuntu-24.04-arm + needs: + - generate-metadata steps: - name: Checkout uses: actions/checkout@v4 @@ -164,19 +182,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Set version - run: | - echo ${{ github.ref_name }} > internal/assets/version - - name: Build and push uses: docker/build-push-action@v6 id: build with: - context: . platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} tags: ghcr.io/${{ github.repository_owner }}/tinyauth outputs: type=image,push-by-digest=true,name-canonical=true,push=true + build-args: | + VERSION=${{ needs.generate-metadata.outputs.VERSION }} + COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} + BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} - name: Export digest run: | diff --git a/Dockerfile b/Dockerfile index aab29b8..4bcddd8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,8 @@ +# Arguments +ARG VERSION +ARG COMMIT_HASH +ARG BUILD_TIMESTAMP + # Site builder FROM oven/bun:1.2.12-alpine AS frontend-builder @@ -35,8 +40,8 @@ COPY ./cmd ./cmd COPY ./internal ./internal COPY --from=frontend-builder /frontend/dist ./internal/assets/dist -RUN CGO_ENABLED=0 go build -ldflags "-s -w" - +RUN go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${VERSION} -X tinyauth/internal/constants.CommitHash=${COMMIT_HASH} -X tinyauth/internal/constants.BuildTimestamp=${BUILD_TIMESTAMP}" + # Runner FROM alpine:3.21 AS runner diff --git a/air.toml b/air.toml index 1353f66..58e2132 100644 --- a/air.toml +++ b/air.toml @@ -2,7 +2,7 @@ root = "/tinyauth" tmp_dir = "tmp" [build] -pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html", "echo development > internal/assets/version"] +pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html"] cmd = "go build -o ./tmp/tinyauth ." bin = "tmp/tinyauth" include_ext = ["go"] diff --git a/cmd/root.go b/cmd/root.go index 84822c1..0786f8a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,8 +8,8 @@ import ( totpCmd "tinyauth/cmd/totp" userCmd "tinyauth/cmd/user" "tinyauth/internal/api" - "tinyauth/internal/assets" "tinyauth/internal/auth" + "tinyauth/internal/constants" "tinyauth/internal/docker" "tinyauth/internal/handlers" "tinyauth/internal/hooks" @@ -50,7 +50,7 @@ var rootCmd = &cobra.Command{ // Logger log.Logger = log.Level(zerolog.Level(config.LogLevel)) - log.Info().Str("version", strings.TrimSpace(assets.Version)).Msg("Starting tinyauth") + log.Info().Str("version", strings.TrimSpace(constants.Version)).Msg("Starting tinyauth") // Users log.Info().Msg("Parsing users") diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..0c6f600 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + "tinyauth/internal/constants" + + "github.com/spf13/cobra" +) + +// Create the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Tinyauth", + Long: `All software has versions. This is Tinyauth's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Version: %s\n", constants.Version) + fmt.Printf("Commit Hash: %s\n", constants.CommitHash) + fmt.Printf("Build Timestamp: %s\n", constants.BuildTimestamp) + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) +} diff --git a/frontend/bun.lockb b/frontend/bun.lockb index a0f152b8b9f4d9ead52a34236f052c813a8bd137..51ee79049e04962c8a320665891dc93439f24efb 100755 GIT binary patch delta 11557 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 DGwi`+ delta 11706 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