Compare commits
1 commit
main
...
feat/build
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b3a64bc2a |
4 changed files with 463 additions and 1 deletions
128
docs/CLI.md
Normal file
128
docs/CLI.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# veilor-os CLI
|
||||||
|
|
||||||
|
User-facing commands shipped at `/usr/local/bin/`. Every veilor-* tool
|
||||||
|
is a small bash script — readable, auditable, no compiled bits.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `veilor-update`
|
||||||
|
|
||||||
|
Wraps `dnf upgrade --refresh -y` plus `flatpak update -y`. One command
|
||||||
|
for "give me everything new". Mirrors the operator habit of always
|
||||||
|
patching both DNF and Flatpak — neither is sufficient on its own.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
veilor-update
|
||||||
|
```
|
||||||
|
|
||||||
|
**What it does:**
|
||||||
|
|
||||||
|
1. Pings `mirrors.fedoraproject.org`. If unreachable, exits early with
|
||||||
|
a helpful message instead of letting `dnf` spin and time out.
|
||||||
|
2. Runs `sudo dnf upgrade --refresh -y` and tees output for live
|
||||||
|
progress.
|
||||||
|
3. Counts packages from the `Upgraded:`/`Installed:` lines of dnf
|
||||||
|
output and reports the total.
|
||||||
|
4. If `flatpak` is installed, runs `flatpak update -y`.
|
||||||
|
5. Compares running kernel to the newest installed kernel and prints
|
||||||
|
a reboot suggestion if they differ.
|
||||||
|
|
||||||
|
**Exit codes:**
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| 0 | dnf and flatpak both succeeded |
|
||||||
|
| 1 | dnf upgrade failed |
|
||||||
|
| 2 | flatpak failed (dnf still ran successfully) |
|
||||||
|
| 3 | no network — pre-check failed |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```
|
||||||
|
=== veilor-update: refreshing DNF metadata + applying updates ===
|
||||||
|
... dnf output ...
|
||||||
|
=== veilor-update: updating flatpaks ===
|
||||||
|
... flatpak output ...
|
||||||
|
=== veilor-update: complete ===
|
||||||
|
Packages updated : 47
|
||||||
|
Running kernel : 6.19.14-200.fc43.x86_64
|
||||||
|
Newest kernel : 6.19.16-200.fc43.x86_64 (reboot suggested)
|
||||||
|
```
|
||||||
|
|
||||||
|
If `gum` is on the system, status banners render with colour and a
|
||||||
|
spinner; otherwise plain ASCII output. Either form is identical in
|
||||||
|
substance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `veilor-doctor`
|
||||||
|
|
||||||
|
Read-only diagnostic. Walks the v0.2 hardening checklist and reports
|
||||||
|
drift. Never modifies system state — fixes are a separate, deliberate
|
||||||
|
step.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
veilor-doctor # full coloured table
|
||||||
|
veilor-doctor --quiet # PASS/FAIL summary only
|
||||||
|
veilor-doctor --json # machine-readable JSON
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sections checked:**
|
||||||
|
|
||||||
|
| Section | Checks |
|
||||||
|
|------------|--------|
|
||||||
|
| System | hostname, OS, kernel, uptime |
|
||||||
|
| Hardening | SELinux mode, USBGuard active, fail2ban active, firewalld zone, `kernel.yama.ptrace_scope`, `kernel.kptr_restrict` |
|
||||||
|
| Disk | LUKS device + cipher, btrfs subvolume count, root free space |
|
||||||
|
| Network | NetworkManager state, default route, DNS servers, public IP |
|
||||||
|
| Updates | last `dnf history` entry, pending update count via `dnf check-update` |
|
||||||
|
| veilor | state of `veilor-firstboot.service` + `veilor-modules-lock.service` |
|
||||||
|
|
||||||
|
**Exit codes:**
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| 0 | all checks passed |
|
||||||
|
| 1 | one or more checks failed |
|
||||||
|
| 2 | bad CLI flag |
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
|
||||||
|
```
|
||||||
|
── System ──
|
||||||
|
[OK] hostname veilor
|
||||||
|
[OK] os veilor-os
|
||||||
|
[OK] kernel 6.19.14-200.fc43.x86_64
|
||||||
|
[OK] uptime up 3 hours, 21 minutes
|
||||||
|
|
||||||
|
── Hardening ──
|
||||||
|
[OK] selinux Enforcing
|
||||||
|
[OK] usbguard active
|
||||||
|
[OK] fail2ban active
|
||||||
|
[OK] firewalld_zone drop
|
||||||
|
[OK] ptrace_scope 2
|
||||||
|
[OK] kptr_restrict 2
|
||||||
|
|
||||||
|
── Disk ──
|
||||||
|
[OK] luks dm-0: aes-xts-plain64
|
||||||
|
[OK] btrfs 4 subvolume(s)
|
||||||
|
[OK] root_free 72G free / 234G (32% used)
|
||||||
|
|
||||||
|
19 checks passed.
|
||||||
|
```
|
||||||
|
|
||||||
|
`veilor-doctor --json` emits a single-line JSON object with `pass`,
|
||||||
|
`fail`, and `checks` keys. Suitable for piping into a monitoring
|
||||||
|
agent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- `veilor-power` — switch tuned profile (save / mid / perf)
|
||||||
|
- `veilor-firstboot` — root-owned, runs once on first boot
|
||||||
|
- `veilor-installer` — TTY1 TUI installer (live ISO only)
|
||||||
|
|
@ -205,7 +205,7 @@ echo " veilor-os install — %post"
|
||||||
echo "════════════════════════════════════════════════════════"
|
echo "════════════════════════════════════════════════════════"
|
||||||
|
|
||||||
REPO=/usr/share/veilor-os
|
REPO=/usr/share/veilor-os
|
||||||
chmod +x $REPO/scripts/*.sh $REPO/scripts/selinux/*.sh /usr/local/bin/veilor-power /usr/local/sbin/veilor-firstboot /usr/local/sbin/veilor-installer
|
chmod +x $REPO/scripts/*.sh $REPO/scripts/selinux/*.sh /usr/local/bin/veilor-power /usr/local/bin/veilor-update /usr/local/bin/veilor-doctor /usr/local/sbin/veilor-firstboot /usr/local/sbin/veilor-installer
|
||||||
|
|
||||||
# Live image plumbing (matches upstream Fedora live ks). Without these the
|
# Live image plumbing (matches upstream Fedora live ks). Without these the
|
||||||
# squashfs/EFI build fails — livesys-scripts ships systemd units lorax expects.
|
# squashfs/EFI build fails — livesys-scripts ships systemd units lorax expects.
|
||||||
|
|
|
||||||
240
overlay/usr/local/bin/veilor-doctor
Executable file
240
overlay/usr/local/bin/veilor-doctor
Executable file
|
|
@ -0,0 +1,240 @@
|
||||||
|
#!/usr/bin/bash
|
||||||
|
# veilor-doctor — read-only diagnostic / health check.
|
||||||
|
# User-facing CLI shipped in /usr/local/bin/. v0.6 ergonomic tooling.
|
||||||
|
#
|
||||||
|
# Reports on system, hardening, disk, network, updates, veilor units.
|
||||||
|
# No fixes are ever applied — output only. Use this to verify drift
|
||||||
|
# from the v0.2+ baseline.
|
||||||
|
#
|
||||||
|
# Flags:
|
||||||
|
# --quiet print only PASS/FAIL summary
|
||||||
|
# --json emit JSON for monitoring
|
||||||
|
# -h|--help
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
QUIET=0
|
||||||
|
JSON=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--quiet|-q) QUIET=1 ;;
|
||||||
|
--json) JSON=1 ;;
|
||||||
|
-h|--help)
|
||||||
|
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown flag: $arg" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
have() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
# ── Output helpers ──────────────────────────────────────────────────
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
ROWS=() # human table rows: "Section|Check|Status|Detail"
|
||||||
|
JSON_ROWS=() # JSON-serialisable rows
|
||||||
|
|
||||||
|
# Use color only if stdout is a TTY and we're not in --quiet/--json mode.
|
||||||
|
if [[ -t 1 && $QUIET -eq 0 && $JSON -eq 0 ]]; then
|
||||||
|
GREEN=$'\033[32m'; RED=$'\033[31m'; DIM=$'\033[2m'; OFF=$'\033[0m'
|
||||||
|
else
|
||||||
|
GREEN=""; RED=""; DIM=""; OFF=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JSON-escape a string for embedding.
|
||||||
|
json_esc() {
|
||||||
|
local s=$1
|
||||||
|
s=${s//\\/\\\\}
|
||||||
|
s=${s//\"/\\\"}
|
||||||
|
s=${s//$'\n'/\\n}
|
||||||
|
s=${s//$'\t'/\\t}
|
||||||
|
printf '%s' "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
# check <section> <name> <pass|fail> <detail>
|
||||||
|
check() {
|
||||||
|
local section=$1 name=$2 status=$3 detail=$4
|
||||||
|
if [[ $status == pass ]]; then
|
||||||
|
PASS=$((PASS+1))
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL+1))
|
||||||
|
fi
|
||||||
|
ROWS+=("${section}|${name}|${status}|${detail}")
|
||||||
|
JSON_ROWS+=("{\"section\":\"$(json_esc "$section")\",\"name\":\"$(json_esc "$name")\",\"status\":\"$status\",\"detail\":\"$(json_esc "$detail")\"}")
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── 1. System ───────────────────────────────────────────────────────
|
||||||
|
HOSTNAME_VAL=$(hostnamectl --static 2>/dev/null || hostname)
|
||||||
|
OS_NAME=$(. /etc/os-release 2>/dev/null && echo "${PRETTY_NAME:-unknown}")
|
||||||
|
KERNEL=$(uname -r)
|
||||||
|
UPTIME=$(uptime -p 2>/dev/null || uptime)
|
||||||
|
check System hostname pass "$HOSTNAME_VAL"
|
||||||
|
check System os pass "$OS_NAME"
|
||||||
|
check System kernel pass "$KERNEL"
|
||||||
|
check System uptime pass "$UPTIME"
|
||||||
|
|
||||||
|
# ── 2. Hardening ────────────────────────────────────────────────────
|
||||||
|
SELINUX=$(getenforce 2>/dev/null || echo "unknown")
|
||||||
|
[[ $SELINUX == "Enforcing" ]] && check Hardening selinux pass "$SELINUX" \
|
||||||
|
|| check Hardening selinux fail "$SELINUX (expected Enforcing)"
|
||||||
|
|
||||||
|
if systemctl is-active --quiet usbguard; then
|
||||||
|
check Hardening usbguard pass active
|
||||||
|
else
|
||||||
|
check Hardening usbguard fail "$(systemctl is-active usbguard 2>/dev/null || echo missing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if systemctl is-active --quiet fail2ban; then
|
||||||
|
check Hardening fail2ban pass active
|
||||||
|
else
|
||||||
|
check Hardening fail2ban fail "$(systemctl is-active fail2ban 2>/dev/null || echo missing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FW_ZONE=$(firewall-cmd --get-default-zone 2>/dev/null || echo unknown)
|
||||||
|
[[ $FW_ZONE == "drop" ]] && check Hardening firewalld_zone pass "$FW_ZONE" \
|
||||||
|
|| check Hardening firewalld_zone fail "$FW_ZONE (expected drop)"
|
||||||
|
|
||||||
|
PTRACE=$(sysctl -n kernel.yama.ptrace_scope 2>/dev/null || echo "")
|
||||||
|
[[ ${PTRACE:-0} -ge 2 ]] && check Hardening ptrace_scope pass "$PTRACE" \
|
||||||
|
|| check Hardening ptrace_scope fail "${PTRACE:-unset} (expected >=2)"
|
||||||
|
|
||||||
|
KPTR=$(sysctl -n kernel.kptr_restrict 2>/dev/null || echo "")
|
||||||
|
[[ ${KPTR:-0} -ge 2 ]] && check Hardening kptr_restrict pass "$KPTR" \
|
||||||
|
|| check Hardening kptr_restrict fail "${KPTR:-unset} (expected >=2)"
|
||||||
|
|
||||||
|
# ── 3. Disk ─────────────────────────────────────────────────────────
|
||||||
|
LUKS_DEV=$(lsblk -lno NAME,TYPE 2>/dev/null | awk '$2=="crypt" {print $1; exit}')
|
||||||
|
if [[ -n $LUKS_DEV ]]; then
|
||||||
|
LUKS_STATUS=$(cryptsetup status "$LUKS_DEV" 2>/dev/null \
|
||||||
|
| awk -F: '/cipher/ {gsub(/^ +/,"",$2); print $2; exit}')
|
||||||
|
check Disk luks pass "${LUKS_DEV}: ${LUKS_STATUS:-active}"
|
||||||
|
else
|
||||||
|
check Disk luks fail "no LUKS device found (full-disk encryption expected)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if have btrfs && btrfs filesystem df / >/dev/null 2>&1; then
|
||||||
|
SUBVOLS=$(btrfs subvolume list / 2>/dev/null | wc -l)
|
||||||
|
check Disk btrfs pass "${SUBVOLS} subvolume(s)"
|
||||||
|
else
|
||||||
|
check Disk btrfs fail "btrfs not detected on /"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT_FREE=$(df -h / 2>/dev/null | awk 'NR==2 {print $4 " free / " $2 " (" $5 " used)"}')
|
||||||
|
check Disk root_free pass "${ROOT_FREE:-unknown}"
|
||||||
|
|
||||||
|
# ── 4. Network ──────────────────────────────────────────────────────
|
||||||
|
if systemctl is-active --quiet NetworkManager; then
|
||||||
|
check Network networkmanager pass active
|
||||||
|
else
|
||||||
|
check Network networkmanager fail inactive
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEFAULT_ROUTE=$(ip -o route show default 2>/dev/null | awk '{print $3 " via " $5; exit}')
|
||||||
|
[[ -n $DEFAULT_ROUTE ]] && check Network default_route pass "$DEFAULT_ROUTE" \
|
||||||
|
|| check Network default_route fail "no default route"
|
||||||
|
|
||||||
|
DNS_LIST=$(awk '/^nameserver/ {print $2}' /etc/resolv.conf 2>/dev/null \
|
||||||
|
| paste -sd, - 2>/dev/null)
|
||||||
|
[[ -n $DNS_LIST ]] && check Network dns pass "$DNS_LIST" \
|
||||||
|
|| check Network dns fail "no nameservers"
|
||||||
|
|
||||||
|
PUBLIC_IP=$(curl -s --max-time 3 ifconfig.me 2>/dev/null || echo "")
|
||||||
|
[[ -n $PUBLIC_IP ]] && check Network public_ip pass "$PUBLIC_IP" \
|
||||||
|
|| check Network public_ip fail "lookup timed out"
|
||||||
|
|
||||||
|
# ── 5. Updates ──────────────────────────────────────────────────────
|
||||||
|
LAST_DNF=$(sudo -n dnf history list 2>/dev/null \
|
||||||
|
| awk 'NR==4 {for(i=4;i<NF;i++)printf "%s ", $i; print $NF; exit}')
|
||||||
|
[[ -n $LAST_DNF ]] && check Updates last_dnf pass "$LAST_DNF" \
|
||||||
|
|| check Updates last_dnf pass "(unknown — try \`sudo dnf history\`)"
|
||||||
|
|
||||||
|
# `dnf check-update` exits 100 if updates available, 0 if not.
|
||||||
|
sudo -n dnf check-update -q >/dev/null 2>&1
|
||||||
|
RC=$?
|
||||||
|
case $RC in
|
||||||
|
0) check Updates pending pass "system up-to-date" ;;
|
||||||
|
100)
|
||||||
|
AVAIL=$(sudo -n dnf check-update -q 2>/dev/null \
|
||||||
|
| awk 'NF>=3 && $1!~/^Last/ {n++} END {print n+0}')
|
||||||
|
check Updates pending fail "${AVAIL} update(s) available — run \`veilor-update\`"
|
||||||
|
;;
|
||||||
|
*) check Updates pending fail "dnf check-update returned $RC (need sudo?)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── 6. veilor services ──────────────────────────────────────────────
|
||||||
|
for unit in veilor-firstboot.service veilor-modules-lock.service; do
|
||||||
|
if systemctl list-unit-files "$unit" 2>/dev/null | grep -q "$unit"; then
|
||||||
|
STATE=$(systemctl is-enabled "$unit" 2>/dev/null || echo unknown)
|
||||||
|
ACTIVE=$(systemctl is-active "$unit" 2>/dev/null || echo unknown)
|
||||||
|
# firstboot is meant to be one-shot + disabled after run.
|
||||||
|
check veilor "$unit" pass "${STATE} (${ACTIVE})"
|
||||||
|
else
|
||||||
|
check veilor "$unit" fail "unit not installed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Output ──────────────────────────────────────────────────────────
|
||||||
|
if [[ $JSON -eq 1 ]]; then
|
||||||
|
printf '{"pass":%d,"fail":%d,"checks":[' "$PASS" "$FAIL"
|
||||||
|
for i in "${!JSON_ROWS[@]}"; do
|
||||||
|
[[ $i -gt 0 ]] && printf ','
|
||||||
|
printf '%s' "${JSON_ROWS[$i]}"
|
||||||
|
done
|
||||||
|
printf ']}\n'
|
||||||
|
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $QUIET -eq 1 ]]; then
|
||||||
|
if [[ $FAIL -eq 0 ]]; then
|
||||||
|
echo "PASS ($PASS checks)"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "FAIL ($FAIL of $((PASS+FAIL)) checks failed)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
_print_plain_table() {
|
||||||
|
local last_section=""
|
||||||
|
for r in "${ROWS[@]}"; do
|
||||||
|
IFS='|' read -r sec name status detail <<<"$r"
|
||||||
|
if [[ $sec != "$last_section" ]]; then
|
||||||
|
printf '\n%s%s%s\n' "$DIM" "── $sec ──" "$OFF"
|
||||||
|
last_section=$sec
|
||||||
|
fi
|
||||||
|
if [[ $status == pass ]]; then
|
||||||
|
printf ' %s[OK]%s %-20s %s\n' "$GREEN" "$OFF" "$name" "$detail"
|
||||||
|
else
|
||||||
|
printf ' %s[FAIL]%s %-20s %s\n' "$RED" "$OFF" "$name" "$detail"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pretty table — gum if available, else plain. gum table reads tab-separated
|
||||||
|
# input on stdin; we feed it the rows then fall back to the plain printer
|
||||||
|
# if gum exits non-zero (e.g. no TTY).
|
||||||
|
if have gum; then
|
||||||
|
{
|
||||||
|
printf 'Section\tCheck\tStatus\tDetail\n'
|
||||||
|
for r in "${ROWS[@]}"; do
|
||||||
|
IFS='|' read -r sec name status detail <<<"$r"
|
||||||
|
mark=$([[ $status == pass ]] && echo "OK" || echo "FAIL")
|
||||||
|
printf '%s\t%s\t%s\t%s\n' "$sec" "$name" "$mark" "$detail"
|
||||||
|
done
|
||||||
|
} | gum table --print --separator $'\t' 2>/dev/null || _print_plain_table
|
||||||
|
else
|
||||||
|
_print_plain_table
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [[ $FAIL -eq 0 ]]; then
|
||||||
|
printf '%s%d checks passed.%s\n' "$GREEN" "$PASS" "$OFF"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
printf '%s%d of %d checks failed.%s\n' "$RED" "$FAIL" "$((PASS+FAIL))" "$OFF"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
94
overlay/usr/local/bin/veilor-update
Executable file
94
overlay/usr/local/bin/veilor-update
Executable file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#!/usr/bin/bash
|
||||||
|
# veilor-update — system update wrapper.
|
||||||
|
# Wraps `dnf upgrade --refresh` + `flatpak update` behind a single command.
|
||||||
|
# User-facing CLI shipped in /usr/local/bin/. v0.6 ergonomic tooling.
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success
|
||||||
|
# 1 dnf failed
|
||||||
|
# 2 flatpak failed (dnf still ran successfully)
|
||||||
|
# 3 no network
|
||||||
|
#
|
||||||
|
# Uses `gum` for spinner output if present, falls back to plain stdout.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# ── Helpers ─────────────────────────────────────────────────────────
|
||||||
|
have() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
GUM=$(have gum && echo gum || echo "")
|
||||||
|
|
||||||
|
say() {
|
||||||
|
# Print a status line. Coloured if gum present, else plain.
|
||||||
|
if [[ -n $GUM ]]; then
|
||||||
|
gum style --foreground 212 --bold "$1"
|
||||||
|
else
|
||||||
|
printf '\n=== %s ===\n' "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_with_spinner() {
|
||||||
|
local title=$1; shift
|
||||||
|
if [[ -n $GUM ]]; then
|
||||||
|
gum spin --spinner dot --title "$title" -- "$@"
|
||||||
|
else
|
||||||
|
echo "[+] $title"
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Pre-flight: network check ───────────────────────────────────────
|
||||||
|
say "veilor-update: checking network"
|
||||||
|
if ! ping -c 1 -W 2 mirrors.fedoraproject.org >/dev/null 2>&1; then
|
||||||
|
echo
|
||||||
|
echo " No route to mirrors.fedoraproject.org."
|
||||||
|
echo " Connect to a network and re-run \`veilor-update\`."
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Snapshot kernel before upgrade so we can warn about reboot need ─
|
||||||
|
KERNEL_BEFORE=$(uname -r)
|
||||||
|
|
||||||
|
# ── DNF upgrade ─────────────────────────────────────────────────────
|
||||||
|
say "veilor-update: refreshing DNF metadata + applying updates"
|
||||||
|
# Capture upgrade output so we can count packages afterwards. Tee to
|
||||||
|
# stdout for live progress; swallow into a tempfile for the count.
|
||||||
|
LOG=$(mktemp -t veilor-update.XXXXXX)
|
||||||
|
trap 'rm -f "$LOG"' EXIT
|
||||||
|
|
||||||
|
if ! sudo dnf upgrade --refresh -y 2>&1 | tee "$LOG"; then
|
||||||
|
echo
|
||||||
|
echo " dnf upgrade failed. See output above."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Count packages updated ──────────────────────────────────────────
|
||||||
|
# DNF prints "Upgraded: N", "Installed: N", "Removed: N" at end.
|
||||||
|
# Sum the upgrade/install lines for the user-visible total.
|
||||||
|
UPDATED=$(grep -E '^(Upgraded|Installed)\b' "$LOG" 2>/dev/null \
|
||||||
|
| awk -F: '{ gsub(/[^0-9]/,"",$2); s+=$2 } END { print s+0 }')
|
||||||
|
|
||||||
|
# ── Flatpak (best-effort) ───────────────────────────────────────────
|
||||||
|
FLATPAK_RC=0
|
||||||
|
if have flatpak; then
|
||||||
|
say "veilor-update: updating flatpaks"
|
||||||
|
if ! flatpak update -y; then
|
||||||
|
FLATPAK_RC=2
|
||||||
|
echo " flatpak update failed; continuing anyway."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " (flatpak not installed — skipping)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Post-update: reboot hint if kernel changed ──────────────────────
|
||||||
|
KERNEL_AFTER_LATEST=$(rpm -q kernel --last 2>/dev/null \
|
||||||
|
| awk 'NR==1 { sub(/^kernel-/,"",$1); print $1 }')
|
||||||
|
|
||||||
|
say "veilor-update: complete"
|
||||||
|
printf ' Packages updated : %s\n' "${UPDATED:-0}"
|
||||||
|
printf ' Running kernel : %s\n' "$KERNEL_BEFORE"
|
||||||
|
if [[ -n ${KERNEL_AFTER_LATEST:-} && $KERNEL_AFTER_LATEST != "$KERNEL_BEFORE" ]]; then
|
||||||
|
printf ' Newest kernel : %s (reboot suggested)\n' "$KERNEL_AFTER_LATEST"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $FLATPAK_RC
|
||||||
Loading…
Reference in a new issue