Veröffentlichte Quellen
Quellen als ZIP herunterladen
CH-J Tools
Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.
CH-J / Server Manager
872 Dateien
core-20261030T220507Z-61e4558d889f
Hinzugefügt: · Erstellt:
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 the3
# 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 not5
# stop a running change. A job is never restarted automatically; its state and log stay on the server.6
set -u7
umask 0778
LC_ALL=C; export LC_ALL9
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH10
STATE_DIR=/var/lib/chj-server-manager/disks11
JOBS=$STATE_DIR/jobs12
BACKUPS=$STATE_DIR/backups13
LOCK=/run/chj-disks.lock14
op=${1:-}15
[ $# -gt 0 ] && shift16
[ "$op" = job-run ] || printf '\036CHJ_READY\n'18
fail() { printf '\036CHJ_ERR=%s\n' "$1"; [ $# -gt 1 ] && printf '%s\n' "$2" | head -c 2000 >&2; exit 3; }19
have() { command -v "$1" >/dev/null 2>&1; }20
is_root() { [ "$(id -u)" = 0 ]; }21
need_root() { is_root || fail PRIVILEGE_REQUIRED "Root privileges are required."; }22
sec() { printf '\036CHJ_SECTION=%s\n' "$1"; }23
clean() { tr -d '|\r\n' | cut -c1-200; }24
readf() { [ -r "$1" ] && head -c 400 "$1" 2>/dev/null | tr -d '|\r\n' | sed 's/[[:space:]]*$//'; }25
b64d() {26
if printf 'YQ==' | base64 -d >/dev/null 2>&1; then base64 -d27
elif printf 'YQ==' | openssl base64 -d -A >/dev/null 2>&1; then openssl base64 -d -A28
else return 127; fi29
}30
sha_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) ----------33
valid_dev() {34
case $1 in /dev/*) ;; *) return 1 ;; esac35
case $1 in *[!A-Za-z0-9._/:+-]*|*..*|*//*) return 1 ;; esac36
[ ${#1} -le 96 ] && [ -b "$1" ]37
}38
valid_num() { case $1 in ''|*[!0-9]*) return 1 ;; esac; [ ${#1} -le 19 ]; }39
valid_label() { case $1 in -) return 0 ;; ''|*[!A-Za-z0-9._-]*) return 1 ;; esac; [ ${#1} -le 36 ]; }40
valid_id() { case $1 in ''|*[!a-z0-9-]*) return 1 ;; esac; [ ${#1} -ge 8 ] && [ ${#1} -le 64 ]; }41
valid_lv() { case $1 in */*/*|/*|*/) return 1 ;; */*) ;; *) return 1 ;; esac; case $1 in *[!A-Za-z0-9._+/-]*|*..*) return 1 ;; esac; [ ${#1} -le 128 ]; }42
valid_type() { case $1 in ''|*[!0-9A-Fa-f-]*) return 1 ;; esac; [ ${#1} -le 36 ]; }43
valid_attrs() { case $1 in -) return 0 ;; ''|*[!A-Za-z0-9,:-]*) return 1 ;; esac; [ ${#1} -le 120 ]; }44
valid_bootlist() { case $1 in -) return 0 ;; ''|*[!0-9,]*) return 1 ;; esac; [ ${#1} -le 40 ]; }45
valid_mapname() { case $1 in ''|*[!A-Za-z0-9._+-]*|*..*) return 1 ;; esac; [ ${#1} -le 96 ]; }46
valid_mountpoint() {47
case $1 in /mnt/*|/media/*) ;; *) return 1 ;; esac48
case $1 in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) return 1 ;; esac49
[ ${#1} -le 120 ]50
}51
# Kernel name (sda1, dm-3, nvme0n1p2) of a block device path, following /dev/mapper links.52
kname() { r=$(readlink -f "$1" 2>/dev/null) || return 1; printf '%s\n' "${r##*/}"; }53
majmin() { cat "/sys/class/block/$(kname "$1")/dev" 2>/dev/null; }54
sectors_of() { cat "/sys/class/block/$(kname "$1")/size" 2>/dev/null; }55
# Partition node of DISK number NUM (works for sda1, nvme0n1p1, loop0p1, mmcblk0p1).56
part_node() {57
d=$(kname "$1"); n=$258
for p in /sys/block/"$d"/"$d"*; do59
[ -r "$p/partition" ] || continue60
[ "$(cat "$p/partition")" = "$n" ] && { printf '/dev/%s\n' "${p##*/}"; return 0; }61
done62
return 163
}64
is_mounted() { mm=$(majmin "$1") || return 1; [ -n "$mm" ] && awk -v mm="$mm" '$3==mm { found=1 } END { exit !found }' /proc/self/mountinfo; }65
is_swap_active() { awk -v d="$(readlink -f "$1")" 'NR>1 && $1==d { found=1 } END { exit !found }' /proc/swaps; }66
holders_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).68
not_in_use() {69
is_mounted "$1" && return 170
is_swap_active "$1" && return 171
[ -z "$(holders_of "$1")" ]72
}73
# Any partition of DISK in use?74
disk_busy() {75
is_mounted "$1" && return 076
[ -n "$(holders_of "$1")" ] && return 077
d=$(kname "$1")78
for p in /sys/block/"$d"/"$d"*; do79
[ -r "$p/partition" ] || continue80
not_in_use "/dev/${p##*/}" || return 081
done82
return 183
}84
mountpoint_of() { mm=$(majmin "$1"); awk -v mm="$mm" '$3==mm { gsub(/\\040/, " ", $5); print $5; exit }' /proc/self/mountinfo; }85
table_hash() { sfdisk -d "$1" 2>/dev/null | sha_stream; }87
# ---------- inventory ----------88
op_inspect() {89
sec meta90
# shellcheck disable=SC109191
[ -r /etc/os-release ] && . /etc/os-release 2>/dev/null92
printf 'uid=%s\nos=%s\nosId=%s\nosLike=%s\nkernel=%s\n' "$(id -u)" "${PRETTY_NAME:-}" "${ID:-}" "${ID_LIKE:-}" "$(uname -r)" | clean_lines93
[ -d /run/systemd/system ] && echo systemd=1 || echo systemd=094
{ [ -d /etc/pve ] || have pveversion; } && echo pve=1 || echo pve=095
have getenforce && printf 'selinux=%s\n' "$(getenforce 2>/dev/null | clean)"96
[ -r /proc/self/mountinfo ] && echo mountinfo=197
printf 'time=%s\n' "$(date +%s)"98
sec tools99
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; do100
p=$(command -v "$t" 2>/dev/null) && printf '%s=%s\n' "$t" "$p"101
done102
sec versions103
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 sysblock114
for d in /sys/class/block/*; do115
[ -r "$d/size" ] || continue116
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=$d121
if [ "$part" != - ]; then parent=${real%/*}; parent=${parent##*/}; base=/sys/block/$parent; fi122
q=$base/queue123
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
done131
if have lsblk; then132
sec lsblk133
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/null134
fi135
sec blkid136
if have blkid; then137
if is_root; then blkid -c /dev/null -o export 2>/dev/null; else blkid -o export 2>/dev/null; fi138
fi139
sec mountinfo140
cat /proc/self/mountinfo 2>/dev/null141
sec df142
if have timeout; then timeout 15 df -kP 2>/dev/null; else df -kP 2>/dev/null; fi143
sec swaps144
cat /proc/swaps 2>/dev/null145
sec mdstat146
cat /proc/mdstat 2>/dev/null147
if have zpool; then148
sec zpool149
if have timeout; then timeout 15 zpool list -vHP 2>/dev/null; else zpool list -vHP 2>/dev/null; fi150
fi151
is_root || return 0152
sec pt153
have sfdisk && for d in /sys/block/*; do154
n=${d##*/}155
case $n in sr*|ram*|zram*|fd*|dm-*|md*|nbd*) continue ;; esac156
[ "$(cat "$d/size" 2>/dev/null)" = 0 ] && continue157
case $n in loop*) [ -n "$(readf "$d/loop/backing_file")" ] && [ "$(readf "$d/ro")" = 0 ] || continue ;; esac158
json=$(sfdisk --json "/dev/$n" 2>/dev/null) || continue159
printf '@@disk %s %s\n' "$n" "$(table_hash "/dev/$n")"160
printf '%s\n' "$json"161
printf '@@end\n'162
done163
if have pvs; then164
sec lvm165
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/null166
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/null167
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/null168
fi169
if have cryptsetup; then170
sec crypt171
for d in /sys/class/block/dm-*; do172
[ -r "$d/dm/uuid" ] || continue173
case $(cat "$d/dm/uuid") in CRYPT-*) ;; *) continue ;; esac174
name=$(cat "$d/dm/name")175
valid_mapname "$name" || continue176
printf '@@map %s\n' "$name"177
cryptsetup status "$name" 2>/dev/null | sed 's/^[[:space:]]*//'178
done179
fi180
sec fsinfo181
if have blkid; then182
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; do184
valid_dev "$dev" || continue185
case $type in186
ext2|ext3|ext4)187
have dumpe2fs || continue188
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 || continue192
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
esac196
done197
fi198
if have btrfs; then199
sec btrfs200
btrfs filesystem show --raw 2>/dev/null201
fi202
}203
clean_lines() { tr -d '|\r' | cut -c1-200; }205
# ---------- SMART / NVMe health (read-only) ----------206
op_smart() {207
dev=${1:-}208
valid_dev "$dev" || fail ARGUMENTS_INVALID "Invalid device."209
sec smartctl210
if have smartctl; then smartctl -j -a "$dev" 2>/dev/null || true; else echo '{"chj_unavailable":"smartctl"}'; fi211
case $(kname "$dev") in212
nvme*)213
sec nvme214
if have nvme; then215
printf '@@smart\n'; nvme smart-log -o json "$dev" 2>/dev/null216
printf '@@id\n'; nvme id-ctrl -o json "$dev" 2>/dev/null | head -c 200000217
fi ;;218
esac219
}221
# ---------- jobs ----------222
now() { date -u +%Y-%m-%dT%H:%M:%SZ; }223
put() { printf '%s\n' "$2" > "$JOBDIR/$1.tmp" && mv -f "$JOBDIR/$1.tmp" "$JOBDIR/$1"; }224
log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }225
runner_alive() {226
dir=$1227
[ -r "$dir/pid" ] || return 1228
pid=$(cat "$dir/pid"); valid_num "$pid" || return 1229
kill -0 "$pid" 2>/dev/null || return 1230
tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q "job-run ${dir##*/}"231
}232
lock_owner_alive() {233
[ -d "$LOCK" ] || return 1234
owner=$(cat "$LOCK/job" 2>/dev/null)235
valid_id "$owner" && runner_alive "$JOBS/$owner"236
}238
# Each plan line is "STEP arg..." with arguments from a restricted character set (no quoting, no globbing).239
plan_line_valid() {240
case $1 in *[!A-Za-z0-9._/:+,=@\ -]*) return 1 ;; esac241
[ ${#1} -le 400 ]242
}244
op_job_start() {245
need_root246
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; do250
valid_id "${old##*/}" && ! runner_alive "$old" && rm -rf -- "$old"251
done252
JOBDIR=$JOBS/$id253
# 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=0257
while IFS= read -r line; do258
case $line in259
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
esac263
[ $got_plan = 1 ] && [ $got_helper = 1 ] && [ -f "$JOBDIR/title" ] && break264
done265
[ $got_plan = 1 ] && [ $got_helper = 1 ] || { rm -rf -- "$JOBDIR"; fail PROTOCOL_ERROR "The plan was not received."; }266
total=0267
while IFS= read -r line; do268
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; then274
put runner systemd275
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
else279
put runner setsid280
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 & fi282
fi283
i=0; tick=0.1; sleep 0.1 2>/dev/null || tick=1284
while [ $i -lt 50 ] && [ "$(cat "$JOBDIR/state")" = queued ]; do sleep $tick; i=$((i + 1)); [ $tick = 1 ] && i=$((i + 9)); done285
sec job286
printf 'id=%s\nstate=%s\nrunner=%s\n' "$id" "$(cat "$JOBDIR/state")" "$(cat "$JOBDIR/runner")"287
}289
op_job_status() {290
need_root291
id=${1:-}; valid_id "$id" || fail ARGUMENTS_INVALID "Invalid job ID."292
offset=${2:-0}; valid_num "$offset" || offset=0293
JOBDIR=$JOBS/$id294
[ -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=1297
sec job298
printf 'id=%s\nalive=%s\n' "$id" "$alive"299
for f in state step total created started finished runner error title; do300
[ -r "$JOBDIR/$f" ] && printf '%s=%s\n' "$f" "$(head -c 400 "$JOBDIR/$f" | tr -d '\r\n')"301
done302
size=$(wc -c < "$JOBDIR/log" | tr -d ' ')303
printf 'logSize=%s\n' "$size"304
sec plan305
cat "$JOBDIR/plan"306
sec log307
[ "$offset" -lt "$size" ] && tail -c +"$((offset + 1))" "$JOBDIR/log" | head -c 65536308
return 0309
}311
op_job_list() {312
need_root313
sec jobs314
[ -d "$JOBS" ] || return 0315
ls -1t "$JOBS" 2>/dev/null | head -n 30 | while IFS= read -r id; do316
valid_id "$id" || continue317
dir=$JOBS/$id318
alive=0; runner_alive "$dir" && alive=1319
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
done322
}324
# ---------- steps (each returns 0 = ok, 10 = stop: reboot/rescue needed, anything else = failed) ----------325
ERR=326
step_fail() { ERR="$1|$2"; log "ERROR $1: $2"; return 1; }327
need_tool() { have "$1" || { step_fail TOOL_MISSING "$1 is not installed on this server."; return 1; }; }328
need_dev() { valid_dev "$1" || { step_fail DEVICE_INVALID "$1 is not a valid block device."; return 1; }; }329
require_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; }; }330
settle() { have udevadm && udevadm settle -t 15 >/dev/null 2>&1; return 0; }331
part_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.333
expect_kernel() {334
disk=$1; num=$2; want_start=$3; want_size=$4335
lbs=$(cat "/sys/block/$(kname "$disk")/queue/logical_block_size"); f=$((lbs / 512))336
geo=$(part_geometry "$disk" "$num") || return 1337
[ "$geo" = "$((want_start * f)) $((want_size * f))" ]338
}339
# Tell the kernel about partition NUM (add|update|delete). A disk with nothing in use is re-read completely.340
kernel_sync() {341
KS_DISK=$1; KS_ACTION=$2; KS_NUM=$3342
settle343
if ! disk_busy "$KS_DISK" && have blockdev; then344
if blockdev --rereadpt "$KS_DISK" 2>&1; then settle; log "kernel: partition table of $KS_DISK re-read"; return 0; fi345
log "kernel: re-reading $KS_DISK failed; updating partition $KS_NUM only"346
fi347
if have partx; then348
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>&1349
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
fi352
settle353
return 0354
}355
pending_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 10360
}362
s_verify_disk() { # DISK SECTORS TABLEHASH363
need_dev "$1" || return 1364
[ "$(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 1366
h=$(table_hash "$1"); [ -n "$(sfdisk -d "$1" 2>/dev/null)" ] || h=none367
[ "$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"369
}370
s_verify_size() { # DEV BYTES (current kernel size)371
need_dev "$1" || return 1372
[ "$(( $(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"374
}375
s_backup_table() { # DISK376
need_dev "$1" || return 1; need_tool sfdisk || return 1377
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" ]; then380
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
else383
log "$1 has no partition table; nothing to save"384
fi385
# 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"; done387
}388
s_require_free() { need_dev "$1" || return 1; require_free "$1" || return 1; log "checked: $1 is not in use"; }389
s_gpt_relocate() { # DISK390
need_dev "$1" || return 1; need_tool sfdisk || return 1391
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"393
}394
s_create_table() { # DISK gpt|dos395
need_dev "$1" || return 1; need_tool sfdisk || return 1396
case $2 in gpt|dos) ;; *) step_fail ARGUMENTS_INVALID "Unsupported partition table type."; return 1 ;; esac397
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 1400
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; }; done402
log "new empty $2 partition table written to $1"403
}404
s_create_part() { # DISK NUM START SIZE TYPE NAME405
need_dev "$1" || return 1; need_tool sfdisk || return 1406
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)"413
}414
s_delete_part() { # DISK NUM START SIZE415
need_dev "$1" || return 1; need_tool sfdisk || return 1416
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 1419
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"423
}424
s_resize_part() { # DISK NUM START OLDSIZE NEWSIZE425
need_dev "$1" || return 1; need_tool sfdisk || return 1426
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)"432
}433
s_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"; }434
s_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'"; }435
s_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'"; }436
s_set_boot() { # DISK LIST(1,3 or -)437
need_dev "$1" || return 1; need_tool sfdisk || return 1438
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"440
}441
s_mkfs() { # DEV FSTYPE LABEL442
need_dev "$1" || return 1; require_free "$1" || return 1443
l=$3444
case $2 in445
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
settle455
log "created $2 on $1"456
}457
s_set_label() { # DEV FSTYPE LABEL458
need_dev "$1" || return 1; l=$3; [ "$l" = - ] && l=""459
case $2 in460
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'"469
}470
s_fsck() { # DEV FSTYPE check|repair471
need_dev "$1" || return 1; require_free "$1" || return 1472
case $2:$3 in473
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
esac482
[ "$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"484
}485
ext_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 }'; }486
s_grow_fs() { # DEV FSTYPE487
need_dev "$1" || return 1488
devbytes=$(( $(sectors_of "$1") * 512 ))489
case $2 in490
ext2|ext3|ext4)491
need_tool resize2fs || return 1492
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 1496
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 1501
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 1508
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 1513
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= ;; esac515
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
esac520
settle521
}522
s_shrink_fs() { # DEV FSTYPE NEWBYTES523
need_dev "$1" || return 1; valid_num "$3" || { step_fail ARGUMENTS_INVALID "Invalid size."; return 1; }524
case $2 in525
ext2|ext3|ext4)526
need_tool resize2fs || return 1; need_tool e2fsck || return 1527
require_free "$1" || return 1528
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=134217728535
[ "$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 1543
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=268435456551
[ "$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 1556
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
esac561
settle562
}563
s_luks_resize() { # MAPNAME564
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"569
}570
s_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"; }571
s_lv_extend() { # VG/LV BYTES|max572
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"575
}576
s_lv_reduce() { # VG/LV BYTES577
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 1580
lvreduce -f -L "${2}b" "$1" || { step_fail TOOL_FAILED "lvreduce failed."; return 1; }581
settle; log "logical volume $1 reduced to $2 bytes"582
}583
s_mount() { # DEV MOUNTPOINT584
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"; fi588
mount "$1" "$2" || { step_fail TOOL_FAILED "mount failed."; return 1; }589
log "$1 mounted at $2 (temporary, /etc/fstab unchanged)"590
}591
system_mount() {592
case $1 in /|/boot|/boot/*|/efi|/usr|/usr/*|/var|/var/*|/home|/opt|/srv|/root|/tmp|/nix|/snap|/snap/*) return 0 ;; esac593
return 1594
}595
s_unmount() { # DEV596
need_dev "$1" || return 1597
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='601
'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; }; done603
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; }; done604
IFS=$oldifs605
log "$1 unmounted"606
}607
s_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"; }608
s_verify_fs() { # DEV FSTYPE609
need_dev "$1" || return 1610
case $2 in611
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
esac617
}618
s_disabled() { step_fail OPERATION_DISABLED "This operation is not enabled in this version."; }620
run_step() {621
name=$1; shift622
case $name in623
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
esac652
rc=$?653
[ $rc = 0 ] || [ -n "$ERR" ] || ERR="PLAN_INVALID|Invalid arguments for step $name."654
return $rc655
}657
op_job_run() {658
JOBID=${1:-}; valid_id "$JOBID" || exit 2659
JOBDIR=$JOBS/$JOBID660
[ -d "$JOBDIR" ] && [ "$(cat "$JOBDIR/state" 2>/dev/null)" = queued ] || exit 2661
trap '' HUP INT PIPE662
cd / || exit 2663
exec >>"$JOBDIR/log" 2>&1 </dev/null664
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; then667
if lock_owner_alive; then put state failed; put error "BUSY|Another disk job is running on this server."; put finished "$(now)"; exit 1; fi668
rm -rf -- "$LOCK"; mkdir "$LOCK" || { put state failed; put error "BUSY|The job lock is unavailable."; exit 1; }669
fi670
printf '%s\n' "$JOBID" > "$LOCK/job"671
trap 'rm -rf -- "$LOCK"' EXIT672
put started "$(now)"; put state running673
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=succeeded676
while IFS= read -r JOB_LINE <&3; do677
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=SC2086684
run_step $JOB_LINE685
JOB_RC=$?686
if [ $JOB_RC = 0 ]; then printf '\036STEP %s|%s|done|\n' "$JOB_N" "$JOB_TOTAL"; continue; fi687
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"; fi688
break689
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"693
}695
case $op in696
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." ;;703
esacSHA-256
05099d53ccdf14777d7810e78416eef9fb42883957345ab4b40297abfec17df2CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.
SHA-256 des Archivs
77380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df