diff --git a/.github/workflows/desktop-app.yml b/.github/workflows/desktop-app.yml
index 9efcb2e..23a2da4 100644
--- a/.github/workflows/desktop-app.yml
+++ b/.github/workflows/desktop-app.yml
@@ -233,6 +233,8 @@ jobs:
if [ "${{ runner.os }}" = "macOS" ]; then
cp scripts/install-macos-app.sh dist/DevOpster-macos-install.sh
chmod +x dist/DevOpster-macos-install.sh
+ cp scripts/install-macos-app.command dist/DevOpster-macos-install.command
+ chmod +x dist/DevOpster-macos-install.command
fi
ls -la dist || true
@@ -398,6 +400,8 @@ jobs:
fi
cp scripts/install-macos-app.sh dist/DevOpster-macos-install.sh
chmod +x dist/DevOpster-macos-install.sh
+ cp scripts/install-macos-app.command dist/DevOpster-macos-install.command
+ chmod +x dist/DevOpster-macos-install.command
ls -la dist || true
- name: Upload artifacts
@@ -461,6 +465,7 @@ jobs:
|---|---|---|
| **macOS** | `*.dmg` | Recommended. Drag `DevOpster.app` to `/Applications` or `~/Applications` |
| **macOS** | `*.app.tar.gz` | Advanced/manual extraction |
+ | **macOS** | `DevOpster-macos-install.command` | Double-click guided installer (choose install location) |
| **macOS** | `DevOpster-macos-install.sh` | One-click installer script (path-aware + quarantine clear) |
| **Windows** | `*-setup.exe` | Recommended NSIS installer |
| **Windows** | `*.msi` | MSI deployment/install option |
@@ -471,11 +476,20 @@ jobs:
### macOS trust and verification
- Stable releases are signed and notarized when Apple credentials are configured in CI.
- - You can run the one-click installer from the mounted DMG or extracted assets:
+ - If macOS blocks opening the downloaded DMG itself, clear quarantine on the DMG and open it:
+
+ ```bash
+ DMG_PATH="$HOME/Downloads/DevOpster_0.1.0_aarch64.dmg"
+ xattr -dr com.apple.quarantine "$DMG_PATH"
+ open "$DMG_PATH"
+ ```
+
+ - Easiest option: double-click `DevOpster-macos-install.command` from Finder.
+ - Terminal option:
```bash
chmod +x ./DevOpster-macos-install.sh
- ./DevOpster-macos-install.sh
+ ./DevOpster-macos-install.sh "$HOME/Downloads/DevOpster_0.1.0_aarch64.dmg"
```
- If you are testing an unsigned prerelease build and Gatekeeper blocks launch, run with your chosen install path:
diff --git a/docs/index.html b/docs/index.html
index b961913..f9a7e75 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -437,81 +437,98 @@
.platform-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 14px;
+ gap: 16px;
}
.platform-grid > * { min-width: 0; }
.platform-card {
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--card);
- padding: 22px 18px;
- text-align: center;
+ padding: 24px 20px;
+ text-align: left;
color: var(--fg);
- display: block;
+ display: flex;
+ flex-direction: column;
min-width: 0;
- transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
+ transition: border-color 140ms ease, box-shadow 140ms ease;
}
.platform-card:hover {
border-color: var(--link);
box-shadow: 0 4px 16px color-mix(in oklab, var(--link) 18%, transparent);
- transform: translateY(-2px);
}
- .platform-card .platform-icon {
- width: 48px;
- height: 48px;
- margin: 0 auto 12px;
- display: inline-flex;
+ .platform-card-header {
+ display: flex;
+ align-items: flex-start;
+ gap: 14px;
+ margin-bottom: 12px;
+ }
+ .platform-icon {
+ width: 36px;
+ height: 36px;
+ flex-shrink: 0;
+ display: flex;
align-items: center;
justify-content: center;
- color: var(--fg);
+ margin-top: 2px;
}
- .platform-card .platform-icon svg { width: 100%; height: 100%; display: block; }
- .platform-card .platform-icon img { width: 100%; height: 100%; display: block; object-fit: contain; }
- .platform-icon--macos { color: #111111; }
+ .platform-icon svg { width: 100%; height: 100%; display: block; }
+ .platform-icon img { width: 100%; height: 100%; display: block; object-fit: contain; }
+ .platform-icon--macos { color: var(--fg); }
.platform-icon--windows { color: #0f6cbd; }
- .platform-icon--linux { color: #111111; }
- .platform-card h3 { margin: 0 0 4px; font-size: 1.05rem; }
- .platform-card .formats {
+ .platform-icon--linux { color: #FCC624; }
+ .platform-card-title { flex: 1; min-width: 0; }
+ .platform-card-title h3 { margin: 0 0 2px; font-size: 1.1rem; }
+ .platform-subtitle {
+ color: var(--muted);
+ font-size: 0.78rem;
+ margin: 0;
+ font-weight: 500;
+ }
+ .platform-desc {
color: var(--muted);
font-size: 0.85rem;
- margin: 0 0 16px;
+ margin: 0 0 14px;
+ line-height: 1.5;
}
.download-label {
- display: inline-flex;
+ display: flex;
align-items: center;
justify-content: center;
gap: 6px;
- padding: 9px 20px;
+ padding: 10px 16px;
border-radius: var(--radius);
background: var(--link);
color: #ffffff;
font-weight: 600;
font-size: 0.88rem;
text-decoration: none;
+ width: 100%;
+ box-sizing: border-box;
+ margin-bottom: 10px;
}
.download-label:focus-visible {
outline: 2px solid var(--link);
outline-offset: 3px;
}
.asset-files {
- margin-top: 10px;
display: grid;
- gap: 8px;
+ gap: 6px;
+ margin-bottom: 12px;
}
.asset-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
- border-radius: 10px;
+ border-radius: 8px;
background: color-mix(in oklab, var(--surface) 72%, var(--card));
- padding: 10px 12px;
+ padding: 8px 12px;
font-size: 0.82rem;
}
.asset-row a {
color: var(--link);
text-decoration: none;
- font-weight: 600;
+ font-weight: 500;
overflow-wrap: anywhere;
}
.asset-row a:hover { text-decoration: underline; }
@@ -519,41 +536,33 @@
color: var(--muted);
flex-shrink: 0;
font-variant-numeric: tabular-nums;
+ font-size: 0.78rem;
}
.asset-empty {
- border-radius: 10px;
+ border-radius: 8px;
background: color-mix(in oklab, var(--surface) 72%, var(--card));
- padding: 10px 12px;
+ padding: 8px 12px;
font-size: 0.8rem;
color: var(--muted);
- text-align: left;
- }
- .platform-steps {
- margin: 14px 0 0;
- padding: 0;
- list-style: none;
- text-align: left;
- display: grid;
- gap: 8px;
}
- .platform-note {
- margin: 0;
+ .platform-tip {
+ margin: auto 0 0;
padding: 10px 12px;
- border-radius: 12px;
+ border-radius: 10px;
background: color-mix(in oklab, var(--surface) 72%, var(--card));
color: var(--muted);
- font-size: 0.8rem;
- line-height: 1.5;
- text-align: left;
- min-width: 0;
+ font-size: 0.79rem;
+ line-height: 1.55;
}
- .platform-note strong { color: var(--fg); }
- .platform-note code {
+ .platform-tip strong { color: var(--fg); }
+ .platform-tip code {
background: color-mix(in oklab, var(--surface2) 80%, var(--card));
- padding: 2px 6px;
- border-radius: 6px;
- font-size: 0.9em;
+ padding: 1px 5px;
+ border-radius: 5px;
+ font-size: 0.88em;
}
+ /* legacy .platform-note kept for compat */
+ .platform-note { display: none; }
.platform-note .copy-cmd {
width: 100%;
box-sizing: border-box;
@@ -822,68 +831,131 @@
Distribute like a product
-
For end users
-
Download DevOpster
+
Artifacts
+
Choose your platform
- Grab the latest installer for your operating system. Builds publish
- automatically to GitHub Releases when a version tag (v*) is
- pushed or when the release workflow runs manually.
+ Each card shows the preferred download first, then the remaining release files for that platform.
+ Installation notes stay with the relevant operating system.
-
Loading release assets...
+
+
+
+
+
Install Guide
+
How to download, install, and use it
+
+
+
+ macOS
+
+
+ - Download the latest DMG from the macOS card.
+ - Open the disk image and drag
DevOpster.app into /Applications.
+ - If macOS blocks it at first launch, open Terminal and run:
xattr -cr "/Applications/DevOpster.app"
+ - Reopen the app and continue.
+
+
+
+
+ 🪟 Windows
+
+
+ - Download the
-setup.exe from the Windows card.
+ - Run the installer and continue through the wizard. Choose install location when prompted.
+ - If SmartScreen appears, click More info then Run anyway.
+ - Launch DevOpster from the Start menu or desktop shortcut.
+
+
+
+
+ 🐧 Linux
+
+
+ - Choose the artifact that fits your system: AppImage, DEB, or RPM.
+ - For AppImage:
chmod +x DevOpster*.AppImage then run it.
+ - For DEB:
sudo apt install ./DevOpster*.deb
+ - For RPM:
sudo rpm -i DevOpster*.rpm
+
+
+
+
@@ -1044,7 +1116,7 @@ Owner / Organization
function pickAssets(assets, platform) {
var rules = {
- macos: [/.dmg$/i, /\.app\.tar\.gz$/i, /macos-install\.sh$/i, /macos.*\.zip$/i, /darwin.*\.zip$/i],
+ macos: [/.dmg$/i, /\.app\.tar\.gz$/i, /macos-install\.command$/i, /macos-install\.sh$/i, /macos.*\.zip$/i, /darwin.*\.zip$/i],
windows: [/-setup\.exe$/i, /\.msi$/i, /\.exe$/i, /windows.*\.zip$/i, /win.*\.zip$/i, /\.zip$/i],
linux: [/\.AppImage$/i, /\.deb$/i, /\.rpm$/i, /linux.*\.tar\.gz$/i, /\.tar\.gz$/i]
};
@@ -1079,7 +1151,7 @@ Owner / Organization
card.label.href = primary.browser_download_url;
card.label.textContent = '↓ Download ' + primary.name;
- assets.slice(0, 3).forEach(function (asset) {
+ assets.forEach(function (asset) {
var row = document.createElement('div');
row.className = 'asset-row';
@@ -1104,7 +1176,8 @@ Owner / Organization
throw new Error('No published releases with assets found');
}
if (statusEl) {
- statusEl.innerHTML = 'Showing files from ' + rel.tag_name + '.';
+ var published = rel.published_at ? new Date(rel.published_at).toLocaleDateString('en-US', {year:'numeric', month:'short', day:'numeric'}) : '';
+ statusEl.innerHTML = 'Latest published release: DevOpster ' + rel.tag_name + '' + (published ? ' — ' + published : '') + '. Use the platform cards below for direct artifact downloads and install steps.';
}
var assets = rel.assets || [];
diff --git a/scripts/install-macos-app.command b/scripts/install-macos-app.command
new file mode 100755
index 0000000..c58a8ea
--- /dev/null
+++ b/scripts/install-macos-app.command
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
+
+echo "DevOpster macOS installer"
+echo ""
+"$SCRIPT_DIR/install-macos-app.sh" "$@"
+echo ""
+read -r -n 1 -s -p "Installation finished. Press any key to close..."
+echo ""
diff --git a/scripts/install-macos-app.sh b/scripts/install-macos-app.sh
index 27cabab..4e51147 100755
--- a/scripts/install-macos-app.sh
+++ b/scripts/install-macos-app.sh
@@ -2,6 +2,7 @@
set -euo pipefail
APP_NAME="DevOpster.app"
+MOUNT_DEVICE=""
print_usage() {
cat <<'EOF'
@@ -10,12 +11,14 @@ Usage:
Examples:
./install-macos-app.sh
+ ./install-macos-app.sh "$HOME/Downloads/DevOpster_0.1.0_aarch64.dmg"
./install-macos-app.sh "/Volumes/devopster-cli/DevOpster.app"
./install-macos-app.sh "/Volumes/devopster-cli/DevOpster.app" "$HOME/Applications"
Behavior:
+ - SOURCE_APP_PATH can be a mounted DevOpster.app path or a .dmg file.
- If SOURCE_APP_PATH is omitted, the script tries to auto-detect DevOpster.app
- from the current directory and mounted volumes.
+ first, then a DevOpster*.dmg in the current directory or Downloads.
- If INSTALL_DIR is omitted, it uses /Applications when writable, otherwise
$HOME/Applications.
EOF
@@ -36,9 +39,52 @@ detect_source_app() {
fi
done
+ for candidate in ./DevOpster*.dmg "$HOME"/Downloads/DevOpster*.dmg; do
+ if [[ -f "${candidate}" ]]; then
+ printf '%s\n' "${candidate}"
+ return 0
+ fi
+ done
+
return 1
}
+cleanup_mount() {
+ if [[ -n "${MOUNT_DEVICE}" ]]; then
+ hdiutil detach "${MOUNT_DEVICE}" -quiet || true
+ fi
+}
+
+choose_install_dir() {
+ local choice custom_dir
+ echo "Choose install location:"
+ echo " 1) /Applications (all users, requires admin write access)"
+ echo " 2) $HOME/Applications (current user)"
+ echo " 3) Custom path"
+ printf "Select [1-3] (default 2): "
+ read -r choice
+ case "${choice:-2}" in
+ 1) INSTALL_DIR="/Applications" ;;
+ 2) INSTALL_DIR="$HOME/Applications" ;;
+ 3)
+ printf "Enter custom install directory: "
+ read -r custom_dir
+ if [[ -z "${custom_dir}" ]]; then
+ echo "No custom path entered; using $HOME/Applications"
+ INSTALL_DIR="$HOME/Applications"
+ else
+ INSTALL_DIR="${custom_dir}"
+ fi
+ ;;
+ *)
+ echo "Invalid choice; using $HOME/Applications"
+ INSTALL_DIR="$HOME/Applications"
+ ;;
+ esac
+}
+
+trap cleanup_mount EXIT
+
SOURCE_APP_PATH="${1:-}"
INSTALL_DIR="${2:-}"
@@ -50,6 +96,19 @@ if [[ -z "${SOURCE_APP_PATH}" ]]; then
fi
fi
+if [[ -f "${SOURCE_APP_PATH}" && "${SOURCE_APP_PATH}" == *.dmg ]]; then
+ echo "Detected DMG source: ${SOURCE_APP_PATH}"
+ xattr -dr com.apple.quarantine "${SOURCE_APP_PATH}" || true
+ ATTACH_OUTPUT="$(hdiutil attach "${SOURCE_APP_PATH}" -nobrowse)"
+ MOUNT_DEVICE="$(printf '%s\n' "${ATTACH_OUTPUT}" | awk '/^\/dev\// {print $1; exit}')"
+ MOUNT_POINT="$(printf '%s\n' "${ATTACH_OUTPUT}" | awk -F '\t' '/\/Volumes\// {print $NF; exit}')"
+ if [[ -z "${MOUNT_POINT}" ]]; then
+ echo "Could not determine mounted DMG path."
+ exit 1
+ fi
+ SOURCE_APP_PATH="${MOUNT_POINT}/${APP_NAME}"
+fi
+
if [[ ! -d "${SOURCE_APP_PATH}" ]]; then
echo "Source app not found: ${SOURCE_APP_PATH}"
print_usage
@@ -57,10 +116,14 @@ if [[ ! -d "${SOURCE_APP_PATH}" ]]; then
fi
if [[ -z "${INSTALL_DIR}" ]]; then
- if [[ -w "/Applications" ]]; then
- INSTALL_DIR="/Applications"
+ if [[ -t 0 ]]; then
+ choose_install_dir
else
- INSTALL_DIR="$HOME/Applications"
+ if [[ -w "/Applications" ]]; then
+ INSTALL_DIR="/Applications"
+ else
+ INSTALL_DIR="$HOME/Applications"
+ fi
fi
fi