Fix: Adopt a Terra the machine already trusts
The repository audit made any Terra that is not Panama's own pinned form a trust-root failure, and status 78 then stopped every stage before it ran. A machine that installed Terra the way Terra documents it -- terra-release's own repo file, a metalink, the key at its stock path -- was classified hostile and had no way back, because install_terra_repository refused to touch a machine terra-release had already reached. A gate with no door. The trust root is the signing key, and that key is byte-for-byte the fingerprint this repository reviewed and pinned, with every signature check already on. So verify the fingerprint and adopt the configuration into the pinned form instead of refusing it. Adoption needs no network and no DNF, it runs before any other transaction in the stage, and it is repeatable, which it has to be: terra-release owns that file and restores it on update. Adoption stays narrow. The pinned fingerprint must match both the reviewed key and the key the machine actually verifies against, the gpgkey must be a local file under the system trust directory, and the endpoint must be one Terra itself serves -- so the reviewed baseurl or the reviewed metalink host, now pinned as TERRA_METALINK_BASEURL. An unknown key, a redirected baseurl, a second enabled Terra, or a disabled signature check is still a hard refusal. A refusal also stops less than it did. It suppresses the stages that open DNF and the migrations, which may run a transaction of their own, and the run still exits 78. It no longer stops link-dotfiles, link-skills or link-user, which read no repository and install no package. Exiting before them is what left this laptop with a stale ~/.claude/skills and no shipped skill reachable. Also stub ensure_flathub_remote in the extras contract, which has been failing since that call was added to install_extra_category without one. Claude-Session: https://claude.ai/code/session_01PeTrG9dGY89UWuhGm4Pr1s
This commit is contained in:
@@ -149,20 +149,37 @@ record_packages_hash() {
|
||||
}
|
||||
|
||||
# Repository trust is checked before the installer can reach its bootstrap DNF.
|
||||
# Status 78 is reserved for a trust-root failure and is propagated unchanged so
|
||||
# no later stage, especially install-hardware, can invoke DNF with that repo.
|
||||
# Status 78 is reserved for a trust-root failure. It suppresses every stage that
|
||||
# opens DNF -- install-hardware included, which would otherwise pull drivers
|
||||
# through the very repository in doubt -- and the run still exits 78 at the end.
|
||||
#
|
||||
# It suppresses nothing else. Linking dotfiles, skills and user content reads no
|
||||
# repository and installs no package, and a machine whose Terra is in question
|
||||
# still wants its configuration. Refusing the safe work because the unsafe work
|
||||
# is unavailable does not make the machine safer, it just leaves the machine
|
||||
# unconfigured with no way to fix itself. Exiting here instead meant link-skills
|
||||
# never ran on a machine whose Terra was merely unadopted, so ~/.claude/skills
|
||||
# stayed the whole-directory symlink it had been before skills were linked one
|
||||
# by one, and not one shipped skill was reachable.
|
||||
TERRA_TRUST_FAILURE_STATUS=78
|
||||
DNF_STAGES=(install-packages change-settings install-hardware)
|
||||
package_trust_refused=0
|
||||
|
||||
stage_opens_dnf() {
|
||||
local candidate="$1" dnf_stage
|
||||
for dnf_stage in "${DNF_STAGES[@]}"; do
|
||||
[[ "$candidate" == "$dnf_stage" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
trust_preflight="$PANAMA_PATH/setup/scripts/install-packages"
|
||||
if [[ ! -x "$trust_preflight" ]]; then
|
||||
printf 'install: package repository trust preflight is unavailable\n' >&2
|
||||
exit "$TERRA_TRUST_FAILURE_STATUS"
|
||||
fi
|
||||
if "$trust_preflight" --trust-preflight; then
|
||||
:
|
||||
else
|
||||
trust_status=$?
|
||||
package_trust_refused=1
|
||||
elif ! "$trust_preflight" --trust-preflight; then
|
||||
printf 'install: package repository trust preflight failed\n' >&2
|
||||
exit "$trust_status"
|
||||
package_trust_refused=1
|
||||
fi
|
||||
|
||||
# ── The interview ────────────────────────────────────────────────────────────
|
||||
@@ -182,7 +199,7 @@ fi
|
||||
# Gated exactly like the interview itself: under --upgrade no questions are
|
||||
# asked, so nothing here is used, and a machine that cannot install gum must
|
||||
# not have that stop an upgrade that never needed it.
|
||||
if (( ! UPGRADE )); then
|
||||
if (( ! UPGRADE && ! package_trust_refused )); then
|
||||
bootstrap=()
|
||||
command -v gum >/dev/null 2>&1 || bootstrap+=(gum)
|
||||
# The probe tools serve only the hardware questions, which a server is never
|
||||
@@ -349,6 +366,10 @@ for stage in "${STAGES[@]}"; do
|
||||
script="$PANAMA_PATH/setup/scripts/$stage"
|
||||
[[ -x "$script" ]] || continue
|
||||
printf '\n=== %s ===\n' "$stage"
|
||||
if (( package_trust_refused )) && stage_opens_dnf "$stage"; then
|
||||
echo "Skipped: the package repository trust check refused package work."
|
||||
continue
|
||||
fi
|
||||
if [[ "$stage" == install-packages ]]; then
|
||||
package_state_status=0
|
||||
package_start_hash="$(hash_packages)" || package_state_status=2
|
||||
@@ -374,9 +395,12 @@ for stage in "${STAGES[@]}"; do
|
||||
fi
|
||||
else
|
||||
stage_status=$?
|
||||
# A configuration change between the preflight and this stage. Suppress the
|
||||
# remaining DNF stages, keep the safe ones, and carry the status to the end.
|
||||
if [[ "$stage" == install-packages && "$stage_status" -eq "$TERRA_TRUST_FAILURE_STATUS" ]]; then
|
||||
printf '!!! %s stopped on an untrusted package repository\n' "$stage" >&2
|
||||
exit "$stage_status"
|
||||
package_trust_refused=1
|
||||
continue
|
||||
fi
|
||||
failed+=("$stage")
|
||||
printf '!!! %s failed\n' "$stage" >&2
|
||||
@@ -402,8 +426,16 @@ done
|
||||
# written for -- and baselining would skip every one of them forever. Every
|
||||
# migration is self-guarding and a no-op where it does not apply, so running
|
||||
# them is the safe direction.
|
||||
#
|
||||
# Held back when package work was refused. A migration is free to run a DNF
|
||||
# transaction -- the ChatGPT package replacement does exactly that -- so the
|
||||
# repositories have to be trustworthy before any of them is allowed to run.
|
||||
# They are not marked applied either, so the next run still has them pending.
|
||||
migrate="$PANAMA_PATH/bin/panama-migrate"
|
||||
if [[ -x "$migrate" ]]; then
|
||||
if (( package_trust_refused )) && [[ -x "$migrate" ]]; then
|
||||
printf '\n=== migrations ===\n'
|
||||
echo "Skipped: the package repository trust check refused package work."
|
||||
elif [[ -x "$migrate" ]]; then
|
||||
printf '\n=== migrations ===\n'
|
||||
if (( UPGRADE )) || [[ -d "$STATE_DIR/migrations" ]]; then
|
||||
"$migrate" run || failed+=(migrations)
|
||||
@@ -451,6 +483,17 @@ else
|
||||
retry='./install'
|
||||
fi
|
||||
|
||||
# Reported last and on its own, because it is not an ordinary stage failure:
|
||||
# everything safe did run, and what did not run is named rather than buried in a
|
||||
# list. The exit status stays 78 so a caller can still tell the two apart.
|
||||
if (( package_trust_refused )); then
|
||||
printf 'Package work was refused: the Terra repository configuration on this\n' >&2
|
||||
printf 'machine is not one Panama can verify. Skipped: %s\n' "${DNF_STAGES[*]}" >&2
|
||||
printf 'Everything that touches no repository was still applied.\n' >&2
|
||||
printf 'Inspect it with: panama diagnose\n' >&2
|
||||
exit "$TERRA_TRUST_FAILURE_STATUS"
|
||||
fi
|
||||
|
||||
if (( ${#failed[@]} == 0 )); then
|
||||
if (( UPGRADE )); then
|
||||
echo "Panama is up to date."
|
||||
|
||||
@@ -112,6 +112,7 @@ load_installer_provenance() {
|
||||
RUSTDESK_VERSION RUSTDESK_X86_64_URL RUSTDESK_X86_64_SHA256 RUSTDESK_X86_64_MAX_BYTES \
|
||||
FEDORA_RELEASE RPMFUSION_FREE_RELEASE_URL RPMFUSION_FREE_RELEASE_MAX_BYTES \
|
||||
RPMFUSION_NONFREE_RELEASE_URL RPMFUSION_NONFREE_RELEASE_MAX_BYTES TERRA_BASEURL \
|
||||
TERRA_METALINK_BASEURL \
|
||||
HYPRLAND_COPR_BASEURL FLATHUB_DESCRIPTOR_URL FLATHUB_DESCRIPTOR_MAX_BYTES \
|
||||
CLAUDE_CODE_BASEURL CLAUDE_DESKTOP_BASEURL TERRA_FINGERPRINT CLAUDE_CODE_FINGERPRINT \
|
||||
BUN_FINGERPRINT RPMFUSION_FREE_FINGERPRINT RPMFUSION_NONFREE_FINGERPRINT \
|
||||
|
||||
@@ -35,6 +35,7 @@ RPMFUSION_FREE_RELEASE_MAX_BYTES=4194304
|
||||
RPMFUSION_NONFREE_RELEASE_URL=https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-44.noarch.rpm
|
||||
RPMFUSION_NONFREE_RELEASE_MAX_BYTES=4194304
|
||||
TERRA_BASEURL=https://repos.fyralabs.com/terra44
|
||||
TERRA_METALINK_BASEURL=https://tetsudou.fyralabs.com/metalink
|
||||
HYPRLAND_COPR_BASEURL=https://download.copr.fedorainfracloud.org/results/lionheartp/Hyprland/fedora-$releasever-$basearch/
|
||||
FLATHUB_DESCRIPTOR_URL=https://flathub.org/repo/flathub.flatpakrepo
|
||||
FLATHUB_DESCRIPTOR_MAX_BYTES=1048576
|
||||
|
||||
+158
-19
@@ -725,8 +725,17 @@ _publish_repository_pair() {
|
||||
return "$status"
|
||||
}
|
||||
|
||||
# Reads the gpgkey of the single enabled Terra identity out of dnf's effective
|
||||
# configuration, applying the structural safety rules either way: one enabled
|
||||
# identity, named terra, with every signature check turned on.
|
||||
#
|
||||
# With require_pinned set it additionally demands Panama's own reviewed baseurl
|
||||
# and key path. Without it, the answer is just "what trust root is this machine
|
||||
# actually verifying against?" -- the question adoption turns on.
|
||||
_effective_terra_key() {
|
||||
awk -v reviewed_baseurl="${INSTALLER_PROVENANCE[TERRA_BASEURL]}" '
|
||||
awk -v reviewed_baseurl="${INSTALLER_PROVENANCE[TERRA_BASEURL]}" \
|
||||
-v reviewed_metalink="${INSTALLER_PROVENANCE[TERRA_METALINK_BASEURL]}" \
|
||||
-v require_pinned="${1:-}" '
|
||||
function reset_block() {
|
||||
delete values
|
||||
delete seen
|
||||
@@ -746,10 +755,22 @@ _effective_terra_key() {
|
||||
for (key in required) {
|
||||
if (seen[key] != 1) bad = 1
|
||||
}
|
||||
if (require_pinned != "") {
|
||||
if (values["baseurl"] != reviewed_baseurl || values["metalink"] != "" \
|
||||
|| values["mirrorlist"] != "" || values["gpgcheck"] != "1" \
|
||||
|| values["pkg_gpgcheck"] != "1" || values["repo_gpgcheck"] != "1" \
|
||||
|| values["mirrorlist"] != "" \
|
||||
|| values["gpgkey"] != "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama") bad = 1
|
||||
} else if (values["mirrorlist"] != "") {
|
||||
bad = 1
|
||||
} else if (values["baseurl"] == reviewed_baseurl && values["metalink"] == "") {
|
||||
# Already on the reviewed baseurl, just not via the Panama key path.
|
||||
} else if (values["baseurl"] == "" \
|
||||
&& index(values["metalink"], reviewed_metalink "?") == 1) {
|
||||
# The stock terra-release metalink, on the reviewed host.
|
||||
} else {
|
||||
bad = 1
|
||||
}
|
||||
if (values["gpgcheck"] != "1" || values["pkg_gpgcheck"] != "1" \
|
||||
|| values["repo_gpgcheck"] != "1" || values["gpgkey"] == "") bad = 1
|
||||
trusted_key = values["gpgkey"]
|
||||
}
|
||||
BEGIN {
|
||||
@@ -807,12 +828,24 @@ _effective_terra_key() {
|
||||
'
|
||||
}
|
||||
|
||||
_terra_repo_config_dump() {
|
||||
LC_ALL=C dnf --quiet --no-plugins --dump-repo-config='*'
|
||||
}
|
||||
|
||||
# Status 0 is one trusted effective Terra identity, 1 is no enabled Terra
|
||||
# identity, and 2 is an unsafe, duplicated, or unreadable effective state.
|
||||
#
|
||||
# Both this and _terra_adoptable_status take an already-read dump when the
|
||||
# caller needs both verdicts, so the two cannot disagree about a configuration
|
||||
# that changed between them.
|
||||
_terra_effective_status() {
|
||||
local dump gpgkey parse_status=0 local_key
|
||||
dump="$(LC_ALL=C dnf --quiet --no-plugins --dump-repo-config='*')" || return 2
|
||||
gpgkey="$(printf '%s\n' "$dump" | _effective_terra_key)" || parse_status=$?
|
||||
if (( $# > 0 )); then
|
||||
dump="$1"
|
||||
else
|
||||
dump="$(_terra_repo_config_dump)" || return 2
|
||||
fi
|
||||
gpgkey="$(printf '%s\n' "$dump" | _effective_terra_key pinned)" || parse_status=$?
|
||||
(( parse_status == 0 )) || return "$parse_status"
|
||||
[[ "$gpgkey" == 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama' ]] || return 2
|
||||
local_key="$PANAMA_SYSTEM_ETC/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama"
|
||||
@@ -824,18 +857,71 @@ _terra_effective_status() {
|
||||
|| return 2
|
||||
}
|
||||
|
||||
# Whether an effective Terra that is not in Panama's pinned form can be safely
|
||||
# converted into it rather than refused.
|
||||
#
|
||||
# The trust root is the signing key, not the URL it is served from. A machine
|
||||
# that installed Terra the way Terra documents has terra-release's own repo
|
||||
# file: a metalink instead of the reviewed baseurl, and the key at
|
||||
# RPM-GPG-KEY-terra44 rather than Panama's renamed copy. Every signature check
|
||||
# is already on, and that key is the same fingerprint this repository reviewed
|
||||
# and pinned. Cosmetics, in other words -- not a compromised trust root.
|
||||
#
|
||||
# Refusing it outright built a gate with no door. install_terra_repository
|
||||
# declined to touch a machine terra-release had already reached, so an ordinary
|
||||
# Fedora desktop could never reach the pinned state, and a routine `panama
|
||||
# update` died before it ran a single stage. Adoption is the door.
|
||||
#
|
||||
# It is deliberately narrow: the pinned fingerprint must match on both the
|
||||
# reviewed key and the key the machine actually verifies against, and the
|
||||
# gpgkey must be a local file under the system trust directory. An unknown key,
|
||||
# a remote gpgkey, a second enabled Terra, or a disabled signature check is
|
||||
# still a hard refusal.
|
||||
_terra_adoptable_status() {
|
||||
local dump gpgkey parse_status=0 key_file key_path
|
||||
_require_policy_value TERRA_METALINK_BASEURL 'https://tetsudou.fyralabs.com/metalink' || return 2
|
||||
if (( $# > 0 )); then
|
||||
dump="$1"
|
||||
else
|
||||
dump="$(_terra_repo_config_dump)" || return 2
|
||||
fi
|
||||
gpgkey="$(printf '%s\n' "$dump" | _effective_terra_key)" || parse_status=$?
|
||||
(( parse_status == 0 )) || return "$parse_status"
|
||||
key_file="${gpgkey#file://}"
|
||||
[[ "$gpgkey" == "file://$key_file" && "$key_file" == /etc/pki/rpm-gpg/* ]] || return 2
|
||||
[[ "$key_file" != *..* ]] || return 2
|
||||
key_path="$PANAMA_SYSTEM_ETC${key_file#/etc}"
|
||||
[[ -f "$key_path" && ! -L "$key_path" ]] || return 2
|
||||
key_fingerprint_matches "$PANAMA_PATH/setup/provenance/keys/terra44.asc" \
|
||||
"${INSTALLER_PROVENANCE[TERRA_FINGERPRINT]}" \
|
||||
&& key_fingerprint_matches "$key_path" \
|
||||
"${INSTALLER_PROVENANCE[TERRA_FINGERPRINT]}" \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
TERRA_TRUST_FAILURE_STATUS=78
|
||||
|
||||
preflight_terra_trust() {
|
||||
local status=0
|
||||
local status=0 adoptable_status=0 dump
|
||||
_require_policy_value TERRA_BASEURL 'https://repos.fyralabs.com/terra44' \
|
||||
|| return "$TERRA_TRUST_FAILURE_STATUS"
|
||||
_require_policy_value TERRA_FINGERPRINT AE09157A4DE88B497EA1D5D300CDAB43DE226D6F \
|
||||
|| return "$TERRA_TRUST_FAILURE_STATUS"
|
||||
_terra_effective_status || status=$?
|
||||
dump="$(_terra_repo_config_dump)" || {
|
||||
log "Effective Terra repository configuration is not trusted; refusing all package work"
|
||||
return "$TERRA_TRUST_FAILURE_STATUS"
|
||||
}
|
||||
_terra_effective_status "$dump" || status=$?
|
||||
if (( status == 0 || status == 1 )); then
|
||||
return 0
|
||||
fi
|
||||
# Terra signed by the pinned key, on a reviewed endpoint, passes here because
|
||||
# install-packages adopts it into the pinned form before it opens any other
|
||||
# DNF transaction.
|
||||
_terra_adoptable_status "$dump" || adoptable_status=$?
|
||||
if (( adoptable_status == 0 )); then
|
||||
return 0
|
||||
fi
|
||||
log "Effective Terra repository configuration is not trusted; refusing all package work"
|
||||
return "$TERRA_TRUST_FAILURE_STATUS"
|
||||
}
|
||||
@@ -907,19 +993,68 @@ install_rpmfusion_repositories() {
|
||||
return "$status"
|
||||
}
|
||||
|
||||
# Rewrites an adoptable Terra into Panama's pinned form.
|
||||
#
|
||||
# No network and no DNF: terra-release is already installed, so this is only the
|
||||
# key copy and the repository file, published as one pair so a half-written
|
||||
# trust root rolls back. The effective state is re-read afterwards, because the
|
||||
# only acceptable proof that adoption worked is the check that judged it.
|
||||
#
|
||||
# terra-release owns /etc/yum.repos.d/terra.repo, so a later update to that
|
||||
# package restores the stock file. That is fine and deliberate: the next run
|
||||
# adopts it again, which is why adoption has to be repeatable rather than a
|
||||
# one-time migration.
|
||||
adopt_terra_repository() {
|
||||
local work staged_key staged_repo status=0 effective_status=0
|
||||
work="$(mktemp -d)" || return 1
|
||||
chmod 0700 "$work"
|
||||
staged_key="$work/terra44.asc"
|
||||
staged_repo="$work/terra.repo"
|
||||
if ! _stage_reviewed_key "$PANAMA_PATH/setup/provenance/keys/terra44.asc" "$staged_key" \
|
||||
TERRA_FINGERPRINT AE09157A4DE88B497EA1D5D300CDAB43DE226D6F; then
|
||||
rm -rf -- "$work"
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' \
|
||||
'[terra]' \
|
||||
'name=Panama reviewed Terra 44' \
|
||||
"baseurl=${INSTALLER_PROVENANCE[TERRA_BASEURL]}" \
|
||||
'enabled=1' \
|
||||
'gpgcheck=1' \
|
||||
'repo_gpgcheck=1' \
|
||||
'gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama' > "$staged_repo"
|
||||
chmod 0600 "$staged_repo"
|
||||
_publish_repository_pair \
|
||||
"$staged_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama \
|
||||
"$staged_repo" /etc/yum.repos.d/terra.repo || status=$?
|
||||
if (( status == 0 )); then
|
||||
_terra_effective_status || effective_status=$?
|
||||
(( effective_status == 0 )) || status="$TERRA_TRUST_FAILURE_STATUS"
|
||||
fi
|
||||
rm -rf -- "$work"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
install_terra_repository() {
|
||||
local work staged_key staged_repo status effective_status=0
|
||||
local work staged_key staged_repo status effective_status=0 adoptable_status=0 dump
|
||||
require_reviewed_fedora_release || return 1
|
||||
_require_policy_value TERRA_BASEURL 'https://repos.fyralabs.com/terra44' || return 1
|
||||
_require_policy_value TERRA_FINGERPRINT AE09157A4DE88B497EA1D5D300CDAB43DE226D6F || return 1
|
||||
_terra_effective_status || effective_status=$?
|
||||
dump="$(_terra_repo_config_dump)" || return 1
|
||||
_terra_effective_status "$dump" || effective_status=$?
|
||||
if (( effective_status == 0 )); then
|
||||
log "Terra repository already configured and verified"
|
||||
return 0
|
||||
elif (( effective_status != 1 )); then
|
||||
_terra_adoptable_status "$dump" || adoptable_status=$?
|
||||
if (( adoptable_status != 0 )); then
|
||||
log "Effective Terra repository configuration is not trusted"
|
||||
return "$TERRA_TRUST_FAILURE_STATUS"
|
||||
fi
|
||||
log "Adopting the existing Terra repository into Panama's reviewed form"
|
||||
adopt_terra_repository
|
||||
return $?
|
||||
fi
|
||||
if rpm -q terra-release >/dev/null 2>&1; then
|
||||
log "terra-release is installed without one trusted enabled Terra repository"
|
||||
return "$TERRA_TRUST_FAILURE_STATUS"
|
||||
@@ -1177,16 +1312,9 @@ if [[ "$ROLE" == server ]]; then
|
||||
fi
|
||||
|
||||
echo -e "\n--- Installing Repositories ---"
|
||||
log "Installing RPM Fusion Free and Nonfree Repositories"
|
||||
install_rpmfusion_repositories > /dev/null
|
||||
log "Enabling Fedora Cisco OpenH264 Repository"
|
||||
# soft: this repo does not exist on every spin, and its absence must not cost
|
||||
# the desktop -- the ordering rule at soft()'s definition applies to the
|
||||
# repository extras just as much as to the codec swaps below.
|
||||
soft "enabling the openh264 repository" sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1
|
||||
log "Installing RPM Fusion AppStream Metadata"
|
||||
soft "the core group update" sudo dnf update @core -y
|
||||
soft "the RPM Fusion appstream metadata" sudo dnf install -y rpmfusion-\*-appstream-data
|
||||
# Terra goes first so a machine whose Terra is enabled but not yet in Panama's
|
||||
# reviewed form is adopted before any other transaction below runs against it.
|
||||
#
|
||||
# Terra bootstraps itself: --repofrompath defines a throwaway repo just long
|
||||
# enough to install terra-release, which then writes the real /etc/yum.repos.d
|
||||
# entry. Doing that a second time is not harmless -- dnf5 refuses the whole
|
||||
@@ -1200,6 +1328,17 @@ soft "the RPM Fusion appstream metadata" sudo dnf install -y rpmfusion-\*-appstr
|
||||
log "Installing Terra Repository"
|
||||
install_terra_repository > /dev/null
|
||||
|
||||
log "Installing RPM Fusion Free and Nonfree Repositories"
|
||||
install_rpmfusion_repositories > /dev/null
|
||||
log "Enabling Fedora Cisco OpenH264 Repository"
|
||||
# soft: this repo does not exist on every spin, and its absence must not cost
|
||||
# the desktop -- the ordering rule at soft()'s definition applies to the
|
||||
# repository extras just as much as to the codec swaps below.
|
||||
soft "enabling the openh264 repository" sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1
|
||||
log "Installing RPM Fusion AppStream Metadata"
|
||||
soft "the core group update" sudo dnf update @core -y
|
||||
soft "the RPM Fusion appstream metadata" sudo dnf install -y rpmfusion-\*-appstream-data
|
||||
|
||||
echo -e "\n--- Installing relevant packages ---"
|
||||
log "Updating all packages. This may take a while"
|
||||
sudo dnf update -y --refresh > /dev/null
|
||||
|
||||
@@ -65,6 +65,12 @@ trap 'rm -rf "$work"' EXIT
|
||||
|
||||
filter="$(sed -n '/^packages_in()/,/^}/p' "$installer")"
|
||||
loop="$(sed -n '/^install_extra_category()/,/^}/p' "$installer")"
|
||||
# install_extra_category verifies the Flathub remote before installing a
|
||||
# flatpak, and records a soft failure when it cannot. Both live outside the
|
||||
# extracted function and have contracts of their own, so they stand in here as
|
||||
# trusted -- what is under test is which targets reach which installer.
|
||||
deps='ensure_flathub_remote() { :; }
|
||||
softly_failed=()'
|
||||
[[ -n "$filter" && -n "$loop" ]] || {
|
||||
printf 'extras contract: install-packages no longer defines packages_in and install_extra_category\n' >&2
|
||||
exit 1
|
||||
@@ -99,6 +105,7 @@ LIST
|
||||
source "$catalog"
|
||||
eval "$filter"
|
||||
eval "$loop"
|
||||
eval "$deps"
|
||||
install_extra_category "$fixture"
|
||||
)
|
||||
|
||||
@@ -132,6 +139,7 @@ printf 'flatpak:org.example.OnlyFlatpak\n' >"$flatpak_only"
|
||||
source "$catalog"
|
||||
eval "$filter"
|
||||
eval "$loop"
|
||||
eval "$deps"
|
||||
install_extra_category "$flatpak_only"
|
||||
)
|
||||
flatpak_only_status=$?
|
||||
@@ -149,6 +157,7 @@ grep -q 'flatpak install -y flathub org.example.OnlyFlatpak' <<<"$(cat "$calls"
|
||||
source "$catalog"
|
||||
eval "$filter"
|
||||
eval "$loop"
|
||||
eval "$deps"
|
||||
EXTRAS_DIR="$extras_dir"
|
||||
for extra in ${PANAMA_EXTRAS:-}; do
|
||||
[[ -f "$EXTRAS_DIR/$extra" ]] && install_extra_category "$EXTRAS_DIR/$extra"
|
||||
|
||||
@@ -894,7 +894,16 @@ if [[ -n "$query" ]]; then
|
||||
trusted|wrong-key) mode=trusted ;;
|
||||
nogpg) mode=legacy ;;
|
||||
wrong-url) mode=override-url ;;
|
||||
stock|stock-wrong-key) mode=stock ;;
|
||||
esac
|
||||
# Adoption rewrites the repository file. Once it is the pinned form the
|
||||
# dump has to say so, or the re-verification adoption performs on itself
|
||||
# could never pass.
|
||||
if [[ "$mode" == stock && -f "$STUB_ETC/yum.repos.d/terra.repo" ]] \
|
||||
&& grep -q '^gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama$' \
|
||||
"$STUB_ETC/yum.repos.d/terra.repo"; then
|
||||
mode=trusted
|
||||
fi
|
||||
if [[ "$mode" == auto && -f "$STUB_ETC/yum.repos.d/terra.repo" ]] \
|
||||
&& grep -q '^baseurl=https://repos.fyralabs.com/terra44$' "$STUB_ETC/yum.repos.d/terra.repo"; then
|
||||
mode=trusted
|
||||
@@ -926,6 +935,13 @@ if [[ -n "$query" ]]; then
|
||||
printf 'metalink = https://tetsudou.fyralabs.com/terra44\nmirrorlist = \n'
|
||||
printf 'pkg_gpgcheck = 0\nrepo_gpgcheck = 0\n'
|
||||
;;
|
||||
stock)
|
||||
printf '======== "terra" repository configuration: ========\n'
|
||||
printf 'baseurl = \nenabled = 1\ngpgcheck = 1\n'
|
||||
printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44\n'
|
||||
printf 'metalink = https://tetsudou.fyralabs.com/metalink?repo=terra44&arch=x86_64\n'
|
||||
printf 'mirrorlist = \npkg_gpgcheck = 1\nrepo_gpgcheck = 1\n'
|
||||
;;
|
||||
override-url)
|
||||
printf '======== "terra" repository configuration: ========\n'
|
||||
printf 'baseurl = https://evil.invalid/terra44\nenabled = 1\ngpgcheck = 1\n'
|
||||
@@ -1120,6 +1136,17 @@ run_installer_function() {
|
||||
printf '[terra]\nbaseurl=https://evil.invalid/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \
|
||||
> "$case_root/etc/yum.repos.d/terra.repo"
|
||||
;;
|
||||
stock|stock-wrong-key)
|
||||
if [[ "${STUB_TERRA_REPO_MODE}" == stock ]]; then
|
||||
cp "$installer_fixture/setup/provenance/keys/terra44.asc" \
|
||||
"$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44"
|
||||
else
|
||||
cp "$installer_fixture/setup/provenance/keys/flathub.asc" \
|
||||
"$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44"
|
||||
fi
|
||||
printf '[terra]\nmetalink=https://tetsudou.fyralabs.com/metalink?repo=terra44&arch=$basearch\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44\n' \
|
||||
> "$case_root/etc/yum.repos.d/terra.repo"
|
||||
;;
|
||||
wrong-key)
|
||||
cp "$installer_fixture/setup/provenance/keys/flathub.asc" \
|
||||
"$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama"
|
||||
@@ -1770,6 +1797,39 @@ for mode in nogpg wrong-url wrong-key absent; do
|
||||
|| fail "untrusted existing Terra $mode state reached a mutation"
|
||||
done
|
||||
|
||||
# A machine that installed Terra the way Terra documents it. The repository file
|
||||
# is terra-release's own -- a metalink, and the key at its stock path -- so it is
|
||||
# not Panama's pinned form, but it IS the fingerprint this repository reviewed,
|
||||
# with every signature check on. That is an adoption, not a compromise.
|
||||
#
|
||||
# Refusing it was a gate with no door: the ordinary Fedora desktop could never
|
||||
# reach the pinned state, and status 78 then stopped every stage of every run,
|
||||
# including the ones that never open DNF.
|
||||
reset_installer_fixture
|
||||
STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE=stock \
|
||||
expect_success run_installer_function terra-stock-preflight preflight_terra_trust
|
||||
|
||||
reset_installer_fixture
|
||||
STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE=stock \
|
||||
expect_success run_installer_function terra-stock-adopt install_terra_repository
|
||||
terra_adopted="$test_tmp/cases/terra-stock-adopt/etc/yum.repos.d/terra.repo"
|
||||
grep -qx 'baseurl=https://repos.fyralabs.com/terra44' "$terra_adopted" \
|
||||
|| fail 'adoption left Terra off the reviewed baseurl'
|
||||
grep -qx 'gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama' "$terra_adopted" \
|
||||
|| fail 'adoption left Terra off the reviewed key path'
|
||||
grep -q 'metalink' "$terra_adopted" \
|
||||
&& fail 'adoption kept the metalink it was supposed to replace'
|
||||
[[ "$(<"$test_tmp/cases/terra-stock-adopt/commands.log")" != *'dnf:install'* ]] \
|
||||
|| fail 'adoption opened a DNF transaction it does not need'
|
||||
|
||||
# Adoption is anchored on the fingerprint, not the URL. The same stock shape
|
||||
# verifying against a key that is not Terra's is still a hard refusal.
|
||||
reset_installer_fixture
|
||||
STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE=stock-wrong-key \
|
||||
expect_failure run_installer_function terra-stock-wrong-key install_terra_repository
|
||||
[[ "$(<"$test_tmp/cases/terra-stock-wrong-key/commands.log")" != *'sudo:'* ]] \
|
||||
|| fail 'a stock Terra signed by an unreviewed key reached a mutation'
|
||||
|
||||
# An optional security field may be absent, but duplicates are malformed even
|
||||
# when one copy looks safe. These cases catch the absent/duplicate conflation.
|
||||
for duplicate_case in \
|
||||
|
||||
@@ -360,30 +360,48 @@ fi
|
||||
grep -qx 'link-dotfiles' "$tmp/c/ran" \
|
||||
|| note 'an ordinary package-stage failure no longer allows later safe stages'
|
||||
|
||||
# An invalid enabled Terra root is not an ordinary package failure. It must
|
||||
# stop before the installer's bootstrap DNF and before every stage.
|
||||
# An untrusted Terra root is not an ordinary package failure, and it is not a
|
||||
# reason to abandon the machine either. It suppresses the stages that open DNF
|
||||
# and the migrations, which are free to run a transaction of their own. Every
|
||||
# stage that only links configuration still runs, and the status stays 78.
|
||||
SAFE_STAGES=(link-dotfiles link-skills link-user link-vicinae-scripts)
|
||||
DNF_SUPPRESSED=(install-packages change-settings install-hardware)
|
||||
|
||||
assert_trust_refusal() {
|
||||
local root="$1" label="$2" suppressed safe
|
||||
for suppressed in "${DNF_SUPPRESSED[@]}"; do
|
||||
grep -qx "$suppressed" "$root/ran" \
|
||||
&& note "$label still ran $suppressed"
|
||||
done
|
||||
grep -q '^migrate ' "$root/ran" \
|
||||
&& note "$label still ran migrations, which may open a DNF transaction"
|
||||
for safe in "${SAFE_STAGES[@]}"; do
|
||||
grep -qx "$safe" "$root/ran" \
|
||||
|| note "$label suppressed $safe, which touches no repository"
|
||||
done
|
||||
}
|
||||
|
||||
build_fixture "$tmp/terra-preflight-hard" 0 78
|
||||
install_status=0
|
||||
run_install "$tmp/terra-preflight-hard" >/dev/null || install_status=$?
|
||||
[[ "$install_status" -eq 78 ]] \
|
||||
|| note "initial Terra trust failure returned $install_status instead of 78"
|
||||
asserted_preflight="$(<"$tmp/terra-preflight-hard/ran")"
|
||||
[[ "$asserted_preflight" == trust-preflight ]] \
|
||||
|| note "initial Terra trust failure allowed later work: ${asserted_preflight//$'\n'/,}"
|
||||
grep -qx 'trust-preflight' "$tmp/terra-preflight-hard/ran" \
|
||||
|| note 'initial Terra trust fixture never reached the preflight'
|
||||
assert_trust_refusal "$tmp/terra-preflight-hard" 'initial Terra trust failure'
|
||||
|
||||
# The trust verifier is itself mandatory. Losing its executable adapter must
|
||||
# fail closed before interview, bootstrap, or stage work.
|
||||
# The trust verifier is itself mandatory. Losing its executable adapter refuses
|
||||
# package work exactly as a failing verdict does, rather than being ignored.
|
||||
build_fixture "$tmp/terra-preflight-missing"
|
||||
rm "$tmp/terra-preflight-missing/setup/scripts/install-packages"
|
||||
install_status=0
|
||||
run_install "$tmp/terra-preflight-missing" >/dev/null || install_status=$?
|
||||
[[ "$install_status" -eq 78 ]] \
|
||||
|| note "missing Terra trust verifier returned $install_status instead of 78"
|
||||
[[ ! -s "$tmp/terra-preflight-missing/ran" ]] \
|
||||
|| note 'missing Terra trust verifier allowed later work'
|
||||
assert_trust_refusal "$tmp/terra-preflight-missing" 'missing Terra trust verifier'
|
||||
|
||||
# The package stage repeats the preflight to close a configuration-change race.
|
||||
# Its hard status must also stop link stages and install-hardware immediately.
|
||||
# Its hard status suppresses the DNF stages that would have followed it.
|
||||
build_fixture "$tmp/terra-stage-hard" 78 0
|
||||
install_status=0
|
||||
run_install "$tmp/terra-stage-hard" >/dev/null || install_status=$?
|
||||
@@ -391,10 +409,16 @@ run_install "$tmp/terra-stage-hard" >/dev/null || install_status=$?
|
||||
|| note "stage-time Terra trust failure returned $install_status instead of 78"
|
||||
grep -qx 'install-packages' "$tmp/terra-stage-hard/ran" \
|
||||
|| note 'stage-time Terra trust fixture never reached install-packages'
|
||||
for suppressed in link-dotfiles link-skills link-user change-settings install-hardware dnf-transaction; do
|
||||
for suppressed in change-settings install-hardware; do
|
||||
grep -qx "$suppressed" "$tmp/terra-stage-hard/ran" \
|
||||
&& note "stage-time Terra trust failure still ran $suppressed"
|
||||
done
|
||||
grep -q '^migrate ' "$tmp/terra-stage-hard/ran" \
|
||||
&& note 'stage-time Terra trust failure still ran migrations'
|
||||
for safe in "${SAFE_STAGES[@]}"; do
|
||||
grep -qx "$safe" "$tmp/terra-stage-hard/ran" \
|
||||
|| note "stage-time Terra trust failure suppressed $safe"
|
||||
done
|
||||
|
||||
# A full install always runs the stage, whatever any recorded hash says.
|
||||
build_fixture "$tmp/d"
|
||||
|
||||
Reference in New Issue
Block a user