From 8d790607ff1ad40e32343ca2116d8fdcf3244c8d Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 23 Oct 2026 11:17:32 -0400 Subject: [PATCH] packaging: bash test and function hygiene (SonarCloud S7688, S7679, S7682, S131, S7677) (#1201) - Use [[ ]] instead of [ ] for conditional tests in the bash packaging scripts (S7688). Every converted test compares quoted values to literals, so no pattern-matching change. - Name positional parameters with locals inside functions (S7679). - End the env.sh helpers with an explicit return that keeps their exit status (S7682). - Give case statements a default branch (S131). - Send the check-app-specific.sh error to stderr (S7677). packaging/freebsd/package.sh is left alone while #1158 rewrites it. --- packaging/check-app-specific.sh | 2 +- packaging/env.sh | 26 ++++++---- packaging/linux/apprun-test.sh | 30 +++++------ packaging/linux/flatpak-bundle.sh | 10 ++-- packaging/linux/package.sh | 18 +++---- packaging/macos/import-cert.sh | 6 +-- packaging/macos/package.sh | 26 +++++----- packaging/macos/verify.sh | 30 +++++------ packaging/server/install-test.sh | 6 +-- packaging/server/install.sh | 84 ++++++++++++++++--------------- packaging/server/package.sh | 3 +- packaging/web/package.sh | 10 ++-- 12 files changed, 131 insertions(+), 120 deletions(-) diff --git a/packaging/check-app-specific.sh b/packaging/check-app-specific.sh index ebf23c0b..e9b03946 100644 --- a/packaging/check-app-specific.sh +++ b/packaging/check-app-specific.sh @@ -6,7 +6,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.." bash packaging/server/install-test.sh if grep -rniE 'photoshop|psd|photocraft|vectorcraft|illustrator|adobe|figma' packaging/ .github/ \ --exclude=packaging-lint.yml --exclude=check-app-specific.sh; then - echo "::error::package metadata mentions another product (see above)" + echo "::error::package metadata mentions another product (see above)" >&2 exit 1 fi diff --git a/packaging/env.sh b/packaging/env.sh index b68406a0..eb6547c9 100644 --- a/packaging/env.sh +++ b/packaging/env.sh @@ -24,10 +24,11 @@ workspace_version() { /^\[/ { in_pkg = ($0 == "[workspace.package]") ; next } in_pkg && $1 == "version" { gsub(/[" ]/, "", $3); print $3; exit } ' "$ROOT/Cargo.toml" + return $? } VERSION="${FIGCRAFT_VERSION:-$(workspace_version)}" -if [ -z "$VERSION" ]; then +if [[ -z "$VERSION" ]]; then echo "error: could not read [workspace.package] version from $ROOT/Cargo.toml" >&2 exit 1 fi @@ -37,7 +38,7 @@ DIST="${DIST:-$ROOT/dist/release}" mkdir -p "$DIST" export DIST -if [ -z "${FIGCRAFT_BUILD_SHA:-}" ]; then +if [[ -z "${FIGCRAFT_BUILD_SHA:-}" ]]; then FIGCRAFT_BUILD_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || true)" fi export FIGCRAFT_BUILD_SHA @@ -47,7 +48,7 @@ export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}" # The headless CLI ships next to the app once apps/figcraft-cli exists. BINS=(figcraft) HAS_CLI=0 -if [ -f "$ROOT/apps/figcraft-cli/Cargo.toml" ]; then +if [[ -f "$ROOT/apps/figcraft-cli/Cargo.toml" ]]; then BINS+=(figcraft-cli) HAS_CLI=1 fi @@ -58,7 +59,8 @@ unset _bin # The binary used for a `--version` smoke test: the CLI when shipped, else the app (which answers # --version before opening a window). smoke_bin() { - if [ "$HAS_CLI" = 1 ]; then echo figcraft-cli; else echo figcraft; fi + if [[ "$HAS_CLI" = 1 ]]; then echo figcraft-cli; else echo figcraft; fi + return 0 } # Emit a GitHub Actions warning (plain stderr outside Actions). @@ -68,31 +70,33 @@ retry() { local attempt for attempt in 1 2 3 4 5; do "$@" && return 0 - [ "$attempt" = 5 ] || { echo "retrying ($attempt/5): $*" >&2; sleep 15; } + [[ "$attempt" = 5 ]] || { echo "retrying ($attempt/5): $*" >&2; sleep 15; } done return 1 } warn() { - if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi + return 0 } # Copy licence and readme files that exist into a package directory. copy_docs() { local dest="$1" f for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE NOTICE COPYRIGHT; do - if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi + if [[ -f "$ROOT/$f" ]]; then cp "$ROOT/$f" "$dest/"; fi done copy_font_licences "$dest" + return $? } # Builds made with craft-fonts (CRAFT_FONTS_DIR, all official releases) embed its fonts, which # are OFL-1.1: ship each font's licence as OFL-.txt. No-op without CRAFT_FONTS_DIR. copy_font_licences() { local dest="$1" lic fam - [ -n "${CRAFT_FONTS_DIR:-}" ] || return 0 + [[ -n "${CRAFT_FONTS_DIR:-}" ]] || return 0 for lic in "$CRAFT_FONTS_DIR"/fonts/*/OFL.txt; do - [ -f "$lic" ] || continue + [[ -f "$lic" ]] || continue fam="$(basename "$(dirname "$lic")")" cp "$lic" "$dest/OFL-$fam.txt" done @@ -100,5 +104,7 @@ copy_font_licences() { # Portable SHA-256 of a file (prints just the hash). sha256() { - if command -v sha256sum >/dev/null; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi + local file="$1" + if command -v sha256sum >/dev/null; then sha256sum "$file" | cut -d' ' -f1; else shasum -a 256 "$file" | cut -d' ' -f1; fi + return $? } diff --git a/packaging/linux/apprun-test.sh b/packaging/linux/apprun-test.sh index c6d98da0..75625480 100755 --- a/packaging/linux/apprun-test.sh +++ b/packaging/linux/apprun-test.sh @@ -37,10 +37,10 @@ fail() { # 1. Launch works and passes arguments through. out="$("$APPDIR/AppRun" open file.png)" || fail "AppRun exited non-zero" -[ "$out" = "stub: open file.png" ] || fail "arguments not forwarded: $out" +[[ "$out" = "stub: open file.png" ]] || fail "arguments not forwarded: $out" DESKTOP="$XDG_DATA_HOME/applications/$APP_ID.desktop" -[ -f "$DESKTOP" ] || fail "desktop entry not installed" +[[ -f "$DESKTOP" ]] || fail "desktop entry not installed" # 2. Exec rewritten to the AppImage (quoted, spaces intact), TryExec dropped, # everything else preserved from the packaged entry. @@ -55,15 +55,15 @@ fi # 3. Icons landed under the name the .desktop entry references. for f in "16x16/apps/$APP_ID.png" "256x256/apps/$APP_ID.png" "scalable/apps/$APP_ID.svg"; do - [ -f "$XDG_DATA_HOME/icons/hicolor/$f" ] || fail "icon missing: $f" + [[ -f "$XDG_DATA_HOME/icons/hicolor/$f" ]] || fail "icon missing: $f" done # 4. Idempotent: a second launch must not rewrite anything (inode unchanged). before="$(stat -c %i "$DESKTOP")" out="$("$APPDIR/AppRun")" || fail "second launch exited non-zero" -[ "$out" = "stub: " ] || fail "second launch output: $out" +[[ "$out" = "stub: " ]] || fail "second launch output: $out" after="$(stat -c %i "$DESKTOP")" -[ "$before" = "$after" ] || fail "desktop entry rewritten although unchanged" +[[ "$before" = "$after" ]] || fail "desktop entry rewritten although unchanged" # 5. Moving the AppImage refreshes Exec. mv "$APPIMAGE" "$TMP/moved.AppImage" @@ -74,40 +74,40 @@ grep -Fxq "Exec=\"$(readlink -f "$APPIMAGE")\" %U" "$DESKTOP" || fail "Exec not # 6. Opt-out installs nothing. rm -rf "$XDG_DATA_HOME" FIGCRAFT_NO_DESKTOP_INTEGRATION=1 "$APPDIR/AppRun" >/dev/null || fail "opt-out launch exited non-zero" -[ ! -e "$XDG_DATA_HOME" ] || fail "opt-out still integrated" +[[ ! -e "$XDG_DATA_HOME" ]] || fail "opt-out still integrated" # 7. An unwritable data dir never blocks the launch. mkdir -p "$TMP/ro" && chmod a-w "$TMP/ro" out="$(XDG_DATA_HOME="$TMP/ro/data" "$APPDIR/AppRun" out.txt)" || fail "launch blocked by unwritable data dir" -[ "$out" = "stub: out.txt" ] || fail "stub not run against unwritable data dir: $out" +[[ "$out" = "stub: out.txt" ]] || fail "stub not run against unwritable data dir: $out" chmod u+w "$TMP/ro" # 8. No $APPIMAGE (extracted AppDir): launch only, nothing installed. rm -rf "$XDG_DATA_HOME" env -u APPIMAGE "$APPDIR/AppRun" >/dev/null || fail "extracted-AppDir launch exited non-zero" -[ ! -e "$XDG_DATA_HOME" ] || fail "integrated without $APPIMAGE" +[[ ! -e "$XDG_DATA_HOME" ]] || fail "integrated without $APPIMAGE" # 9. A path that would need Exec escaping (here a `$`): launch only, nothing installed. rm -rf "$XDG_DATA_HOME" odd="$TMP/odd\$(id).AppImage" : >"$odd" out="$(APPIMAGE="$odd" "$APPDIR/AppRun" x)" || fail "launch with an odd path exited non-zero" -[ "$out" = "stub: x" ] || fail "odd path: $out" -[ ! -e "$XDG_DATA_HOME" ] || fail "integrated an AppImage path that needs escaping" +[[ "$out" = "stub: x" ]] || fail "odd path: $out" +[[ ! -e "$XDG_DATA_HOME" ]] || fail "integrated an AppImage path that needs escaping" # 10. `cli ` runs figcraft-cli with the remaining arguments and integrates nothing. rm -rf "$XDG_DATA_HOME" out="$("$APPDIR/AppRun" cli mcp --attach 7979 2>&1)" && fail "cli ran without figcraft-cli in the AppDir" -[ "$out" = "figcraft: this AppImage was built without figcraft-cli" ] || fail "missing cli: $out" +[[ "$out" = "figcraft: this AppImage was built without figcraft-cli" ]] || fail "missing cli: $out" printf '#!/bin/sh\necho "cli stub: $*"\n' >"$APPDIR/usr/bin/figcraft-cli" chmod +x "$APPDIR/usr/bin/figcraft-cli" out="$("$APPDIR/AppRun" cli mcp --attach 7979)" || fail "cli launch exited non-zero" -[ "$out" = "cli stub: mcp --attach 7979" ] || fail "cli arguments not forwarded: $out" +[[ "$out" = "cli stub: mcp --attach 7979" ]] || fail "cli arguments not forwarded: $out" out="$("$APPDIR/AppRun" cli)" || fail "bare cli launch exited non-zero" -[ "$out" = "cli stub: " ] || fail "bare cli output: $out" -[ ! -e "$XDG_DATA_HOME" ] || fail "cli launch integrated the desktop" +[[ "$out" = "cli stub: " ]] || fail "bare cli output: $out" +[[ ! -e "$XDG_DATA_HOME" ]] || fail "cli launch integrated the desktop" # Only the first argument selects the CLI. out="$("$APPDIR/AppRun" file.figcraft cli)" || fail "app launch with a later cli argument exited non-zero" -[ "$out" = "stub: file.figcraft cli" ] || fail "later cli argument hijacked the launch: $out" +[[ "$out" = "stub: file.figcraft cli" ]] || fail "later cli argument hijacked the launch: $out" echo "AppRun desktop integration ok" diff --git a/packaging/linux/flatpak-bundle.sh b/packaging/linux/flatpak-bundle.sh index 127c3af9..b3353b38 100755 --- a/packaging/linux/flatpak-bundle.sh +++ b/packaging/linux/flatpak-bundle.sh @@ -22,7 +22,7 @@ FLATHUB=https://dl.flathub.org/repo/flathub.flatpakrepo TEST=1 TARBALL="" -while [ $# -gt 0 ]; do +while [[ $# -gt 0 ]]; do case "$1" in --no-test) TEST=0; shift ;; -h | --help) sed -n '2,14p' "$0"; exit 0 ;; @@ -39,7 +39,7 @@ case "$ARCH" in esac BASENAME="figcraft-$VERSION-linux-$ARCH" TARBALL="${TARBALL:-$DIST/$BASENAME.tar.gz}" -[ -f "$TARBALL" ] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; } +[[ -f "$TARBALL" ]] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; } for tool in flatpak flatpak-builder; do command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; } done @@ -50,7 +50,7 @@ WORK="$CARGO_TARGET_DIR/flatpak-bundle" rm -rf "$WORK" mkdir -p "$WORK/stage" tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1 -[ -x "$WORK/stage/bin/figcraft" ] || { echo "error: $TARBALL has no bin/figcraft" >&2; exit 1; } +[[ -x "$WORK/stage/bin/figcraft" ]] || { echo "error: $TARBALL has no bin/figcraft" >&2; exit 1; } cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml" flatpak remote-add --user --if-not-exists flathub "$FLATHUB" @@ -63,10 +63,10 @@ OUT="$DIST/$BASENAME.flatpak" flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable echo "wrote $OUT" -if [ "$TEST" = 1 ]; then +if [[ "$TEST" = 1 ]]; then flatpak install --user -y --noninteractive --reinstall "$OUT" flatpak info --user "$APP_ID" - if [ -x "$WORK/stage/bin/figcraft-cli" ]; then smoke=figcraft-cli; else smoke=figcraft; fi + if [[ -x "$WORK/stage/bin/figcraft-cli" ]]; then smoke=figcraft-cli; else smoke=figcraft; fi flatpak run --command="$smoke" "$APP_ID" --version flatpak run --command=sh "$APP_ID" -c 'ls /app/share/applications /app/share/metainfo /app/share/mime/packages /app/share/icons/hicolor/256x256/apps' fi diff --git a/packaging/linux/package.sh b/packaging/linux/package.sh index d38dc0bc..2480f204 100755 --- a/packaging/linux/package.sh +++ b/packaging/linux/package.sh @@ -27,7 +27,7 @@ APP_ID=ai.storyteller.figcraft SKIP_BUILD=0 FORMATS="appimage deb rpm tar" -while [ $# -gt 0 ]; do +while [[ $# -gt 0 ]]; do case "$1" in --skip-build) SKIP_BUILD=1; shift ;; --formats) FORMATS="$2"; shift 2 ;; @@ -48,7 +48,7 @@ BASENAME="figcraft-$VERSION-linux-$ARCH" echo "==> FigCraft $VERSION for Linux $ARCH ($FORMATS; ${BINS[*]})" -if [ "$SKIP_BUILD" = 0 ]; then +if [[ "$SKIP_BUILD" = 0 ]]; then (cd "$ROOT" && cargo build --release --locked "${CARGO_PKGS[@]}" ${CROSS_TARGET:+--target "$CROSS_TARGET"}) fi BIN="$CARGO_TARGET_DIR/${CROSS_TARGET:+$CROSS_TARGET/}release" @@ -78,7 +78,7 @@ if command -v appstreamcli >/dev/null; then appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml" fi -has() { case " $FORMATS " in *" $1 "*) return 0 ;; *) return 1 ;; esac; } +has() { local format="$1"; case " $FORMATS " in *" $format "*) return 0 ;; *) return 1 ;; esac; } # ---- .tar.gz ------------------------------------------------------------------------------------ if has tar; then @@ -96,7 +96,7 @@ if has deb || has rpm; then # nfpm expands env vars in fields like `version` and `arch`, but not in `contents[].src`. # Without the CLI, drop its `#cli` lines. cli_filter='/#cli$/d' - [ "$HAS_CLI" = 1 ] && cli_filter='s/ #cli$//' + [[ "$HAS_CLI" = 1 ]] && cli_filter='s/ #cli$//' sed -e "s|\${STAGE}|$STAGE|g" -e "$cli_filter" "$HERE/nfpm.yaml" >"$WORK/nfpm.yaml" for fmt in deb rpm; do if has "$fmt"; then (cd "$ROOT" && nfpm package -f "$WORK/nfpm.yaml" -p "$fmt" -t "$DIST/$BASENAME.$fmt"); fi @@ -118,9 +118,9 @@ if has appimage; then ln -s "$APP_ID.png" "$APPDIR/.DirIcon" TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}" - if [ -z "$TOOL" ]; then + if [[ -z "$TOOL" ]]; then TOOL="$CARGO_TARGET_DIR/appimagetool-$ARCH.AppImage" - if [ ! -x "$TOOL" ]; then + if [[ ! -x "$TOOL" ]]; then curl --proto '=https' -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage" chmod +x "$TOOL" fi @@ -143,16 +143,16 @@ if has appimage; then # package) appimagetool also writes the .zsync, into its working directory, hence the cd. (cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream -u "$UPDATE_INFO" "$APPDIR" "$OUT") echo "wrote $OUT" - if [ -s "$OUT.zsync" ]; then + if [[ -s "$OUT.zsync" ]]; then echo "wrote $OUT.zsync" else warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it" fi fi -if [ -z "${CROSS_TARGET:-}" ]; then +if [[ -z "${CROSS_TARGET:-}" ]]; then "$STAGE/usr/bin/$(smoke_bin)" --version -elif [ -n "${EMULATOR:-}" ]; then +elif [[ -n "${EMULATOR:-}" ]]; then "$EMULATOR" "$STAGE/usr/bin/$(smoke_bin)" --version fi echo "==> done" diff --git a/packaging/macos/import-cert.sh b/packaging/macos/import-cert.sh index 1150163e..77611537 100755 --- a/packaging/macos/import-cert.sh +++ b/packaging/macos/import-cert.sh @@ -12,7 +12,7 @@ umask 077 # shellcheck source=../env.sh . "$(dirname "${BASH_SOURCE[0]}")/../env.sh" -if [ -z "${APPLE_CERTIFICATE:-}" ]; then +if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then warn "APPLE_CERTIFICATE is not set: macOS builds will be ad-hoc signed and not notarized" exit 0 fi @@ -36,13 +36,13 @@ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KC_PASS" security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"') IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" | awk '/Developer ID Application/ { print $2; exit }')" -if [ -z "$IDENTITY" ]; then +if [[ -z "$IDENTITY" ]]; then security find-identity -v -p codesigning "$KEYCHAIN" >&2 echo "error: no 'Developer ID Application' identity in APPLE_CERTIFICATE" >&2 exit 1 fi echo "Imported signing identity $IDENTITY" -if [ -n "${GITHUB_ENV:-}" ]; then +if [[ -n "${GITHUB_ENV:-}" ]]; then { echo "MACOS_SIGN_IDENTITY=$IDENTITY"; echo "MACOS_KEYCHAIN=$KEYCHAIN"; } >>"$GITHUB_ENV" else echo "MACOS_SIGN_IDENTITY=$IDENTITY" diff --git a/packaging/macos/package.sh b/packaging/macos/package.sh index 87abe94c..7407af7b 100755 --- a/packaging/macos/package.sh +++ b/packaging/macos/package.sh @@ -19,7 +19,7 @@ HERE="$ROOT/packaging/macos" ARCH=universal SKIP_BUILD=0 -while [ $# -gt 0 ]; do +while [[ $# -gt 0 ]]; do case "$1" in --arch) ARCH="$2"; shift 2 ;; --skip-build) SKIP_BUILD=1; shift ;; @@ -44,9 +44,9 @@ DMG="$DIST/figcraft-$VERSION-macos-$ARCH.dmg" CLI_ZIP="$DIST/figcraft-cli-$VERSION-macos-$ARCH.zip" NOTARIZE=0 -if [ "$IDENTITY" = "-" ]; then +if [[ "$IDENTITY" = "-" ]]; then warn "macOS: ad-hoc signing (no MACOS_SIGN_IDENTITY); artifacts are not notarized" -elif [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then +elif [[ -n "${APPLE_ID:-}" ]] && [[ -n "${APPLE_PASSWORD:-}" ]] && [[ -n "${APPLE_TEAM_ID:-}" ]]; then NOTARIZE=1 else warn "macOS: APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID incomplete; signed but not notarized" @@ -55,7 +55,7 @@ fi echo "==> FigCraft $VERSION for macOS ($ARCH; ${BINS[*]}), identity: $IDENTITY, notarize: $NOTARIZE" # ---- build ------------------------------------------------------------------------------------- -if [ "$SKIP_BUILD" = 0 ]; then +if [[ "$SKIP_BUILD" = 0 ]]; then args=() for t in "${TARGETS[@]}"; do args+=(--target "$t"); done (cd "$ROOT" && cargo build --release --locked "${CARGO_PKGS[@]}" "${args[@]}") @@ -74,9 +74,9 @@ done sign() { # Hardened runtime + secure timestamp for a real identity; ad-hoc can't be timestamped. local ts=(--timestamp) - [ "$IDENTITY" = "-" ] && ts=(--timestamp=none) + [[ "$IDENTITY" = "-" ]] && ts=(--timestamp=none) local kc=() - [ -n "${MACOS_KEYCHAIN:-}" ] && kc=(--keychain "$MACOS_KEYCHAIN") + [[ -n "${MACOS_KEYCHAIN:-}" ]] && kc=(--keychain "$MACOS_KEYCHAIN") codesign --force --sign "$IDENTITY" ${kc[@]+"${kc[@]}"} "${ts[@]}" "$@" } @@ -88,8 +88,8 @@ notarize() { echo "$out" id="$(printf '%s' "$out" | plutil -extract id raw -o - - 2>/dev/null || true)" status="$(printf '%s' "$out" | plutil -extract status raw -o - - 2>/dev/null || true)" - if [ "$status" != "Accepted" ]; then - if [ -n "$id" ]; then + if [[ "$status" != "Accepted" ]]; then + if [[ -n "$id" ]]; then xcrun notarytool log "$id" --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" || true fi echo "error: notarization of $(basename "$file") failed (status: ${status:-unknown})" >&2 @@ -116,7 +116,7 @@ mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" cp "$WORK/bin/figcraft" "$APP/Contents/MacOS/FigCraft" cp "$WORK/FigCraft.icns" "$APP/Contents/Resources/FigCraft.icns" # Licences of the embedded craft-fonts fonts (only when built with CRAFT_FONTS_DIR). -if [ -n "${CRAFT_FONTS_DIR:-}" ]; then +if [[ -n "${CRAFT_FONTS_DIR:-}" ]]; then mkdir -p "$APP/Contents/Resources/Licenses" copy_font_licences "$APP/Contents/Resources/Licenses" fi @@ -132,7 +132,7 @@ sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP/Contents/ sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP" codesign --verify --strict --deep --verbose=2 "$APP" -if [ "$NOTARIZE" = 1 ]; then +if [[ "$NOTARIZE" = 1 ]]; then ditto -c -k --keepParent "$APP" "$WORK/FigCraft-notarize.zip" notarize "$WORK/FigCraft-notarize.zip" retry xcrun stapler staple "$APP" @@ -154,7 +154,7 @@ hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG" rm -f "$WORK/raw.dmg" sign "$DMG" codesign --verify --strict --verbose=2 "$DMG" -if [ "$NOTARIZE" = 1 ]; then +if [[ "$NOTARIZE" = 1 ]]; then notarize "$DMG" retry xcrun stapler staple "$DMG" retry xcrun stapler validate "$DMG" @@ -162,7 +162,7 @@ if [ "$NOTARIZE" = 1 ]; then fi # ---- CLI --------------------------------------------------------------------------------------- -if [ "$HAS_CLI" = 1 ]; then +if [[ "$HAS_CLI" = 1 ]]; then echo "==> building $CLI_ZIP" CLI_DIR="$WORK/figcraft-cli-$VERSION-macos-$ARCH" mkdir -p "$CLI_DIR" @@ -176,7 +176,7 @@ if [ "$HAS_CLI" = 1 ]; then ditto -c -k --keepParent "$CLI_DIR" "$CLI_ZIP" # notarytool accepts a zip of the bare binary. A bare Mach-O can't carry a stapled ticket, so # Gatekeeper looks the notarization up online when a quarantined copy first runs. - if [ "$NOTARIZE" = 1 ]; then notarize "$CLI_ZIP"; fi + if [[ "$NOTARIZE" = 1 ]]; then notarize "$CLI_ZIP"; fi "$WORK/bin/figcraft-cli" --version else warn "apps/figcraft-cli is not in this workspace: no CLI zip" diff --git a/packaging/macos/verify.sh b/packaging/macos/verify.sh index 6080cf87..071629cb 100755 --- a/packaging/macos/verify.sh +++ b/packaging/macos/verify.sh @@ -23,7 +23,7 @@ set -euo pipefail . "$(dirname "${BASH_SOURCE[0]}")/../env.sh" ARCH=universal -while [ $# -gt 0 ]; do +while [[ $# -gt 0 ]]; do case "$1" in --arch) ARCH="$2"; shift 2 ;; -h | --help) sed -n '2,20p' "$0"; exit 0 ;; @@ -41,13 +41,13 @@ DMG="$DIST/figcraft-$VERSION-macos-$ARCH.dmg" CLI_ZIP="$DIST/figcraft-cli-$VERSION-macos-$ARCH.zip" IDENTITY="${MACOS_SIGN_IDENTITY:--}" STRICT=0 -if [ "$IDENTITY" != "-" ] && [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then +if [[ "$IDENTITY" != "-" ]] && [[ -n "${APPLE_ID:-}" ]] && [[ -n "${APPLE_PASSWORD:-}" ]] && [[ -n "${APPLE_TEAM_ID:-}" ]]; then STRICT=1 fi FAILED=0 fail() { - if [ "$STRICT" = 1 ]; then + if [[ "$STRICT" = 1 ]]; then echo "error: $*" >&2 FAILED=1 else @@ -58,22 +58,22 @@ fail() { # spctl's online ticket lookup can lag a few seconds behind notarytool's "Accepted". assess() { local out attempt tries=1 - [ "$STRICT" = 1 ] && tries=5 + [[ "$STRICT" = 1 ]] && tries=5 for attempt in $(seq "$tries"); do if out="$(spctl "$@" 2>&1)"; then printf '%s\n' "$out" return 0 fi - [ "$attempt" = "$tries" ] || sleep 10 + [[ "$attempt" = "$tries" ]] || sleep 10 done printf '%s\n' "$out" return 1 } FILES=("$DMG") -if [ "$HAS_CLI" = 1 ]; then FILES+=("$CLI_ZIP"); fi +if [[ "$HAS_CLI" = 1 ]]; then FILES+=("$CLI_ZIP"); fi for f in "${FILES[@]}"; do - if [ ! -f "$f" ]; then + if [[ ! -f "$f" ]]; then echo "error: missing $f" >&2 exit 1 fi @@ -83,11 +83,11 @@ WORK="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/figcraft-verify.XXXXXX")" trap 'rm -rf "$WORK"' EXIT # ---- CLI zip (when apps/figcraft-cli exists) ---------------------------------------------------- -if [ "$HAS_CLI" = 1 ]; then +if [[ "$HAS_CLI" = 1 ]]; then echo "==> $(basename "$CLI_ZIP")" ditto -x -k "$CLI_ZIP" "$WORK/cli" CLI="$WORK/cli/figcraft-cli-$VERSION-macos-$ARCH/figcraft-cli" - if [ ! -x "$CLI" ]; then + if [[ ! -x "$CLI" ]]; then echo "error: $(basename "$CLI_ZIP") has no executable figcraft-cli-$VERSION-macos-$ARCH/figcraft-cli" >&2 exit 1 fi @@ -111,7 +111,7 @@ if [ "$HAS_CLI" = 1 ]; then || fail "figcraft-cli is not signed with a Developer ID Application identity" printf '%s\n' "$details" | grep -q '^Timestamp=' \ || fail "figcraft-cli signature has no secure timestamp" - if [ -n "${APPLE_TEAM_ID:-}" ]; then + if [[ -n "${APPLE_TEAM_ID:-}" ]]; then printf '%s\n' "$details" | grep -qx "TeamIdentifier=$APPLE_TEAM_ID" \ || fail "figcraft-cli is signed by a different team" fi @@ -129,25 +129,25 @@ fi # ---- DMG --------------------------------------------------------------------------------------- echo "==> $(basename "$DMG")" codesign --verify --strict --verbose=2 "$DMG" -if [ "$STRICT" = 1 ]; then +if [[ "$STRICT" = 1 ]]; then retry xcrun stapler validate "$DMG" || fail "no notarization ticket stapled to the DMG" assess --assess --type open --context context:primary-signature -vv "$DMG" \ || fail "Gatekeeper rejects the DMG" fi -if [ "$FAILED" != 0 ]; then +if [[ "$FAILED" != 0 ]]; then echo "error: macOS artifacts failed verification" >&2 exit 1 fi -if [ "$STRICT" = 1 ]; then +if [[ "$STRICT" = 1 ]]; then result="signed (Developer ID, hardened runtime), notarized; DMG stapled" else result="signature intact; Developer ID and notarization not required (no signing secrets)" fi echo "==> verified: $result" -if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then +if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then names="\`$(basename "$DMG")\`" - if [ "$HAS_CLI" = 1 ]; then names="$names, \`$(basename "$CLI_ZIP")\`"; fi + if [[ "$HAS_CLI" = 1 ]]; then names="$names, \`$(basename "$CLI_ZIP")\`"; fi echo "- macOS $names: $result" >>"$GITHUB_STEP_SUMMARY" fi diff --git a/packaging/server/install-test.sh b/packaging/server/install-test.sh index 303d08de..2ebe5176 100755 --- a/packaging/server/install-test.sh +++ b/packaging/server/install-test.sh @@ -10,7 +10,7 @@ WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT FAILED=0 -main() { bash "$HERE/install.sh" --caddy-main "$1" ops@example.com; } +main() { local caddyfile="$1"; bash "$HERE/install.sh" --caddy-main "$caddyfile" ops@example.com; } fail() { printf 'FAIL: %s\n' "$*" >&2; FAILED=1; } # Missing: ours. @@ -28,7 +28,7 @@ printf 'shop.example.com {\n\treverse_proxy 127.0.0.1:8080\n}\n' >"$WORK/theirs" cp "$WORK/theirs" "$WORK/run1" main "$WORK/run1" >"$WORK/run2" grep -q 'shop.example.com' "$WORK/run2" || fail "the operator's site is kept" -[ "$(grep -c 'import figcraft-server.caddy' "$WORK/run2")" = 1 ] || fail "one import line is added" +[[ "$(grep -c 'import figcraft-server.caddy' "$WORK/run2")" = 1 ]] || fail "one import line is added" main "$WORK/run2" >"$WORK/run3" cmp -s "$WORK/run2" "$WORK/run3" || fail "running again leaves a Caddyfile with other sites as it is" main "$WORK/run3" >"$WORK/run4" @@ -39,5 +39,5 @@ main "$WORK/none" >"$WORK/ours1" main "$WORK/ours1" >"$WORK/ours2" cmp -s "$WORK/ours1" "$WORK/ours2" || fail "our own Caddyfile stays the same" -if [ "$FAILED" = 0 ]; then echo "install.sh Caddyfile handling: ok"; fi +if [[ "$FAILED" = 0 ]]; then echo "install.sh Caddyfile handling: ok"; fi exit "$FAILED" diff --git a/packaging/server/install.sh b/packaging/server/install.sh index 826c2bb4..94acd578 100755 --- a/packaging/server/install.sh +++ b/packaging/server/install.sh @@ -41,7 +41,7 @@ SERVICE=figcraft-server HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # The source tree this script sits in (packaging/server/ of a copy or checkout), if any. LOCAL_SRC="" -if [ -f "$HERE/../../Cargo.toml" ] && [ -d "$HERE/../../apps/figcraft-server" ]; then +if [[ -f "$HERE/../../Cargo.toml" ]] && [[ -d "$HERE/../../apps/figcraft-server" ]]; then LOCAL_SRC="$(cd "$HERE/../.." && pwd)" fi @@ -64,9 +64,9 @@ MARK="# figcraft-server (install.sh)" caddy_main() { # caddy_main FILE EMAIL: the main Caddyfile to install, on stdout local file="$1" email="$2" - if [ -f "$file" ] && grep -qE '^[[:space:]]*import[[:space:]]+figcraft-server\.caddy[[:space:]]*$' "$file"; then + if [[ -f "$file" ]] && grep -qE '^[[:space:]]*import[[:space:]]+figcraft-server\.caddy[[:space:]]*$' "$file"; then cat "$file" # it imports our site already: kept exactly as it is - elif [ ! -f "$file" ] || ! grep -qvE '^[[:space:]]*(#|$|:80|\{|\}|root|file_server)' "$file"; then + elif [[ ! -f "$file" ]] || ! grep -qvE '^[[:space:]]*(#|$|:80|\{|\}|root|file_server)' "$file"; then # Missing, or only the example site the package ships: ours, with the certificate email. printf '%s\n{\n\temail %s\n}\n\nimport figcraft-server.caddy\n' "$MARK" "$email" else @@ -76,7 +76,7 @@ caddy_main() { # caddy_main FILE EMAIL: the main Caddyfile to install, on stdout fi } -while [ $# -gt 0 ]; do +while [[ $# -gt 0 ]]; do case "$1" in # For tests (packaging/server/install-test.sh): print the Caddyfile this script would write. --caddy-main) caddy_main "${2:-}" "${3:-}"; exit 0 ;; @@ -95,7 +95,7 @@ while [ $# -gt 0 ]; do esac done -[ "$(id -u)" -eq 0 ] || die "run it as root (sudo ./install.sh ...)" +[[ "$(id -u)" -eq 0 ]] || die "run it as root (sudo ./install.sh ...)" # shellcheck source=/dev/null . /etc/os-release case " ${ID:-} ${ID_LIKE:-} " in @@ -104,9 +104,9 @@ case " ${ID:-} ${ID_LIKE:-} " in esac ask() { # ask VAR "question" FLAG: read an answer from the terminal when the value wasn't given - local var="$1" question="$2" answer - if [ -z "${!var}" ]; then - [ -t 0 ] || die "give $3 (no terminal to ask on)" + local var="$1" question="$2" flag="$3" answer + if [[ -z "${!var}" ]]; then + [[ -t 0 ]] || die "give $flag (no terminal to ask on)" read -r -p "$question " answer printf -v "$var" '%s' "$answer" fi @@ -115,6 +115,7 @@ ask HOST "DNS name people will use (e.g. figcraft.example.com):" "--host" ask EMAIL "Email for Let's Encrypt and the abuse contact shown to users:" "--email" case "$HOST" in "" | *[!A-Za-z0-9.-]* | .* | *. ) die "--host must be a DNS name like figcraft.example.com" ;; + *) ;; esac case "$EMAIL" in *@*.*) ;; @@ -126,6 +127,7 @@ case "$TERMS" in esac case "$EMAIL$TERMS" in *[[:space:]\"\\]*) die "--email and --terms can't contain spaces, quotes or backslashes" ;; + *) ;; esac export DEBIAN_FRONTEND=noninteractive @@ -138,6 +140,7 @@ ARCH="$(uname -m)" case "$ARCH" in amd64) ARCH=x86_64 ;; arm64) ARCH=aarch64 ;; + *) ;; # already a release name (x86_64, aarch64) esac # --- The binary ----------------------------------------------------------------------------- @@ -146,9 +149,9 @@ FILES="$HERE" # where the units and the Caddyfile come from from_release() { # download and check the release archive; sets NEW_BIN and FILES local version="$VERSION" name dir - if [ -z "$version" ]; then + if [[ -z "$version" ]]; then version="$(curl --proto '=https' -fsSL "https://api.github.com/repos/$REPO/releases/latest" | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p' | head -n 1)" || return 1 - [ -n "$version" ] || return 1 + [[ -n "$version" ]] || return 1 fi name="figcraft-server-$version-linux-$ARCH" dir="$WORK/release" @@ -165,7 +168,7 @@ from_release() { # download and check the release archive; sets NEW_BIN and FILE SWAP="" cleanup() { - if [ -n "$SWAP" ]; then + if [[ -n "$SWAP" ]]; then swapoff "$SWAP" 2>/dev/null || true rm -f "$SWAP" fi @@ -174,7 +177,7 @@ trap cleanup EXIT as_builder() { # run a command as the unprivileged build user, with its own toolchain local jobs=() - if [ -n "${CARGO_BUILD_JOBS:-}" ]; then jobs=("CARGO_BUILD_JOBS=$CARGO_BUILD_JOBS"); fi + if [[ -n "${CARGO_BUILD_JOBS:-}" ]]; then jobs=("CARGO_BUILD_JOBS=$CARGO_BUILD_JOBS"); fi runuser -u "$BUILDER" -- env HOME="$BUILD" RUSTUP_HOME="$BUILD/rustup" CARGO_HOME="$BUILD/cargo" \ CARGO_TARGET_DIR="$BUILD/target" "${jobs[@]}" "$@" } @@ -184,27 +187,27 @@ from_source() { # build the source tree around this script, else a fresh clone; apt-get install -y -qq build-essential git pkg-config >/dev/null id -u "$BUILDER" >/dev/null 2>&1 || useradd --system --home-dir "$BUILD" --shell /usr/sbin/nologin "$BUILDER" install -d -o "$BUILDER" -g "$BUILDER" -m 755 "$BUILD" - if [ ! -x "$BUILD/cargo/bin/cargo" ]; then + if [[ ! -x "$BUILD/cargo/bin/cargo" ]]; then as_builder sh -c "curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable --no-modify-path" >/dev/null else as_builder "$BUILD/cargo/bin/rustup" update stable >/dev/null fi # The source root installs files from is root's own (the copy beside this script, or root's # clone); the build user only ever builds a copy of it. - if [ -n "$LOCAL_SRC" ]; then + if [[ -n "$LOCAL_SRC" ]]; then src="$LOCAL_SRC" say "Building the source in $LOCAL_SRC: this takes a while" else src="$WORK/src" - if [ -d "$src/.git" ]; then + if [[ -d "$src/.git" ]]; then git -C "$src" fetch --quiet --tags origin else git clone --quiet "https://github.com/$REPO" "$src" || die "can't clone $REPO (a private repository: copy the source to this machine and run its packaging/server/install.sh)" fi - if [ -z "$ref" ]; then - if [ -n "$VERSION" ]; then ref="v$VERSION"; else ref="$(git -C "$src" tag --list 'v*' --sort=-v:refname | head -n 1)"; fi - [ -n "$ref" ] || ref=main + if [[ -z "$ref" ]]; then + if [[ -n "$VERSION" ]]; then ref="v$VERSION"; else ref="$(git -C "$src" tag --list 'v*' --sort=-v:refname | head -n 1)"; fi + [[ -n "$ref" ]] || ref=main fi say "Building $ref from a clone: this takes a while" # A branch as it is upstream now, or a tag. @@ -216,7 +219,7 @@ from_source() { # build the source tree around this script, else a fresh clone; tar -C "$src" --exclude=./target --exclude=./plugins/target --exclude=./.git -cf - . | as_builder tar -C "$copy" -xf - # The compiler needs about 2 GB: add a temporary swap file on smaller machines. mem_kb="$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)" - if [ "$mem_kb" -lt 2000000 ] && [ -z "$(swapon --show --noheadings)" ]; then + if [[ "$mem_kb" -lt 2000000 ]] && [[ -z "$(swapon --show --noheadings)" ]]; then SWAP="$WORK/swapfile" say "Adding a temporary 2 GB swap file for the build" fallocate -l 2G "$SWAP" 2>/dev/null || dd if=/dev/zero of="$SWAP" bs=1M count=2048 status=none @@ -237,19 +240,19 @@ from_source() { # build the source tree around this script, else a fresh clone; FILES="$src/packaging/server" } -if [ -x "$HERE/figcraft-server" ] && [ "$FROM_SOURCE" = false ]; then +if [[ -x "$HERE/figcraft-server" ]] && [[ "$FROM_SOURCE" = false ]]; then NEW_BIN="$HERE/figcraft-server" # run from an unpacked release archive -elif [ -n "$LOCAL_SRC" ] && [ -z "$VERSION" ]; then +elif [[ -n "$LOCAL_SRC" ]] && [[ -z "$VERSION" ]]; then from_source # the source copied to (or checked out on) this machine, as it is -elif [ "$FROM_SOURCE" = true ] || ! from_release; then - [ "$FROM_SOURCE" = true ] || say "No release archive for linux-$ARCH: building from source instead" +elif [[ "$FROM_SOURCE" = true ]] || ! from_release; then + [[ "$FROM_SOURCE" = true ]] || say "No release archive for linux-$ARCH: building from source instead" from_source fi -[ -x "$NEW_BIN" ] || die "no figcraft-server binary to install" +[[ -x "$NEW_BIN" ]] || die "no figcraft-server binary to install" # The units and the Caddyfile beside this script when it has them (a checkout or an archive). -[ -f "$HERE/figcraft-backup.timer" ] && FILES="$HERE" +[[ -f "$HERE/figcraft-backup.timer" ]] && FILES="$HERE" for f in figcraft-server.service Caddyfile figcraft-backup.service figcraft-backup.timer; do - [ -f "$FILES/$f" ] || die "$FILES/$f is missing" + [[ -f "$FILES/$f" ]] || die "$FILES/$f is missing" done # The server's own account runs the binary, from here on too (root never runs what was built). @@ -267,7 +270,7 @@ fi # --- Data and settings ---------------------------------------------------------------------- install -d -o figcraft -g figcraft -m 700 "$DATA" install -d -m 755 "$CONFIG_DIR" -if [ ! -f "$CONFIG" ]; then +if [[ ! -f "$CONFIG" ]]; then say "Writing $CONFIG" { printf '# figcraft-server settings (docs/server.md, "Configuration"). install.sh never overwrites this file.\n' @@ -303,8 +306,9 @@ as_server "$BIN" --config "$CONFIG" check >/dev/null || die "$CONFIG isn't right for this figcraft-server (see above; a release older than open hosting needs --from-source)" install_file() { # install_file SOURCE TARGET MODE: true when it changed - if cmp -s "$1" "$2"; then return 1; fi - install -m "$3" "$1" "$2" + local source="$1" target="$2" mode="$3" + if cmp -s "$source" "$target"; then return 1; fi + install -m "$mode" "$source" "$target" } # --- systemd -------------------------------------------------------------------------------- @@ -312,16 +316,16 @@ UNITS=false install_file "$FILES/figcraft-server.service" /etc/systemd/system/figcraft-server.service 644 && UNITS=true install_file "$FILES/figcraft-backup.service" /etc/systemd/system/figcraft-backup.service 644 && UNITS=true install_file "$FILES/figcraft-backup.timer" /etc/systemd/system/figcraft-backup.timer 644 && UNITS=true -if [ "$UNITS" = true ]; then +if [[ "$UNITS" = true ]]; then systemctl daemon-reload CHANGED=true fi systemctl enable --quiet "$SERVICE" -if [ "$CHANGED" = true ] || ! systemctl is-active --quiet "$SERVICE"; then +if [[ "$CHANGED" = true ]] || ! systemctl is-active --quiet "$SERVICE"; then say "Starting figcraft-server" systemctl restart "$SERVICE" fi -if [ "$BACKUPS" = true ]; then +if [[ "$BACKUPS" = true ]]; then systemctl enable --now --quiet figcraft-backup.timer fi @@ -339,15 +343,15 @@ MAIN="$(mktemp)" caddy_main "$CADDYFILE" "$EMAIL" >"$MAIN" CADDY_CHANGED=false for f in "$CADDYFILE" "$SITE_FILE"; do - if [ -f "$f" ]; then cp -p "$f" "$f.before-figcraft"; else rm -f "$f.before-figcraft"; fi + if [[ -f "$f" ]]; then cp -p "$f" "$f.before-figcraft"; else rm -f "$f.before-figcraft"; fi done install_file "$SITE" "$SITE_FILE" 644 && CADDY_CHANGED=true install_file "$MAIN" "$CADDYFILE" 644 && CADDY_CHANGED=true rm -f "$SITE" "$MAIN" -if [ "$CADDY_CHANGED" = true ]; then +if [[ "$CADDY_CHANGED" = true ]]; then if ! caddy validate --config "$CADDYFILE" --adapter caddyfile >/dev/null 2>&1; then for f in "$CADDYFILE" "$SITE_FILE"; do - if [ -f "$f.before-figcraft" ]; then cp -p "$f.before-figcraft" "$f"; else rm -f "$f"; fi + if [[ -f "$f.before-figcraft" ]]; then cp -p "$f.before-figcraft" "$f"; else rm -f "$f"; fi done die "Caddy doesn't accept the new configuration (the old one is back; see: caddy validate --config $CADDYFILE)" fi @@ -356,10 +360,10 @@ fi systemctl enable --now --quiet caddy # --- Firewall ------------------------------------------------------------------------------- -if [ "$FIREWALL" = true ]; then +if [[ "$FIREWALL" = true ]]; then # The ports sshd really listens on (sudo doesn't pass SSH_CONNECTION on). SSH_PORTS="$(sshd -T 2>/dev/null | awk '/^port / {print $2}' | sort -u)" - [ -n "$SSH_PORTS" ] || SSH_PORTS=22 + [[ -n "$SSH_PORTS" ]] || SSH_PORTS=22 say "Firewall: SSH ($(echo "$SSH_PORTS" | tr '\n' ' ' | sed 's/ $//')), 80 and 443" for p in $SSH_PORTS; do ufw allow "$p/tcp" >/dev/null; done ufw allow 80/tcp >/dev/null @@ -370,12 +374,12 @@ if [ "$FIREWALL" = true ]; then # Other services reachable from outside would be cut off by enabling ufw. OTHERS="" for a in $(ss -Hltn | awk '{print $4}'); do - case "$a" in 127.* | "[::1]:"* | ::1:*) continue ;; esac + case "$a" in 127.* | "[::1]:"* | ::1:*) continue ;; *) ;; esac port="${a##*:}" case " $(echo "$SSH_PORTS" | tr '\n' ' ') 80 443 " in *" $port "*) continue ;; esac OTHERS="$OTHERS $port" done - if [ -n "$OTHERS" ] && [ "$FIREWALL_ANYWAY" = false ]; then + if [[ -n "$OTHERS" ]] && [[ "$FIREWALL_ANYWAY" = false ]]; then warn "other services listen on port(s)$OTHERS: ufw was not enabled (rules added; allow those ports, then 'ufw enable', or run again with --firewall-anyway)" else ufw --force enable >/dev/null @@ -395,7 +399,7 @@ cat < Publish to server..., address wss://$HOST/ - $(if [ "$OPEN" = true ]; then echo "(open to the public: no key needed; each file gets an owner token)"; else echo "(with the key: $ADMIN key)"; fi) + $(if [[ "$OPEN" = true ]]; then echo "(open to the public: no key needed; each file gets an owner token)"; else echo "(with the key: $ADMIN key)"; fi) Check: curl https://$HOST/healthz (once DNS points here and Caddy has its certificate) Logs: journalctl -u $SERVICE -f diff --git a/packaging/server/package.sh b/packaging/server/package.sh index 7d2f9c54..5f306327 100755 --- a/packaging/server/package.sh +++ b/packaging/server/package.sh @@ -25,11 +25,12 @@ ARCH="$(uname -m)" case "$ARCH" in arm64) ARCH=aarch64 ;; amd64) ARCH=x86_64 ;; + *) ;; # already a release name (x86_64, aarch64) esac NAME="figcraft-server-$VERSION-$OS-$ARCH" echo "==> figcraft-server $VERSION for $OS $ARCH" -if [ "$SKIP_BUILD" = 0 ]; then +if [[ "$SKIP_BUILD" = 0 ]]; then (cd "$ROOT" && cargo build --release --locked -p figcraft-server) fi WORK="$CARGO_TARGET_DIR/server-package" diff --git a/packaging/web/package.sh b/packaging/web/package.sh index 0e45c515..f9ee34a7 100755 --- a/packaging/web/package.sh +++ b/packaging/web/package.sh @@ -11,13 +11,13 @@ set -euo pipefail . "$(dirname "${BASH_SOURCE[0]}")/../env.sh" HERE="$ROOT/packaging/web" -if [ "${1:-}" != "--skip-build" ]; then +if [[ "${1:-}" != "--skip-build" ]]; then command -v trunk >/dev/null || { echo "error: trunk not found (cargo install trunk --locked)" >&2; exit 1; } (cd "$ROOT/apps/figcraft-web" && trunk build --release) fi SITE="$ROOT/dist/web" -[ -f "$SITE/index.html" ] || { echo "error: $SITE/index.html missing; run without --skip-build" >&2; exit 1; } +[[ -f "$SITE/index.html" ]] || { echo "error: $SITE/index.html missing; run without --skip-build" >&2; exit 1; } # Paths must be relative so the site works under any prefix (public_url = "./" in Trunk.toml). if grep -Eq '(src|href)="/[^/]' "$SITE/index.html"; then echo "error: $SITE/index.html has root-absolute URLs; it would break when served from a sub-path" >&2 @@ -30,16 +30,16 @@ fi MAX_WASM_BYTES="${FIGCRAFT_WASM_MAX_BYTES:-25165824}" # 24 MiB WASM_COUNT=0 for wasm in "$SITE"/*.wasm; do - [ -f "$wasm" ] || continue + [[ -f "$wasm" ]] || continue WASM_COUNT=$((WASM_COUNT + 1)) size=$(wc -c <"$wasm" | tr -d ' ') echo "$(basename "$wasm"): $size bytes ($((size / 1048576)) MiB; limit $MAX_WASM_BYTES)" - if [ "$size" -gt "$MAX_WASM_BYTES" ]; then + if [[ "$size" -gt "$MAX_WASM_BYTES" ]]; then echo "error: $(basename "$wasm") is $size bytes, over the $MAX_WASM_BYTES-byte limit (Cloudflare's per-file cap is 25 MiB)" >&2 exit 1 fi done -[ "$WASM_COUNT" -gt 0 ] || { echo "error: no .wasm in $SITE" >&2; exit 1; } +[[ "$WASM_COUNT" -gt 0 ]] || { echo "error: no .wasm in $SITE" >&2; exit 1; } NAME="figcraft-web-$VERSION" WORK="$CARGO_TARGET_DIR/web-package"