name: Release on: push: tags: - "v*.*.*" workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: GO_VERSION: "1.26.1" NODE_VERSION: "24" PNPM_VERSION: "10" ARTIFACT_RETENTION_DAYS: 60 ARTIFACT_PREFIX: memos jobs: prepare: name: Extract Version runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} tag: ${{ steps.version.outputs.tag }} steps: - name: Extract version id: version env: REF_NAME: ${{ github.ref_name }} EVENT_NAME: ${{ github.event_name }} run: | if [ "$EVENT_NAME" = "workflow_dispatch" ]; then echo "tag=" >> "$GITHUB_OUTPUT" echo "version=manual-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" exit 0 fi echo "tag=${REF_NAME}" >> "$GITHUB_OUTPUT" echo "version=${REF_NAME#v}" >> "$GITHUB_OUTPUT" build-frontend: name: Build Frontend needs: prepare runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup pnpm uses: pnpm/action-setup@v4.2.0 with: version: ${{ env.PNPM_VERSION }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm cache-dependency-path: web/pnpm-lock.yaml - name: Get pnpm store directory id: pnpm-cache shell: bash run: echo "STORE_PATH=$(pnpm store path)" >> "$GITHUB_OUTPUT" - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('web/pnpm-lock.yaml') }} restore-keys: ${{ runner.os }}-pnpm-store- - name: Install dependencies working-directory: web run: pnpm install --frozen-lockfile - name: Build frontend release assets working-directory: web run: pnpm release - name: Upload frontend artifacts uses: actions/upload-artifact@v4 with: name: frontend-dist path: server/router/frontend/dist retention-days: 1 build-binaries: name: Build ${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm && format('v{0}', matrix.goarm) || '' }} needs: [prepare, build-frontend] runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - goos: linux goarch: amd64 - goos: linux goarch: arm64 - goos: linux goarch: arm goarm: "7" - goos: darwin goarch: amd64 - goos: darwin goarch: arm64 - goos: windows goarch: amd64 steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} cache: true - name: Download frontend artifacts uses: actions/download-artifact@v4 with: name: frontend-dist path: server/router/frontend/dist - name: Build binary env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} CGO_ENABLED: "0" run: | output_name="memos" if [ "$GOOS" = "windows" ]; then output_name="memos.exe" fi mkdir -p build go build \ -trimpath \ -ldflags="-s -w -X github.com/usememos/memos/internal/version.Version=${{ needs.prepare.outputs.version }} -extldflags '-static'" \ -tags netgo,osusergo \ -o "build/${output_name}" \ ./cmd/memos - name: Package binary env: VERSION: ${{ needs.prepare.outputs.version }} GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} run: | cd build package_name="${ARTIFACT_PREFIX}_${VERSION}_${GOOS}_${GOARCH}" if [ -n "$GOARM" ]; then package_name="${package_name}v${GOARM}" fi if [ "$GOOS" = "windows" ]; then artifact_name="${package_name}.zip" zip -q "${artifact_name}" memos.exe else artifact_name="${package_name}.tar.gz" tar czf "${artifact_name}" memos fi echo "artifact_name=${artifact_name}" >> "$GITHUB_ENV" - name: Upload binary artifact uses: actions/upload-artifact@v4 with: name: ${{ env.artifact_name }} path: build/${{ env.artifact_name }} retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} checksums: name: Generate Checksums needs: [prepare, build-binaries] runs-on: ubuntu-latest steps: - name: Download binary artifacts uses: actions/download-artifact@v4 with: path: artifacts pattern: ${{ env.ARTIFACT_PREFIX }}_* merge-multiple: true - name: Generate checksums working-directory: artifacts run: sha256sum * > checksums.txt - name: Upload checksum artifact uses: actions/upload-artifact@v4 with: name: checksums path: artifacts/checksums.txt retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} release: name: Publish GitHub Release needs: [prepare, build-binaries, checksums] if: github.event_name != 'workflow_dispatch' runs-on: ubuntu-latest permissions: contents: write steps: - name: Download binary artifacts uses: actions/download-artifact@v4 with: path: artifacts pattern: ${{ env.ARTIFACT_PREFIX }}_* merge-multiple: true - name: Download checksum artifact uses: actions/download-artifact@v4 with: name: checksums path: artifacts - name: Publish release assets uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare.outputs.tag }} name: ${{ needs.prepare.outputs.tag }} generate_release_notes: true files: artifacts/* build-push: name: Build Image ${{ matrix.platform }} needs: [prepare, build-frontend] if: github.event_name != 'workflow_dispatch' runs-on: ubuntu-latest permissions: contents: read packages: write strategy: fail-fast: false matrix: platform: - linux/amd64 - linux/arm/v7 - linux/arm64 steps: - name: Checkout code uses: actions/checkout@v6 - name: Download frontend artifacts uses: actions/download-artifact@v4 with: name: frontend-dist path: server/router/frontend/dist - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Build and push by digest id: build uses: docker/build-push-action@v6 with: context: . file: ./scripts/Dockerfile platforms: ${{ matrix.platform }} build-args: | VERSION=${{ needs.prepare.outputs.version }} COMMIT=${{ github.sha }} cache-from: type=gha,scope=release-${{ matrix.platform }} cache-to: type=gha,mode=max,scope=release-${{ matrix.platform }} outputs: type=image,name=neosmemo/memos,push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v4 with: name: digests-${{ strategy.job-index }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge-images: name: Publish Stable Image Tags needs: [prepare, build-push] if: github.event_name != 'workflow_dispatch' runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Download digests uses: actions/download-artifact@v4 with: pattern: digests-* merge-multiple: true path: /tmp/digests - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Create manifest list and push working-directory: /tmp/digests run: | version="${{ needs.prepare.outputs.version }}" major_minor=$(echo "$version" | cut -d. -f1,2) docker buildx imagetools create \ -t "neosmemo/memos:${version}" \ -t "neosmemo/memos:${major_minor}" \ -t "neosmemo/memos:stable" \ -t "ghcr.io/usememos/memos:${version}" \ -t "ghcr.io/usememos/memos:${major_minor}" \ -t "ghcr.io/usememos/memos:stable" \ $(printf 'neosmemo/memos@sha256:%s ' *) - name: Inspect images run: | docker buildx imagetools inspect neosmemo/memos:${{ needs.prepare.outputs.version }} docker buildx imagetools inspect neosmemo/memos:stable