|
| 1 | +#!/bin/bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 5 | +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 6 | +SNAP_DIR="$PROJECT_DIR/snap" |
| 7 | +LOCAL_DIR="$SNAP_DIR/local" |
| 8 | +BUILD_DIR="$PROJECT_DIR/build/snap" |
| 9 | + |
| 10 | +APPIMAGE_PATH="" |
| 11 | +UNPACKED_PATH="" |
| 12 | +ARCH="" |
| 13 | +VERSION="" |
| 14 | +SNAP_OUT="" |
| 15 | + |
| 16 | +log_info() { echo -e "\033[1;34m[INFO]\033[0m $*"; } |
| 17 | +log_ok() { echo -e "\033[1;32m[OK]\033[0m $*"; } |
| 18 | +log_error() { echo -e "\033[1;31m[ERROR]\033[0m $*"; } |
| 19 | + |
| 20 | +usage() { |
| 21 | + cat <<EOF |
| 22 | +Usage: $(basename "$0") (--unpacked <dir> | --local <AppImage>) [--arch arm64|x64] |
| 23 | +
|
| 24 | +Options: |
| 25 | + --unpacked <dir> Path to electron-builder's linux-*-unpacked directory |
| 26 | + (preferred — skips AppImage round-trip and avoids |
| 27 | + mksquashfs glibc issues on newer hosts). |
| 28 | + --local <path> Path to a locally-built AppImage. Used as a fallback |
| 29 | + when --unpacked is unavailable. |
| 30 | + --arch <arch> Target architecture (default: auto-detect) |
| 31 | + -h, --help Show this help message |
| 32 | +
|
| 33 | +Examples: |
| 34 | + $(basename "$0") --unpacked ./out/linux-arm64-unpacked |
| 35 | + $(basename "$0") --local ./out/PearPass.AppImage |
| 36 | +EOF |
| 37 | + exit 0 |
| 38 | +} |
| 39 | + |
| 40 | +detect_arch() { |
| 41 | + local machine |
| 42 | + machine="$(uname -m)" |
| 43 | + case "$machine" in |
| 44 | + x86_64) echo "amd64" ;; |
| 45 | + aarch64|arm64) echo "arm64" ;; |
| 46 | + *) log_error "Unsupported architecture: $machine"; exit 1 ;; |
| 47 | + esac |
| 48 | +} |
| 49 | + |
| 50 | +check_prerequisites() { |
| 51 | + local missing=() |
| 52 | + command -v snapcraft >/dev/null 2>&1 || missing+=("snapcraft") |
| 53 | + |
| 54 | + if (( ${#missing[@]} )); then |
| 55 | + log_error "Missing tools: ${missing[*]}" |
| 56 | + log_error "Install snapcraft: sudo snap install snapcraft --classic" |
| 57 | + exit 1 |
| 58 | + fi |
| 59 | + |
| 60 | + # snapcraft 8+ on core22 needs lxd (default) or multipass as a backend. |
| 61 | + if ! command -v lxd >/dev/null 2>&1 && ! command -v multipass >/dev/null 2>&1; then |
| 62 | + log_error "snapcraft needs an LXD or Multipass backend" |
| 63 | + log_error " sudo snap install lxd && sudo lxd init --auto && sudo usermod -aG lxd \$USER" |
| 64 | + log_error " (then log out and back in, or run: newgrp lxd)" |
| 65 | + exit 1 |
| 66 | + fi |
| 67 | + |
| 68 | + log_ok "Prerequisites satisfied" |
| 69 | +} |
| 70 | + |
| 71 | +parse_args() { |
| 72 | + while [[ $# -gt 0 ]]; do |
| 73 | + case "$1" in |
| 74 | + --local) APPIMAGE_PATH="${2:?--local requires a path}"; shift 2 ;; |
| 75 | + --unpacked) |
| 76 | + # Bare --unpacked → auto-detect below. --unpacked <dir> → explicit. |
| 77 | + if [[ $# -ge 2 && "$2" != --* ]]; then |
| 78 | + UNPACKED_PATH="$2"; shift 2 |
| 79 | + else |
| 80 | + shift 1 |
| 81 | + fi |
| 82 | + ;; |
| 83 | + --arch) ARCH="${2:?--arch requires a value}"; shift 2 ;; |
| 84 | + -h|--help) usage ;; |
| 85 | + *) log_error "Unknown option: $1"; usage ;; |
| 86 | + esac |
| 87 | + done |
| 88 | + |
| 89 | + [[ -z "$ARCH" ]] && ARCH="$(detect_arch)" |
| 90 | + |
| 91 | + if [[ -z "$UNPACKED_PATH" && -z "$APPIMAGE_PATH" ]]; then |
| 92 | + # Match electron-builder's naming: linux-arm64-unpacked for arm64, |
| 93 | + # linux-unpacked for x64. |
| 94 | + case "$ARCH" in |
| 95 | + arm64) arch_dir="linux-arm64-unpacked" ;; |
| 96 | + amd64|x64) arch_dir="linux-unpacked" ;; |
| 97 | + *) log_error "Unsupported arch for auto-detect: $ARCH"; exit 1 ;; |
| 98 | + esac |
| 99 | + for candidate in "$PROJECT_DIR/out/$arch_dir" "$PROJECT_DIR/dist/$arch_dir"; do |
| 100 | + if [[ -d "$candidate" ]]; then |
| 101 | + UNPACKED_PATH="$candidate" |
| 102 | + log_info "Auto-detected unpacked dir: $UNPACKED_PATH" |
| 103 | + break |
| 104 | + fi |
| 105 | + done |
| 106 | + fi |
| 107 | + |
| 108 | + if [[ -z "$UNPACKED_PATH" && -z "$APPIMAGE_PATH" ]]; then |
| 109 | + log_error "Pass --unpacked <dir> or --local <AppImage>" |
| 110 | + usage |
| 111 | + fi |
| 112 | + |
| 113 | + if [[ -n "$UNPACKED_PATH" ]]; then |
| 114 | + case "$UNPACKED_PATH" in /*) ;; *) UNPACKED_PATH="$PWD/$UNPACKED_PATH" ;; esac |
| 115 | + if [[ ! -d "$UNPACKED_PATH" ]]; then |
| 116 | + log_error "Unpacked directory not found: $UNPACKED_PATH" |
| 117 | + exit 1 |
| 118 | + fi |
| 119 | + elif [[ -n "$APPIMAGE_PATH" ]]; then |
| 120 | + case "$APPIMAGE_PATH" in /*) ;; *) APPIMAGE_PATH="$PWD/$APPIMAGE_PATH" ;; esac |
| 121 | + if [[ ! -f "$APPIMAGE_PATH" ]]; then |
| 122 | + log_error "AppImage not found: $APPIMAGE_PATH" |
| 123 | + log_error "Build one first with: npm run dist:linux:<arch>" |
| 124 | + exit 1 |
| 125 | + fi |
| 126 | + fi |
| 127 | + |
| 128 | + VERSION="$(jq -r '.version' "$PROJECT_DIR/package.json")" |
| 129 | + log_info "Source : ${UNPACKED_PATH:-$APPIMAGE_PATH}" |
| 130 | + log_info "Arch : $ARCH" |
| 131 | + log_info "Version : $VERSION" |
| 132 | +} |
| 133 | + |
| 134 | +stage_sources() { |
| 135 | + mkdir -p "$LOCAL_DIR" |
| 136 | + # Clean any previous staging so the picked branch in snapcraft.yaml is |
| 137 | + # deterministic (presence of unpacked/ wins over PearPass.AppImage). |
| 138 | + rm -rf "$LOCAL_DIR/unpacked" "$LOCAL_DIR/PearPass.AppImage" |
| 139 | + |
| 140 | + if [[ -n "$UNPACKED_PATH" ]]; then |
| 141 | + log_info "Staging unpacked Electron payload into snap/local/unpacked/ ..." |
| 142 | + # Hard-link tree to avoid duplicating ~1 GB on the same filesystem. |
| 143 | + # Snapcraft copies sources into the build env, so it picks up files |
| 144 | + # by content; the link form just keeps the host disk footprint low. |
| 145 | + cp -al "$UNPACKED_PATH" "$LOCAL_DIR/unpacked" |
| 146 | + else |
| 147 | + log_info "Staging AppImage into snap/local/ ..." |
| 148 | + cp "$APPIMAGE_PATH" "$LOCAL_DIR/PearPass.AppImage" |
| 149 | + chmod +x "$LOCAL_DIR/PearPass.AppImage" |
| 150 | + fi |
| 151 | +} |
| 152 | + |
| 153 | +build_snap() { |
| 154 | + log_info "Building snap (this may take several minutes on first run) ..." |
| 155 | + mkdir -p "$BUILD_DIR" |
| 156 | + |
| 157 | + cd "$PROJECT_DIR" |
| 158 | + # Run pack from PROJECT_DIR. Two reasons we glob the result instead of |
| 159 | + # hard-coding the filename: |
| 160 | + # 1. `--output <abs-path>` is inconsistent across snapcraft+LXD versions |
| 161 | + # — snapcraft announces creation, but the host-side copy-back does |
| 162 | + # not always honor an absolute path outside PROJECT_DIR. |
| 163 | + # 2. snapcraft.yaml uses `adopt-info: pearpass` to pull the version |
| 164 | + # from flatpak/com.pears.pass.metainfo.xml, which can lag behind |
| 165 | + # package.json. Globbing the actual produced snap avoids the |
| 166 | + # mismatch. |
| 167 | + snapcraft pack |
| 168 | + local OUT |
| 169 | + OUT="$(ls -1t "$PROJECT_DIR"/pearpass_*_"${ARCH}".snap 2>/dev/null | head -n1)" |
| 170 | + if [[ -z "$OUT" || ! -f "$OUT" ]]; then |
| 171 | + log_error "snapcraft reported success but no pearpass_*_${ARCH}.snap was found in $PROJECT_DIR" |
| 172 | + exit 1 |
| 173 | + fi |
| 174 | + SNAP_OUT="$BUILD_DIR/$(basename "$OUT")" |
| 175 | + mv -f "$OUT" "$SNAP_OUT" |
| 176 | + |
| 177 | + log_ok "Snap bundle: $SNAP_OUT" |
| 178 | + ls -lh "$SNAP_OUT" |
| 179 | +} |
| 180 | + |
| 181 | +cleanup() { |
| 182 | + log_info "Cleaning staging files ..." |
| 183 | + rm -rf "$LOCAL_DIR/unpacked" |
| 184 | + rm -f "$LOCAL_DIR/PearPass.AppImage" |
| 185 | +} |
| 186 | + |
| 187 | +# ── Main ──────────────────────────────────────────────────────────────── |
| 188 | +parse_args "$@" |
| 189 | +check_prerequisites |
| 190 | +stage_sources |
| 191 | +build_snap |
| 192 | +cleanup |
| 193 | + |
| 194 | +SNAP_NAME="$(awk '/^name:/ {print $2; exit}' "$SNAP_DIR/snapcraft.yaml")" |
| 195 | +log_ok "Done!" |
| 196 | +log_info "Install with:" |
| 197 | +log_info " sudo snap install --dangerous $SNAP_OUT" |
| 198 | +log_info " sudo snap connect ${SNAP_NAME}:browser-native-messaging" |
0 commit comments