on: workflow_dispatch: push: branches: - main paths-ignore: - flake.nix jobs: build-web: runs-on: nixos steps: - name: Install node run: nix profile add nixpkgs#nodejs_24 - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Pull git submodules run: git submodule update --init --recursive - name: Install dependencies run: nix develop .#electron --command pnpm install --frozen-lockfile - name: Copy licenses run: nix develop .#electron --command pnpm run copy-licenses - name: Build packages run: nix develop .#electron --command pnpm run build:packages - name: Build web run: nix develop .#electron --command pnpm run build:web - name: Deploy run: nix develop .#electron --command rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-prod/ build-mobile: runs-on: nixos steps: - name: Install node run: nix profile add nixpkgs#nodejs_24 - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Pull git submodules run: git submodule update --init --recursive - name: Install dependencies run: nix develop .#tauri --command pnpm install --frozen-lockfile - name: Copy licenses run: nix develop .#tauri --command pnpm run copy-licenses - name: Build packages run: nix develop .#tauri --command pnpm run build:packages - name: Setup Android Keystore env: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} run: | nix profile add nixpkgs#gnused set -euo pipefail if [ -z "$KEYSTORE_BASE64" ]; then echo "ANDROID_KEYSTORE_BASE64 secret is missing or empty" exit 1 fi if [ -z "$KEYSTORE_PROPERTIES" ]; then echo "ANDROID_KEYSTORE_PROPERTIES secret is missing or empty" exit 1 fi printf '%s' "$KEYSTORE_BASE64" \ | tr -d '[:space:]' \ | base64 -d > keystore.jks printf '%s' "$KEYSTORE_PROPERTIES" \ | sed 's/\\n/\n/g' \ | tr -d '\r' \ | sed 's|^[[:space:]]*storeFile[[:space:]]*=.*|storeFile=keystore.jks|' \ > keystore.properties grep -q '^[[:space:]]*storeFile[[:space:]]*=' keystore.properties || printf '\nstoreFile=keystore.jks\n' >> keystore.properties if [ ! -s keystore.jks ]; then echo "Decoded keystore.jks is missing or empty" exit 1 fi if [ ! -s keystore.properties ]; then echo "Generated keystore.properties is missing or empty" exit 1 fi if ! grep -q '^[[:space:]]*keyAlias[[:space:]]*=' keystore.properties; then echo "keystore.properties is missing keyAlias" exit 1 fi if ! grep -Eq '^[[:space:]]*(keyPassword|password)[[:space:]]*=' keystore.properties; then echo "keystore.properties is missing keyPassword or password" exit 1 fi if ! grep -Eq '^[[:space:]]*(storePassword|password)[[:space:]]*=' keystore.properties; then echo "keystore.properties is missing storePassword or password" exit 1 fi - name: Build mobile run: nix develop .#tauri --command pnpm run build:mobile - name: Upload mobile artifact uses: https://data.forgejo.org/actions/upload-artifact@v3 with: name: mobile-apk path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk build-desktop: runs-on: nixos strategy: matrix: target: [linux] steps: - name: Install node run: nix profile add nixpkgs#nodejs_24 - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Pull git submodules run: git submodule update --init --recursive - name: Install dependencies run: nix develop .#electron --command pnpm install --frozen-lockfile - name: Copy licenses run: nix develop .#electron --command pnpm run copy-licenses - name: Build packages run: nix develop .#electron --command pnpm run build:packages - name: Set Electron prod version run: | nix develop .#electron --command bash <<'EOF' set -euo pipefail VERSION="$(node -p "require('./package.json').version")" export VERSION node -e ' const fs = require("fs"); const path = "apps/electron/package.json"; const pkg = JSON.parse(fs.readFileSync(path, "utf8")); pkg.version = process.env.VERSION; fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); ' EOF - name: Build Electron desktop run: | nix develop .#electron --command bash <<'EOF' set -euo pipefail cd apps/electron pnpm run package:raw EOF - name: Upload desktop artifacts uses: https://data.forgejo.org/actions/upload-artifact@v3 with: name: electron-desktop-${{ matrix.target }} path: apps/electron/release/ release: runs-on: nixos needs: [build-web, build-mobile, build-desktop] steps: - name: Install node run: nix profile add nixpkgs#nodejs_24 - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Pull git submodules run: git submodule update --init --recursive - name: Install dependencies run: nix develop .#electron --command pnpm install --frozen-lockfile - name: Download mobile artifact uses: https://data.forgejo.org/actions/download-artifact@v3 with: name: mobile-apk path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/ - name: Download desktop artifacts uses: https://data.forgejo.org/actions/download-artifact@v3 with: name: electron-desktop-linux path: apps/electron/release/ - name: Read version id: version run: | nix develop .#electron --command bash <<'EOF' set -euo pipefail VERSION="$(node -p "require('./package.json').version")" echo "version=$VERSION" >> "$FORGEJO_OUTPUT" echo "tag=$VERSION" >> "$FORGEJO_OUTPUT" EOF - name: Copy releases env: TENSAMIN_RELEASE_VERSION: ${{ steps.version.outputs.tag }} TENSAMIN_RELEASE_TAG: ${{ steps.version.outputs.tag }} run: | nix develop .#electron --command bash <<'EOF' set -euo pipefail ASSET_BASE_URL="${{ forgejo.api_url }}" ASSET_BASE_URL="${ASSET_BASE_URL%/api/v1}/${{ forgejo.repository }}/releases/download/${{ steps.version.outputs.tag }}" FORGEJO_RELEASE_ASSET_BASE_URL="$ASSET_BASE_URL" pnpm run copy-releases EOF - name: Create release and upload files env: TOKEN: ${{ forgejo.token }} API: ${{ forgejo.api_url }} REPO: ${{ forgejo.repository }} SHA: ${{ forgejo.sha }} TAG: ${{ steps.version.outputs.tag }} run: | nix develop .#electron --command bash <<'EOF' set -eu test -d releases find releases -type f | grep -q . COMMIT_MSG="$(git log -1 --pretty=%B | sed 's/$/ /')" HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") if [ "$HTTP_STATUS" = "200" ]; then echo "Release $TAG already exists." exit 0 fi echo "Creating new release for $TAG" RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg tag "$TAG" \ --arg name "$TAG" \ --arg body "$COMMIT_MSG" \ --arg target "$SHA" \ '{ tag_name: $tag, name: $name, body: $body, target_commitish: $target, draft: false, prerelease: false }')")" RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" ASSET_BASE_URL="${API%/api/v1}/$REPO/releases/download/$TAG" export ASSET_BASE_URL node -e ' const fs = require("fs"); const path = "releases/electron-release-metadata.json"; const metadata = JSON.parse(fs.readFileSync(path, "utf8")); metadata.version = process.env.TAG; metadata.tag = process.env.TAG; for (const artifact of metadata.artifacts || []) { artifact.url = `${process.env.ASSET_BASE_URL}/${encodeURIComponent(artifact.name)}`; } fs.writeFileSync(path, `${JSON.stringify(metadata, null, 2)}\n`); ' find releases -type f -print0 | while IFS= read -r -d '' file; do name="$(basename "$file")" curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ -H "Authorization: token $TOKEN" \ -F "attachment=@$file" done EOF - name: Delete dev releases env: TOKEN: ${{ forgejo.token }} API: ${{ forgejo.api_url }} REPO: ${{ forgejo.repository }} run: | nix develop .#electron --command bash <<'EOF' set -eu PAGE=1 DELETE_RELEASES=delete-dev-releases.tsv : > "$DELETE_RELEASES" while :; do curl -fsS \ -H "Authorization: token $TOKEN" \ "$API/repos/$REPO/releases?page=$PAGE&limit=50&pre-release=true" \ -o releases.json COUNT="$(jq 'length' releases.json)" test "$COUNT" -gt 0 || break jq -r \ '.[] | select(.prerelease == true) | select(.tag_name | contains("-dev-")) | [.id, .tag_name] | @tsv' releases.json \ >> "$DELETE_RELEASES" PAGE="$((PAGE + 1))" done while IFS="$(printf '\t')" read -r release_id release_tag; do test -n "$release_id" || continue echo "Deleting dev release $release_tag" curl -fsS -X DELETE \ -H "Authorization: token $TOKEN" \ "$API/repos/$REPO/releases/$release_id" done < "$DELETE_RELEASES" EOF - name: Update root flake release hash env: TAG: ${{ steps.version.outputs.tag }} run: | nix develop .#electron --command bash <<'EOF' set -eu DEB="$(find releases -maxdepth 1 -type f -name 'Tensamin-*-linux-amd64.deb' -print -quit)" test -n "$DEB" HASH="$(node -e 'const fs = require("fs"); const crypto = require("crypto"); const file = process.argv[1]; console.log("sha256-" + crypto.createHash("sha256").update(fs.readFileSync(file)).digest("base64"));' "$DEB")" export HASH node -e ' const fs = require("fs"); const version = process.env.TAG; const hash = process.env.HASH; let content = fs.readFileSync("flake.nix", "utf8"); content = content.replace(/version = "[^"]+";/, `version = "${version}";`); content = content.replace(/x86_64DebHash = "sha256-[^"]+";/, `x86_64DebHash = "${hash}";`); fs.writeFileSync("flake.nix", content); ' if git diff --quiet -- flake.nix; then echo "flake.nix already has the current release hash on main." else git add flake.nix git -c user.name="forgejo-actions" -c user.email="forgejo-actions@localhost" commit -m "(qol): update release flake hash" git push fi git fetch origin dev git worktree add ../dev-flake-update origin/dev cd ../dev-flake-update node -e ' const fs = require("fs"); const version = process.env.TAG; const hash = process.env.HASH; let content = fs.readFileSync("flake.nix", "utf8"); content = content.replace(/version = "[^"]+";/, `version = "${version}";`); content = content.replace(/x86_64DebHash = "sha256-[^"]+";/, `x86_64DebHash = "${hash}";`); fs.writeFileSync("flake.nix", content); ' if git diff --quiet -- flake.nix; then echo "flake.nix already has the current release hash on dev." exit 0 fi git add flake.nix git -c user.name="forgejo-actions" -c user.email="forgejo-actions@localhost" commit -m "(qol): update release flake hash" git push origin HEAD:dev EOF