CLAUDE.md, KWallet fix, install.sh bootstrap #12
@@ -0,0 +1,89 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
A Fedora automated mass-installation and post-setup scripting collection. It uses an **OEMDRV** partition (BTRFS, mounted at `/opt/sys_config`) that Anaconda/Kickstart picks up automatically during Fedora installation. The system requires:
|
||||||
|
- A FreeIPA server (domain controller, KRA vault for encryption keys)
|
||||||
|
- A Nextcloud instance (config and software sync, WebDAV token auth)
|
||||||
|
- Client PCs with an OEMDRV partition prepared beforehand
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Before any script runs, copy the dist file and fill in your environment:
|
||||||
|
```sh
|
||||||
|
cp /opt/sys_config/config/setup_system.conf.dist /opt/sys_config/config/setup_system.conf
|
||||||
|
# Edit setup_system.conf with your domain, server FQDNs, paths, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
Local per-machine overrides go in `config.d/*.conf` (gitignored). These are sourced after `setup_system.conf` and can override any exported variable (e.g. `config.d/system_defines.conf` overrides `UPGRADEBRANCH`).
|
||||||
|
|
||||||
|
`config/skel.tar.zst` (gitignored) holds the `/etc/skel` archive deployed to new installs. The `.dist` version is the default. To modify skel: extract, edit, then repack:
|
||||||
|
```sh
|
||||||
|
cd /opt/sys_config/config
|
||||||
|
tar -I 'zstd -9' -cf skel.tar.zst skel/ # or use pack_skel.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation lifecycle
|
||||||
|
|
||||||
|
1. **Kickstart pre** — `ks_base_profiles/basic_pre_script.inc` runs inside Anaconda's `%pre` section. It locates the OEMDRV partition, identifies the target drive, and **deletes all other partitions on that drive** (non-interactive, no prompt).
|
||||||
|
|
||||||
|
2. **Kickstart post** — After Fedora installs, `setup_system_full.sh install` runs (as root, non-interactive). It adjusts umask, adds the OEMDRV fstab entry, deploys `/etc/skel` via `setup_skel.sh`, and installs the `setup-system.service` systemd unit.
|
||||||
|
|
||||||
|
3. **First boot** (`firstrun_run` mode) — The `setup-system.service` unit runs `setup_system_full.sh firstrun_run` on `/dev/tty2`. It calls `ipa_register_host` which prompts for domain credentials and joins the PC to FreeIPA via `ipa-client-install`. The service then disables itself.
|
||||||
|
|
||||||
|
4. **User logon** — `logon_script.sh` is triggered by KDE autostart. It:
|
||||||
|
- Mounts the gocryptfs-encrypted home directory (`mount_ecrypt_home.sh`) using a key stored in the FreeIPA KRA vault (`IPAVAULTNAME`)
|
||||||
|
- Obtains a Nextcloud WebDAV app token (`get_nc_token` in `setup_system.inc.sh`)
|
||||||
|
- Checks for a matching IPA sudo rule, then calls `sync_client_software.sh install` as root (preserving env) to sync configs and run software installs
|
||||||
|
- Calls `client_software/user_run.sh` (as the logged-in user)
|
||||||
|
- Syncs Firefox and Thunderbird profiles via Nextcloud (`mozilla_starter.sh`)
|
||||||
|
|
||||||
|
## Script roles
|
||||||
|
|
||||||
|
| Script | Who runs it | How called |
|
||||||
|
|---|---|---|
|
||||||
|
| `system_setup/setup_system.inc.sh` | sourced, never executed directly | `source`d by all other scripts |
|
||||||
|
| `system_setup/setup_system_full.sh` | root | kickstart post, firstrun service, or manual |
|
||||||
|
| `system_setup/logon_script.sh` | domain user | KDE autostart (via `.desktop` in autostart) |
|
||||||
|
| `system_setup/sync_client_software.sh` | root (sudo, preserve-env) | called by logon_script.sh |
|
||||||
|
| `system_setup/mount_ecrypt_home.sh` | user | called by logon_script.sh |
|
||||||
|
| `system_setup/mozilla_starter.sh` | user | called by logon_script.sh; args: `firefox\|thunderbird run\|sync [profile]` |
|
||||||
|
| `system_setup/setup_skel.sh` | root | called by setup_system_full.sh or manually |
|
||||||
|
| `config/pack_skel.sh` | root | manually, to repack skel archive after editing |
|
||||||
|
| `system_setup/create_nc_package_from_sys_config.sh` | user | manually, creates `~/temp/sys_config.tar.zst` |
|
||||||
|
|
||||||
|
## client_software layout
|
||||||
|
|
||||||
|
`client_software/` is synced from Nextcloud (`CLIENT_SOFTWARE_SRC` → `CLIENT_SOFTWARE_DST`). Each numbered subdirectory may contain:
|
||||||
|
- `install.sh` — run as root by `client_software/install.sh` (iterates sorted dirs)
|
||||||
|
- `user_run.sh` — run as the logged-in user by `client_software/user_run.sh`
|
||||||
|
|
||||||
|
Naming convention: directories `< 0100` are base installs, `>= 0100` are additional apps. Pass a filter string to run only matching directories:
|
||||||
|
```sh
|
||||||
|
# Run only the kwallet install:
|
||||||
|
${CLIENT_SOFTWARE_DST}/install.sh 0010_kwallet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kickstart files
|
||||||
|
|
||||||
|
- `ks.cfg` — the primary kickstart used for production installs (Fedora 43, KDE, x86_64, German locale/keyboard)
|
||||||
|
- `ks_base_profiles/kde_fullsetup.cfg` — an alternate/reference profile generated by Anaconda
|
||||||
|
- `ks_base_profiles/minimal_setup.cfg`, `part_sda.cfg` — additional profile fragments
|
||||||
|
- `ks_pc_prof/` — per-machine kickstart overrides, named by system UUID suffix (e.g. `pc-9cdb93ef7c20.cfg`)
|
||||||
|
|
||||||
|
## Sudo rule required for logon_script
|
||||||
|
|
||||||
|
The logon script requires a FreeIPA sudo rule allowing the domain user group to run `sync_client_software.sh` as root without a password, with environment preservation. The rule must include `!authenticate` and `setenv` options. The expected command pattern:
|
||||||
|
```
|
||||||
|
^/opt/sys_config/system_setup/sync_client_software\.sh.*$
|
||||||
|
```
|
||||||
|
|
||||||
|
## gitignore notes
|
||||||
|
|
||||||
|
The following are intentionally excluded from git and must be set up locally:
|
||||||
|
- `config/setup_system.conf` — site-specific config (copy from `.dist`)
|
||||||
|
- `config/skel.tar.zst` — skel archive (copy from `.dist` or rebuild)
|
||||||
|
- `config.d/*.conf` — local overrides
|
||||||
|
- `client_software/.sync_*.db` — Nextcloud sync DB files
|
||||||
@@ -93,6 +93,35 @@ for i in {0..99}; do
|
|||||||
echo "Please check the above output!"
|
echo "Please check the above output!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# The Flatpak autoprovisioning may not successfully write the apppassword to
|
||||||
|
# KWallet from inside the sandbox, so write it directly via D-Bus.
|
||||||
|
# Nextcloud stores HTTP credentials in folder "Nextcloud" with keys:
|
||||||
|
# user:url/:0 (legacy password entry)
|
||||||
|
# user_app-password:url/:0 (app password entry, used for auth)
|
||||||
|
NC_WALLET_URL="https://${SERVERFQDN_NC}/"
|
||||||
|
NC_WALLET_APPID="logon_script"
|
||||||
|
NC_QB_CMD="qdbus-qt6"
|
||||||
|
if ! command -v ${NC_QB_CMD} >/dev/null 2>&1; then NC_QB_CMD="qdbus"; fi
|
||||||
|
NC_QB_SVC="org.kde.kwalletd"
|
||||||
|
NC_QB_PATH="/modules/kwalletd6"
|
||||||
|
if ! ( ${NC_QB_CMD} "${NC_QB_SVC}" | grep -q "${NC_QB_PATH}" ); then
|
||||||
|
NC_QB_PATH="/modules/kwalletd5"
|
||||||
|
fi
|
||||||
|
echo "Writing Nextcloud app password to KWallet via D-Bus (${NC_QB_PATH})"
|
||||||
|
NC_WALLET_HANDLE=$(${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.open "kdewallet" 0 "${NC_WALLET_APPID}")
|
||||||
|
if [[ -n "${NC_WALLET_HANDLE}" && "${NC_WALLET_HANDLE}" != "-1" ]]; then
|
||||||
|
HAS_FOLDER=$(${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.hasFolder "${NC_WALLET_HANDLE}" "Nextcloud" "${NC_WALLET_APPID}")
|
||||||
|
if [[ "${HAS_FOLDER}" != "true" ]]; then
|
||||||
|
${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.createFolder "${NC_WALLET_HANDLE}" "Nextcloud" "${NC_WALLET_APPID}" >/dev/null
|
||||||
|
fi
|
||||||
|
${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.writePassword "${NC_WALLET_HANDLE}" "Nextcloud" "${DAVTOKEN_USER}:${NC_WALLET_URL}:0" "${DAVTOKEN_PASS}" "${NC_WALLET_APPID}" >/dev/null
|
||||||
|
${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.writePassword "${NC_WALLET_HANDLE}" "Nextcloud" "${DAVTOKEN_USER}_app-password:${NC_WALLET_URL}:0" "${DAVTOKEN_PASS}" "${NC_WALLET_APPID}" >/dev/null
|
||||||
|
${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.sync "${NC_WALLET_HANDLE}" "${NC_WALLET_APPID}" >/dev/null
|
||||||
|
${NC_QB_CMD} ${NC_QB_SVC} ${NC_QB_PATH} org.kde.KWallet.close "${NC_WALLET_HANDLE}" false "${NC_WALLET_APPID}" >/dev/null
|
||||||
|
echo "Nextcloud app password written to KWallet successfully."
|
||||||
|
else
|
||||||
|
echo "Warning: Could not open KWallet (handle: ${NC_WALLET_HANDLE}). Nextcloud may prompt for credentials on next start."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
# OEMDRV Bootstrap — install.sh
|
||||||
|
|
||||||
|
the script `./system_setup/install.sh` prepares a target machine for automated Fedora deployment. It shrinks an existing partition to carve out a dedicated **OEMDRV** partition, which Anaconda/Kickstart will detect automatically during installation.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
1. Lists all ext4 and btrfs partitions that have enough free space to be shrunk.
|
||||||
|
2. Asks you to select one and shrinks it by **4 GiB**.
|
||||||
|
3. Creates a new 4 GiB BTRFS partition labeled `OEMDRV` in the freed space.
|
||||||
|
4. Mounts it to `/opt/sys_config` with `compress=zstd:6`.
|
||||||
|
5. Clones this repository (depth 1) into `/opt/sys_config`.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Run as **root** on the target machine (live system or installed OS).
|
||||||
|
- The following tools must be present: `parted`, `e2fsck`, `resize2fs` or `btrfs-progs`, `mkfs.btrfs`, `git`, `curl`.
|
||||||
|
- The partition you want to shrink must **not** be the root filesystem (`/`) and must have at least **4.5 GiB free**.
|
||||||
|
- Network access to `gitea.dtext.online`.
|
||||||
|
|
||||||
|
## Run directly from the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash <(curl -fsSL https://gitea.dtext.online/obel1x/fedora-OEMDRV/raw/branch/main/system_setup/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
> The script requires root. Run the command above as root, or prefix with `sudo`.
|
||||||
|
|
||||||
|
## After the script completes
|
||||||
|
|
||||||
|
Configure your environment before running any installation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp /opt/sys_config/config/setup_system.conf.dist /opt/sys_config/config/setup_system.conf
|
||||||
|
# Edit setup_system.conf — set TLDOMAIN, SERVERFQDN_IPA, SERVERFQDN_NC, and paths.
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally add local per-machine overrides in `config.d/`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Example: use the devel branch on this machine
|
||||||
|
echo 'export UPGRADEBRANCH="devel"' > /opt/sys_config/config.d/system_defines.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Once configured, boot the Fedora installer from USB — Anaconda will detect the `OEMDRV` partition and run the Kickstart automatically.
|
||||||
|
|
||||||
|
## Supported filesystems for shrinking
|
||||||
|
|
||||||
|
| Filesystem | Method |
|
||||||
|
|---|---|
|
||||||
|
| ext4 | `e2fsck` + `resize2fs` (offline) |
|
||||||
|
| btrfs | `btrfs filesystem resize` (temporary mount) |
|
||||||
Executable
+339
@@ -0,0 +1,339 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-FileCopyrightText: Daniel Pätzold
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# Bootstrap script: creates an OEMDRV partition by shrinking an existing
|
||||||
|
# ext4 or btrfs partition by 4 GiB, formats the freed space as BTRFS with
|
||||||
|
# label OEMDRV, mounts it to /opt/sys_config and clones the repository.
|
||||||
|
#
|
||||||
|
# Run as root on a target machine before any Kickstart installation.
|
||||||
|
|
||||||
|
SHRINK_MIB=4096
|
||||||
|
OEMDRV_LABEL="OEMDRV"
|
||||||
|
MOUNT_POINT="/opt/sys_config"
|
||||||
|
MOUNT_OPTS="compress=zstd:6"
|
||||||
|
REPO_URL="https://gitea.dtext.online/obel1x/fedora-OEMDRV.git"
|
||||||
|
MIN_FREE_MIB=$(( SHRINK_MIB + 512 )) # require 512 MiB headroom above the shrink size
|
||||||
|
|
||||||
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
die() { echo; echo "ERROR: $*" >&2; exit 1; }
|
||||||
|
info() { echo; echo ">>> $*"; }
|
||||||
|
hr() { printf '%.0s─' {1..100}; echo; }
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
[[ "$EUID" -eq 0 ]] || die "This script must be run as root."
|
||||||
|
}
|
||||||
|
|
||||||
|
check_tools() {
|
||||||
|
local missing=()
|
||||||
|
for tool in lsblk blkid parted partprobe mkfs.btrfs git e2fsck resize2fs tune2fs; do
|
||||||
|
command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
|
||||||
|
done
|
||||||
|
[[ ${#missing[@]} -eq 0 ]] || die "Missing required tools: ${missing[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Free-space helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Free MiB for a mounted device via df
|
||||||
|
mounted_free_mib() {
|
||||||
|
df --output=avail -BM "$1" 2>/dev/null | awk 'NR==2{gsub("M",""); print $1+0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Free MiB for an unmounted ext4 device via tune2fs (no mount needed)
|
||||||
|
ext4_free_mib() {
|
||||||
|
local dev="$1" fb bs
|
||||||
|
fb=$(tune2fs -l "$dev" 2>/dev/null | awk '/^Free blocks:/{print $3}')
|
||||||
|
bs=$(tune2fs -l "$dev" 2>/dev/null | awk '/^Block size:/{print $3}')
|
||||||
|
[[ -n "$fb" && -n "$bs" ]] || { echo 0; return; }
|
||||||
|
echo $(( fb * bs / 1048576 ))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Free MiB for a btrfs device – mounts temporarily if not already mounted
|
||||||
|
btrfs_free_mib() {
|
||||||
|
local dev="$1" mnt free tmp=0
|
||||||
|
mnt=$(lsblk -n -o MOUNTPOINT "$dev" 2>/dev/null | grep -v '^$' | head -1)
|
||||||
|
if [[ -z "$mnt" ]]; then
|
||||||
|
mnt=$(mktemp -d /tmp/oemdrv_check.XXXXXX)
|
||||||
|
mount -o ro "$dev" "$mnt" 2>/dev/null && tmp=1 || { rmdir "$mnt"; echo 0; return; }
|
||||||
|
fi
|
||||||
|
free=$(mounted_free_mib "$mnt")
|
||||||
|
[[ $tmp -eq 1 ]] && { umount "$mnt"; rmdir "$mnt"; }
|
||||||
|
echo "${free:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Partition discovery ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Parallel arrays, indexed 0..PT_IDX-1
|
||||||
|
PT_IDX=0
|
||||||
|
PT_DEV=() # /dev/sda2
|
||||||
|
PT_DISK=() # /dev/sda
|
||||||
|
PT_PNUM=() # 2
|
||||||
|
PT_SIZE=() # "100G"
|
||||||
|
PT_FS=() # ext4 | btrfs
|
||||||
|
PT_LABEL=() # home | -
|
||||||
|
PT_UUID=() # xxxxxxxx | -
|
||||||
|
PT_MNT=() # /home | -
|
||||||
|
PT_FREE=() # free MiB (integer)
|
||||||
|
PT_OK=() # 1 = shrinkable
|
||||||
|
|
||||||
|
collect_partitions() {
|
||||||
|
local NAME SIZE FSTYPE LABEL UUID MOUNTPOINT TYPE PKNAME
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
NAME="" SIZE="" FSTYPE="" LABEL="" UUID="" MOUNTPOINT="" TYPE="" PKNAME=""
|
||||||
|
eval "$line"
|
||||||
|
|
||||||
|
[[ "$TYPE" != "part" ]] && continue
|
||||||
|
[[ "$FSTYPE" != "ext4" && "$FSTYPE" != "btrfs" ]] && continue
|
||||||
|
[[ "$LABEL" == "$OEMDRV_LABEL" ]] && continue
|
||||||
|
[[ -z "$PKNAME" ]] && continue
|
||||||
|
|
||||||
|
local disk="/dev/$PKNAME"
|
||||||
|
local pnum
|
||||||
|
pnum=$(cat /sys/class/block/"${NAME##/dev/}"/partition 2>/dev/null) || continue
|
||||||
|
|
||||||
|
# Free space check
|
||||||
|
local free_mib
|
||||||
|
if [[ -n "$MOUNTPOINT" ]]; then
|
||||||
|
free_mib=$(mounted_free_mib "$NAME")
|
||||||
|
elif [[ "$FSTYPE" == "ext4" ]]; then
|
||||||
|
free_mib=$(ext4_free_mib "$NAME")
|
||||||
|
else
|
||||||
|
free_mib=$(btrfs_free_mib "$NAME")
|
||||||
|
fi
|
||||||
|
free_mib=${free_mib:-0}
|
||||||
|
|
||||||
|
# Shrinkable: not /, enough free space
|
||||||
|
local ok=0
|
||||||
|
[[ "$MOUNTPOINT" != "/" && "$free_mib" -ge "$MIN_FREE_MIB" ]] && ok=1
|
||||||
|
|
||||||
|
PT_DEV[$PT_IDX]="$NAME"
|
||||||
|
PT_DISK[$PT_IDX]="$disk"
|
||||||
|
PT_PNUM[$PT_IDX]="$pnum"
|
||||||
|
PT_SIZE[$PT_IDX]="$SIZE"
|
||||||
|
PT_FS[$PT_IDX]="$FSTYPE"
|
||||||
|
PT_LABEL[$PT_IDX]="${LABEL:--}"
|
||||||
|
PT_UUID[$PT_IDX]="${UUID:--}"
|
||||||
|
PT_MNT[$PT_IDX]="${MOUNTPOINT:--}"
|
||||||
|
PT_FREE[$PT_IDX]="$free_mib"
|
||||||
|
PT_OK[$PT_IDX]="$ok"
|
||||||
|
(( PT_IDX++ )) || true
|
||||||
|
done < <(lsblk -o NAME,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINT,TYPE,PKNAME -p -P -n 2>/dev/null)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Table display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
show_table() {
|
||||||
|
hr
|
||||||
|
printf " %-4s %-14s %-6s %-8s %-10s %-6s %-18s %-36s %s\n" \
|
||||||
|
"#" "Device" "Disk" "Size" "Free (MiB)" "FS" "Label" "UUID" "Mountpoint"
|
||||||
|
hr
|
||||||
|
local i mark note
|
||||||
|
for (( i=0; i<PT_IDX; i++ )); do
|
||||||
|
mark=" "
|
||||||
|
note=""
|
||||||
|
if [[ "${PT_OK[$i]}" == "1" ]]; then
|
||||||
|
mark="* "
|
||||||
|
elif [[ "${PT_MNT[$i]}" == "/" ]]; then
|
||||||
|
note=" (root — skip)"
|
||||||
|
else
|
||||||
|
note=" (not enough free space)"
|
||||||
|
fi
|
||||||
|
printf "%s%-4s %-14s %-6s %-8s %-10s %-6s %-18s %-36s %s%s\n" \
|
||||||
|
"$mark" \
|
||||||
|
"$((i+1))" \
|
||||||
|
"${PT_DEV[$i]}" \
|
||||||
|
"${PT_DISK[$i]##/dev/}" \
|
||||||
|
"${PT_SIZE[$i]}" \
|
||||||
|
"${PT_FREE[$i]}" \
|
||||||
|
"${PT_FS[$i]}" \
|
||||||
|
"${PT_LABEL[$i]}" \
|
||||||
|
"${PT_UUID[$i]}" \
|
||||||
|
"${PT_MNT[$i]}" \
|
||||||
|
"$note"
|
||||||
|
done
|
||||||
|
hr
|
||||||
|
printf " * = shrinkable: will be reduced by %s MiB to create OEMDRV\n" "$SHRINK_MIB"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Partition geometry via parted -m ─────────────────────────────────────────
|
||||||
|
|
||||||
|
part_end_mib() {
|
||||||
|
parted -s "$1" -m unit MiB print 2>/dev/null \
|
||||||
|
| awk -F: -v p="$2" '$1==p{gsub("MiB","",$3); printf "%.0f\n",$3; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
part_start_mib() {
|
||||||
|
parted -s "$1" -m unit MiB print 2>/dev/null \
|
||||||
|
| awk -F: -v p="$2" '$1==p{gsub("MiB","",$2); printf "%.0f\n",$2; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Device name for a new partition on a given disk
|
||||||
|
new_part_device() {
|
||||||
|
local disk="$1" pnum="$2"
|
||||||
|
if [[ "$disk" =~ (nvme|mmcblk) ]]; then
|
||||||
|
echo "${disk}p${pnum}"
|
||||||
|
else
|
||||||
|
echo "${disk}${pnum}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
require_root
|
||||||
|
check_tools
|
||||||
|
|
||||||
|
info "Scanning partitions for shrinkable ext4 / btrfs volumes..."
|
||||||
|
collect_partitions
|
||||||
|
|
||||||
|
[[ $PT_IDX -gt 0 ]] || die "No ext4 or btrfs partitions found on this system."
|
||||||
|
|
||||||
|
show_table
|
||||||
|
|
||||||
|
# Count shrinkable
|
||||||
|
shrink_count=0
|
||||||
|
for (( i=0; i<PT_IDX; i++ )); do [[ "${PT_OK[$i]}" == "1" ]] && (( shrink_count++ )) || true; done
|
||||||
|
[[ $shrink_count -gt 0 ]] || die "No shrinkable partitions found. A non-root ext4 or btrfs partition needs at least ${MIN_FREE_MIB} MiB free."
|
||||||
|
|
||||||
|
# ── Selection ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SEL=-1
|
||||||
|
while true; do
|
||||||
|
read -r -p "Enter number of partition to shrink, or q to quit: " INPUT
|
||||||
|
[[ "$INPUT" =~ ^[qQ]$ ]] && { echo "Aborted."; exit 0; }
|
||||||
|
[[ "$INPUT" =~ ^[0-9]+$ ]] || { echo " Please enter a number."; continue; }
|
||||||
|
local_i=$(( INPUT - 1 ))
|
||||||
|
(( local_i >= 0 && local_i < PT_IDX )) || { echo " Number out of range."; continue; }
|
||||||
|
[[ "${PT_OK[$local_i]}" == "1" ]] || { echo " That partition cannot be shrunk (see marks above)."; continue; }
|
||||||
|
SEL=$local_i
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
S_DEV="${PT_DEV[$SEL]}"
|
||||||
|
S_DISK="${PT_DISK[$SEL]}"
|
||||||
|
S_PNUM="${PT_PNUM[$SEL]}"
|
||||||
|
S_FS="${PT_FS[$SEL]}"
|
||||||
|
S_MNT="${PT_MNT[$SEL]}"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo " Partition : $S_DEV (${PT_FS[$SEL]}, ${PT_SIZE[$SEL]}, ${PT_FREE[$SEL]} MiB free)"
|
||||||
|
echo " Disk : $S_DISK Partition number: $S_PNUM"
|
||||||
|
echo " Mountpoint: $S_MNT"
|
||||||
|
echo
|
||||||
|
echo " $S_DEV will be shrunk by ${SHRINK_MIB} MiB."
|
||||||
|
echo " A new ${SHRINK_MIB} MiB BTRFS partition labeled OEMDRV will be created."
|
||||||
|
echo " It will be mounted at $MOUNT_POINT and the repository cloned into it."
|
||||||
|
echo
|
||||||
|
read -r -p " Type YES (uppercase) to confirm, anything else to abort: " CONFIRM
|
||||||
|
[[ "$CONFIRM" == "YES" ]] || { echo "Aborted."; exit 0; }
|
||||||
|
|
||||||
|
# ── Geometry ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
info "Reading partition geometry on $S_DISK..."
|
||||||
|
|
||||||
|
CURR_END=$(part_end_mib "$S_DISK" "$S_PNUM")
|
||||||
|
CURR_START=$(part_start_mib "$S_DISK" "$S_PNUM")
|
||||||
|
[[ -n "$CURR_END" && -n "$CURR_START" ]] || die "Could not read geometry for partition $S_PNUM on $S_DISK."
|
||||||
|
|
||||||
|
NEW_END=$(( CURR_END - SHRINK_MIB ))
|
||||||
|
NEW_FS_MIB=$(( NEW_END - CURR_START ))
|
||||||
|
OEMDRV_START=$NEW_END
|
||||||
|
OEMDRV_END=$CURR_END
|
||||||
|
|
||||||
|
echo " Current partition : ${CURR_START} MiB – ${CURR_END} MiB"
|
||||||
|
echo " After shrink : ${CURR_START} MiB – ${NEW_END} MiB (filesystem: ${NEW_FS_MIB} MiB)"
|
||||||
|
echo " New OEMDRV : ${OEMDRV_START} MiB – ${OEMDRV_END} MiB (${SHRINK_MIB} MiB)"
|
||||||
|
|
||||||
|
# ── Unmount ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
WAS_MOUNTED=0
|
||||||
|
ORIG_MNT=""
|
||||||
|
if [[ "$S_MNT" != "-" && -n "$S_MNT" ]]; then
|
||||||
|
info "Unmounting $S_DEV from $S_MNT..."
|
||||||
|
ORIG_MNT="$S_MNT"
|
||||||
|
umount "$S_DEV" || die "Cannot unmount $S_DEV. Close all open files and try again."
|
||||||
|
WAS_MOUNTED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Filesystem resize ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if [[ "$S_FS" == "ext4" ]]; then
|
||||||
|
info "Running e2fsck on $S_DEV..."
|
||||||
|
e2fsck -f "$S_DEV" || die "e2fsck found errors on $S_DEV. Fix them before retrying."
|
||||||
|
|
||||||
|
info "Shrinking ext4 filesystem to ${NEW_FS_MIB} MiB..."
|
||||||
|
resize2fs "$S_DEV" "${NEW_FS_MIB}M" || die "resize2fs failed."
|
||||||
|
|
||||||
|
elif [[ "$S_FS" == "btrfs" ]]; then
|
||||||
|
info "Shrinking btrfs filesystem on $S_DEV to ${NEW_FS_MIB} MiB..."
|
||||||
|
TMP_MNT=$(mktemp -d /tmp/oemdrv_btrfs.XXXXXX)
|
||||||
|
mount "$S_DEV" "$TMP_MNT" || { rmdir "$TMP_MNT"; die "Cannot mount $S_DEV for btrfs resize."; }
|
||||||
|
btrfs filesystem resize "${NEW_FS_MIB}m" "$TMP_MNT" \
|
||||||
|
|| { umount "$TMP_MNT"; rmdir "$TMP_MNT"; die "btrfs filesystem resize failed."; }
|
||||||
|
sync
|
||||||
|
umount "$TMP_MNT" && rmdir "$TMP_MNT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Partition table update ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
info "Shrinking partition $S_PNUM to ${NEW_END} MiB in partition table..."
|
||||||
|
parted -s "$S_DISK" resizepart "$S_PNUM" "${NEW_END}MiB" \
|
||||||
|
|| die "parted resizepart failed."
|
||||||
|
|
||||||
|
info "Creating new OEMDRV partition (${OEMDRV_START}–${OEMDRV_END} MiB)..."
|
||||||
|
parted -s "$S_DISK" mkpart primary btrfs "${OEMDRV_START}MiB" "${OEMDRV_END}MiB" \
|
||||||
|
|| die "parted mkpart failed. Check that the target area is free space on $S_DISK."
|
||||||
|
|
||||||
|
partprobe "$S_DISK"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Determine new partition number (highest on the disk after partprobe)
|
||||||
|
NEW_PNUM=$(parted -s "$S_DISK" -m unit MiB print 2>/dev/null \
|
||||||
|
| awk -F: '/^[0-9]/{n=$1} END{print n}')
|
||||||
|
[[ -n "$NEW_PNUM" ]] || die "Could not determine new partition number on $S_DISK."
|
||||||
|
|
||||||
|
OEMDRV_DEV=$(new_part_device "$S_DISK" "$NEW_PNUM")
|
||||||
|
[[ -b "$OEMDRV_DEV" ]] || die "New partition device $OEMDRV_DEV not found after partprobe."
|
||||||
|
|
||||||
|
info "New partition device: $OEMDRV_DEV"
|
||||||
|
|
||||||
|
# ── Format ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
info "Formatting $OEMDRV_DEV as BTRFS (label: $OEMDRV_LABEL)..."
|
||||||
|
mkfs.btrfs -f -L "$OEMDRV_LABEL" "$OEMDRV_DEV" || die "mkfs.btrfs failed."
|
||||||
|
|
||||||
|
# ── Remount original partition ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if [[ $WAS_MOUNTED -eq 1 && -n "$ORIG_MNT" ]]; then
|
||||||
|
info "Remounting $S_DEV to $ORIG_MNT..."
|
||||||
|
mount "$S_DEV" "$ORIG_MNT" \
|
||||||
|
|| echo "WARNING: Could not remount $S_DEV to $ORIG_MNT — remount manually."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Mount OEMDRV ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
info "Mounting $OEMDRV_DEV to $MOUNT_POINT (options: $MOUNT_OPTS)..."
|
||||||
|
[[ -d "$MOUNT_POINT" ]] || mkdir -p "$MOUNT_POINT"
|
||||||
|
mount -o "$MOUNT_OPTS" "$OEMDRV_DEV" "$MOUNT_POINT" || die "mount failed."
|
||||||
|
|
||||||
|
# ── Clone repository ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
info "Cloning $REPO_URL into $MOUNT_POINT..."
|
||||||
|
cd "$MOUNT_POINT" || die "Cannot cd to $MOUNT_POINT."
|
||||||
|
git clone --progress --depth 1 "$REPO_URL" . || die "git clone failed."
|
||||||
|
|
||||||
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
info "Done."
|
||||||
|
echo
|
||||||
|
echo " OEMDRV device : $OEMDRV_DEV"
|
||||||
|
echo " Mounted at : $MOUNT_POINT"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. cp $MOUNT_POINT/config/setup_system.conf.dist \\"
|
||||||
|
echo " $MOUNT_POINT/config/setup_system.conf"
|
||||||
|
echo " 2. Edit setup_system.conf with your domain, IPA/Nextcloud FQDNs, and paths."
|
||||||
|
echo " 3. Boot the Kickstart installer — it will detect the OEMDRV partition automatically."
|
||||||
|
echo
|
||||||
Reference in New Issue
Block a user