|
| 1 | +#!/bin/bash |
| 2 | +set -e |
| 3 | + |
| 4 | +# Script to create AppImage from OONI Probe distributable |
| 5 | +# Usage: ./scripts/create-appimage.sh [version] |
| 6 | + |
| 7 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 8 | +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" |
| 9 | + |
| 10 | +# Colors for output (only used when stdout is a TTY) |
| 11 | +_IS_TTY=0 |
| 12 | +if [ -t 1 ]; then |
| 13 | + _IS_TTY=1 |
| 14 | +fi |
| 15 | + |
| 16 | +RED='\033[0;31m' |
| 17 | +GREEN='\033[0;32m' |
| 18 | +YELLOW='\033[1;33m' |
| 19 | +NC='\033[0m' # No Color |
| 20 | + |
| 21 | +# Gradle-style logging helpers |
| 22 | +# Usage: log.lifecycle "message"; log.info "message"; log.warn "message"; log.error "message"; log.success "message" |
| 23 | +log_prefix() { |
| 24 | + local level="$1" |
| 25 | + printf "%s" "[${level}]" |
| 26 | +} |
| 27 | + |
| 28 | +log_print() { |
| 29 | + local level="$1"; shift |
| 30 | + local color="$1"; shift |
| 31 | + local msg="$*" |
| 32 | + if [ "${_IS_TTY}" -eq 1 ]; then |
| 33 | + printf "%s %b%s%b\n" "$(log_prefix "$level")" "${color}" "${msg}" "${NC}" |
| 34 | + else |
| 35 | + printf "%s %s\n" "$(log_prefix "$level")" "${msg}" |
| 36 | + fi |
| 37 | +} |
| 38 | + |
| 39 | +log.lifecycle() { log_print LIFECYCLE "${GREEN}" "$*"; } |
| 40 | +log.info() { log_print INFO "" "$*"; } |
| 41 | +log.warn() { log_print WARN "${YELLOW}" "$*"; } |
| 42 | +log.error() { log_print ERROR "${RED}" "$*"; } |
| 43 | +log.success() { log_print SUCCESS "${GREEN}" "$*"; } |
| 44 | +# Highlight helper for important single-line values (paths, commands) |
| 45 | +log.highlight() { |
| 46 | + local msg="$*" |
| 47 | + if [ "${_IS_TTY}" -eq 1 ]; then |
| 48 | + printf " %b%s%b\n" "${YELLOW}" "${msg}" "${NC}" |
| 49 | + else |
| 50 | + printf " %s\n" "${msg}" |
| 51 | + fi |
| 52 | +} |
| 53 | + |
| 54 | +# Configuration |
| 55 | +APP_NAME="OONI Probe" |
| 56 | +APP_ID="org.ooni.probe" |
| 57 | +DESKTOP_FILE_NAME="ooniprobe" |
| 58 | +BUILD_DIR="${PROJECT_ROOT}/composeApp/build/compose/binaries/main/app" |
| 59 | +DIST_DIR="${BUILD_DIR}/${APP_NAME}" |
| 60 | +APPDIR_NAME="OONIProbe.AppDir" |
| 61 | +APPIMAGE_TOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" |
| 62 | + |
| 63 | +# Get version from gradle.properties or use parameter |
| 64 | +if [ -n "$1" ]; then |
| 65 | + VERSION="$1" |
| 66 | +else |
| 67 | + VERSION_FILE="${PROJECT_ROOT}/composeApp/build.gradle.kts" |
| 68 | + |
| 69 | + VERSION="" |
| 70 | + |
| 71 | + # Try to extract versionName from composeApp/build.gradle.kts (supports single/double quotes) |
| 72 | + if [ -f "$VERSION_FILE" ]; then |
| 73 | + # Use grep -Po to extract the versionName value (handles single or double quotes) |
| 74 | + VERSION=$(grep -Po "versionName\s*=\s*['\"]\K[^'\"]+" "$VERSION_FILE" | head -n1 || true) |
| 75 | + fi |
| 76 | + |
| 77 | + # Final fallback |
| 78 | + if [ -z "$VERSION" ]; then |
| 79 | + VERSION="1.0.0" |
| 80 | + fi |
| 81 | +fi |
| 82 | + |
| 83 | +log.lifecycle "OONI Probe AppImage Creator" |
| 84 | +log.info "Version: ${VERSION}" |
| 85 | +log.info "Project Root: ${PROJECT_ROOT}" |
| 86 | + |
| 87 | + |
| 88 | +# Check if distributable exists |
| 89 | +if [ ! -d "${DIST_DIR}" ]; then |
| 90 | + log.warn "Distributable not found. Building..." |
| 91 | + cd "${PROJECT_ROOT}" |
| 92 | + ./gradlew createDistributable |
| 93 | + |
| 94 | + if [ ! -d "${DIST_DIR}" ]; then |
| 95 | + log.error "Error: Failed to create distributable at ${DIST_DIR}" |
| 96 | + exit 1 |
| 97 | + fi |
| 98 | +fi |
| 99 | + |
| 100 | +log.success "✓ Distributable found" |
| 101 | + |
| 102 | +# Create workspace |
| 103 | +WORKSPACE="${PROJECT_ROOT}/composeApp/build/compose/binaries/main/appimage-workspace" |
| 104 | +mkdir -p "${WORKSPACE}" |
| 105 | +cd "${WORKSPACE}" |
| 106 | + |
| 107 | +log.lifecycle "Creating AppDir structure..." |
| 108 | + |
| 109 | +# Clean previous AppDir if exists |
| 110 | +rm -rf "${APPDIR_NAME}" |
| 111 | +mkdir -p "${APPDIR_NAME}/usr" |
| 112 | + |
| 113 | +# Copy application files |
| 114 | +log.info "Copying application files..." |
| 115 | +cp -r "${DIST_DIR}/bin" "${APPDIR_NAME}/usr/" |
| 116 | +cp -r "${DIST_DIR}/lib" "${APPDIR_NAME}/usr/" |
| 117 | + |
| 118 | +# Create AppRun script |
| 119 | +log.info "Creating AppRun script..." |
| 120 | +cat > "${APPDIR_NAME}/AppRun" << 'EOF' |
| 121 | +#!/bin/bash |
| 122 | +# AppRun script for OONI Probe |
| 123 | +
|
| 124 | +# Get the directory where the AppImage is mounted |
| 125 | +HERE="$(dirname "$(readlink -f "${0}")")" |
| 126 | +
|
| 127 | +# Set up library paths |
| 128 | +export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/runtime/lib:${LD_LIBRARY_PATH}" |
| 129 | +export PATH="${HERE}/usr/bin:${PATH}" |
| 130 | +
|
| 131 | +# Set up Java-related paths |
| 132 | +export JAVA_HOME="${HERE}/usr/lib/runtime" |
| 133 | +export PATH="${JAVA_HOME}/bin:${PATH}" |
| 134 | +
|
| 135 | +# Launch OONI Probe |
| 136 | +exec "${HERE}/usr/bin/OONI Probe" "$@" |
| 137 | +EOF |
| 138 | + |
| 139 | +# Create desktop entry |
| 140 | +log.info "Creating desktop entry..." |
| 141 | +cat > "${APPDIR_NAME}/${DESKTOP_FILE_NAME}.desktop" << EOF |
| 142 | +[Desktop Entry] |
| 143 | +Type=Application |
| 144 | +Name=OONI Probe |
| 145 | +GenericName=Network Measurement Tool |
| 146 | +Comment=Measure internet censorship and network interference |
| 147 | +Exec=OONI Probe %u |
| 148 | +Icon=${DESKTOP_FILE_NAME} |
| 149 | +Categories=Network;Utility; |
| 150 | +Terminal=false |
| 151 | +StartupWMClass=OONI Probe |
| 152 | +Keywords=censorship;network;measurement;ooni; |
| 153 | +MimeType=x-scheme-handler/ooni; |
| 154 | +EOF |
| 155 | + |
| 156 | +# Copy icon |
| 157 | +log.info "Copying application icon..." |
| 158 | +if [ -f "${DIST_DIR}/lib/${APP_NAME}.png" ]; then |
| 159 | + cp "${DIST_DIR}/lib/${APP_NAME}.png" "${APPDIR_NAME}/${DESKTOP_FILE_NAME}.png" |
| 160 | +elif [ -f "${PROJECT_ROOT}/icons/app.png" ]; then |
| 161 | + cp "${PROJECT_ROOT}/icons/app.png" "${APPDIR_NAME}/${DESKTOP_FILE_NAME}.png" |
| 162 | +else |
| 163 | + log.warn "Warning: No icon found. Using placeholder." |
| 164 | +fi |
| 165 | + |
| 166 | +# Also copy icon to standard location |
| 167 | +mkdir -p "${APPDIR_NAME}/usr/share/icons/hicolor/256x256/apps" |
| 168 | +if [ -f "${APPDIR_NAME}/${DESKTOP_FILE_NAME}.png" ]; then |
| 169 | + cp "${APPDIR_NAME}/${DESKTOP_FILE_NAME}.png" "${APPDIR_NAME}/usr/share/icons/hicolor/256x256/apps/${DESKTOP_FILE_NAME}.png" |
| 170 | +fi |
| 171 | + |
| 172 | +# Download appimagetool if not present |
| 173 | +APPIMAGETOOL="appimagetool-x86_64.AppImage" |
| 174 | +if [ ! -f "${APPIMAGETOOL}" ]; then |
| 175 | + echo "Downloading appimagetool..." |
| 176 | + wget -q --show-progress "${APPIMAGE_TOOL_URL}" -O "${APPIMAGETOOL}" |
| 177 | + chmod +x "${APPIMAGETOOL}" |
| 178 | +fi |
| 179 | + |
| 180 | +log.success "✓ AppDir created" |
| 181 | + |
| 182 | +# Build AppImage |
| 183 | +OUTPUT_NAME="OONI-Probe-${VERSION}-x86_64.AppImage" |
| 184 | +log.lifecycle "Building AppImage: ${OUTPUT_NAME}" |
| 185 | + |
| 186 | +ARCH=x86_64 "./${APPIMAGETOOL}" --no-appstream "${APPDIR_NAME}" "${OUTPUT_NAME}" |
| 187 | + |
| 188 | +if [ $? -eq 0 ] && [ -f "${OUTPUT_NAME}" ]; then |
| 189 | + echo "" |
| 190 | + log.success "AppImage created: ${WORKSPACE}/${OUTPUT_NAME}" |
| 191 | + |
| 192 | + # Make it executable |
| 193 | + chmod +x "${OUTPUT_NAME}" |
| 194 | + |
| 195 | + # Get file size |
| 196 | + SIZE=$(du -h "${OUTPUT_NAME}" | cut -f1) |
| 197 | + log.info "Size: ${SIZE}" |
| 198 | + |
| 199 | + # Calculate SHA256 |
| 200 | + log.info "Calculating SHA256 checksum..." |
| 201 | + sha256sum "${OUTPUT_NAME}" > "${OUTPUT_NAME}.sha256" |
| 202 | + log.success "Checksum saved to: ${OUTPUT_NAME}.sha256" |
| 203 | + cat "${OUTPUT_NAME}.sha256" |
| 204 | + |
| 205 | + log.info "To test the AppImage, run:" |
| 206 | + log.highlight "${WORKSPACE}/${OUTPUT_NAME}" |
| 207 | + log.info "To move it to the project dist directory:" |
| 208 | + log.highlight "mkdir -p ${PROJECT_ROOT}/dist && mv ${WORKSPACE}/${OUTPUT_NAME}* ${PROJECT_ROOT}/dist/" |
| 209 | +else |
| 210 | + log.error "Error: Failed to create AppImage" |
| 211 | + exit 1 |
| 212 | +fi |
0 commit comments