Compare commits
5 commits
57b5ede338
...
101f80a3d5
| Author | SHA1 | Date | |
|---|---|---|---|
| 101f80a3d5 | |||
|
|
1de406bfa6 | ||
|
|
33e23bf422 | ||
|
|
e1844ff919 | ||
|
|
236ef048b5 |
8 changed files with 272 additions and 102 deletions
142
.github/workflows/release.yml
vendored
Normal file
142
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
name: Release Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
name: Bump Version & Create Tag
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.bump.outputs.version }}
|
||||
tag: ${{ steps.bump.outputs.tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Compute Next Version and Tag
|
||||
id: bump
|
||||
run: |
|
||||
git fetch --tags
|
||||
LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1)
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
NEXT_VERSION="1.0.0"
|
||||
else
|
||||
CLEAN_VERSION="${LATEST_TAG#v}"
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$CLEAN_VERSION"
|
||||
PATCH=$((PATCH + 1))
|
||||
NEXT_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||
fi
|
||||
|
||||
NEXT_TAG="v${NEXT_VERSION}"
|
||||
echo "Latest tag found: ${LATEST_TAG:-none}"
|
||||
echo "Calculated next release version: ${NEXT_VERSION}"
|
||||
echo "Calculated next release tag: ${NEXT_TAG}"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag "${NEXT_TAG}"
|
||||
git push origin "${NEXT_TAG}"
|
||||
|
||||
echo "version=${NEXT_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${NEXT_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
package:
|
||||
name: Package ${{ matrix.os-name }}
|
||||
needs: bump-version
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
os-name: macOS (DMG)
|
||||
artifact-name: macos-dmg
|
||||
artifact-path: composeApp/build/compose/binaries/main/dmg/*.dmg
|
||||
gradle-task: ":composeApp:packageDmg"
|
||||
- os: windows-latest
|
||||
os-name: Windows (MSI)
|
||||
artifact-name: windows-msi
|
||||
artifact-path: composeApp/build/compose/binaries/main/msi/*.msi
|
||||
gradle-task: ":composeApp:packageMsi"
|
||||
- os: ubuntu-latest
|
||||
os-name: Linux (DEB)
|
||||
artifact-name: linux-deb
|
||||
artifact-path: composeApp/build/compose/binaries/main/deb/*.deb
|
||||
gradle-task: ":composeApp:packageDeb"
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build Package (${{ matrix.os-name }})
|
||||
shell: bash
|
||||
run: |
|
||||
./gradlew ${{ matrix.gradle-task }} -PappVersion=${{ needs.bump-version.outputs.version }}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: ${{ matrix.artifact-path }}
|
||||
if-no-files-found: error
|
||||
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- bump-version
|
||||
- package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all packaged artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-assets
|
||||
|
||||
- name: Publish GitHub Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.bump-version.outputs.tag }}
|
||||
RELEASE_VERSION: ${{ needs.bump-version.outputs.version }}
|
||||
run: |
|
||||
mkdir -p dist
|
||||
find release-assets -type f \( -name "*.dmg" -o -name "*.msi" -o -name "*.deb" \) -exec cp {} dist/ \;
|
||||
|
||||
echo "Files prepared for release:"
|
||||
ls -lh dist/
|
||||
|
||||
RELEASE_BODY="### WoW Backup ${RELEASE_TAG}
|
||||
|
||||
Automated release build for **WoW Backup v${RELEASE_VERSION}**.
|
||||
|
||||
#### Available Packages
|
||||
- **macOS**: \`.dmg\` installer (Apple Silicon & Intel)
|
||||
- **Windows**: \`.msi\` Windows Installer
|
||||
- **Linux**: \`.deb\` package (Debian / Ubuntu)
|
||||
|
||||
---
|
||||
For detailed setup and installation notes, please refer to [INSTALL.md](https://github.com/${{ github.repository }}/blob/${RELEASE_TAG}/INSTALL.md)."
|
||||
|
||||
gh release create "${RELEASE_TAG}" dist/* \
|
||||
--title "WoW Backup ${RELEASE_TAG}" \
|
||||
--notes "${RELEASE_BODY}"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -17,3 +17,5 @@ captures
|
|||
!*.xcworkspace/contents.xcworkspacedata
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
node_modules/
|
||||
.feature-specs/
|
||||
.junie/
|
||||
84
INSTALL.md
Normal file
84
INSTALL.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Installing WoW Backup
|
||||
|
||||
This guide provides step-by-step instructions for installing and running **WoW Backup** on macOS, Windows, and Linux.
|
||||
|
||||
---
|
||||
|
||||
## Download
|
||||
|
||||
Download the appropriate installer for your platform from the **[GitHub Releases](https://github.com/rukira/wow-backup/releases)** page:
|
||||
|
||||
| Platform | Package Format | Installer File Pattern |
|
||||
|---|---|---|
|
||||
| **macOS** | DMG Disk Image | `WoW Backup-<version>.dmg` |
|
||||
| **Windows** | Windows Installer (MSI) | `WoW Backup-<version>.msi` |
|
||||
| **Linux** | Debian Package (DEB) | `wow-backup_<version>_amd64.deb` |
|
||||
|
||||
---
|
||||
|
||||
## macOS Installation
|
||||
|
||||
1. Download the `.dmg` file from the latest release.
|
||||
2. Double-click the `.dmg` file to mount the disk image.
|
||||
3. Drag **WoW Backup.app** into your `/Applications` folder.
|
||||
4. Eject the disk image.
|
||||
|
||||
### Unsigned Application / Gatekeeper Notice
|
||||
Because WoW Backup is distributed without Apple Developer ID code-signing notarization, macOS Gatekeeper may show a warning on first launch (*"WoW Backup cannot be opened because the developer cannot be verified"* or *"Apple could not verify that WoW Backup is free of malware"*).
|
||||
|
||||
To launch the app for the first time:
|
||||
|
||||
- **Option A (Finder)**:
|
||||
1. Open Finder and navigate to `/Applications`.
|
||||
2. Right-click (or Control-click) on **WoW Backup.app** and choose **Open**.
|
||||
3. In the security popup, click **Open**.
|
||||
- **Option B (Terminal)**:
|
||||
Run the following command in Terminal to clear the quarantine attribute:
|
||||
```shell
|
||||
xattr -d com.apple.quarantine "/Applications/WoW Backup.app"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Windows Installation
|
||||
|
||||
1. Download the `.msi` installer from the latest release.
|
||||
2. Double-click the `.msi` file to start the installation wizard.
|
||||
3. Follow the on-screen prompts to complete installation.
|
||||
4. Launch **WoW Backup** from the Start Menu or desktop shortcut.
|
||||
|
||||
### Windows Defender SmartScreen
|
||||
If Windows Defender SmartScreen flags the installer (*"Windows protected your PC — Microsoft Defender SmartScreen prevented an unrecognized app from starting"*):
|
||||
1. Click **More info**.
|
||||
2. Click **Run anyway**.
|
||||
|
||||
---
|
||||
|
||||
## Linux Installation
|
||||
|
||||
1. Download the `.deb` package from the latest release.
|
||||
2. Open your terminal in the download folder and install the package:
|
||||
```shell
|
||||
sudo apt install ./wow-backup_*.deb
|
||||
```
|
||||
*Alternatively, with `dpkg`:*
|
||||
```shell
|
||||
sudo dpkg -i wow-backup_*.deb
|
||||
sudo apt-get install -f
|
||||
```
|
||||
3. Launch **WoW Backup** from your desktop application launcher or run `wowbackup` in the terminal.
|
||||
|
||||
---
|
||||
|
||||
## First-Time Setup & Overview
|
||||
|
||||
Once started, WoW Backup runs discreetly in the system tray / menu bar:
|
||||
|
||||
1. **Accessing Dashboard**: Click the tray icon in the macOS menu bar or Windows/Linux system tray to open the application window.
|
||||
2. **Settings Configuration**:
|
||||
- Navigate to **Settings** (gear icon).
|
||||
- Set your **World of Warcraft Installation Folder** (e.g. `_retail_` or `_classic_`).
|
||||
- Select your **Backup Destination Folder**.
|
||||
- Set your preferred **Daily Backup Time**, **Retention Policy** (number of backups to keep), and **Compression Mode** (`ZIP` or folder replication).
|
||||
- Optionally enable **Launch at Startup** so backups occur automatically without manual intervention.
|
||||
3. **Lifecycle**: Closing the main window hides it to the tray while keeping the scheduler running. To quit the application completely, select **Quit** from the tray menu.
|
||||
|
|
@ -32,6 +32,14 @@ World of Warcraft stores all UI configurations, macros, keybindings, and addon s
|
|||
|
||||
---
|
||||
|
||||
## Installation & Download
|
||||
|
||||
Pre-built binaries for macOS, Windows, and Linux are automatically packaged and published on each release.
|
||||
|
||||
👉 **See [INSTALL.md](INSTALL.md)** for download links, step-by-step installation instructions, Gatekeeper/SmartScreen bypass guidance, and initial setup notes.
|
||||
|
||||
---
|
||||
|
||||
## Build & Run
|
||||
|
||||
### Prerequisites
|
||||
|
|
@ -73,6 +81,7 @@ World of Warcraft stores all UI configurations, macros, keybindings, and addon s
|
|||
| **3. Backup Engine** | `docs/plans/feature-3-backup.md` | ✅ **Completed** | Parallel copy, ZIP compression, scheduler, history pruning, notifications |
|
||||
| **4. Status Screen** | `docs/plans/feature-4-status.md` | ✅ **Completed** | Live status dashboard, progress indicator, manual backup trigger, shortcuts |
|
||||
| **5. Restore Screen** | `docs/plans/feature-5-restore.md` | 🚧 **Pending** | Backup list browser, ZIP extraction, confirmation flow, safety checks |
|
||||
| **6. Release Pipeline** | `docs/plans/feature-6-release-pipeline.md` | ✅ **Completed** | GitHub Actions multi-platform release pipeline & packaging automation |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ kotlin {
|
|||
}
|
||||
}
|
||||
|
||||
val appVersion = "1.0.0"
|
||||
val appVersion = (project.findProperty("appVersion") as? String)?.takeIf { it.isNotBlank() } ?: "1.0.0"
|
||||
|
||||
val generatedSrcDir = layout.buildDirectory.dir("generated/src/jvmMain/kotlin")
|
||||
|
||||
|
|
|
|||
23
docs/plans/feature-6-release-pipeline.md
Normal file
23
docs/plans/feature-6-release-pipeline.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Feature 6: Build Automation & Release Pipeline
|
||||
|
||||
**Status:** ✅ **Completed**
|
||||
|
||||
## Context
|
||||
Automated multi-platform packaging and release pipeline for WoW Backup using GitHub Actions. Whenever changes are merged/pushed to the `main` branch, the pipeline calculates the next release version and tag, compiles native distributables for macOS, Windows, and Linux, and attaches all packaged binaries to a published GitHub Release.
|
||||
|
||||
## Deliverables
|
||||
1. **Dynamic Versioning in Gradle**:
|
||||
- `composeApp/build.gradle.kts` dynamically reads `appVersion` via `-PappVersion=...` or defaults to `1.0.0`.
|
||||
- `BuildConfig.kt` and native distribution packaging tasks respect the injected version.
|
||||
2. **GitHub Actions Workflow (`.github/workflows/release.yml`)**:
|
||||
- Automated semantic patch version computation and git tagging on push to `main` and `workflow_dispatch`.
|
||||
- Parallel runner matrix (`macos-latest`, `windows-latest`, `ubuntu-latest`) packaging `.dmg`, `.msi`, and `.deb`.
|
||||
- Automated GitHub Release creation with attached artifacts and release notes.
|
||||
3. **Installation Guide (`INSTALL.md`)**:
|
||||
- Detailed user installation guide for macOS (Gatekeeper bypass), Windows (SmartScreen bypass), and Linux (APT/DPKG).
|
||||
- Linked directly from `README.md`.
|
||||
|
||||
## Verification
|
||||
- `./gradlew :composeApp:generateBuildConfig -PappVersion=1.2.3` generates `BuildConfig.VERSION = "1.2.3"`.
|
||||
- Packaging tasks (`packageDmg`, `packageMsi`, `packageDeb`) are wired and functional.
|
||||
- Workflow YAML syntax validated.
|
||||
|
|
@ -56,17 +56,19 @@ Currently, the project contains no automated test suite (`src/jvmTest` is empty)
|
|||
|
||||
---
|
||||
|
||||
## 3. Build Automation & Release Pipeline
|
||||
## 3. Build Automation & Release Pipeline (Completed)
|
||||
|
||||
The project contains packaging configurations in `composeApp/build.gradle.kts` and a `release.sh` script, but lacks automated CI/CD workflows.
|
||||
Automated multi-platform packaging and release pipeline implemented via GitHub Actions (`.github/workflows/release.yml`) and documented in `INSTALL.md` and `docs/plans/feature-6-release-pipeline.md`.
|
||||
|
||||
### Tasks:
|
||||
1. **GitHub Actions CI Workflow**
|
||||
- Automated compile, lint, and test execution on pull requests and pushes to `main`.
|
||||
- Matrix builds testing on macOS (`macos-latest`) and Windows (`windows-latest`).
|
||||
2. **Automated Distribution Packaging**
|
||||
- Automated artifact generation for `.dmg` (macOS), `.msi` / `.zip` (Windows), and `.deb` (Linux) on GitHub releases.
|
||||
- Code signing and notarization configuration for macOS distributions.
|
||||
### Implemented:
|
||||
1. **GitHub Actions Release Pipeline**
|
||||
- Automated semantic patch version computation and git tagging on push to `main` and `workflow_dispatch`.
|
||||
- Matrix builds on macOS (`macos-latest`), Windows (`windows-latest`), and Linux (`ubuntu-latest`).
|
||||
2. **Automated Distribution Packaging & GitHub Releases**
|
||||
- Automated artifact generation and release publishing for `.dmg` (macOS), `.msi` (Windows), and `.deb` (Linux).
|
||||
- Dynamic version injection via `-PappVersion` in `composeApp/build.gradle.kts`.
|
||||
3. **Installation Documentation**
|
||||
- Detailed `INSTALL.md` guide covering installation and security prompts (Gatekeeper / SmartScreen) across all platforms.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
92
release.sh
92
release.sh
|
|
@ -1,92 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ── Read version from build.gradle.kts ──────────────────────────────────────
|
||||
VERSION=$(sed -n 's/^val appVersion = "\(.*\)"/\1/p' composeApp/build.gradle.kts)
|
||||
TAG="v${VERSION}"
|
||||
echo "==> Version: ${VERSION} (tag: ${TAG})"
|
||||
|
||||
# ── Require FORGEJO_TOKEN ────────────────────────────────────────────────────
|
||||
if [[ -z "${FORGEJO_TOKEN:-}" ]]; then
|
||||
echo "ERROR: FORGEJO_TOKEN env var is not set." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Detect OS and set build task / artifact glob ─────────────────────────────
|
||||
OS="$(uname -s)"
|
||||
case "${OS}" in
|
||||
Darwin)
|
||||
TASK=":composeApp:packageDmg"
|
||||
GLOB="composeApp/build/compose/binaries/main/dmg/*.dmg"
|
||||
;;
|
||||
MINGW*|MSYS*|CYGWIN*|Windows_NT)
|
||||
TASK=":composeApp:packageMsi"
|
||||
GLOB="composeApp/build/compose/binaries/main/msi/*.msi"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unsupported OS '${OS}'." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "==> OS detected: ${OS} — task: ${TASK}"
|
||||
|
||||
# ── Build ────────────────────────────────────────────────────────────────────
|
||||
echo "==> Building..."
|
||||
./gradlew ${TASK}
|
||||
|
||||
# ── Locate artifact ──────────────────────────────────────────────────────────
|
||||
ARTIFACT=$(ls ${GLOB} 2>/dev/null | head -1)
|
||||
if [[ -z "${ARTIFACT}" ]]; then
|
||||
echo "ERROR: No artifact found matching ${GLOB}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> Artifact: ${ARTIFACT}"
|
||||
|
||||
# ── Forgejo API setup ────────────────────────────────────────────────────────
|
||||
API_BASE="https://git.asarius.site/api/v1"
|
||||
REPO="rukira/wow-backup"
|
||||
|
||||
# ── Check for existing release ───────────────────────────────────────────────
|
||||
echo "==> Checking for existing release ${TAG}..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" \
|
||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||
"${API_BASE}/repos/${REPO}/releases/tags/${TAG}")
|
||||
|
||||
if [[ "${HTTP_CODE}" == "200" ]]; then
|
||||
RELEASE_ID=$(python3 -c "import json; print(json.load(open('/tmp/release_response.json'))['id'])")
|
||||
echo "==> Found existing release (id: ${RELEASE_ID})"
|
||||
else
|
||||
echo "==> Creating release ${TAG}..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/release_create.json -w "%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\", \"body\": \"Release ${TAG}\"}" \
|
||||
"${API_BASE}/repos/${REPO}/releases")
|
||||
|
||||
if [[ "${HTTP_CODE}" != "201" ]]; then
|
||||
echo "ERROR: Failed to create release (HTTP ${HTTP_CODE}):" >&2
|
||||
cat /tmp/release_create.json >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_ID=$(python3 -c "import json; print(json.load(open('/tmp/release_create.json'))['id'])")
|
||||
echo "==> Created release (id: ${RELEASE_ID})"
|
||||
fi
|
||||
|
||||
# ── Upload artifact ──────────────────────────────────────────────────────────
|
||||
FILENAME=$(basename "${ARTIFACT}")
|
||||
echo "==> Uploading ${FILENAME}..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/upload_response.json -w "%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||
-F "attachment=@${ARTIFACT}" \
|
||||
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}")
|
||||
|
||||
if [[ "${HTTP_CODE}" != "201" ]]; then
|
||||
echo "ERROR: Failed to upload artifact (HTTP ${HTTP_CODE}):" >&2
|
||||
cat /tmp/upload_response.json >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Done! ${FILENAME} uploaded to release ${TAG}."
|
||||
Loading…
Add table
Add a link
Reference in a new issue