CH-J Toolsspráva serverů přes SSH
Menu
Vydané zdroje

CH-J Tools

Procházejte adresáře a soubory konkrétního vydání aplikace.

Stáhnout zdroje ZIP
CH-J / Server Manager 872 souborů
core-20261030T220507Z-61e4558d889f Přidáno: · Sestaveno:
remoteDisks.sh Přidáno: Datum není evidováno 40,9 KB · 703 řádků
Kód Stáhnout soubor
1# CH-J Server Manager - fixed remote helper for chj.disks (POSIX sh: dash, bash, BusyBox ash).
2# Started by Core as "sh -c SCRIPT chj OPERATION ARGS..." through PrivilegedExecutor (root/sudo for anything beyond the
3# unprivileged inventory). Every argument is validated by Core and again here; there is no generic command execution.
4# Changes run only as a detached job (job-start -> job-run) from a plan of whitelisted steps, so an SSH outage does not
5# stop a running change. A job is never restarted automatically; its state and log stay on the server.
6set -u
7umask 077
8LC_ALL=C; export LC_ALL
9PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
10STATE_DIR=/var/lib/chj-server-manager/disks
11JOBS=$STATE_DIR/jobs
12BACKUPS=$STATE_DIR/backups
13LOCK=/run/chj-disks.lock
14op=${1:-}
15[ $# -gt 0 ] && shift
16[ "$op" = job-run ] || printf '\036CHJ_READY\n'
18fail() { printf '\036CHJ_ERR=%s\n' "$1"; [ $# -gt 1 ] && printf '%s\n' "$2" | head -c 2000 >&2; exit 3; }
19have() { command -v "$1" >/dev/null 2>&1; }
20is_root() { [ "$(id -u)" = 0 ]; }
21need_root() { is_root || fail PRIVILEGE_REQUIRED "Root privileges are required."; }
22sec() { printf '\036CHJ_SECTION=%s\n' "$1"; }
23clean() { tr -d '|\r\n' | cut -c1-200; }
24readf() { [ -r "$1" ] && head -c 400 "$1" 2>/dev/null | tr -d '|\r\n' | sed 's/[[:space:]]*$//'; }
25b64d() {
26 if printf 'YQ==' | base64 -d >/dev/null 2>&1; then base64 -d
27 elif printf 'YQ==' | openssl base64 -d -A >/dev/null 2>&1; then openssl base64 -d -A
28 else return 127; fi
30sha_stream() { if have sha256sum; then sha256sum | cut -c1-64; else openssl dgst -sha256 | sed 's/^.*= *//'; fi; }
32# ---------- validation (Core validates first; this is the second, independent check) ----------
33valid_dev() {
34 case $1 in /dev/*) ;; *) return 1 ;; esac
35 case $1 in *[!A-Za-z0-9._/:+-]*|*..*|*//*) return 1 ;; esac
36 [ ${#1} -le 96 ] && [ -b "$1" ]
38valid_num() { case $1 in ''|*[!0-9]*) return 1 ;; esac; [ ${#1} -le 19 ]; }
39valid_label() { case $1 in -) return 0 ;; ''|*[!A-Za-z0-9._-]*) return 1 ;; esac; [ ${#1} -le 36 ]; }
40valid_id() { case $1 in ''|*[!a-z0-9-]*) return 1 ;; esac; [ ${#1} -ge 8 ] && [ ${#1} -le 64 ]; }
41valid_lv() { case $1 in */*/*|/*|*/) return 1 ;; */*) ;; *) return 1 ;; esac; case $1 in *[!A-Za-z0-9._+/-]*|*..*) return 1 ;; esac; [ ${#1} -le 128 ]; }
42valid_type() { case $1 in ''|*[!0-9A-Fa-f-]*) return 1 ;; esac; [ ${#1} -le 36 ]; }
43valid_attrs() { case $1 in -) return 0 ;; ''|*[!A-Za-z0-9,:-]*) return 1 ;; esac; [ ${#1} -le 120 ]; }
44valid_bootlist() { case $1 in -) return 0 ;; ''|*[!0-9,]*) return 1 ;; esac; [ ${#1} -le 40 ]; }
45valid_mapname() { case $1 in ''|*[!A-Za-z0-9._+-]*|*..*) return 1 ;; esac; [ ${#1} -le 96 ]; }
46valid_mountpoint() {
47 case $1 in /mnt/*|/media/*) ;; *) return 1 ;; esac
48 case $1 in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) return 1 ;; esac
49 [ ${#1} -le 120 ]
51# Kernel name (sda1, dm-3, nvme0n1p2) of a block device path, following /dev/mapper links.
52kname() { r=$(readlink -f "$1" 2>/dev/null) || return 1; printf '%s\n' "${r##*/}"; }
53majmin() { cat "/sys/class/block/$(kname "$1")/dev" 2>/dev/null; }
54sectors_of() { cat "/sys/class/block/$(kname "$1")/size" 2>/dev/null; }
55# Partition node of DISK number NUM (works for sda1, nvme0n1p1, loop0p1, mmcblk0p1).
56part_node() {
57 d=$(kname "$1"); n=$2
58 for p in /sys/block/"$d"/"$d"*; do
59 [ -r "$p/partition" ] || continue
60 [ "$(cat "$p/partition")" = "$n" ] && { printf '/dev/%s\n' "${p##*/}"; return 0; }
61 done
62 return 1
64is_mounted() { mm=$(majmin "$1") || return 1; [ -n "$mm" ] && awk -v mm="$mm" '$3==mm { found=1 } END { exit !found }' /proc/self/mountinfo; }
65is_swap_active() { awk -v d="$(readlink -f "$1")" 'NR>1 && $1==d { found=1 } END { exit !found }' /proc/swaps; }
66holders_of() { ls "/sys/class/block/$(kname "$1")/holders" 2>/dev/null; }
67# Not mounted, not active swap and no device stacked on top (dm-crypt, LVM, md).
68not_in_use() {
69 is_mounted "$1" && return 1
70 is_swap_active "$1" && return 1
71 [ -z "$(holders_of "$1")" ]
73# Any partition of DISK in use?
74disk_busy() {
75 is_mounted "$1" && return 0
76 [ -n "$(holders_of "$1")" ] && return 0
77 d=$(kname "$1")
78 for p in /sys/block/"$d"/"$d"*; do
79 [ -r "$p/partition" ] || continue
80 not_in_use "/dev/${p##*/}" || return 0
81 done
82 return 1
84mountpoint_of() { mm=$(majmin "$1"); awk -v mm="$mm" '$3==mm { gsub(/\\040/, " ", $5); print $5; exit }' /proc/self/mountinfo; }
85table_hash() { sfdisk -d "$1" 2>/dev/null | sha_stream; }
87# ---------- inventory ----------
88op_inspect() {
89 sec meta
90 # shellcheck disable=SC1091
91 [ -r /etc/os-release ] && . /etc/os-release 2>/dev/null
92 printf 'uid=%s\nos=%s\nosId=%s\nosLike=%s\nkernel=%s\n' "$(id -u)" "${PRETTY_NAME:-}" "${ID:-}" "${ID_LIKE:-}" "$(uname -r)" | clean_lines
93 [ -d /run/systemd/system ] && echo systemd=1 || echo systemd=0
94 { [ -d /etc/pve ] || have pveversion; } && echo pve=1 || echo pve=0
95 have getenforce && printf 'selinux=%s\n' "$(getenforce 2>/dev/null | clean)"
96 [ -r /proc/self/mountinfo ] && echo mountinfo=1
97 printf 'time=%s\n' "$(date +%s)"
98 sec tools
99 for t in lsblk blkid findmnt sfdisk sgdisk parted partx partprobe blockdev wipefs udevadm growpart e2fsck resize2fs dumpe2fs tune2fs e2label mkfs.ext4 xfs_growfs xfs_repair xfs_admin xfs_info mkfs.xfs btrfs mkfs.btrfs fsck.vfat fatlabel mkfs.vfat fatresize ntfsresize ntfslabel mkfs.ntfs mkswap swaplabel pvs vgs lvs pvresize lvextend lvreduce cryptsetup mdadm smartctl nvme zpool systemd-run setsid nohup base64 sha256sum mount umount; do
100 p=$(command -v "$t" 2>/dev/null) && printf '%s=%s\n' "$t" "$p"
101 done
102 sec versions
103 have sfdisk && printf 'util-linux=%s\n' "$(sfdisk --version 2>&1 | head -n 1 | clean)"
104 have e2fsck && printf 'e2fsprogs=%s\n' "$(e2fsck -V 2>&1 | head -n 1 | clean)"
105 have mkfs.xfs && printf 'xfsprogs=%s\n' "$(mkfs.xfs -V 2>&1 | head -n 1 | clean)"
106 have btrfs && printf 'btrfs-progs=%s\n' "$(btrfs --version 2>&1 | head -n 1 | clean)"
107 have lvm && printf 'lvm2=%s\n' "$(lvm version 2>/dev/null | head -n 1 | clean)"
108 have cryptsetup && printf 'cryptsetup=%s\n' "$(cryptsetup --version 2>&1 | head -n 1 | clean)"
109 have parted && printf 'parted=%s\n' "$(parted --version 2>&1 | head -n 1 | clean)"
110 have smartctl && printf 'smartmontools=%s\n' "$(smartctl --version 2>&1 | head -n 1 | clean)"
111 have nvme && printf 'nvme-cli=%s\n' "$(nvme version 2>&1 | head -n 1 | clean)"
112 have ntfsresize && printf 'ntfs-3g=%s\n' "$(ntfsresize --version 2>&1 | head -n 1 | clean)"
113 sec sysblock
114 for d in /sys/class/block/*; do
115 [ -r "$d/size" ] || continue
116 n=${d##*/}
117 real=$(readlink -f "$d")
118 part=$(cat "$d/partition" 2>/dev/null || echo -)
119 start=$(cat "$d/start" 2>/dev/null || echo -)
120 parent=-; base=$d
121 if [ "$part" != - ]; then parent=${real%/*}; parent=${parent##*/}; base=/sys/block/$parent; fi
122 q=$base/queue
123 printf '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s\n' \
124 "$n" "$(readf "$d/dev")" "$(readf "$d/size")" "$(readf "$d/ro")" "$(readf "$base/removable")" "$part" "$start" "$parent" \
125 "$(readf "$q/rotational")" "$(readf "$q/logical_block_size")" "$(readf "$q/physical_block_size")" \
126 "$(readf "$base/device/model")" "$(readf "$base/device/vendor")" "$(readf "$base/device/serial")" "$(readf "$base/device/wwid")" \
127 "$(ls "$d/holders" 2>/dev/null | tr '\n' ',')" "$(ls "$d/slaves" 2>/dev/null | tr '\n' ',')" \
128 "$(readf "$d/dm/name")" "$(readf "$d/dm/uuid")" "$(readf "$d/loop/backing_file")" "$(readf "$d/md/level")" \
129 "$(printf '%s' "$real" | clean)" "$(readf "$q/discard_max_bytes")"
130 done
131 if have lsblk; then
132 sec lsblk
133 lsblk -J -b -l -O 2>/dev/null || lsblk -J -b -l -o NAME,KNAME,PATH,TYPE,SIZE,FSTYPE,FSVER,LABEL,UUID,PARTUUID,PARTLABEL,PARTTYPE,PARTFLAGS,PTUUID,PTTYPE,MOUNTPOINT,TRAN,ROTA,MODEL,SERIAL,VENDOR,HCTL,WWN 2>/dev/null
134 fi
135 sec blkid
136 if have blkid; then
137 if is_root; then blkid -c /dev/null -o export 2>/dev/null; else blkid -o export 2>/dev/null; fi
138 fi
139 sec mountinfo
140 cat /proc/self/mountinfo 2>/dev/null
141 sec df
142 if have timeout; then timeout 15 df -kP 2>/dev/null; else df -kP 2>/dev/null; fi
143 sec swaps
144 cat /proc/swaps 2>/dev/null
145 sec mdstat
146 cat /proc/mdstat 2>/dev/null
147 if have zpool; then
148 sec zpool
149 if have timeout; then timeout 15 zpool list -vHP 2>/dev/null; else zpool list -vHP 2>/dev/null; fi
150 fi
151 is_root || return 0
152 sec pt
153 have sfdisk && for d in /sys/block/*; do
154 n=${d##*/}
155 case $n in sr*|ram*|zram*|fd*|dm-*|md*|nbd*) continue ;; esac
156 [ "$(cat "$d/size" 2>/dev/null)" = 0 ] && continue
157 case $n in loop*) [ -n "$(readf "$d/loop/backing_file")" ] && [ "$(readf "$d/ro")" = 0 ] || continue ;; esac
158 json=$(sfdisk --json "/dev/$n" 2>/dev/null) || continue
159 printf '@@disk %s %s\n' "$n" "$(table_hash "/dev/$n")"
160 printf '%s\n' "$json"
161 printf '@@end\n'
162 done
163 if have pvs; then
164 sec lvm
165 printf '@@pvs\n'; pvs --reportformat json --units b --nosuffix -o pv_name,vg_name,pv_size,pv_free,dev_size,pv_uuid,pv_attr 2>/dev/null
166 printf '@@vgs\n'; vgs --reportformat json --units b --nosuffix -o vg_name,vg_uuid,vg_size,vg_free,vg_extent_size,vg_free_count,pv_count,lv_count,vg_attr 2>/dev/null
167 printf '@@lvs\n'; lvs -a --reportformat json --units b --nosuffix -o lv_name,vg_name,lv_uuid,lv_size,lv_attr,lv_path,lv_dm_path,pool_lv,data_percent,metadata_percent,segtype,origin 2>/dev/null
168 fi
169 if have cryptsetup; then
170 sec crypt
171 for d in /sys/class/block/dm-*; do
172 [ -r "$d/dm/uuid" ] || continue
173 case $(cat "$d/dm/uuid") in CRYPT-*) ;; *) continue ;; esac
174 name=$(cat "$d/dm/name")
175 valid_mapname "$name" || continue
176 printf '@@map %s\n' "$name"
177 cryptsetup status "$name" 2>/dev/null | sed 's/^[[:space:]]*//'
178 done
179 fi
180 sec fsinfo
181 if have blkid; then
182 blkid -c /dev/null -o export 2>/dev/null | awk 'BEGIN { RS="" } { dev=""; type=""; for (i = 1; i <= NF; i++) { if ($i ~ /^DEVNAME=/) dev = substr($i, 9); if ($i ~ /^TYPE=/) type = substr($i, 6) } if (dev != "") print dev, type }' |
183 while read -r dev type; do
184 valid_dev "$dev" || continue
185 case $type in
186 ext2|ext3|ext4)
187 have dumpe2fs || continue
188 printf '@@fs %s %s\n' "$dev" "$type"
189 dumpe2fs -h "$dev" 2>/dev/null | grep -E '^(Block count|Block size|Free blocks|Reserved block count|Filesystem state|Filesystem features):' ;;
190 xfs)
191 have xfs_info || continue
192 mp=$(mountpoint_of "$dev")
193 printf '@@fs %s %s\n' "$dev" "$type"
194 if [ -n "$mp" ]; then xfs_info "$mp" 2>/dev/null | grep -E '^(data|meta-data)'; else xfs_info "$dev" 2>/dev/null | grep -E '^(data|meta-data)'; fi ;;
195 esac
196 done
197 fi
198 if have btrfs; then
199 sec btrfs
200 btrfs filesystem show --raw 2>/dev/null
201 fi
203clean_lines() { tr -d '|\r' | cut -c1-200; }
205# ---------- SMART / NVMe health (read-only) ----------
206op_smart() {
207 dev=${1:-}
208 valid_dev "$dev" || fail ARGUMENTS_INVALID "Invalid device."
209 sec smartctl
210 if have smartctl; then smartctl -j -a "$dev" 2>/dev/null || true; else echo '{"chj_unavailable":"smartctl"}'; fi
211 case $(kname "$dev") in
212 nvme*)
213 sec nvme
214 if have nvme; then
215 printf '@@smart\n'; nvme smart-log -o json "$dev" 2>/dev/null
216 printf '@@id\n'; nvme id-ctrl -o json "$dev" 2>/dev/null | head -c 200000
217 fi ;;
218 esac
221# ---------- jobs ----------
222now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
223put() { printf '%s\n' "$2" > "$JOBDIR/$1.tmp" && mv -f "$JOBDIR/$1.tmp" "$JOBDIR/$1"; }
224log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
225runner_alive() {
226 dir=$1
227 [ -r "$dir/pid" ] || return 1
228 pid=$(cat "$dir/pid"); valid_num "$pid" || return 1
229 kill -0 "$pid" 2>/dev/null || return 1
230 tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q "job-run ${dir##*/}"
232lock_owner_alive() {
233 [ -d "$LOCK" ] || return 1
234 owner=$(cat "$LOCK/job" 2>/dev/null)
235 valid_id "$owner" && runner_alive "$JOBS/$owner"
238# Each plan line is "STEP arg..." with arguments from a restricted character set (no quoting, no globbing).
239plan_line_valid() {
240 case $1 in *[!A-Za-z0-9._/:+,=@\ -]*) return 1 ;; esac
241 [ ${#1} -le 400 ]
244op_job_start() {
245 need_root
246 id=${1:-}; valid_id "$id" || fail ARGUMENTS_INVALID "Invalid job ID."
247 b64d </dev/null >/dev/null 2>&1; [ $? -ne 127 ] || fail TOOL_MISSING "base64 is required."
248 mkdir -p "$JOBS" "$BACKUPS" && chmod 700 "$STATE_DIR" "$JOBS" "$BACKUPS" || fail STATE_DIR_FAILED "The job directory cannot be created."
249 find "$JOBS" -mindepth 1 -maxdepth 1 -type d -mtime +30 -name '[a-z0-9]*' 2>/dev/null | while IFS= read -r old; do
250 valid_id "${old##*/}" && ! runner_alive "$old" && rm -rf -- "$old"
251 done
252 JOBDIR=$JOBS/$id
253 # The same job ID never starts twice: a retried request after a lost connection is refused here.
254 mkdir "$JOBDIR" 2>/dev/null || fail JOB_EXISTS "This job was already submitted; check its status instead of starting it again."
255 lock_owner_alive && { rm -rf -- "$JOBDIR"; fail BUSY "Another disk job is running on this server."; }
256 got_plan=0; got_helper=0
257 while IFS= read -r line; do
258 case $line in
259 CHJPLAN:*) printf '%s' "${line#CHJPLAN:}" | b64d > "$JOBDIR/plan" && got_plan=1 ;;
260 CHJHELPER:*) printf '%s' "${line#CHJHELPER:}" | b64d > "$JOBDIR/helper.sh" && got_helper=1 ;;
261 CHJTITLE:*) printf '%s' "${line#CHJTITLE:}" | b64d | head -c 300 | tr -d '\r\n|' > "$JOBDIR/title" ;;
262 esac
263 [ $got_plan = 1 ] && [ $got_helper = 1 ] && [ -f "$JOBDIR/title" ] && break
264 done
265 [ $got_plan = 1 ] && [ $got_helper = 1 ] || { rm -rf -- "$JOBDIR"; fail PROTOCOL_ERROR "The plan was not received."; }
266 total=0
267 while IFS= read -r line; do
268 plan_line_valid "$line" || { rm -rf -- "$JOBDIR"; fail PLAN_INVALID "The plan contains an invalid step."; }
269 total=$((total + 1))
270 done < "$JOBDIR/plan"
271 [ $total -ge 1 ] && [ $total -le 200 ] || { rm -rf -- "$JOBDIR"; fail PLAN_INVALID "The plan has no steps or too many steps."; }
272 put total "$total"; put created "$(now)"; put state queued; : > "$JOBDIR/log"
273 if [ -d /run/systemd/system ] && have systemd-run; then
274 put runner systemd
275 systemd-run --unit="chj-disks-$id" --description="CH-J Server Manager disk job $id" --collect --quiet /bin/sh "$JOBDIR/helper.sh" job-run "$id" >/dev/null 2>&1 ||
276 systemd-run --unit="chj-disks-$id" --description="CH-J Server Manager disk job $id" --quiet /bin/sh "$JOBDIR/helper.sh" job-run "$id" >/dev/null 2>&1 ||
277 { put state failed; put error "RUNNER_FAILED|systemd-run could not start the job."; fail RUNNER_FAILED "systemd-run could not start the job."; }
278 else
279 put runner setsid
280 if have setsid; then setsid /bin/sh "$JOBDIR/helper.sh" job-run "$id" </dev/null >/dev/null 2>&1 &
281 else nohup /bin/sh "$JOBDIR/helper.sh" job-run "$id" </dev/null >/dev/null 2>&1 & fi
282 fi
283 i=0; tick=0.1; sleep 0.1 2>/dev/null || tick=1
284 while [ $i -lt 50 ] && [ "$(cat "$JOBDIR/state")" = queued ]; do sleep $tick; i=$((i + 1)); [ $tick = 1 ] && i=$((i + 9)); done
285 sec job
286 printf 'id=%s\nstate=%s\nrunner=%s\n' "$id" "$(cat "$JOBDIR/state")" "$(cat "$JOBDIR/runner")"
289op_job_status() {
290 need_root
291 id=${1:-}; valid_id "$id" || fail ARGUMENTS_INVALID "Invalid job ID."
292 offset=${2:-0}; valid_num "$offset" || offset=0
293 JOBDIR=$JOBS/$id
294 [ -d "$JOBDIR" ] || fail JOB_NOT_FOUND "The job is unknown on this server."
295 # Liveness first, state second: a runner that finishes in between then shows its final state, never "unknown".
296 alive=0; runner_alive "$JOBDIR" && alive=1
297 sec job
298 printf 'id=%s\nalive=%s\n' "$id" "$alive"
299 for f in state step total created started finished runner error title; do
300 [ -r "$JOBDIR/$f" ] && printf '%s=%s\n' "$f" "$(head -c 400 "$JOBDIR/$f" | tr -d '\r\n')"
301 done
302 size=$(wc -c < "$JOBDIR/log" | tr -d ' ')
303 printf 'logSize=%s\n' "$size"
304 sec plan
305 cat "$JOBDIR/plan"
306 sec log
307 [ "$offset" -lt "$size" ] && tail -c +"$((offset + 1))" "$JOBDIR/log" | head -c 65536
308 return 0
311op_job_list() {
312 need_root
313 sec jobs
314 [ -d "$JOBS" ] || return 0
315 ls -1t "$JOBS" 2>/dev/null | head -n 30 | while IFS= read -r id; do
316 valid_id "$id" || continue
317 dir=$JOBS/$id
318 alive=0; runner_alive "$dir" && alive=1
319 printf '%s|%s|%s|%s|%s|%s|%s|%s\n' "$id" "$(cat "$dir/state" 2>/dev/null)" "$(cat "$dir/step" 2>/dev/null)" "$(cat "$dir/total" 2>/dev/null)" \
320 "$(cat "$dir/created" 2>/dev/null)" "$(cat "$dir/finished" 2>/dev/null)" "$alive" "$(head -c 300 "$dir/title" 2>/dev/null | tr -d '|\r\n')"
321 done
324# ---------- steps (each returns 0 = ok, 10 = stop: reboot/rescue needed, anything else = failed) ----------
325ERR=
326step_fail() { ERR="$1|$2"; log "ERROR $1: $2"; return 1; }
327need_tool() { have "$1" || { step_fail TOOL_MISSING "$1 is not installed on this server."; return 1; }; }
328need_dev() { valid_dev "$1" || { step_fail DEVICE_INVALID "$1 is not a valid block device."; return 1; }; }
329require_free() { not_in_use "$1" || { step_fail DEVICE_IN_USE "$1 is mounted, active swap or used by another device; it is never released automatically."; return 1; }; }
330settle() { have udevadm && udevadm settle -t 15 >/dev/null 2>&1; return 0; }
331part_geometry() { pn=$(part_node "$1" "$2") || return 1; k=$(kname "$pn"); printf '%s %s\n' "$(cat "/sys/class/block/$k/start")" "$(cat "/sys/class/block/$k/size")"; }
332# Kernel-side geometry of partition NUM, in 512-byte units as sysfs reports, converted from table sectors.
333expect_kernel() {
334 disk=$1; num=$2; want_start=$3; want_size=$4
335 lbs=$(cat "/sys/block/$(kname "$disk")/queue/logical_block_size"); f=$((lbs / 512))
336 geo=$(part_geometry "$disk" "$num") || return 1
337 [ "$geo" = "$((want_start * f)) $((want_size * f))" ]
339# Tell the kernel about partition NUM (add|update|delete). A disk with nothing in use is re-read completely.
340kernel_sync() {
341 KS_DISK=$1; KS_ACTION=$2; KS_NUM=$3
342 settle
343 if ! disk_busy "$KS_DISK" && have blockdev; then
344 if blockdev --rereadpt "$KS_DISK" 2>&1; then settle; log "kernel: partition table of $KS_DISK re-read"; return 0; fi
345 log "kernel: re-reading $KS_DISK failed; updating partition $KS_NUM only"
346 fi
347 if have partx; then
348 case $KS_ACTION in add) partx -a -n "$KS_NUM" "$KS_DISK" ;; update) partx -u -n "$KS_NUM" "$KS_DISK" ;; delete) partx -d -n "$KS_NUM" "$KS_DISK" ;; esac 2>&1
349 log "kernel: partx $KS_ACTION partition $KS_NUM of $KS_DISK (exit code $?)"
350 elif have partprobe; then partprobe "$KS_DISK" 2>&1; log "kernel: partprobe $KS_DISK (exit code $?)"
351 fi
352 settle
353 return 0
355pending_reboot() {
356 ERR="REBOOT_REQUIRED|$1"
357 log "STOP: $1"
358 log "The partition table on disk is already changed. The running kernel still uses the old layout; reboot the server (or finish in a rescue system) before continuing. The remaining steps were not executed."
359 return 10
362s_verify_disk() { # DISK SECTORS TABLEHASH
363 need_dev "$1" || return 1
364 [ "$(sectors_of "$1")" = "$2" ] || { step_fail LAYOUT_CHANGED "The size of $1 changed since the plan was made."; return 1; }
365 need_tool sfdisk || return 1
366 h=$(table_hash "$1"); [ -n "$(sfdisk -d "$1" 2>/dev/null)" ] || h=none
367 [ "$h" = "$3" ] || { step_fail LAYOUT_CHANGED "The partition table of $1 changed since the plan was made; nothing was modified."; return 1; }
368 log "verified: $1 has the planned size and partition table"
370s_verify_size() { # DEV BYTES (current kernel size)
371 need_dev "$1" || return 1
372 [ "$(( $(sectors_of "$1") * 512 ))" = "$2" ] || { step_fail LAYOUT_CHANGED "The size of $1 changed since the plan was made."; return 1; }
373 log "verified: $1 is $2 bytes"
375s_backup_table() { # DISK
376 need_dev "$1" || return 1; need_tool sfdisk || return 1
377 n=$(kname "$1"); stamp=$(date -u +%Y%m%dT%H%M%SZ)
378 mkdir -p "$BACKUPS" && chmod 700 "$BACKUPS"
379 if sfdisk -d "$1" > "$JOBDIR/table-$n.sfdisk" 2>/dev/null && [ -s "$JOBDIR/table-$n.sfdisk" ]; then
380 cp "$JOBDIR/table-$n.sfdisk" "$BACKUPS/$n-$stamp-$JOBID.sfdisk"
381 log "partition table metadata of $1 saved to $BACKUPS/$n-$stamp-$JOBID.sfdisk (restore with: sfdisk $1 < FILE). This is NOT a backup of the data."
382 else
383 log "$1 has no partition table; nothing to save"
384 fi
385 # Keep the 50 newest metadata backups.
386 ls -1t "$BACKUPS" 2>/dev/null | tail -n +51 | while IFS= read -r old; do rm -f -- "$BACKUPS/$old"; done
388s_require_free() { need_dev "$1" || return 1; require_free "$1" || return 1; log "checked: $1 is not in use"; }
389s_gpt_relocate() { # DISK
390 need_dev "$1" || return 1; need_tool sfdisk || return 1
391 sfdisk --no-reread --no-tell-kernel --relocate gpt-bak-std "$1" || { have sgdisk && sgdisk -e "$1"; } || { step_fail TOOL_FAILED "The backup GPT header could not be moved to the end of $1."; return 1; }
392 log "backup GPT header moved to the end of $1"
394s_create_table() { # DISK gpt|dos
395 need_dev "$1" || return 1; need_tool sfdisk || return 1
396 case $2 in gpt|dos) ;; *) step_fail ARGUMENTS_INVALID "Unsupported partition table type."; return 1 ;; esac
397 disk_busy "$1" && { step_fail DEVICE_IN_USE "A partition of $1 is in use."; return 1; }
398 printf 'label: %s\n' "$2" | sfdisk --wipe always --no-reread --no-tell-kernel "$1" || { step_fail TOOL_FAILED "sfdisk could not write the new table."; return 1; }
399 kernel_sync "$1" add 1
400 d=$(kname "$1")
401 for p in /sys/block/"$d"/"$d"*; do [ -r "$p/partition" ] && { pending_reboot "The kernel still lists old partitions of $1."; return 10; }; done
402 log "new empty $2 partition table written to $1"
404s_create_part() { # DISK NUM START SIZE TYPE NAME
405 need_dev "$1" || return 1; need_tool sfdisk || return 1
406 part_node "$1" "$2" >/dev/null && { step_fail LAYOUT_CHANGED "Partition $2 already exists on $1."; return 1; }
407 line="start=$3, size=$4, type=$5"; [ "$6" != - ] && line="$line, name=\"$6\""
408 # Fields are digits/GUID/hex and a validated name; sfdisk reads them from stdin, never from a shell expansion.
409 printf '%s\n' "$line" | sfdisk --no-reread --no-tell-kernel --wipe-partitions always -N "$2" "$1" || { step_fail TOOL_FAILED "sfdisk could not create partition $2."; return 1; }
410 kernel_sync "$1" add "$2"
411 expect_kernel "$1" "$2" "$3" "$4" || { pending_reboot "The kernel did not register the new partition $2 of $1."; return 10; }
412 log "created partition $2 on $1 (start $3, $4 sectors)"
414s_delete_part() { # DISK NUM START SIZE
415 need_dev "$1" || return 1; need_tool sfdisk || return 1
416 pn=$(part_node "$1" "$2") || { step_fail LAYOUT_CHANGED "Partition $2 of $1 does not exist."; return 1; }
417 expect_kernel "$1" "$2" "$3" "$4" || { step_fail LAYOUT_CHANGED "Partition $2 of $1 is not where the plan expects it."; return 1; }
418 require_free "$pn" || return 1
419 sfdisk --no-reread --no-tell-kernel --delete "$1" "$2" || { step_fail TOOL_FAILED "sfdisk could not delete partition $2."; return 1; }
420 kernel_sync "$1" delete "$2"
421 part_node "$1" "$2" >/dev/null && { pending_reboot "The kernel still lists the deleted partition $2 of $1."; return 10; }
422 log "deleted partition $2 of $1"
424s_resize_part() { # DISK NUM START OLDSIZE NEWSIZE
425 need_dev "$1" || return 1; need_tool sfdisk || return 1
426 part_node "$1" "$2" >/dev/null || { step_fail LAYOUT_CHANGED "Partition $2 of $1 does not exist."; return 1; }
427 expect_kernel "$1" "$2" "$3" "$4" || { step_fail LAYOUT_CHANGED "Partition $2 of $1 is not where the plan expects it."; return 1; }
428 printf 'start=%s, size=%s\n' "$3" "$5" | sfdisk --no-reread --no-tell-kernel -N "$2" "$1" || { step_fail TOOL_FAILED "sfdisk could not resize partition $2."; return 1; }
429 kernel_sync "$1" update "$2"
430 expect_kernel "$1" "$2" "$3" "$5" || { pending_reboot "The kernel did not take over the new size of partition $2 of $1."; return 10; }
431 log "partition $2 of $1 now has $5 sectors (was $4)"
433s_set_type() { need_dev "$1" || return 1; need_tool sfdisk || return 1; sfdisk --no-reread --no-tell-kernel --part-type "$1" "$2" "$3" || { step_fail TOOL_FAILED "The partition type could not be changed."; return 1; }; settle; log "partition $2 of $1: type $3"; }
434s_set_name() { need_dev "$1" || return 1; need_tool sfdisk || return 1; n=$3; [ "$n" = - ] && n=""; sfdisk --no-reread --no-tell-kernel --part-label "$1" "$2" "$n" || { step_fail TOOL_FAILED "The partition name could not be changed."; return 1; }; settle; log "partition $2 of $1: name '$n'"; }
435s_set_attrs() { need_dev "$1" || return 1; need_tool sfdisk || return 1; a=$3; [ "$a" = - ] && a=""; sfdisk --no-reread --no-tell-kernel --part-attrs "$1" "$2" "$a" || { step_fail TOOL_FAILED "The GPT attributes could not be changed."; return 1; }; settle; log "partition $2 of $1: attributes '$a'"; }
436s_set_boot() { # DISK LIST(1,3 or -)
437 need_dev "$1" || return 1; need_tool sfdisk || return 1
438 if [ "$2" = - ]; then sfdisk --no-reread --no-tell-kernel --activate "$1" - ; else sfdisk --no-reread --no-tell-kernel --activate "$1" $(printf '%s' "$2" | tr ',' ' '); fi || { step_fail TOOL_FAILED "The boot flag could not be changed."; return 1; }
439 log "boot flag of $1: $2"
441s_mkfs() { # DEV FSTYPE LABEL
442 need_dev "$1" || return 1; require_free "$1" || return 1
443 l=$3
444 case $2 in
445 ext4) need_tool mkfs.ext4 || return 1; if [ "$l" = - ]; then mkfs.ext4 -F -q "$1"; else mkfs.ext4 -F -q -L "$l" "$1"; fi ;;
446 xfs) need_tool mkfs.xfs || return 1; if [ "$l" = - ]; then mkfs.xfs -f -q "$1"; else mkfs.xfs -f -q -L "$l" "$1"; fi ;;
447 btrfs) need_tool mkfs.btrfs || return 1; if [ "$l" = - ]; then mkfs.btrfs -f -q "$1"; else mkfs.btrfs -f -q -L "$l" "$1"; fi ;;
448 vfat) need_tool mkfs.vfat || return 1; if [ "$l" = - ]; then mkfs.vfat -F 32 "$1"; else mkfs.vfat -F 32 -n "$l" "$1"; fi ;;
449 ntfs) need_tool mkfs.ntfs || return 1; if [ "$l" = - ]; then mkfs.ntfs -Q -F "$1"; else mkfs.ntfs -Q -F -L "$l" "$1"; fi ;;
450 swap) need_tool mkswap || return 1; if [ "$l" = - ]; then mkswap "$1"; else mkswap -L "$l" "$1"; fi ;;
451 wipe) need_tool wipefs || return 1; wipefs -a "$1" ;;
452 *) step_fail ARGUMENTS_INVALID "Unsupported file system."; return 1 ;;
453 esac >/dev/null || { step_fail TOOL_FAILED "Creating $2 on $1 failed."; return 1; }
454 settle
455 log "created $2 on $1"
457s_set_label() { # DEV FSTYPE LABEL
458 need_dev "$1" || return 1; l=$3; [ "$l" = - ] && l=""
459 case $2 in
460 ext2|ext3|ext4) need_tool e2label || return 1; e2label "$1" "$l" ;;
461 xfs) need_tool xfs_admin || return 1; require_free "$1" || return 1; if [ -n "$l" ]; then xfs_admin -L "$l" "$1"; else xfs_admin -L -- "$1"; fi ;;
462 btrfs) need_tool btrfs || return 1; mp=$(mountpoint_of "$1"); btrfs filesystem label "${mp:-$1}" "$l" ;;
463 vfat) need_tool fatlabel || return 1; require_free "$1" || return 1; [ -n "$l" ] || { step_fail ARGUMENTS_INVALID "FAT labels cannot be removed here."; return 1; }; fatlabel "$1" "$l" ;;
464 ntfs) need_tool ntfslabel || return 1; require_free "$1" || return 1; ntfslabel "$1" "$l" ;;
465 swap) need_tool swaplabel || return 1; swaplabel -L "$l" "$1" ;;
466 *) step_fail ARGUMENTS_INVALID "Labels of this file system are not supported."; return 1 ;;
467 esac >/dev/null || { step_fail TOOL_FAILED "The label could not be set."; return 1; }
468 settle; log "label of $1: '$l'"
470s_fsck() { # DEV FSTYPE check|repair
471 need_dev "$1" || return 1; require_free "$1" || return 1
472 case $2:$3 in
473 ext2:check|ext3:check|ext4:check) need_tool e2fsck || return 1; e2fsck -f -n "$1"; rc=$? ;;
474 ext2:repair|ext3:repair|ext4:repair) need_tool e2fsck || return 1; e2fsck -f -p "$1"; rc=$?; [ $rc -le 1 ] && rc=0 ;;
475 xfs:check) need_tool xfs_repair || return 1; xfs_repair -n "$1"; rc=$? ;;
476 xfs:repair) need_tool xfs_repair || return 1; xfs_repair "$1"; rc=$? ;;
477 btrfs:check) need_tool btrfs || return 1; btrfs check --readonly "$1"; rc=$? ;;
478 vfat:check) need_tool fsck.vfat || return 1; fsck.vfat -n "$1"; rc=$? ;;
479 vfat:repair) need_tool fsck.vfat || return 1; fsck.vfat -a "$1"; rc=$?; [ $rc -le 1 ] && rc=0 ;;
480 *) step_fail ARGUMENTS_INVALID "This check is not supported for $2."; return 1 ;;
481 esac
482 [ "$rc" = 0 ] || { step_fail FSCK_ERRORS "The check of $1 reported problems (exit code $rc); see the log."; return 1; }
483 log "file system check of $1 ($3): clean"
485ext_size() { dumpe2fs -h "$1" 2>/dev/null | awk -F: '/^Block count/ { c=$2+0 } /^Block size/ { s=$2+0 } END { printf "%.0f\n", c*s }'; }
486s_grow_fs() { # DEV FSTYPE
487 need_dev "$1" || return 1
488 devbytes=$(( $(sectors_of "$1") * 512 ))
489 case $2 in
490 ext2|ext3|ext4)
491 need_tool resize2fs || return 1
492 resize2fs "$1" || { step_fail TOOL_FAILED "resize2fs could not grow $1."; return 1; }
493 log "ext file system on $1 is now $(ext_size "$1") bytes (device $devbytes)" ;;
494 xfs)
495 need_tool xfs_growfs || return 1
496 mp=$(mountpoint_of "$1"); [ -n "$mp" ] || { step_fail FS_NOT_MOUNTED "XFS can only be grown while mounted; mount $1 first."; return 1; }
497 xfs_growfs "$mp" || { step_fail TOOL_FAILED "xfs_growfs failed."; return 1; }
498 log "XFS on $mp grown to the size of $1" ;;
499 btrfs)
500 need_tool btrfs || return 1
501 mp=$(mountpoint_of "$1"); [ -n "$mp" ] || { step_fail FS_NOT_MOUNTED "Btrfs can only be resized while mounted; mount $1 first."; return 1; }
502 devid=$(btrfs filesystem show "$mp" 2>/dev/null | awk -v d="$(readlink -f "$1")" '$1=="devid" && $NF==d { print $2 }')
503 valid_num "$devid" || { step_fail DEVICE_INVALID "$1 is not a member of the Btrfs file system at $mp."; return 1; }
504 btrfs filesystem resize "$devid:max" "$mp" || { step_fail TOOL_FAILED "btrfs resize failed."; return 1; }
505 log "Btrfs device $devid on $mp grown to max" ;;
506 ntfs)
507 need_tool ntfsresize || return 1; require_free "$1" || return 1
508 ntfsresize --no-action --force "$1" >/dev/null || { step_fail TOOL_FAILED "ntfsresize refused the dry run."; return 1; }
509 printf 'y\n' | ntfsresize --force "$1" || { step_fail TOOL_FAILED "ntfsresize failed."; return 1; }
510 log "NTFS on $1 grown to the partition size" ;;
511 swap)
512 need_tool mkswap || return 1; require_free "$1" || return 1
513 uuid=$(blkid -s UUID -o value "$1" 2>/dev/null); lab=$(blkid -s LABEL -o value "$1" 2>/dev/null)
514 case $uuid in *[!A-Fa-f0-9-]*) uuid= ;; esac
515 set -- "$1"; [ -n "$uuid" ] && set -- -U "$uuid" "$@"; [ -n "$lab" ] && valid_label "$lab" && set -- -L "$lab" "$@"
516 mkswap "$@" >/dev/null || { step_fail TOOL_FAILED "mkswap failed."; return 1; }
517 log "swap signature rewritten for the new size (UUID and label kept)" ;;
518 *) step_fail ARGUMENTS_INVALID "Growing $2 is not supported."; return 1 ;;
519 esac
520 settle
522s_shrink_fs() { # DEV FSTYPE NEWBYTES
523 need_dev "$1" || return 1; valid_num "$3" || { step_fail ARGUMENTS_INVALID "Invalid size."; return 1; }
524 case $2 in
525 ext2|ext3|ext4)
526 need_tool resize2fs || return 1; need_tool e2fsck || return 1
527 require_free "$1" || return 1
528 log "consistency check before shrinking (e2fsck -f -p)"
529 e2fsck -f -p "$1"; rc=$?
530 [ $rc -le 1 ] || { step_fail FSCK_ERRORS "e2fsck reported problems (exit code $rc); the file system was not shrunk."; return 1; }
531 bs=$(dumpe2fs -h "$1" 2>/dev/null | awk -F: '/^Block size/ { print $2+0 }')
532 min=$(resize2fs -P "$1" 2>/dev/null | awk -F: '/[Mm]inimum/ { print $2+0 }')
533 valid_num "$bs" && valid_num "$min" || { step_fail TOOL_FAILED "The minimum size of $1 could not be determined."; return 1; }
534 minbytes=$((min * bs)); margin=$((minbytes / 10)); [ $margin -ge 134217728 ] || margin=134217728
535 [ "$3" -ge $((minbytes + margin)) ] || { step_fail SIZE_TOO_SMALL "The requested size is below the minimum $minbytes bytes plus a safety reserve of $margin bytes."; return 1; }
536 blocks=$(($3 / bs))
537 log "shrinking ext file system on $1 to $blocks blocks of $bs bytes (minimum $min blocks)"
538 resize2fs "$1" "$blocks" || { step_fail TOOL_FAILED "resize2fs could not shrink $1."; return 1; }
539 [ "$(ext_size "$1")" = "$((blocks * bs))" ] || { step_fail VERIFY_FAILED "The file system size after shrinking is not the expected value."; return 1; }
540 log "ext file system on $1 is now $((blocks * bs)) bytes" ;;
541 btrfs)
542 need_tool btrfs || return 1
543 mp=$(mountpoint_of "$1"); [ -n "$mp" ] || { step_fail FS_NOT_MOUNTED "Btrfs can only be resized while mounted."; return 1; }
544 devs=$(btrfs filesystem show "$mp" 2>/dev/null | awk '$1=="devid"' | wc -l)
545 [ "$devs" = 1 ] || { step_fail UNSUPPORTED "Shrinking multi-device Btrfs is not supported."; return 1; }
546 devid=$(btrfs filesystem show --raw "$mp" 2>/dev/null | awk '$1=="devid" { print $2 }')
547 used=$(btrfs filesystem show --raw "$mp" 2>/dev/null | awk '$1=="devid" { print $6 }')
548 valid_num "$devid" || { step_fail TOOL_FAILED "The Btrfs device ID could not be read."; return 1; }
549 valid_num "$used" || { step_fail TOOL_FAILED "The Btrfs usage could not be read."; return 1; }
550 margin=$((used / 10)); [ $margin -ge 268435456 ] || margin=268435456
551 [ "$3" -ge $((used + margin)) ] || { step_fail SIZE_TOO_SMALL "The requested size is below the allocated $used bytes plus a reserve of $margin bytes."; return 1; }
552 btrfs filesystem resize "$devid:$3" "$mp" || { step_fail TOOL_FAILED "btrfs resize failed."; return 1; }
553 log "Btrfs on $mp shrunk to $3 bytes" ;;
554 ntfs)
555 need_tool ntfsresize || return 1; require_free "$1" || return 1
556 ntfsresize --no-action --force -s "$3" "$1" >/dev/null || { step_fail SIZE_TOO_SMALL "ntfsresize refused the dry run for $3 bytes."; return 1; }
557 printf 'y\n' | ntfsresize --force -s "$3" "$1" || { step_fail TOOL_FAILED "ntfsresize failed."; return 1; }
558 log "NTFS on $1 shrunk to $3 bytes" ;;
559 *) step_fail UNSUPPORTED "Shrinking $2 is not supported."; return 1 ;;
560 esac
561 settle
563s_luks_resize() { # MAPNAME
564 need_tool cryptsetup || return 1; valid_mapname "$1" || { step_fail ARGUMENTS_INVALID "Invalid mapping name."; return 1; }
565 [ -b "/dev/mapper/$1" ] || { step_fail DEVICE_INVALID "The LUKS mapping $1 is not open."; return 1; }
566 # Never prompts: without a reusable volume key cryptsetup fails here and nothing else changes.
567 cryptsetup resize --batch-mode "$1" </dev/null || { step_fail LUKS_PASSPHRASE_REQUIRED "cryptsetup needs the passphrase to resize $1; CH-J never handles LUKS secrets. Run 'cryptsetup resize $1' manually, then continue."; return 1; }
568 log "LUKS mapping $1 now uses the whole underlying device"
570s_pv_resize() { need_tool pvresize || return 1; need_dev "$1" || return 1; pvresize "$1" || { step_fail TOOL_FAILED "pvresize failed."; return 1; }; log "physical volume $1 resized"; }
571s_lv_extend() { # VG/LV BYTES|max
572 need_tool lvextend || return 1; valid_lv "$1" || { step_fail ARGUMENTS_INVALID "Invalid logical volume."; return 1; }
573 if [ "$2" = max ]; then lvextend -l +100%FREE "$1"; else valid_num "$2" || { step_fail ARGUMENTS_INVALID "Invalid size."; return 1; }; lvextend -L "${2}b" "$1"; fi || { step_fail TOOL_FAILED "lvextend failed."; return 1; }
574 settle; log "logical volume $1 extended"
576s_lv_reduce() { # VG/LV BYTES
577 need_tool lvreduce || return 1; valid_lv "$1" || { step_fail ARGUMENTS_INVALID "Invalid logical volume."; return 1; }
578 valid_num "$2" || { step_fail ARGUMENTS_INVALID "Invalid size."; return 1; }
579 require_free "/dev/$1" || return 1
580 lvreduce -f -L "${2}b" "$1" || { step_fail TOOL_FAILED "lvreduce failed."; return 1; }
581 settle; log "logical volume $1 reduced to $2 bytes"
583s_mount() { # DEV MOUNTPOINT
584 need_dev "$1" || return 1; valid_mountpoint "$2" || { step_fail ARGUMENTS_INVALID "Mount points must be below /mnt or /media."; return 1; }
585 is_mounted "$1" && { step_fail DEVICE_IN_USE "$1 is already mounted."; return 1; }
586 if [ -e "$2" ]; then [ -d "$2" ] && [ ! -L "$2" ] || { step_fail ARGUMENTS_INVALID "$2 is not a directory."; return 1; }; [ -z "$(ls -A "$2" 2>/dev/null)" ] || { step_fail ARGUMENTS_INVALID "$2 is not empty."; return 1; }
587 else mkdir -p "$2" && chmod 755 "$2"; fi
588 mount "$1" "$2" || { step_fail TOOL_FAILED "mount failed."; return 1; }
589 log "$1 mounted at $2 (temporary, /etc/fstab unchanged)"
591system_mount() {
592 case $1 in /|/boot|/boot/*|/efi|/usr|/usr/*|/var|/var/*|/home|/opt|/srv|/root|/tmp|/nix|/snap|/snap/*) return 0 ;; esac
593 return 1
595s_unmount() { # DEV
596 need_dev "$1" || return 1
597 mm=$(majmin "$1")
598 mps=$(awk -v mm="$mm" '$3==mm { gsub(/\\040/, " ", $5); print $5 }' /proc/self/mountinfo)
599 [ -n "$mps" ] || { log "$1 is not mounted"; return 0; }
600 oldifs=$IFS; IFS='
602 for mp in $mps; do system_mount "$mp" && { IFS=$oldifs; step_fail SYSTEM_MOUNT "$mp is needed by the running system; CH-J never unmounts it."; return 1; }; done
603 for mp in $mps; do umount "$mp" || { IFS=$oldifs; step_fail DEVICE_BUSY "$mp is busy and was not unmounted (no forced or lazy unmount)."; return 1; }; done
604 IFS=$oldifs
605 log "$1 unmounted"
607s_verify_part() { expect_kernel "$1" "$2" "$3" "$4" || { step_fail VERIFY_FAILED "Partition $2 of $1 does not have the expected geometry."; return 1; }; log "verified: partition $2 of $1"; }
608s_verify_fs() { # DEV FSTYPE
609 need_dev "$1" || return 1
610 case $2 in
611 ext2|ext3|ext4)
612 if is_mounted "$1"; then log "mounted ext file system on $1: $(ext_size "$1") bytes (online check skipped)"
613 else e2fsck -f -n "$1" >/dev/null 2>&1 || { step_fail VERIFY_FAILED "e2fsck -n reported problems on $1."; return 1; }; log "verified: e2fsck -n clean, $(ext_size "$1") bytes"; fi ;;
614 xfs) mp=$(mountpoint_of "$1"); if [ -n "$mp" ]; then xfs_info "$mp" 2>/dev/null | grep -E '^data' ; else xfs_repair -n "$1" >/dev/null 2>&1 || { step_fail VERIFY_FAILED "xfs_repair -n reported problems on $1."; return 1; }; fi; log "verified XFS on $1" ;;
615 *) log "verified: $1 present" ;;
616 esac
618s_disabled() { step_fail OPERATION_DISABLED "This operation is not enabled in this version."; }
620run_step() {
621 name=$1; shift
622 case $name in
623 verify-disk) [ $# = 3 ] && s_verify_disk "$@" ;;
624 verify-size) [ $# = 2 ] && s_verify_size "$@" ;;
625 backup-table) [ $# = 1 ] && s_backup_table "$@" ;;
626 require-free) [ $# = 1 ] && s_require_free "$@" ;;
627 gpt-relocate) [ $# = 1 ] && s_gpt_relocate "$@" ;;
628 create-table) [ $# = 2 ] && s_create_table "$@" ;;
629 create-part) [ $# = 6 ] && valid_num "$2" && valid_num "$3" && valid_num "$4" && valid_type "$5" && valid_label "$6" && s_create_part "$@" ;;
630 delete-part) [ $# = 4 ] && valid_num "$2" && valid_num "$3" && valid_num "$4" && s_delete_part "$@" ;;
631 resize-part) [ $# = 5 ] && valid_num "$2" && valid_num "$3" && valid_num "$4" && valid_num "$5" && s_resize_part "$@" ;;
632 set-type) [ $# = 3 ] && valid_num "$2" && valid_type "$3" && s_set_type "$@" ;;
633 set-name) [ $# = 3 ] && valid_num "$2" && valid_label "$3" && s_set_name "$@" ;;
634 set-attrs) [ $# = 3 ] && valid_num "$2" && valid_attrs "$3" && s_set_attrs "$@" ;;
635 set-boot) [ $# = 2 ] && valid_bootlist "$2" && s_set_boot "$@" ;;
636 mkfs) [ $# = 3 ] && valid_label "$3" && s_mkfs "$@" ;;
637 set-label) [ $# = 3 ] && valid_label "$3" && s_set_label "$@" ;;
638 fsck) [ $# = 3 ] && s_fsck "$@" ;;
639 grow-fs) [ $# = 2 ] && s_grow_fs "$@" ;;
640 shrink-fs) [ $# = 3 ] && s_shrink_fs "$@" ;;
641 luks-resize) [ $# = 1 ] && s_luks_resize "$@" ;;
642 pv-resize) [ $# = 1 ] && s_pv_resize "$@" ;;
643 lv-extend) [ $# = 2 ] && s_lv_extend "$@" ;;
644 lv-reduce) [ $# = 2 ] && s_lv_reduce "$@" ;;
645 mount) [ $# = 2 ] && s_mount "$@" ;;
646 unmount) [ $# = 1 ] && s_unmount "$@" ;;
647 verify-part) [ $# = 4 ] && s_verify_part "$@" ;;
648 verify-fs) [ $# = 2 ] && s_verify_fs "$@" ;;
649 move-part|copy-part) s_disabled ;;
650 *) step_fail PLAN_INVALID "Unknown step $name."; return 1 ;;
651 esac
652 rc=$?
653 [ $rc = 0 ] || [ -n "$ERR" ] || ERR="PLAN_INVALID|Invalid arguments for step $name."
654 return $rc
657op_job_run() {
658 JOBID=${1:-}; valid_id "$JOBID" || exit 2
659 JOBDIR=$JOBS/$JOBID
660 [ -d "$JOBDIR" ] && [ "$(cat "$JOBDIR/state" 2>/dev/null)" = queued ] || exit 2
661 trap '' HUP INT PIPE
662 cd / || exit 2
663 exec >>"$JOBDIR/log" 2>&1 </dev/null
664 printf '%s\n' "$$" > "$JOBDIR/pid"
665 # One modifying disk job per server: conflicting changes on shared layers (disk, LUKS, LVM) are impossible.
666 if ! mkdir "$LOCK" 2>/dev/null; then
667 if lock_owner_alive; then put state failed; put error "BUSY|Another disk job is running on this server."; put finished "$(now)"; exit 1; fi
668 rm -rf -- "$LOCK"; mkdir "$LOCK" || { put state failed; put error "BUSY|The job lock is unavailable."; exit 1; }
669 fi
670 printf '%s\n' "$JOBID" > "$LOCK/job"
671 trap 'rm -rf -- "$LOCK"' EXIT
672 put started "$(now)"; put state running
673 log "job $JOBID started ($(cat "$JOBDIR/total") steps, runner $(cat "$JOBDIR/runner" 2>/dev/null))"
674 # Step functions share the global namespace (POSIX sh has no locals), so the runner uses JOB_* names only.
675 JOB_N=0; JOB_TOTAL=$(cat "$JOBDIR/total"); JOB_FINAL=succeeded
676 while IFS= read -r JOB_LINE <&3; do
677 JOB_N=$((JOB_N + 1)); put step "$JOB_N"
678 plan_line_valid "$JOB_LINE" || { ERR="PLAN_INVALID|Invalid step."; JOB_FINAL=failed; break; }
679 printf '\036STEP %s|%s|running|%s\n' "$JOB_N" "$JOB_TOTAL" "$JOB_LINE"
680 log "step $JOB_N/$JOB_TOTAL: $JOB_LINE"
681 ERR=
682 # Plan lines contain no glob or quote characters (plan_line_valid), so plain word splitting is safe.
683 # shellcheck disable=SC2086
684 run_step $JOB_LINE
685 JOB_RC=$?
686 if [ $JOB_RC = 0 ]; then printf '\036STEP %s|%s|done|\n' "$JOB_N" "$JOB_TOTAL"; continue; fi
687 if [ $JOB_RC = 10 ]; then JOB_FINAL=pending-reboot; printf '\036STEP %s|%s|stopped|\n' "$JOB_N" "$JOB_TOTAL"; else JOB_FINAL=failed; printf '\036STEP %s|%s|failed|\n' "$JOB_N" "$JOB_TOTAL"; fi
688 break
689 done 3< "$JOBDIR/plan"
690 [ -n "$ERR" ] && put error "$ERR"
691 put finished "$(now)"; put state "$JOB_FINAL"
692 log "job $JOBID finished: $JOB_FINAL"
695case $op in
696 inspect) op_inspect ;;
697 smart) op_smart "$@" ;;
698 job-start) op_job_start "$@" ;;
699 job-status) op_job_status "$@" ;;
700 job-list) op_job_list ;;
701 job-run) op_job_run "$@" ;;
702 *) fail ARGUMENTS_INVALID "Unknown operation." ;;
703esac
SHA-25605099d53ccdf14777d7810e78416eef9fb42883957345ab4b40297abfec17df2

CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.

SHA-256 archivu77380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df
Soubory