784 lines
24 KiB
Bash
Executable File
784 lines
24 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Updates local fips/ clone (if upstream has new commits), builds release binaries,
|
|
# and installs or updates the local systemd deployment on this x86 host.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
FIPS_DIR="${SCRIPT_DIR}/fips"
|
|
PREFERRED_REMOTE_URL="${PREFERRED_REMOTE_URL:-https://git.laantungir.net/laantungir/fips.git}"
|
|
DEFAULT_DEPLOY_PEERS=$(cat <<'YAML'
|
|
peers:
|
|
- npub: "npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud"
|
|
alias: "laantungir"
|
|
addresses:
|
|
- transport: udp
|
|
addr: "15.235.3.231:2121"
|
|
connect_policy: auto_connect
|
|
- npub: "npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98"
|
|
alias: "fips-test-node"
|
|
addresses:
|
|
- transport: udp
|
|
addr: "217.77.8.91:2121"
|
|
connect_policy: auto_connect
|
|
- npub: "npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n"
|
|
alias: "fips.v0l.io"
|
|
addresses:
|
|
- transport: udp
|
|
addr: "185.18.221.160:2121"
|
|
connect_policy: auto_connect
|
|
YAML
|
|
)
|
|
|
|
if [[ ! -d "${FIPS_DIR}" ]]; then
|
|
echo "Error: Missing ${FIPS_DIR}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "${FIPS_DIR}/.git" ]]; then
|
|
echo "Error: ${FIPS_DIR} is not a git repository." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
echo "Error: git is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
require_sudo() {
|
|
if ! command -v sudo >/dev/null 2>&1; then
|
|
echo "Error: sudo is required to install missing dependencies." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
install_system_packages() {
|
|
local packages=("$@")
|
|
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
require_sudo
|
|
sudo apt-get update
|
|
sudo apt-get install -y "${packages[@]}"
|
|
elif command -v dnf >/dev/null 2>&1; then
|
|
require_sudo
|
|
sudo dnf install -y "${packages[@]}"
|
|
elif command -v yum >/dev/null 2>&1; then
|
|
require_sudo
|
|
sudo yum install -y "${packages[@]}"
|
|
elif command -v pacman >/dev/null 2>&1; then
|
|
require_sudo
|
|
sudo pacman -Sy --needed --noconfirm "${packages[@]}"
|
|
elif command -v zypper >/dev/null 2>&1; then
|
|
require_sudo
|
|
sudo zypper --non-interactive install "${packages[@]}"
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
require_sudo
|
|
sudo apk add --no-cache "${packages[@]}"
|
|
else
|
|
echo "Error: unsupported package manager. Install dependencies manually." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_base_build_deps() {
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
install_system_packages git curl build-essential pkg-config libssl-dev libdbus-1-dev clang libclang-dev ca-certificates
|
|
elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
|
|
install_system_packages git curl gcc gcc-c++ make pkgconf-pkg-config openssl-devel dbus-devel clang clang-devel llvm-devel ca-certificates
|
|
elif command -v pacman >/dev/null 2>&1; then
|
|
install_system_packages git curl base-devel pkgconf openssl dbus clang llvm ca-certificates
|
|
elif command -v zypper >/dev/null 2>&1; then
|
|
install_system_packages git curl gcc gcc-c++ make pkg-config libopenssl-devel dbus-1-devel clang llvm-devel libclang-devel ca-certificates
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
install_system_packages git curl build-base pkgconf openssl-dev dbus-dev clang llvm-dev ca-certificates
|
|
fi
|
|
}
|
|
|
|
ensure_rust_toolchain() {
|
|
# rustup installs into ~/.cargo/bin, which may not be present in non-login shells.
|
|
# Load rustup environment first, then decide if install is actually needed.
|
|
if [[ -f "${HOME}/.cargo/env" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "${HOME}/.cargo/env"
|
|
fi
|
|
export PATH="${HOME}/.cargo/bin:${PATH}"
|
|
|
|
if command -v cargo >/dev/null 2>&1 && command -v rustc >/dev/null 2>&1; then
|
|
return
|
|
fi
|
|
|
|
echo "==> Rust toolchain not found in current PATH; installing rustup + cargo"
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
ensure_base_build_deps
|
|
fi
|
|
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
|
|
if [[ -f "${HOME}/.cargo/env" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "${HOME}/.cargo/env"
|
|
fi
|
|
export PATH="${HOME}/.cargo/bin:${PATH}"
|
|
|
|
if ! command -v cargo >/dev/null 2>&1; then
|
|
echo "Error: cargo still not available after rustup install." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_build_toolchain() {
|
|
echo "==> Verifying build dependencies"
|
|
ensure_base_build_deps
|
|
ensure_rust_toolchain
|
|
}
|
|
|
|
ensure_tun_ready() {
|
|
if [[ -e /dev/net/tun ]]; then
|
|
return
|
|
fi
|
|
|
|
echo "==> /dev/net/tun not present; attempting to load tun module"
|
|
require_sudo
|
|
if ! command -v modprobe >/dev/null 2>&1; then
|
|
echo "Error: modprobe not found; cannot load tun module." >&2
|
|
exit 1
|
|
fi
|
|
|
|
sudo modprobe tun
|
|
if [[ ! -e /dev/net/tun ]]; then
|
|
echo "Error: /dev/net/tun is still unavailable after modprobe tun." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ask_yes_no() {
|
|
local prompt="$1"
|
|
local default_value="$2"
|
|
local default_hint reply
|
|
|
|
if [[ "${default_value}" == "yes" ]]; then
|
|
default_hint="Y/n"
|
|
else
|
|
default_hint="y/N"
|
|
fi
|
|
|
|
if [[ ! -t 0 ]]; then
|
|
printf '%s\n' "${default_value}"
|
|
return
|
|
fi
|
|
|
|
while true; do
|
|
read -r -p "${prompt} [${default_hint}]: " reply || true
|
|
reply="${reply:-}"
|
|
|
|
if [[ -z "${reply}" ]]; then
|
|
printf '%s\n' "${default_value}"
|
|
return
|
|
fi
|
|
|
|
case "${reply}" in
|
|
y|Y|yes|YES)
|
|
printf '%s\n' "yes"
|
|
return
|
|
;;
|
|
n|N|no|NO)
|
|
printf '%s\n' "no"
|
|
return
|
|
;;
|
|
esac
|
|
|
|
echo "Please answer yes or no."
|
|
done
|
|
}
|
|
|
|
print_connectivity_diagnostics() {
|
|
echo "--- ip -6 route ---" >&2
|
|
ip -6 route show >&2 || true
|
|
|
|
if command -v nft >/dev/null 2>&1; then
|
|
echo "--- nft list chain ip6 qubes custom-input ---" >&2
|
|
sudo nft list chain ip6 qubes custom-input >&2 || true
|
|
echo "--- nft list chain ip qubes custom-input ---" >&2
|
|
sudo nft list chain ip qubes custom-input >&2 || true
|
|
fi
|
|
|
|
if command -v ip6tables >/dev/null 2>&1; then
|
|
echo "--- ip6tables -S ---" >&2
|
|
sudo ip6tables -S >&2 || true
|
|
fi
|
|
}
|
|
|
|
restart_unit_with_diagnostics() {
|
|
local unit="$1"
|
|
local required="${2:-yes}"
|
|
local timeout_seconds="${SERVICE_RESTART_TIMEOUT_SECONDS:-45}"
|
|
local journal_lines="${SERVICE_JOURNAL_LINES:-120}"
|
|
local output rc
|
|
|
|
if command -v timeout >/dev/null 2>&1; then
|
|
output="$(sudo timeout "${timeout_seconds}" systemctl restart "${unit}" 2>&1)" || {
|
|
rc=$?
|
|
echo "Error: failed to restart ${unit} (exit ${rc})." >&2
|
|
if [[ ${rc} -eq 124 ]]; then
|
|
echo "Hint: restart timed out after ${timeout_seconds}s." >&2
|
|
fi
|
|
[[ -n "${output}" ]] && printf '%s\n' "${output}" >&2
|
|
echo "--- systemctl status ${unit} ---" >&2
|
|
sudo systemctl status "${unit}" --no-pager >&2 || true
|
|
echo "--- journalctl -u ${unit} (last ${journal_lines} lines) ---" >&2
|
|
sudo journalctl -u "${unit}" -n "${journal_lines}" --no-pager >&2 || true
|
|
print_connectivity_diagnostics
|
|
[[ "${required}" == "yes" ]] && return 1
|
|
return 0
|
|
}
|
|
else
|
|
output="$(sudo systemctl restart "${unit}" 2>&1)" || {
|
|
rc=$?
|
|
echo "Error: failed to restart ${unit} (exit ${rc})." >&2
|
|
[[ -n "${output}" ]] && printf '%s\n' "${output}" >&2
|
|
echo "--- systemctl status ${unit} ---" >&2
|
|
sudo systemctl status "${unit}" --no-pager >&2 || true
|
|
echo "--- journalctl -u ${unit} (last ${journal_lines} lines) ---" >&2
|
|
sudo journalctl -u "${unit}" -n "${journal_lines}" --no-pager >&2 || true
|
|
print_connectivity_diagnostics
|
|
[[ "${required}" == "yes" ]] && return 1
|
|
return 0
|
|
}
|
|
fi
|
|
|
|
if ! sudo systemctl is-active --quiet "${unit}"; then
|
|
echo "Error: ${unit} is not active after restart." >&2
|
|
echo "--- systemctl status ${unit} ---" >&2
|
|
sudo systemctl status "${unit}" --no-pager >&2 || true
|
|
echo "--- journalctl -u ${unit} (last ${journal_lines} lines) ---" >&2
|
|
sudo journalctl -u "${unit}" -n "${journal_lines}" --no-pager >&2 || true
|
|
print_connectivity_diagnostics
|
|
[[ "${required}" == "yes" ]] && return 1
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
ensure_dnsmasq_package() {
|
|
local has_dnsmasq_cmd="no"
|
|
local has_dnsmasq_unit="no"
|
|
|
|
if command -v dnsmasq >/dev/null 2>&1; then
|
|
has_dnsmasq_cmd="yes"
|
|
fi
|
|
if sudo systemctl list-unit-files dnsmasq.service --no-legend 2>/dev/null | grep -q '^dnsmasq\.service'; then
|
|
has_dnsmasq_unit="yes"
|
|
fi
|
|
|
|
# Some systems ship dnsmasq binary without the dnsmasq.service unit.
|
|
# In that case we still need to install the full service package.
|
|
if [[ "${has_dnsmasq_cmd}" == "yes" && "${has_dnsmasq_unit}" == "yes" ]]; then
|
|
return
|
|
fi
|
|
|
|
echo "==> Installing/repairing dnsmasq package for service support"
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
install_system_packages dnsmasq
|
|
elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
|
|
install_system_packages dnsmasq
|
|
elif command -v pacman >/dev/null 2>&1; then
|
|
install_system_packages dnsmasq
|
|
elif command -v zypper >/dev/null 2>&1; then
|
|
install_system_packages dnsmasq
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
install_system_packages dnsmasq
|
|
else
|
|
echo "Warning: unable to auto-install dnsmasq on this distro." >&2
|
|
return 1
|
|
fi
|
|
|
|
if ! command -v dnsmasq >/dev/null 2>&1; then
|
|
echo "Warning: dnsmasq command is still unavailable after package install." >&2
|
|
return 1
|
|
fi
|
|
|
|
if ! sudo systemctl list-unit-files dnsmasq.service --no-legend 2>/dev/null | grep -q '^dnsmasq\.service'; then
|
|
echo "Warning: dnsmasq.service unit still missing after package install." >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
configure_dnsmasq_fips_dns() {
|
|
local conf_file="/etc/dnsmasq.d/fips.conf"
|
|
local resolv_file="/etc/resolv.conf"
|
|
local backup_file
|
|
local tmp_file
|
|
local upstream_dns
|
|
|
|
require_sudo
|
|
ensure_dnsmasq_package || return 1
|
|
|
|
upstream_dns="$(awk '/^nameserver[[:space:]]+/ { print $2 }' "${resolv_file}" 2>/dev/null | grep -Ev '^(127\.0\.0\.1|::1)$' | head -n1 || true)"
|
|
if [[ -z "${upstream_dns}" ]]; then
|
|
upstream_dns="1.1.1.1"
|
|
fi
|
|
|
|
echo "==> Configuring dnsmasq for .fips forwarding"
|
|
sudo mkdir -p "$(dirname "${conf_file}")"
|
|
tmp_file="$(mktemp)"
|
|
cat > "${tmp_file}" <<EOF
|
|
# Managed by update_and_deploy_fips.sh
|
|
port=53
|
|
listen-address=0.0.0.0
|
|
bind-interfaces
|
|
|
|
# .fips domains -> FIPS resolver
|
|
server=/fips/::1#5354
|
|
|
|
# everything else -> upstream
|
|
server=${upstream_dns}
|
|
|
|
no-resolv
|
|
no-hosts
|
|
EOF
|
|
sudo install -m 0644 "${tmp_file}" "${conf_file}"
|
|
rm -f "${tmp_file}"
|
|
|
|
if [[ -f "${resolv_file}" ]]; then
|
|
backup_file="${resolv_file}.bak.$(date +%Y%m%d%H%M%S)"
|
|
sudo cp "${resolv_file}" "${backup_file}" || true
|
|
|
|
if ! grep -Eq '^[[:space:]]*nameserver[[:space:]]+127\.0\.0\.1([[:space:]]|$)' "${resolv_file}"; then
|
|
tmp_file="$(mktemp)"
|
|
{
|
|
echo "nameserver 127.0.0.1"
|
|
awk '!/^[[:space:]]*nameserver[[:space:]]+127\.0\.0\.1([[:space:]]|$)/ { print }' "${resolv_file}" 2>/dev/null || true
|
|
} > "${tmp_file}"
|
|
if ! sudo install -m 0644 "${tmp_file}" "${resolv_file}"; then
|
|
echo "Warning: unable to update ${resolv_file}; configure local nameserver manually." >&2
|
|
fi
|
|
rm -f "${tmp_file}"
|
|
fi
|
|
fi
|
|
|
|
if sudo systemctl list-unit-files dnsmasq.service --no-legend 2>/dev/null | grep -q '^dnsmasq\.service'; then
|
|
sudo systemctl enable dnsmasq.service >/dev/null 2>&1 || true
|
|
restart_unit_with_diagnostics "dnsmasq.service" "no"
|
|
else
|
|
echo "Warning: dnsmasq.service unit not found; install/enable dnsmasq manually." >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
persist_qubes_rc_local_network_overrides() {
|
|
local persist_dns="${1:-no}"
|
|
local rc_file="/rw/config/rc.local"
|
|
local tmp_file
|
|
|
|
[[ -d /rw/config ]] || return 0
|
|
|
|
require_sudo
|
|
sudo mkdir -p /rw/config
|
|
|
|
tmp_file="$(mktemp)"
|
|
if [[ -f "${rc_file}" ]]; then
|
|
sudo cat "${rc_file}" > "${tmp_file}"
|
|
else
|
|
printf '%s\n' '#!/bin/sh' > "${tmp_file}"
|
|
fi
|
|
|
|
awk '
|
|
BEGIN { skip_fips = 0; skip_legacy_ai_route = 0 }
|
|
/^# BEGIN FIPS_DEPLOY_NETWORK_OVERRIDES$/ { skip_fips = 1; next }
|
|
/^# END FIPS_DEPLOY_NETWORK_OVERRIDES$/ { skip_fips = 0; next }
|
|
/^# === AI FIPS ROUTE START ===$/ { skip_legacy_ai_route = 1; next }
|
|
/^# === AI FIPS ROUTE END ===$/ { skip_legacy_ai_route = 0; next }
|
|
skip_fips == 0 && skip_legacy_ai_route == 0 { print }
|
|
' "${tmp_file}" > "${tmp_file}.clean"
|
|
mv "${tmp_file}.clean" "${tmp_file}"
|
|
|
|
{
|
|
echo ""
|
|
echo "# BEGIN FIPS_DEPLOY_NETWORK_OVERRIDES"
|
|
echo "if command -v nft >/dev/null 2>&1; then"
|
|
echo " nft add rule ip6 qubes custom-input iifname \"fips0\" accept 2>/dev/null || true"
|
|
echo " nft add rule ip qubes custom-input iifname \"fips0\" accept 2>/dev/null || true"
|
|
echo "fi"
|
|
if [[ "${persist_dns}" == "yes" ]]; then
|
|
cat <<'SH'
|
|
if ! grep -Eq '^[[:space:]]*nameserver[[:space:]]+127\.0\.0\.1([[:space:]]|$)' /etc/resolv.conf 2>/dev/null; then
|
|
tmp_resolv="$(mktemp)"
|
|
{
|
|
echo "nameserver 127.0.0.1"
|
|
cat /etc/resolv.conf 2>/dev/null || true
|
|
} > "${tmp_resolv}"
|
|
install -m 0644 "${tmp_resolv}" /etc/resolv.conf || true
|
|
rm -f "${tmp_resolv}"
|
|
fi
|
|
SH
|
|
fi
|
|
echo "# END FIPS_DEPLOY_NETWORK_OVERRIDES"
|
|
} >> "${tmp_file}"
|
|
|
|
sudo install -m 0755 "${tmp_file}" "${rc_file}"
|
|
rm -f "${tmp_file}"
|
|
|
|
echo "==> Persisted FIPS network overrides in ${rc_file}"
|
|
}
|
|
|
|
ensure_fips_ingress_allowed() {
|
|
local persist_dns="${1:-no}"
|
|
|
|
require_sudo
|
|
|
|
echo "==> Ensuring inbound traffic on fips0 is allowed"
|
|
|
|
if command -v nft >/dev/null 2>&1; then
|
|
if sudo nft list chain ip6 qubes custom-input >/dev/null 2>&1; then
|
|
if ! sudo nft list chain ip6 qubes custom-input 2>/dev/null | grep -q 'iifname "fips0" accept'; then
|
|
sudo nft add rule ip6 qubes custom-input iifname "fips0" accept
|
|
fi
|
|
if sudo nft list chain ip qubes custom-input >/dev/null 2>&1; then
|
|
if ! sudo nft list chain ip qubes custom-input 2>/dev/null | grep -q 'iifname "fips0" accept'; then
|
|
sudo nft add rule ip qubes custom-input iifname "fips0" accept
|
|
fi
|
|
fi
|
|
persist_qubes_rc_local_network_overrides "${persist_dns}"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if command -v ip6tables >/dev/null 2>&1; then
|
|
if ! sudo ip6tables -C INPUT -i fips0 -j ACCEPT >/dev/null 2>&1; then
|
|
sudo ip6tables -I INPUT 1 -i fips0 -j ACCEPT
|
|
fi
|
|
fi
|
|
|
|
if command -v iptables >/dev/null 2>&1; then
|
|
if ! sudo iptables -C INPUT -i fips0 -j ACCEPT >/dev/null 2>&1; then
|
|
sudo iptables -I INPUT 1 -i fips0 -j ACCEPT
|
|
fi
|
|
fi
|
|
}
|
|
|
|
install_fips_route_cleanup_hook() {
|
|
local helper_path="/usr/local/libexec/fips-route-sanitize.sh"
|
|
local dropin_dir="/etc/systemd/system/fips.service.d"
|
|
local dropin_file="${dropin_dir}/10-route-sanitize.conf"
|
|
local tmp_file
|
|
|
|
require_sudo
|
|
|
|
echo "==> Installing boot-time route cleanup hook for fips.service"
|
|
sudo mkdir -p "$(dirname "${helper_path}")" "${dropin_dir}"
|
|
|
|
tmp_file="$(mktemp)"
|
|
cat > "${tmp_file}" <<'SH'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Remove stale fd00::/8 routes that are not owned by fips0.
|
|
routes="$(ip -6 route show fd00::/8 2>/dev/null || true)"
|
|
[[ -n "${routes}" ]] || exit 0
|
|
|
|
while IFS= read -r line; do
|
|
[[ -n "${line}" ]] || continue
|
|
if [[ "${line}" == *" dev fips0"* ]]; then
|
|
continue
|
|
fi
|
|
|
|
if [[ "${line}" =~ ^fd00::/8[[:space:]]+via[[:space:]]+([^[:space:]]+)[[:space:]]+dev[[:space:]]+([^[:space:]]+) ]]; then
|
|
gw="${BASH_REMATCH[1]}"
|
|
dev="${BASH_REMATCH[2]}"
|
|
ip -6 route del fd00::/8 via "${gw}" dev "${dev}" >/dev/null 2>&1 || true
|
|
elif [[ "${line}" =~ ^fd00::/8[[:space:]]+dev[[:space:]]+([^[:space:]]+) ]]; then
|
|
dev="${BASH_REMATCH[1]}"
|
|
if [[ "${dev}" != "fips0" ]]; then
|
|
ip -6 route del fd00::/8 dev "${dev}" >/dev/null 2>&1 || true
|
|
fi
|
|
else
|
|
ip -6 route del fd00::/8 >/dev/null 2>&1 || true
|
|
fi
|
|
done <<< "${routes}"
|
|
SH
|
|
sudo install -m 0755 "${tmp_file}" "${helper_path}"
|
|
rm -f "${tmp_file}"
|
|
|
|
tmp_file="$(mktemp)"
|
|
cat > "${tmp_file}" <<EOF
|
|
[Service]
|
|
ExecStartPre=${helper_path}
|
|
EOF
|
|
sudo install -m 0644 "${tmp_file}" "${dropin_file}"
|
|
rm -f "${tmp_file}"
|
|
|
|
sudo systemctl daemon-reload
|
|
}
|
|
|
|
ensure_build_toolchain
|
|
ensure_tun_ready
|
|
|
|
cd "${FIPS_DIR}"
|
|
|
|
ensure_preferred_remote() {
|
|
local current_url
|
|
current_url="$(git remote get-url origin 2>/dev/null || true)"
|
|
|
|
if [[ -z "${current_url}" ]]; then
|
|
echo "==> No origin remote found; adding origin ${PREFERRED_REMOTE_URL}"
|
|
git remote add origin "${PREFERRED_REMOTE_URL}"
|
|
return
|
|
fi
|
|
|
|
if [[ "${current_url}" == "${PREFERRED_REMOTE_URL}" ]]; then
|
|
return
|
|
fi
|
|
|
|
if [[ "${current_url}" == git@* || "${current_url}" == ssh://* ]]; then
|
|
echo "==> Switching origin remote from SSH to HTTPS to avoid SSH key failures"
|
|
echo " old: ${current_url}"
|
|
echo " new: ${PREFERRED_REMOTE_URL}"
|
|
git remote set-url origin "${PREFERRED_REMOTE_URL}"
|
|
fi
|
|
}
|
|
|
|
ensure_preferred_remote
|
|
|
|
echo "==> Checking fips repository for updates"
|
|
|
|
UPSTREAM_REF=""
|
|
if UPSTREAM_REF="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)"; then
|
|
git fetch --prune
|
|
|
|
LOCAL_SHA="$(git rev-parse HEAD)"
|
|
REMOTE_SHA="$(git rev-parse "${UPSTREAM_REF}")"
|
|
BASE_SHA="$(git merge-base HEAD "${UPSTREAM_REF}")"
|
|
|
|
if [[ "${LOCAL_SHA}" == "${REMOTE_SHA}" ]]; then
|
|
echo "Repository already up to date (${LOCAL_SHA:0:12})."
|
|
elif [[ "${LOCAL_SHA}" == "${BASE_SHA}" ]]; then
|
|
echo "New commits detected on ${UPSTREAM_REF}; pulling fast-forward updates..."
|
|
git pull --ff-only
|
|
elif [[ "${REMOTE_SHA}" == "${BASE_SHA}" ]]; then
|
|
echo "Local repository is ahead of ${UPSTREAM_REF}; not pulling."
|
|
else
|
|
echo "Error: local and upstream histories have diverged. Resolve manually, then re-run." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "No upstream tracking branch configured; skipping update check/pull."
|
|
fi
|
|
|
|
echo "==> Building release binaries"
|
|
cargo build --release
|
|
|
|
for bin in fips fipsctl fipstop; do
|
|
if [[ ! -x "target/release/${bin}" ]]; then
|
|
echo "Error: missing built binary target/release/${bin}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
INSTALL_MODE="fresh install"
|
|
if [[ -x /usr/local/bin/fips || -f /etc/systemd/system/fips.service ]]; then
|
|
INSTALL_MODE="update"
|
|
fi
|
|
|
|
echo "==> Deployment options (press Enter for defaults)"
|
|
ENABLE_PERSISTENT_IDENTITY="$(ask_yes_no "Enable persistent identity?" "yes")"
|
|
ADD_DEFAULT_PEERS="$(ask_yes_no "Add default static peers (laantungir/fips-test-node/fips.v0l.io)?" "yes")"
|
|
ADD_CURRENT_USER_TO_FIPS_GROUP="$(ask_yes_no "Add current user to fips group?" "yes")"
|
|
START_SERVICE_NOW="$(ask_yes_no "Start/restart fips service now?" "yes")"
|
|
CONFIGURE_DNSMASQ_FOR_FIPS="$(ask_yes_no "Configure dnsmasq for .fips DNS forwarding in this qube?" "yes")"
|
|
ALLOW_FIPS_INGRESS="$(ask_yes_no "Allow inbound traffic on fips0 (needed for inter-qube TCP over FIPS on Qubes)?" "yes")"
|
|
|
|
echo "==> Preparing ${INSTALL_MODE} payload"
|
|
|
|
STAGING_ROOT="$(mktemp -d)"
|
|
trap 'rm -rf "${STAGING_ROOT}"' EXIT
|
|
|
|
mkdir -p "${STAGING_ROOT}/systemd" "${STAGING_ROOT}/common"
|
|
|
|
cp target/release/fips "${STAGING_ROOT}/systemd/"
|
|
cp target/release/fipsctl "${STAGING_ROOT}/systemd/"
|
|
cp target/release/fipstop "${STAGING_ROOT}/systemd/"
|
|
|
|
cp packaging/systemd/install.sh "${STAGING_ROOT}/systemd/"
|
|
cp packaging/systemd/fips.service "${STAGING_ROOT}/systemd/"
|
|
cp packaging/systemd/fips-dns.service "${STAGING_ROOT}/systemd/"
|
|
cp packaging/common/fips.yaml "${STAGING_ROOT}/systemd/"
|
|
cp packaging/common/hosts "${STAGING_ROOT}/systemd/"
|
|
|
|
cp packaging/common/fips-dns-setup "${STAGING_ROOT}/common/"
|
|
cp packaging/common/fips-dns-teardown "${STAGING_ROOT}/common/"
|
|
|
|
chmod +x "${STAGING_ROOT}/systemd/install.sh"
|
|
|
|
echo "==> Installing/updating locally (sudo required)"
|
|
INSTALL_OUTPUT=""
|
|
if ! INSTALL_OUTPUT="$(sudo "${STAGING_ROOT}/systemd/install.sh" 2>&1)"; then
|
|
printf '%s\n' "${INSTALL_OUTPUT}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Upstream installer prints a long manual checklist that this wrapper now handles.
|
|
printf '%s\n' "${INSTALL_OUTPUT}" | awk '
|
|
BEGIN { skip = 0 }
|
|
/^=== Installation complete ===/ { skip = 1 }
|
|
{ if (!skip) print }
|
|
'
|
|
|
|
CONFIG_FILE="/etc/fips/fips.yaml"
|
|
if [[ -f "${CONFIG_FILE}" ]]; then
|
|
echo "==> Applying fips.yaml settings"
|
|
require_sudo
|
|
sudo cp "${CONFIG_FILE}" "${CONFIG_FILE}.bak.$(date +%Y%m%d%H%M%S)"
|
|
|
|
echo " - Enforcing transports.udp.mtu: 1280"
|
|
TMP_CONFIG="$(mktemp)"
|
|
awk '
|
|
function indent_of(s, i,c) {
|
|
for (i = 1; i <= length(s); i++) {
|
|
c = substr(s, i, 1)
|
|
if (c != " ") return i - 1
|
|
}
|
|
return length(s)
|
|
}
|
|
{
|
|
line = $0
|
|
indent = indent_of(line)
|
|
|
|
if (in_udp && indent <= udp_indent && line !~ /^[[:space:]]*$/ && line !~ /^[[:space:]]*#/) {
|
|
if (!udp_has_mtu) {
|
|
print sprintf("%*smtu: 1280", udp_indent + 2, "")
|
|
udp_has_mtu = 1
|
|
}
|
|
in_udp = 0
|
|
}
|
|
|
|
if (in_transports && indent <= transports_indent && line !~ /^[[:space:]]*$/ && line !~ /^[[:space:]]*#/) {
|
|
in_transports = 0
|
|
}
|
|
|
|
if (line ~ /^[[:space:]]*transports:[[:space:]]*$/) {
|
|
in_transports = 1
|
|
transports_indent = indent
|
|
}
|
|
|
|
if (in_transports && line ~ /^[[:space:]]*udp:[[:space:]]*$/) {
|
|
in_udp = 1
|
|
udp_indent = indent
|
|
udp_has_mtu = 0
|
|
}
|
|
|
|
if (in_udp && line ~ /^[[:space:]]*mtu:[[:space:]]*[0-9]+([[:space:]]*#.*)?$/) {
|
|
line = sprintf("%*smtu: 1280", udp_indent + 2, "")
|
|
udp_has_mtu = 1
|
|
}
|
|
|
|
print line
|
|
}
|
|
END {
|
|
if (in_udp && !udp_has_mtu) {
|
|
print sprintf("%*smtu: 1280", udp_indent + 2, "")
|
|
}
|
|
}
|
|
' "${CONFIG_FILE}" > "${TMP_CONFIG}"
|
|
|
|
if ! grep -Eq '^[[:space:]]*transports:[[:space:]]*$' "${TMP_CONFIG}" || ! grep -Eq '^[[:space:]]*udp:[[:space:]]*$' "${TMP_CONFIG}"; then
|
|
cat >> "${TMP_CONFIG}" <<'YAML'
|
|
|
|
transports:
|
|
udp:
|
|
mtu: 1280
|
|
YAML
|
|
fi
|
|
|
|
sudo install -m 0600 "${TMP_CONFIG}" "${CONFIG_FILE}"
|
|
rm -f "${TMP_CONFIG}"
|
|
|
|
if [[ "${ENABLE_PERSISTENT_IDENTITY}" == "yes" ]]; then
|
|
echo " - Enabling node.identity.persistent"
|
|
if grep -Eq '^[[:space:]]*#?[[:space:]]*persistent:[[:space:]]*(true|false)?[[:space:]]*$' "${CONFIG_FILE}"; then
|
|
sudo sed -Ei '0,/^[[:space:]]*#?[[:space:]]*persistent:[[:space:]]*(true|false)?[[:space:]]*$/s// persistent: true/' "${CONFIG_FILE}"
|
|
else
|
|
echo "Warning: could not find persistent setting to update; leaving as-is." >&2
|
|
fi
|
|
fi
|
|
|
|
if [[ "${ADD_DEFAULT_PEERS}" == "yes" ]]; then
|
|
echo " - Installing default static peers"
|
|
TMP_CONFIG="$(mktemp)"
|
|
awk '/^[[:space:]]*peers:[[:space:]]*/{exit} {print}' "${CONFIG_FILE}" > "${TMP_CONFIG}"
|
|
printf '\n%s\n' "${DEFAULT_DEPLOY_PEERS}" >> "${TMP_CONFIG}"
|
|
sudo install -m 0600 "${TMP_CONFIG}" "${CONFIG_FILE}"
|
|
rm -f "${TMP_CONFIG}"
|
|
fi
|
|
fi
|
|
|
|
if [[ "${ADD_CURRENT_USER_TO_FIPS_GROUP}" == "yes" ]]; then
|
|
TARGET_USER="${SUDO_USER:-${USER:-}}"
|
|
if [[ -n "${TARGET_USER}" && "${TARGET_USER}" != "root" ]] && id -u "${TARGET_USER}" >/dev/null 2>&1; then
|
|
echo "==> Adding ${TARGET_USER} to fips group"
|
|
require_sudo
|
|
sudo usermod -aG fips "${TARGET_USER}"
|
|
echo " (log out and back in for group membership to take effect)"
|
|
else
|
|
echo "==> Skipping group update (no non-root target user detected)"
|
|
fi
|
|
fi
|
|
|
|
install_fips_route_cleanup_hook
|
|
|
|
echo "==> Cleaning potentially stale fd00::/8 route state"
|
|
require_sudo
|
|
sudo ip -6 route del fd00::/8 >/dev/null 2>&1 || true
|
|
|
|
DNS_FORWARDING_ACTIVE="no"
|
|
if [[ "${START_SERVICE_NOW}" == "yes" ]]; then
|
|
echo "==> Starting/restarting FIPS services"
|
|
sudo systemctl daemon-reload
|
|
restart_unit_with_diagnostics "fips.service" "yes"
|
|
restart_unit_with_diagnostics "fips-dns.service" "no"
|
|
|
|
if [[ "${CONFIGURE_DNSMASQ_FOR_FIPS}" == "yes" ]]; then
|
|
if configure_dnsmasq_fips_dns; then
|
|
DNS_FORWARDING_ACTIVE="yes"
|
|
else
|
|
echo "Warning: dnsmasq configuration failed; continuing." >&2
|
|
fi
|
|
fi
|
|
|
|
if [[ "${ALLOW_FIPS_INGRESS}" == "yes" ]]; then
|
|
ensure_fips_ingress_allowed "${DNS_FORWARDING_ACTIVE}"
|
|
fi
|
|
|
|
echo "==> Running post-deploy health check"
|
|
if [[ -x "${SCRIPT_DIR}/test_fips.sh" ]]; then
|
|
"${SCRIPT_DIR}/test_fips.sh"
|
|
else
|
|
echo "Warning: ${SCRIPT_DIR}/test_fips.sh not found or not executable; skipping health check." >&2
|
|
fi
|
|
else
|
|
echo "==> Service start skipped by user choice"
|
|
fi
|
|
|
|
echo
|
|
if command -v fipsctl >/dev/null 2>&1; then
|
|
STATUS_JSON="$(sudo fipsctl show status 2>/dev/null || true)"
|
|
if [[ -n "${STATUS_JSON}" ]]; then
|
|
echo "==> Local node summary"
|
|
python3 - "${STATUS_JSON}" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
s = json.loads(sys.argv[1])
|
|
print(f"npub: {s.get('npub', 'n/a')}")
|
|
print(f"ipv6: {s.get('ipv6_addr', 'n/a')}")
|
|
PY
|
|
ETH0_IPV4="$(ip -4 -o addr show dev eth0 2>/dev/null | awk '{print $4}' | head -n1 | cut -d/ -f1)"
|
|
if [[ -n "${ETH0_IPV4}" ]]; then
|
|
echo "connect hint: sudo fipsctl connect <THIS_NODE_NPUB> ${ETH0_IPV4}:2121 udp"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo "Done. fips has been built and ${INSTALL_MODE} has completed."
|
|
echo "Check service status with: sudo systemctl status fips --no-pager"
|
|
echo "Follow logs with: sudo journalctl -u fips -f"
|