--- /dev/null
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+# 1. Checks if we're at the top directory of the Feather Wallet repository
+# 2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## SANITY CHECKS ##
+###################
+
+################
+# Required non-builtin commands should be invocable
+################
+
+check_tools cat mkdir git guix
+
+################
+# Required env vars should be non-empty
+################
+
+cmd_usage() {
+ cat <<EOF
+Synopsis:
+
+ env GUIX_SIGS_REPO=<path/to/feather-sigs> \\
+ ./contrib/guix/guix-codesign
+
+EOF
+}
+
+if [ -z "$GUIX_SIGS_REPO" ]; then
+ cmd_usage
+ exit 1
+fi
+
+################
+# GUIX_BUILD_OPTIONS should be empty
+################
+#
+# GUIX_BUILD_OPTIONS is an environment variable recognized by guix commands that
+# can perform builds. This seems like what we want instead of
+# ADDITIONAL_GUIX_COMMON_FLAGS, but the value of GUIX_BUILD_OPTIONS is actually
+# _appended_ to normal command-line options. Meaning that they will take
+# precedence over the command-specific ADDITIONAL_GUIX_<CMD>_FLAGS.
+#
+# This seems like a poor user experience. Thus we check for GUIX_BUILD_OPTIONS's
+# existence here and direct users of this script to use our (more flexible)
+# custom environment variables.
+if [ -n "$GUIX_BUILD_OPTIONS" ]; then
+cat << EOF
+Error: Environment variable GUIX_BUILD_OPTIONS is not empty:
+ '$GUIX_BUILD_OPTIONS'
+
+Unfortunately this script is incompatible with GUIX_BUILD_OPTIONS, please unset
+GUIX_BUILD_OPTIONS and use ADDITIONAL_GUIX_COMMON_FLAGS to set build options
+across guix commands or ADDITIONAL_GUIX_<CMD>_FLAGS to set build options for a
+specific guix command.
+
+See contrib/guix/README.md for more details.
+EOF
+exit 1
+fi
+
+################
+# The codesignature git worktree should not be dirty
+################
+
+if ! git -C "$GUIX_SIGS_REPO" diff-index --quiet HEAD -- && [ -z "$FORCE_DIRTY_WORKTREE" ]; then
+ cat << EOF
+ERR: The DETACHED CODESIGNATURE git worktree is dirty, which may lead to broken builds.
+
+ Aborting...
+
+Hint: To make your git worktree clean, You may want to:
+ 1. Commit your changes,
+ 2. Stash your changes, or
+ 3. Set the 'FORCE_DIRTY_WORKTREE' environment variable if you insist on
+ using a dirty worktree
+EOF
+ exit 1
+fi
+
+################
+# Build directories should not exist
+################
+
+# Default to building for all supported HOSTs (overridable by environment)
+export HOSTS="${HOSTS:-x86_64-w64-mingw32 x86_64-w64-mingw32.installer}"
+
+# Usage: distsrc_for_host HOST
+#
+# HOST: The current platform triple we're building for
+#
+distsrc_for_host() {
+ echo "${DISTSRC_BASE}/build/distsrc-${VERSION}-${1}-codesigned"
+}
+
+# Accumulate a list of build directories that already exist...
+hosts_distsrc_exists=""
+for host in $HOSTS; do
+ if [ -e "$(distsrc_for_host "$host")" ]; then
+ hosts_distsrc_exists+=" ${host}"
+ fi
+done
+
+if [ -n "$hosts_distsrc_exists" ]; then
+# ...so that we can print them out nicely in an error message
+cat << EOF
+ERR: Build directories for this commit already exist for the following platform
+ triples you're attempting to build, probably because of previous builds.
+ Please remove, or otherwise deal with them prior to starting another build.
+
+ Aborting...
+
+Hint: To blow everything away, you may want to use:
+
+ $ ./contrib/guix/guix-clean
+
+Specifically, this will remove all files without an entry in the index,
+excluding the SDK directory, the depends download cache, the depends built
+packages cache, the garbage collector roots for Guix environments, and the
+output directory.
+EOF
+for host in $hosts_distsrc_exists; do
+ echo " ${host} '$(distsrc_for_host "$host")'"
+done
+exit 1
+else
+ mkdir -p "$DISTSRC_BASE"
+fi
+
+
+################
+# Unsigned files SHOULD exist
+################
+
+# Usage: outdir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+outdir_for_host() {
+ echo "${OUTDIR_BASE}/${1}${2:+-${2}}"
+}
+
+# Usage: logdir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+logdir_for_host() {
+ echo "${LOGDIR_BASE}/${1}${2:+-${2}}"
+}
+
+unsigned_file_for_host() {
+ case "$1" in
+ *mingw32.installer)
+ echo "$(outdir_for_host "$1")/FeatherWalletSetup-${VERSION}-unsigned.exe"
+ ;;
+ *mingw32*)
+ echo "$(outdir_for_host "$1")/${DISTNAME}-unsigned.exe"
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+}
+
+# Accumulate a list of build directories that already exist...
+hosts_file_tarball_missing=""
+for host in $HOSTS; do
+ if [ ! -e "$(unsigned_file_for_host "$host")" ]; then
+ hosts_file_tarball_missing+=" ${host}"
+ fi
+done
+
+if [ -n "$hosts_file_tarball_missing" ]; then
+ # ...so that we can print them out nicely in an error message
+ cat << EOF
+ERR: Unsigned files do not exist
+...
+
+EOF
+for host in $hosts_file_tarball_missing; do
+ echo " ${host} '$(unsigned_file_for_host "$host")'"
+done
+exit 1
+fi
+
+################
+# Check that we can connect to the guix-daemon
+################
+
+cat << EOF
+Checking that we can connect to the guix-daemon...
+
+Hint: If this hangs, you may want to try turning your guix-daemon off and on
+ again.
+
+EOF
+if ! guix gc --list-failures > /dev/null; then
+ cat << EOF
+
+ERR: Failed to connect to the guix-daemon, please ensure that one is running and
+ reachable.
+EOF
+ exit 1
+fi
+
+# Developer note: we could use `guix repl` for this check and run:
+#
+# (import (guix store)) (close-connection (open-connection))
+#
+# However, the internal API is likely to change more than the CLI invocation
+
+
+#########
+# SETUP #
+#########
+
+# Determine the maximum number of jobs to run simultaneously (overridable by
+# environment)
+JOBS="${JOBS:-$(nproc)}"
+
+# Determine the reference time used for determinism (overridable by environment)
+SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -c log.showSignature=false log --format=%at -1)}"
+
+# Make sure an output directory exists for our builds
+OUTDIR_BASE="${OUTDIR_BASE:-${VERSION_BASE}/output}"
+mkdir -p "$OUTDIR_BASE"
+
+# Usage: profiledir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+profiledir_for_host() {
+ echo "${PROFILES_BASE}/${1}${2:+-${2}}"
+}
+
+#########
+# BUILD #
+#########
+
+# Function to be called when codesigning for host ${1} and the user interrupts
+# the codesign
+int_trap() {
+cat << EOF
+** INT received while codesigning ${1}, you may want to clean up the relevant
+ work directories (e.g. distsrc-*) before recodesigning
+
+Hint: To blow everything away, you may want to use:
+
+ $ ./contrib/guix/guix-clean
+
+Specifically, this will remove all files without an entry in the index,
+excluding the SDK directory, the depends download cache, the depends built
+packages cache, the garbage collector roots for Guix environments, and the
+output directory.
+EOF
+}
+
+# shellcheck disable=SC2153
+for host in $HOSTS; do
+
+ # Display proper warning when the user interrupts the build
+ trap 'int_trap ${host}' INT
+
+ (
+ # Required for 'contrib/guix/manifest.scm' to output the right manifest
+ # for the particular $HOST we're building for
+ export HOST="$host"
+
+ # shellcheck disable=SC2030
+cat << EOF
+INFO: Codesigning ${VERSION:?not set} for platform triple ${HOST:?not set}:
+ ...using reference timestamp: ${SOURCE_DATE_EPOCH:?not set}
+ ...from worktree directory: '${PWD}'
+ ...bind-mounted in container to: '/feather'
+ ...in build directory: '$(distsrc_for_host "$HOST")'
+ ...bind-mounted in container to: '$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")'
+ ...outputting in: '$(outdir_for_host "$HOST" codesigned)'
+ ...bind-mounted in container to: '$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST" codesigned)'
+ ...using detached signatures in: '${GUIX_SIGS_REPO:?not set}'
+ ...bind-mounted in container to: '/detached-sigs'
+EOF
+
+ # shellcheck disable=SC2086,SC2031
+ time-machine shell --manifest="${PWD}/contrib/guix/manifest.scm" \
+ --container \
+ --pure \
+ --no-cwd \
+ --share="$PWD"=/feather \
+ --share="$DISTSRC_BASE"=/distsrc-base \
+ --share="$OUTDIR_BASE"=/outdir-base \
+ --share="$LOGDIR_BASE"=/logdir-base \
+ --share="$GUIX_SIGS_REPO"=/guix-sigs \
+ --expose="$(git rev-parse --git-common-dir)" \
+ --expose="$(git -C "$GUIX_SIGS_REPO" rev-parse --git-common-dir)" \
+ ${SOURCES_PATH:+--share="$SOURCES_PATH"} \
+ --cores="$JOBS" \
+ --keep-failed \
+ --fallback \
+ --link-profile \
+ --user="user" \
+ --root="$(profiledir_for_host "${HOST}" codesigned)" \
+ ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
+ ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \
+ -- env HOST="$host" \
+ DISTNAME="$DISTNAME" \
+ VERSION="$VERSION" \
+ JOBS="$JOBS" \
+ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \
+ ${V:+V=1} \
+ ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} \
+ DISTSRC="$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")" \
+ OUTDIR="$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST" codesigned)" \
+ LOGDIR="$(LOGDIR_BASE=/logdir-base && logdir_for_host "$HOST" codesigned)" \
+ GUIX_SIGS_REPO=/guix-sigs \
+ UNSIGNED_FILE="$(OUTDIR_BASE=/outdir-base && unsigned_file_for_host "$HOST")" \
+ bash -c "cd /feather && bash contrib/guix/libexec/codesign.sh"
+ )
+
+done
--- /dev/null
+#!/usr/bin/env bash
+# Copyright (c) 2021-2022 The Bitcoin Core developers
+# Copyright (c) 2024-2024 The Monero Project
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+export LC_ALL=C
+set -e -o pipefail
+export TZ=UTC
+
+# Although Guix _does_ set umask when building its own packages (in our case,
+# this is all packages in manifest.scm), it does not set it for `guix
+# shell`. It does make sense for at least `guix shell --container`
+# to set umask, so if that change gets merged upstream and we bump the
+# time-machine to a commit which includes the aforementioned change, we can
+# remove this line.
+#
+# This line should be placed before any commands which creates files.
+umask 0022
+
+if [ -n "$V" ]; then
+ # Print both unexpanded (-v) and expanded (-x) forms of commands as they are
+ # read from this file.
+ set -vx
+ # Set VERBOSE for CMake-based builds
+ export VERBOSE="$V"
+fi
+
+# Check that required environment variables are set
+cat << EOF
+Required environment variables as seen inside the container:
+ UNSIGNED_FILE: ${UNSIGNED_FILE:?not set}
+ GUIX_SIGS_REPO: ${GUIX_SIGS_REPO:?not set}
+ DISTNAME: ${DISTNAME:?not set}
+ VERSION: ${VERSION:?not set}
+ HOST: ${HOST:?not set}
+ SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set}
+ DISTSRC: ${DISTSRC:?not set}
+ OUTDIR: ${OUTDIR:?not set}
+ LOGDIR: ${LOGDIR:?not set}
+EOF
+
+ACTUAL_OUTDIR="${OUTDIR}"
+OUTDIR="${DISTSRC}/output"
+
+git_head_version() {
+ local recent_tag
+ if recent_tag="$(git -C "$1" describe --exact-match HEAD 2> /dev/null)"; then
+ echo "${recent_tag#v}"
+ else
+ git -C "$1" rev-parse --short=12 HEAD
+ fi
+}
+
+mkdir -p "$OUTDIR"
+
+mkdir -p "$DISTSRC"
+(
+ cd "$DISTSRC"
+
+ case "$HOST" in
+ *mingw32*)
+ infile_base="$(basename "$UNSIGNED_FILE")"
+ outfile_base="${infile_base/-unsigned}"
+
+ # Codesigned *-unsigned.exe and output to OUTDIR
+ osslsigncode attach-signature \
+ -in "$UNSIGNED_FILE" \
+ -out "${OUTDIR}/$outfile_base" \
+ -CAfile "$GUIX_ENVIRONMENT/etc/ssl/certs/ca-certificates.crt" \
+ -sigin /guix-sigs/codesignatures/"${VERSION}"/"$outfile_base".pem
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+) # $DISTSRC
+
+
+(
+ cd "$OUTDIR"
+
+ case "$HOST" in
+ *mingw32.installer)
+ find . -print0 \
+ | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+ find . \
+ | sort \
+ | zip -X@ "${OUTDIR}/${DISTNAME}-win-installer.zip" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-win-installer.zip" && exit 1 )
+ ;;
+ *mingw32*)
+ find . -print0 \
+ | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+ find . \
+ | sort \
+ | zip -X@ "${OUTDIR}/${DISTNAME}-win.zip" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-win.zip" && exit 1 )
+ ;;
+ esac
+)
+
+rm -rf "$ACTUAL_OUTDIR"
+mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
+ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 )
+
+(
+ cd /outdir-base
+ mkdir -p "$LOGDIR"/codesigned
+ {
+ find "$ACTUAL_OUTDIR" -type f
+ } | xargs realpath --relative-base="$PWD" \
+ | xargs sha256sum \
+ | sort -k2 \
+ | sponge "$LOGDIR"/SHA256SUMS.part
+)