#!/usr/bin/env bash set -euo pipefail # User-only installer for Qubes AppVM persistence model. # Nothing is written to /usr, /etc, or other root-owned paths. # # Installs into $HOME: # - nsigner -> ~/.local/bin/nsigner # - startup helper -> ~/start_nsigner.sh # # Usage: # bash install_qube_fips_nsigner.sh # bash install_qube_fips_nsigner.sh --help # # Optional env vars: # NSIGNER_VERSION=v0.0.7 # NSIGNER_GITEA_TOKEN= # if n_signer release assets are private # NSIGNER_BINARY_URL= NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.7}" NSIGNER_RELEASE_PAGE="https://git.laantungir.net/laantungir/n_signer/releases/tag/${NSIGNER_VERSION}" NSIGNER_API_TAG_URL="https://git.laantungir.net/api/v1/repos/laantungir/n_signer/releases/tags/${NSIGNER_VERSION}" PREFIX_BIN="${HOME}/.local/bin" log() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; } warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } show_help() { cat < # required if n_signer release assets are private NSIGNER_BINARY_URL= Install paths: ~/.local/bin/nsigner ~/start_nsigner.sh EOF } require_cmd() { command -v "$1" >/dev/null 2>&1 || { err "Missing command: $1" exit 1 } } install_runtime_deps() { if command -v apt-get >/dev/null 2>&1; then log "Installing runtime dependencies via apt" sudo apt-get update sudo apt-get install -y ca-certificates curl jq elif command -v dnf >/dev/null 2>&1; then log "Installing runtime dependencies via dnf" sudo dnf install -y ca-certificates curl jq else err "Unsupported distro: need apt-get or dnf to install runtime dependencies" exit 1 fi } prepare_dirs() { mkdir -p "${PREFIX_BIN}" } download_nsigner_asset_url() { local headers=() if [[ -n "${NSIGNER_GITEA_TOKEN:-}" ]]; then headers=(-H "Authorization: token ${NSIGNER_GITEA_TOKEN}") fi curl -fsSL "${headers[@]}" "${NSIGNER_API_TAG_URL}" \ | jq -r '.assets[]?.browser_download_url // empty' \ | grep -E 'nsigner_static_x86_64$' \ | head -n1 || true } install_nsigner() { log "Installing n_signer ${NSIGNER_VERSION}" log "Release page: ${NSIGNER_RELEASE_PAGE}" local asset_url="${NSIGNER_BINARY_URL:-}" if [[ -z "${asset_url}" ]]; then asset_url="$(download_nsigner_asset_url)" fi if [[ -z "${asset_url}" ]]; then err "Could not find downloadable n_signer x86_64 release binary for ${NSIGNER_VERSION}." err "Provide NSIGNER_BINARY_URL or NSIGNER_GITEA_TOKEN so the release asset can be resolved." exit 1 fi log "Using n_signer binary URL: ${asset_url}" if [[ -n "${NSIGNER_GITEA_TOKEN:-}" ]]; then curl -fL -H "Authorization: token ${NSIGNER_GITEA_TOKEN}" -o "${PREFIX_BIN}/nsigner" "${asset_url}" else curl -fL -o "${PREFIX_BIN}/nsigner" "${asset_url}" fi chmod 0755 "${PREFIX_BIN}/nsigner" log "Installed ${PREFIX_BIN}/nsigner from release binary" } write_signer_start_script() { local script_path="${HOME}/start_nsigner.sh" cat >"${script_path}" <<'EOF' #!/usr/bin/env bash set -euo pipefail export PATH="$HOME/.local/bin:$PATH" LISTEN_TARGET="${NSIGNER_LISTEN_TARGET:-tcp:[::]:8080}" echo "=== n_signer startup ===" echo "listen target: ${LISTEN_TARGET}" # Optional: print current FIPS identity info if fipsctl is available. if command -v fipsctl >/dev/null 2>&1; then if fipsctl show status >/dev/null 2>&1; then STATUS_JSON="$(fipsctl show status)" elif sudo -n fipsctl show status >/dev/null 2>&1; then STATUS_JSON="$(sudo -n fipsctl show status)" else STATUS_JSON="" fi if [[ -n "${STATUS_JSON}" ]]; then FIPS_IPV6="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"ipv6_addr": "\([^"]*\)".*/\1/p')" FIPS_NPUB="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"npub": "\([^"]*\)".*/\1/p')" [[ -n "${FIPS_IPV6}" ]] && echo "fips ipv6: ${FIPS_IPV6}" [[ -n "${FIPS_NPUB}" ]] && echo "fips npub: ${FIPS_NPUB}" else echo "fips status: unavailable (run as user in fips group or with sudo)" fi fi echo echo "Starting signer..." echo "On first remote request, approve in prompt with [y] or [a]." exec "$HOME/.local/bin/nsigner" --listen "${LISTEN_TARGET}" EOF chmod 0755 "${script_path}" log "Wrote ${script_path}" } post_checks() { export PATH="${PREFIX_BIN}:${PATH}" log "Running post-install checks" require_cmd nsigner nsigner --version || true log "User binaries installed in: ${PREFIX_BIN}" log "If needed, add to shell PATH: export PATH=\"${PREFIX_BIN}:\$PATH\"" } main() { if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then show_help exit 0 fi if [[ $# -gt 0 ]]; then err "Unknown option: $1" show_help exit 1 fi install_runtime_deps prepare_dirs install_nsigner write_signer_start_script post_checks log "Completed user-only install of n_signer" log "Start signer with: ~/start_nsigner.sh" } main "$@"