1233 lines
47 KiB
Bash
1233 lines
47 KiB
Bash
#!/usr/bin/bash
|
|
|
|
set -Eeuo pipefail
|
|
set +x
|
|
umask 077
|
|
ulimit -c 0
|
|
|
|
readonly SWR_WINDOWS_UUID='4EA0196C0C5FA27E'
|
|
readonly SWR_KDBX_RELATIVE='HyeonworksRecovery/vault/hyeonworks-recovery.kdbx'
|
|
readonly SWR_ENTRY_GROUP='/Platform/Observability/Slack'
|
|
readonly SWR_ENTRY_TITLE='Alertmanager webhook - desktop-infra-전체'
|
|
readonly SWR_WORKSPACE_NAME='desktop-infra'
|
|
readonly SWR_CHANNEL_NAME='desktop-infra-전체'
|
|
readonly SWR_ALLOWED_SHELLOPTS="${SHELLOPTS}"
|
|
|
|
_SWR_VAULT_CLIENT_SOCKET=''
|
|
_SWR_VAULT_CLIENT_READ_FD=''
|
|
_SWR_VAULT_CLIENT_WRITE_FD=''
|
|
_SWR_VAULT_CLIENT_PID=''
|
|
|
|
SWR_RECOVERY_DISK_BY_ID=''
|
|
SWR_RECOVERY_PARTITION_BY_ID=''
|
|
SWR_RECOVERY_FS_UUID=''
|
|
SWR_RECOVERY_PARTUUID=''
|
|
SWR_RECOVERY_MODEL=''
|
|
SWR_RECOVERY_SERIAL=''
|
|
SWR_RECOVERY_WWN=''
|
|
SWR_K3S_DISK_BY_ID=''
|
|
SWR_K3S_PARTITION_BY_ID=''
|
|
SWR_RECOVERY_DEVICE=''
|
|
SWR_TRANSACTION_STATE=''
|
|
SWR_PENDING_SIGNAL=0
|
|
|
|
plain_fail() {
|
|
builtin printf 'ERROR: %s\n' "$1" >&2
|
|
return 1
|
|
}
|
|
|
|
_swr_has_imported_function_overrides() {
|
|
local exported_line
|
|
while IFS= read -r exported_line; do
|
|
[[ -z "$exported_line" ]] && continue
|
|
return 0
|
|
done < <(builtin declare -Fx)
|
|
return 1
|
|
}
|
|
|
|
_swr_shellopts_are_safe() {
|
|
local shellopts="${1-}" shellopts_declaration
|
|
[[ "$shellopts" == "$SWR_ALLOWED_SHELLOPTS" ]] || return 1
|
|
shellopts_declaration="$(builtin declare -p SHELLOPTS 2>/dev/null)" || return 1
|
|
[[ "$shellopts_declaration" == 'declare -r SHELLOPTS='* ]]
|
|
}
|
|
|
|
_swr_initial_guard() {
|
|
local effective_uid="${1-}" bash_env_value="${2-}" env_value="${3-}" shellopts_value="${4-}" script_path="${5-}"
|
|
|
|
[[ "$effective_uid" =~ ^[0-9]+$ && "$effective_uid" != 0 ]] || return 1
|
|
[[ -z "$bash_env_value" && -z "$env_value" ]] || return 1
|
|
_swr_shellopts_are_safe "$shellopts_value" || return 1
|
|
[[ -z "${CDPATH:-}" ]] || return 1
|
|
! _swr_has_imported_function_overrides || return 1
|
|
[[ "$script_path" == /* ]] || return 1
|
|
[[ "$script_path" == */scripts/bootstrap/backup-slack-webhook-recovery.sh ]] || return 1
|
|
[[ -f "$script_path" && ! -L "$script_path" ]] || return 1
|
|
}
|
|
|
|
_swr_output_name_is_safe() {
|
|
local destination_name="${1-}" required_prefix="${2-}"
|
|
[[ "$destination_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || return 1
|
|
[[ "$destination_name" == "${required_prefix}"* ]] || return 1
|
|
}
|
|
|
|
_swr_parse_cli() {
|
|
local mode_name="${1-}" path_name="${2-}" mode='DRY_RUN' webhook_path=''
|
|
shift 2 || return 1
|
|
_swr_output_name_is_safe "$mode_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$path_name" _swr_ || return 1
|
|
|
|
case "$#" in
|
|
0)
|
|
;;
|
|
3)
|
|
[[ "$1" == --execute && "$2" == --slack-webhook-file ]] || return 1
|
|
[[ "$3" == /* ]] || return 1
|
|
mode='EXECUTE'
|
|
webhook_path="$3"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
builtin printf -v "$mode_name" '%s' "$mode"
|
|
builtin printf -v "$path_name" '%s' "$webhook_path"
|
|
}
|
|
|
|
_swr_print_contract() {
|
|
builtin printf '%s\n' \
|
|
'SLACK_KEEPASS_RECOVERY_MODE=DRY_RUN' \
|
|
"WINDOWS_SSD_UUID=${SWR_WINDOWS_UUID}" \
|
|
"KDBX_RELATIVE_PATH=${SWR_KDBX_RELATIVE}" \
|
|
'MUTATION=NOT_REQUESTED' \
|
|
'OFF_HOST_RECOVERY_SATISFIED=NO'
|
|
}
|
|
|
|
_swr_require_execute_tty() {
|
|
local tty_fd
|
|
[[ -t 0 ]] || return 1
|
|
exec {tty_fd}<>/dev/tty || return 1
|
|
[[ -t "$tty_fd" ]] || {
|
|
exec {tty_fd}>&-
|
|
return 1
|
|
}
|
|
exec {tty_fd}>&-
|
|
}
|
|
|
|
_swr_validate_runtime() {
|
|
local mode="${1-}" script_path="${2-}"
|
|
_swr_initial_guard "${EUID:-}" "${BASH_ENV-}" "${ENV-}" "${SHELLOPTS-}" "$script_path" || return 1
|
|
case "$mode" in
|
|
DRY_RUN) return 0 ;;
|
|
EXECUTE) _swr_require_execute_tty ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
_swr_command() {
|
|
command "$@"
|
|
}
|
|
|
|
_swr_capture_command() {
|
|
local output_name="${1-}" framed='' captured='' rc_text rc
|
|
local frame_prefix=$'\036SWR_CAPTURE_RC=' frame_suffix
|
|
shift || return 1
|
|
_swr_output_name_is_safe "$output_name" _swr_ || return 1
|
|
framed="$(
|
|
set +e
|
|
_swr_command "$@" 2>/dev/null
|
|
_swr_framed_rc=$?
|
|
builtin printf '\036SWR_CAPTURE_RC=%03d\037' "$_swr_framed_rc"
|
|
)" || return 1
|
|
local LC_ALL=C
|
|
(( ${#framed} >= ${#frame_prefix} + 4 )) || return 1
|
|
frame_suffix="${framed: -(${#frame_prefix} + 4)}"
|
|
[[ "${frame_suffix:0:${#frame_prefix}}" == "$frame_prefix" &&
|
|
"${frame_suffix: -1}" == $'\037' ]] || return 1
|
|
rc_text="${frame_suffix:${#frame_prefix}:3}"
|
|
[[ "$rc_text" =~ ^[0-9]{3}$ ]] || return 1
|
|
rc=$((10#$rc_text))
|
|
(( rc >= 0 && rc <= 255 )) || return 1
|
|
captured="${framed:0:${#framed}-${#frame_suffix}}"
|
|
if (( ${#captured} > 16384 )); then
|
|
builtin printf -v "$output_name" '%s' ''
|
|
return 90
|
|
fi
|
|
builtin printf -v "$output_name" '%s' "$captured"
|
|
return "$rc"
|
|
}
|
|
|
|
_swr_load_recovery_contract() {
|
|
local contract_path="${1-}" line index
|
|
local -a actual=() expected=(
|
|
'K3SLR_SCHEMA_VERSION=1'
|
|
'K3SLR_RECOVERY_DISK_BY_ID=/dev/disk/by-id/wwn-0x500a0751e6aa6254'
|
|
'K3SLR_RECOVERY_PARTITION_BY_ID=/dev/disk/by-id/wwn-0x500a0751e6aa6254-part3'
|
|
'K3SLR_RECOVERY_FS_UUID=4EA0196C0C5FA27E'
|
|
'K3SLR_RECOVERY_PARTUUID=4670aa9f-9045-4bce-930d-9e84dfec9f38'
|
|
'K3SLR_RECOVERY_MODEL=CT1000MX500SSD1'
|
|
'K3SLR_RECOVERY_SERIAL=2306E6AA6254'
|
|
'K3SLR_RECOVERY_WWN=0x500a0751e6aa6254'
|
|
'K3SLR_K3S_DISK_BY_ID=/dev/disk/by-id/wwn-0x500a07512df28bd1'
|
|
'K3SLR_K3S_PARTITION_BY_ID=/dev/disk/by-id/wwn-0x500a07512df28bd1-part1'
|
|
'K3SLR_K3S_FS_UUID=b86086ef-2b3c-4638-abcf-fc7f137dcb97'
|
|
'K3SLR_K3S_PARTUUID=b081b955-d6bc-442c-ac0f-db76560a5245'
|
|
'K3SLR_K3S_MODEL=CT250MX500SSD1'
|
|
'K3SLR_K3S_SERIAL=21132DF28BD1'
|
|
'K3SLR_K3S_WWN=0x500a07512df28bd1'
|
|
'K3SLR_OWNER_UID=1000'
|
|
'K3SLR_OWNER_GID=1000'
|
|
'K3SLR_OUTER_MOUNT=/mnt/k3s-recovery-ssd'
|
|
'K3SLR_INNER_MOUNT=/srv/recovery/k3s'
|
|
'K3SLR_ROOT_RELATIVE=HyeonworksRecovery'
|
|
'K3SLR_DATABASE_RELATIVE=HyeonworksRecovery/vault/hyeonworks-recovery.kdbx'
|
|
'K3SLR_CONTAINER_RELATIVE=HyeonworksRecovery/containers/k3s-recovery.luks'
|
|
'K3SLR_RUNTIME_METADATA_RELATIVE=HyeonworksRecovery/volume.env'
|
|
'K3SLR_MAPPING_NAME=k3s-recovery'
|
|
'K3SLR_PROOF_MAPPING_NAME=k3s-recovery-proof'
|
|
'K3SLR_INNER_LABEL=K3S_RECOVERY'
|
|
'K3SLR_CONTAINER_SIZE_BYTES=34359738368'
|
|
'K3SLR_MINIMUM_FREE_BYTES=10737418240'
|
|
'K3SLR_OUTER_MIN_REMAINING_PERCENT=20'
|
|
)
|
|
|
|
[[ $# -eq 1 && "$contract_path" == /* && -f "$contract_path" && ! -L "$contract_path" ]] || return 1
|
|
while IFS= builtin read -r line; do
|
|
actual+=("$line")
|
|
(( ${#actual[@]} <= ${#expected[@]} )) || return 1
|
|
done <"$contract_path"
|
|
(( ${#actual[@]} == ${#expected[@]} )) || return 1
|
|
for index in "${!expected[@]}"; do
|
|
[[ "${actual[index]}" == "${expected[index]}" ]] || return 1
|
|
done
|
|
|
|
SWR_RECOVERY_DISK_BY_ID='/dev/disk/by-id/wwn-0x500a0751e6aa6254'
|
|
SWR_RECOVERY_PARTITION_BY_ID='/dev/disk/by-id/wwn-0x500a0751e6aa6254-part3'
|
|
SWR_RECOVERY_FS_UUID='4EA0196C0C5FA27E'
|
|
SWR_RECOVERY_PARTUUID='4670aa9f-9045-4bce-930d-9e84dfec9f38'
|
|
SWR_RECOVERY_MODEL='CT1000MX500SSD1'
|
|
SWR_RECOVERY_SERIAL='2306E6AA6254'
|
|
SWR_RECOVERY_WWN='0x500a0751e6aa6254'
|
|
SWR_K3S_DISK_BY_ID='/dev/disk/by-id/wwn-0x500a07512df28bd1'
|
|
SWR_K3S_PARTITION_BY_ID='/dev/disk/by-id/wwn-0x500a07512df28bd1-part1'
|
|
}
|
|
|
|
_swr_capture_single_line() {
|
|
local output_name="${1-}" captured=''
|
|
shift || return 1
|
|
_swr_capture_command "$output_name" "$@" || return 1
|
|
captured="${!output_name}"
|
|
[[ "$captured" == *$'\n' ]] || return 1
|
|
captured="${captured%$'\n'}"
|
|
[[ -n "$captured" && "$captured" != *$'\n'* && "$captured" != *$'\r'* ]] || return 1
|
|
builtin printf -v "$output_name" '%s' "$captured"
|
|
}
|
|
|
|
_swr_resolve_device() {
|
|
local output_name="${1-}"
|
|
local _swr_recovery_disk='' _swr_recovery_partition=''
|
|
local _swr_k3s_disk='' _swr_k3s_partition=''
|
|
local _swr_recovery_parent='' _swr_k3s_parent=''
|
|
local _swr_uuid='' _swr_partuuid='' _swr_type=''
|
|
local _swr_model='' _swr_serial='' _swr_wwn=''
|
|
[[ $# -eq 1 ]] || return 1
|
|
_swr_output_name_is_safe "$output_name" _swr_ || return 1
|
|
[[ -n "$SWR_RECOVERY_DISK_BY_ID" && -n "$SWR_K3S_DISK_BY_ID" ]] || return 1
|
|
|
|
_swr_capture_single_line _swr_recovery_disk /usr/bin/readlink -e -- "$SWR_RECOVERY_DISK_BY_ID" || return 1
|
|
_swr_capture_single_line _swr_recovery_partition /usr/bin/readlink -e -- "$SWR_RECOVERY_PARTITION_BY_ID" || return 1
|
|
_swr_capture_single_line _swr_k3s_disk /usr/bin/readlink -e -- "$SWR_K3S_DISK_BY_ID" || return 1
|
|
_swr_capture_single_line _swr_k3s_partition /usr/bin/readlink -e -- "$SWR_K3S_PARTITION_BY_ID" || return 1
|
|
[[ "$_swr_recovery_disk" == /* && "$_swr_recovery_partition" == /* &&
|
|
"$_swr_k3s_disk" == /* && "$_swr_k3s_partition" == /* ]] || return 1
|
|
[[ "$_swr_recovery_disk" != "$_swr_recovery_partition" &&
|
|
"$_swr_recovery_disk" != "$_swr_k3s_disk" ]] || return 1
|
|
|
|
_swr_capture_single_line _swr_recovery_parent /usr/bin/lsblk --noheadings --raw --paths --output PKNAME -- "$_swr_recovery_partition" || return 1
|
|
_swr_capture_single_line _swr_k3s_parent /usr/bin/lsblk --noheadings --raw --paths --output PKNAME -- "$_swr_k3s_partition" || return 1
|
|
[[ "$_swr_recovery_parent" == "$_swr_recovery_disk" &&
|
|
"$_swr_k3s_parent" == "$_swr_k3s_disk" &&
|
|
"$_swr_recovery_parent" != "$_swr_k3s_parent" ]] || return 1
|
|
|
|
_swr_capture_single_line _swr_uuid /usr/sbin/blkid --output value --match-tag UUID -- "$_swr_recovery_partition" || return 1
|
|
_swr_capture_single_line _swr_partuuid /usr/sbin/blkid --output value --match-tag PARTUUID -- "$_swr_recovery_partition" || return 1
|
|
_swr_capture_single_line _swr_type /usr/sbin/blkid --output value --match-tag TYPE -- "$_swr_recovery_partition" || return 1
|
|
[[ "$_swr_uuid" == "$SWR_RECOVERY_FS_UUID" &&
|
|
"$_swr_partuuid" == "$SWR_RECOVERY_PARTUUID" && "$_swr_type" == ntfs ]] || return 1
|
|
|
|
_swr_capture_single_line _swr_model /usr/bin/lsblk --noheadings --raw --paths --nodeps --output MODEL -- "$_swr_recovery_disk" || return 1
|
|
_swr_capture_single_line _swr_serial /usr/bin/lsblk --noheadings --raw --paths --nodeps --output SERIAL -- "$_swr_recovery_disk" || return 1
|
|
_swr_capture_single_line _swr_wwn /usr/bin/lsblk --noheadings --raw --paths --nodeps --output WWN -- "$_swr_recovery_disk" || return 1
|
|
[[ "$_swr_model" == "$SWR_RECOVERY_MODEL" &&
|
|
"$_swr_serial" == "$SWR_RECOVERY_SERIAL" && "$_swr_wwn" == "$SWR_RECOVERY_WWN" ]] || return 1
|
|
|
|
SWR_RECOVERY_DEVICE="$_swr_recovery_partition"
|
|
builtin printf -v "$output_name" '%s' "$_swr_recovery_partition"
|
|
}
|
|
|
|
_swr_findmnt_capture() {
|
|
local output_name="${1-}" selector="${2-}" value="${3-}" _swr_findmnt_raw='' rc
|
|
[[ $# -eq 3 ]] || return 2
|
|
_swr_output_name_is_safe "$output_name" _swr_ || return 2
|
|
case "$selector" in --source|--mountpoint) ;; *) return 2 ;; esac
|
|
if _swr_capture_command _swr_findmnt_raw /usr/bin/findmnt --json "$selector" "$value" \
|
|
--output SOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
builtin printf -v "$output_name" '%s' "$_swr_findmnt_raw"
|
|
return "$rc"
|
|
}
|
|
|
|
_swr_require_unmounted() {
|
|
local device="${1-}" _swr_findmnt_output='' rc
|
|
[[ $# -eq 1 && "$device" == /* ]] || return 1
|
|
if _swr_findmnt_capture _swr_findmnt_output --source "$device"; then
|
|
return 1
|
|
else
|
|
rc=$?
|
|
fi
|
|
[[ "$rc" == 1 && -z "$_swr_findmnt_output" ]]
|
|
}
|
|
|
|
_swr_require_mountpoint_unused() {
|
|
local mountpoint="${1-}" _swr_findmnt_output='' rc
|
|
[[ $# -eq 1 && "$mountpoint" == /* ]] || return 1
|
|
if _swr_findmnt_capture _swr_findmnt_output --mountpoint "$mountpoint"; then
|
|
return 1
|
|
else
|
|
rc=$?
|
|
fi
|
|
[[ "$rc" == 1 && -z "$_swr_findmnt_output" ]]
|
|
}
|
|
|
|
_swr_runtime_root() {
|
|
local _swr_canonical=''
|
|
_swr_capture_single_line _swr_canonical /usr/bin/readlink -e -- /run/user/1000 || return 1
|
|
[[ "$_swr_canonical" == /run/user/1000 ]] || return 1
|
|
builtin printf '%s\n' "$_swr_canonical"
|
|
}
|
|
|
|
_swr_mountpoint_is_safe() {
|
|
local mountpoint="${1-}" runtime_root metadata=''
|
|
[[ $# -eq 1 ]] || return 1
|
|
runtime_root="$(_swr_runtime_root)" || return 1
|
|
[[ "$runtime_root" == /* && -d "$runtime_root" && ! -L "$runtime_root" && -O "$runtime_root" ]] || return 1
|
|
[[ "$mountpoint" == "$runtime_root/slack-webhook-recovery."* &&
|
|
-d "$mountpoint" && ! -L "$mountpoint" && -O "$mountpoint" ]] || return 1
|
|
_swr_capture_single_line _swr_metadata /usr/bin/stat --format=%u:%g:%a -- "$mountpoint" || return 1
|
|
metadata="$_swr_metadata"
|
|
[[ "$metadata" == 1000:1000:700 ]]
|
|
}
|
|
|
|
_swr_create_mountpoint() {
|
|
local output_name="${1-}" runtime_root _swr_mount_created=''
|
|
[[ $# -eq 1 ]] || return 1
|
|
_swr_output_name_is_safe "$output_name" _swr_ || return 1
|
|
runtime_root="$(_swr_runtime_root)" || return 1
|
|
[[ "$runtime_root" == /* && -d "$runtime_root" && ! -L "$runtime_root" && -O "$runtime_root" ]] || return 1
|
|
_swr_capture_single_line _swr_mount_created /usr/bin/mktemp -d -- "$runtime_root/slack-webhook-recovery.XXXXXXXX" || return 1
|
|
[[ "$_swr_mount_created" == "$runtime_root/slack-webhook-recovery."* ]] || return 1
|
|
builtin printf -v "$output_name" '%s' "$_swr_mount_created"
|
|
_swr_command /usr/bin/chmod 0700 -- "$_swr_mount_created" || return 1
|
|
_swr_mountpoint_is_safe "$_swr_mount_created" || return 1
|
|
}
|
|
|
|
_swr_remove_mountpoint() {
|
|
local mountpoint="${1-}"
|
|
[[ $# -eq 1 ]] || return 1
|
|
_swr_mountpoint_is_safe "$mountpoint" || return 1
|
|
_swr_command /usr/bin/rmdir -- "$mountpoint"
|
|
}
|
|
|
|
_swr_probe_rw() {
|
|
local device="${1-}"
|
|
[[ $# -eq 1 && "$device" == /* ]] || return 1
|
|
_swr_command /usr/bin/sudo -n /usr/bin/timeout --signal=TERM --kill-after=5s 20s \
|
|
/usr/bin/ntfs-3g.probe --readwrite "$device"
|
|
}
|
|
|
|
_swr_mount_volume_command() {
|
|
local device="${1-}" mountpoint="${2-}" phase="${3-}" options
|
|
[[ $# -eq 3 && -n "$SWR_RECOVERY_DEVICE" &&
|
|
"$device" == "$SWR_RECOVERY_DEVICE" ]] || return 1
|
|
case "$phase" in ro|rw) ;; *) return 1 ;; esac
|
|
_swr_mountpoint_is_safe "$mountpoint" || return 1
|
|
options="norecover,no_def_opts,allow_other,nodev,nosuid,noexec,uid=1000,gid=1000,fmask=0177,dmask=0077,${phase}"
|
|
_swr_command /usr/bin/sudo -n /usr/bin/timeout --signal=TERM --kill-after=10s 45s \
|
|
/usr/bin/ntfs-3g -o "$options" "$device" "$mountpoint"
|
|
}
|
|
|
|
_swr_mount_volume() {
|
|
local device="${1-}" mountpoint="${2-}" phase="${3-}"
|
|
[[ $# -eq 3 ]] || return 1
|
|
_swr_mountpoint_is_safe "$mountpoint" || return 1
|
|
_swr_mount_volume_command "$device" "$mountpoint" "$phase"
|
|
}
|
|
|
|
_swr_parse_findmnt_json() {
|
|
local json="${1-}" source_name="${2-}" target_name="${3-}"
|
|
local fstype_name="${4-}" vfs_name="${5-}" fs_name="${6-}" parsed encoded decoded rc index
|
|
local frame_marker=$'\036' LC_ALL=C
|
|
local -a encoded_fields=() fields=()
|
|
[[ $# -eq 6 && ${#json} -le 16384 ]] || return 1
|
|
_swr_output_name_is_safe "$source_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$target_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$fstype_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$vfs_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$fs_name" _swr_ || return 1
|
|
if parsed="$(builtin printf '%s' "$json" | _swr_command /usr/bin/jq -er '
|
|
if (type == "object" and (.filesystems | type) == "array" and (.filesystems | length) == 1)
|
|
then .filesystems[0]
|
|
| if (type == "object" and (.source | type) == "string"
|
|
and (.target | type) == "string" and (.fstype | type) == "string"
|
|
and (."vfs-options" | type) == "string" and (."fs-options" | type) == "string")
|
|
then [.source, .target, .fstype, ."vfs-options", ."fs-options"]
|
|
else error("invalid findmnt record") end
|
|
else error("ambiguous findmnt record") end
|
|
| if all(.[]; length > 0 and (test("[\\x00-\\x20\\x7f]") | not))
|
|
then .[] | @base64
|
|
else error("unsafe findmnt field") end
|
|
' 2>/dev/null)"; then
|
|
(( ${#parsed} <= 16384 )) || return 1
|
|
else
|
|
return 1
|
|
fi
|
|
mapfile -t encoded_fields <<<"$parsed"
|
|
(( ${#encoded_fields[@]} == 5 )) || return 1
|
|
for index in "${!encoded_fields[@]}"; do
|
|
encoded="${encoded_fields[index]}"
|
|
[[ -n "$encoded" && "$encoded" =~ ^[A-Za-z0-9+/]*={0,2}$ ]] || return 1
|
|
decoded="$({
|
|
set +e
|
|
builtin printf '%s' "$encoded" | _swr_command /usr/bin/base64 --decode
|
|
rc=$?
|
|
builtin printf '%s' "$frame_marker"
|
|
exit "$rc"
|
|
})" || return 1
|
|
[[ "$decoded" == *"$frame_marker" ]] || return 1
|
|
decoded="${decoded%"$frame_marker"}"
|
|
[[ -n "$decoded" && "$decoded" != *[$' \t\r\n\v\f']* &&
|
|
"$decoded" != *$'\177'* ]] || return 1
|
|
fields+=("$decoded")
|
|
done
|
|
[[ -n "${fields[0]}" && -n "${fields[1]}" && -n "${fields[2]}" ]] || return 1
|
|
builtin printf -v "$source_name" '%s' "${fields[0]}"
|
|
builtin printf -v "$target_name" '%s' "${fields[1]}"
|
|
builtin printf -v "$fstype_name" '%s' "${fields[2]}"
|
|
builtin printf -v "$vfs_name" '%s' "${fields[3]}"
|
|
builtin printf -v "$fs_name" '%s' "${fields[4]}"
|
|
}
|
|
|
|
_swr_option_present() {
|
|
local options="${1-}" expected="${2-}"
|
|
[[ ",$options," == *",$expected,"* ]]
|
|
}
|
|
|
|
_swr_mount_options_are_safe() {
|
|
local phase="${1-}" vfs_options="${2-}" fs_options="${3-}" token
|
|
local vfs_phase=0 vfs_opposite=0 vfs_nosuid=0 vfs_nodev=0 vfs_noexec=0
|
|
local vfs_relatime=0
|
|
local fs_phase=0 fs_opposite=0 fs_allow_other=0 fs_default_permissions=0
|
|
local fs_user_id=0 fs_group_id=0 fs_blksize=0
|
|
local -a tokens=()
|
|
[[ $# -eq 3 ]] || return 1
|
|
case "$phase" in ro|rw) ;; *) return 1 ;; esac
|
|
|
|
[[ -n "$vfs_options" && "$vfs_options" != ,* && "$vfs_options" != *, &&
|
|
"$vfs_options" != *,,* && "$vfs_options" != *[$' \t\r\n\v\f']* &&
|
|
"$vfs_options" != *$'\177'* ]] || return 1
|
|
IFS=, read -r -a tokens <<<"$vfs_options"
|
|
for token in "${tokens[@]}"; do
|
|
[[ -n "$token" && "$token" != *[$'\t\r\n ']* ]] || return 1
|
|
case "$token" in
|
|
"$phase") vfs_phase=$((vfs_phase + 1)) ;;
|
|
ro|rw) vfs_opposite=$((vfs_opposite + 1)) ;;
|
|
nosuid) vfs_nosuid=$((vfs_nosuid + 1)) ;;
|
|
nodev) vfs_nodev=$((vfs_nodev + 1)) ;;
|
|
noexec) vfs_noexec=$((vfs_noexec + 1)) ;;
|
|
relatime) vfs_relatime=$((vfs_relatime + 1)) ;;
|
|
suid|dev|exec) return 1 ;;
|
|
uid=*|gid=*|fmask=*|dmask=*|umask|umask=*|mask|mask=*) return 1 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
done
|
|
(( vfs_phase == 1 && vfs_opposite == 0 && vfs_nosuid == 1 &&
|
|
vfs_nodev == 1 && vfs_noexec == 1 && vfs_relatime == 1 )) || return 1
|
|
|
|
tokens=()
|
|
[[ -n "$fs_options" && "$fs_options" != ,* && "$fs_options" != *, &&
|
|
"$fs_options" != *,,* && "$fs_options" != *[$' \t\r\n\v\f']* &&
|
|
"$fs_options" != *$'\177'* ]] || return 1
|
|
IFS=, read -r -a tokens <<<"$fs_options"
|
|
for token in "${tokens[@]}"; do
|
|
[[ -n "$token" && "$token" != *[$'\t\r\n ']* ]] || return 1
|
|
case "$token" in
|
|
"$phase") fs_phase=$((fs_phase + 1)) ;;
|
|
ro|rw) fs_opposite=$((fs_opposite + 1)) ;;
|
|
allow_other) fs_allow_other=$((fs_allow_other + 1)) ;;
|
|
default_permissions) fs_default_permissions=$((fs_default_permissions + 1)) ;;
|
|
user_id=*) fs_user_id=$((fs_user_id + 1)); [[ "$token" == user_id=0 ]] || return 1 ;;
|
|
group_id=*) fs_group_id=$((fs_group_id + 1)); [[ "$token" == group_id=0 ]] || return 1 ;;
|
|
blksize=*) fs_blksize=$((fs_blksize + 1)); [[ "$token" == blksize=4096 ]] || return 1 ;;
|
|
uid=*|gid=*|fmask=*|dmask=*) return 1 ;;
|
|
umask|umask=*|mask|mask=*) return 1 ;;
|
|
suid|dev|exec) return 1 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
done
|
|
(( fs_phase == 1 && fs_opposite == 0 && fs_allow_other == 1 &&
|
|
fs_default_permissions == 1 && fs_user_id == 1 && fs_group_id == 1 &&
|
|
fs_blksize == 1 ))
|
|
}
|
|
|
|
_swr_validate_mount() {
|
|
local device="${1-}" mountpoint="${2-}" phase="${3-}"
|
|
local _swr_findmnt_output='' _swr_source='' _swr_target=''
|
|
local _swr_fstype='' _swr_vfs='' _swr_fs='' _swr_metadata=''
|
|
[[ $# -eq 3 && "$device" == /* ]] || return 1
|
|
case "$phase" in ro|rw) ;; *) return 1 ;; esac
|
|
_swr_mountpoint_is_safe "$mountpoint" || return 1
|
|
_swr_findmnt_capture _swr_findmnt_output --mountpoint "$mountpoint" || return 1
|
|
_swr_parse_findmnt_json "$_swr_findmnt_output" _swr_source _swr_target \
|
|
_swr_fstype _swr_vfs _swr_fs || return 1
|
|
[[ "$_swr_source" == "$device" && "$_swr_target" == "$mountpoint" &&
|
|
"$_swr_fstype" == fuseblk ]] || return 1
|
|
_swr_mount_options_are_safe "$phase" "$_swr_vfs" "$_swr_fs" || return 1
|
|
_swr_capture_single_line _swr_metadata /usr/bin/stat --format=%u:%g:%a -- "$mountpoint" || return 1
|
|
[[ "$_swr_metadata" == 1000:1000:700 ]]
|
|
}
|
|
|
|
_swr_stably_unmounted() {
|
|
local device="${1-}"
|
|
_swr_require_unmounted "$device" || return 1
|
|
_swr_require_unmounted "$device"
|
|
}
|
|
|
|
_swr_unmount_volume() {
|
|
local device="${1-}" mountpoint="${2-}" unmount_rc=0
|
|
[[ $# -eq 2 && "$device" == /* && "$mountpoint" == /* ]] || return 1
|
|
_swr_command /usr/bin/sudo -n /usr/bin/timeout --signal=TERM --kill-after=10s 45s \
|
|
/usr/bin/umount -- "$mountpoint" >/dev/null 2>&1 || unmount_rc=$?
|
|
if _swr_stably_unmounted "$device"; then
|
|
return 0
|
|
fi
|
|
plain_fail "manual recovery required: mount state is present or ambiguous after unmount status ${unmount_rc}"
|
|
}
|
|
|
|
_swr_volume_action() {
|
|
local result_name="${1-}"
|
|
[[ $# -eq 3 ]] || return 1
|
|
_swr_output_name_is_safe "$result_name" _swr_ || return 1
|
|
plain_fail 'encrypted database snapshot boundary is unavailable'
|
|
}
|
|
|
|
_swr_restore_trap() {
|
|
local saved="${1-}" signal_name="${2-}"
|
|
if [[ -n "$saved" ]]; then
|
|
builtin eval "$saved"
|
|
else
|
|
builtin trap - "$signal_name"
|
|
fi
|
|
}
|
|
|
|
_swr_run_volume_cycle() {
|
|
local device="${1-}" mountpoint="${2-}" result_name="${3-}"
|
|
local _swr_phase_result='' phase action_rc=0 cleanup_rc=0 mount_rc=0 final_rc=0
|
|
local _swr_cycle_signal=0 mounted=0 attempted=0
|
|
local old_hup old_int old_term
|
|
[[ $# -eq 2 || $# -eq 3 ]] || return 1
|
|
[[ -z "$result_name" ]] || _swr_output_name_is_safe "$result_name" _swr_ || return 1
|
|
old_hup="$(builtin trap -p HUP)"
|
|
old_int="$(builtin trap -p INT)"
|
|
old_term="$(builtin trap -p TERM)"
|
|
builtin trap '_swr_cycle_signal=129' HUP
|
|
builtin trap '_swr_cycle_signal=130' INT
|
|
builtin trap '_swr_cycle_signal=143' TERM
|
|
|
|
if ! _swr_require_mountpoint_unused "$mountpoint"; then
|
|
final_rc=1
|
|
elif ! _swr_mountpoint_is_safe "$mountpoint"; then
|
|
final_rc=1
|
|
elif ! _swr_probe_rw "$device"; then
|
|
final_rc=1
|
|
else
|
|
for phase in ro rw; do
|
|
if [[ "$phase" == rw ]]; then
|
|
[[ "$_swr_phase_result" == mutation ]] || break
|
|
if ! _swr_mountpoint_is_safe "$mountpoint"; then
|
|
final_rc=1
|
|
break
|
|
elif ! _swr_probe_rw "$device"; then
|
|
final_rc=1
|
|
break
|
|
fi
|
|
fi
|
|
attempted=1
|
|
if _swr_mount_volume_command "$device" "$mountpoint" "$phase"; then
|
|
mounted=1
|
|
else
|
|
mount_rc=$?
|
|
_swr_unmount_volume "$device" "$mountpoint" || cleanup_rc=$?
|
|
final_rc="$mount_rc"
|
|
(( cleanup_rc == 0 )) || final_rc="$cleanup_rc"
|
|
break
|
|
fi
|
|
if (( _swr_cycle_signal != 0 )); then
|
|
action_rc="$_swr_cycle_signal"
|
|
elif ! _swr_validate_mount "$device" "$mountpoint" "$phase"; then
|
|
action_rc=1
|
|
elif ! _swr_volume_action _swr_phase_result "$phase" "$mountpoint"; then
|
|
action_rc=$?
|
|
(( action_rc == 0 )) && action_rc=1
|
|
fi
|
|
_swr_unmount_volume "$device" "$mountpoint" || cleanup_rc=$?
|
|
mounted=0
|
|
if (( cleanup_rc != 0 )); then
|
|
final_rc="$cleanup_rc"
|
|
break
|
|
fi
|
|
if (( _swr_cycle_signal != 0 )); then
|
|
final_rc="$_swr_cycle_signal"
|
|
break
|
|
fi
|
|
if (( action_rc != 0 )); then
|
|
final_rc="$action_rc"
|
|
break
|
|
fi
|
|
case "$phase:$_swr_phase_result" in
|
|
ro:exact-noop) break ;;
|
|
ro:mutation) ;;
|
|
rw:complete) break ;;
|
|
*) final_rc=1; break ;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
if (( mounted != 0 )); then
|
|
_swr_unmount_volume "$device" "$mountpoint" || cleanup_rc=$?
|
|
(( cleanup_rc == 0 )) || final_rc="$cleanup_rc"
|
|
fi
|
|
_swr_restore_trap "$old_hup" HUP
|
|
_swr_restore_trap "$old_int" INT
|
|
_swr_restore_trap "$old_term" TERM
|
|
if (( final_rc == 0 )) && [[ -n "$result_name" ]]; then
|
|
builtin printf -v "$result_name" '%s' "$_swr_phase_result"
|
|
fi
|
|
return "$final_rc"
|
|
}
|
|
|
|
_swr_open_recovery_boundary() {
|
|
local result_name="${1-}" contract_path="${2-}"
|
|
local _swr_device='' _swr_mountpoint='' _swr_boundary_result='' rc=0
|
|
[[ $# -eq 2 ]] || return 1
|
|
_swr_output_name_is_safe "$result_name" _swr_ || return 1
|
|
_swr_load_recovery_contract "$contract_path" || return 1
|
|
_swr_resolve_device _swr_device || return 1
|
|
_swr_require_unmounted "$_swr_device" || return 1
|
|
_swr_runtime_root >/dev/null || return 1
|
|
_swr_create_mountpoint _swr_mountpoint || return 1
|
|
_swr_require_mountpoint_unused "$_swr_mountpoint" || return 1
|
|
if ! _swr_command /usr/bin/sudo -v; then
|
|
_swr_require_mountpoint_unused "$_swr_mountpoint" &&
|
|
_swr_remove_mountpoint "$_swr_mountpoint" >/dev/null 2>&1 || true
|
|
return 1
|
|
fi
|
|
if ! _swr_mountpoint_is_safe "$_swr_mountpoint" ||
|
|
! _swr_require_mountpoint_unused "$_swr_mountpoint"; then
|
|
_swr_require_mountpoint_unused "$_swr_mountpoint" &&
|
|
_swr_remove_mountpoint "$_swr_mountpoint" >/dev/null 2>&1 || true
|
|
return 1
|
|
fi
|
|
if ! _swr_run_volume_cycle "$_swr_device" "$_swr_mountpoint" _swr_boundary_result; then
|
|
rc=$?
|
|
(( rc == 0 )) && rc=1
|
|
fi
|
|
if _swr_require_unmounted "$_swr_device" &&
|
|
_swr_require_mountpoint_unused "$_swr_mountpoint"; then
|
|
_swr_remove_mountpoint "$_swr_mountpoint" || rc=1
|
|
else
|
|
rc=1
|
|
fi
|
|
if (( rc == 0 )); then
|
|
builtin printf -v "$result_name" '%s' "$_swr_boundary_result"
|
|
fi
|
|
return "$rc"
|
|
}
|
|
|
|
_swr_terminate_vault_child() {
|
|
local child_pid="${1-}" requested_signal="${2-TERM}"
|
|
[[ ( $# -eq 1 || $# -eq 2 ) && "$child_pid" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
case "$requested_signal" in HUP|INT|TERM) ;; *) return 1 ;; esac
|
|
kill -"$requested_signal" "$child_pid" 2>/dev/null || true
|
|
if ! /usr/bin/timeout --signal=TERM --kill-after=0.1s 1s \
|
|
/usr/bin/tail --pid="$child_pid" -f /dev/null >/dev/null 2>&1; then
|
|
kill -KILL "$child_pid" 2>/dev/null || true
|
|
fi
|
|
wait "$child_pid" 2>/dev/null || true
|
|
! kill -0 "$child_pid" 2>/dev/null
|
|
}
|
|
|
|
_swr_wait_vault_ready() {
|
|
local socket_path="${1-}" child_pid="${2-}" attempts="${3-}" attempt
|
|
[[ $# -eq 3 && "$socket_path" == /* && "$child_pid" =~ ^[1-9][0-9]*$ &&
|
|
"$attempts" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
for (( attempt = 0; attempt < attempts; attempt++ )); do
|
|
(( SWR_PENDING_SIGNAL == 0 )) || return 2
|
|
if [[ -S "$socket_path" && ! -L "$socket_path" ]]; then
|
|
return 0
|
|
fi
|
|
[[ ! -e "$socket_path" && ! -L "$socket_path" ]] || return 1
|
|
if ! kill -0 "$child_pid" 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
/usr/bin/sleep 0.05
|
|
done
|
|
return 1
|
|
}
|
|
|
|
_swr_start_vault() {
|
|
local pid_name="${1-}" socket_name="${2-}" runtime_root="${3-}" webhook_file="${4-}"
|
|
local repository_root="${SWR_REPOSITORY_ROOT-}" helper socket_path child_pid readiness_rc=0
|
|
[[ $# -eq 4 ]] || return 1
|
|
_swr_output_name_is_safe "$pid_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$socket_name" _swr_ || return 1
|
|
[[ "$repository_root" == /* && "$runtime_root" == /* && "$webhook_file" == /* ]] || return 1
|
|
helper="${repository_root}/scripts/libexec/slack-webhook-recovery-vault.py"
|
|
socket_path="${runtime_root}/vault.sock"
|
|
[[ -f "$helper" && ! -L "$helper" ]] || return 1
|
|
[[ -d "$runtime_root" && ! -L "$runtime_root" ]] || return 1
|
|
[[ ! -e "$socket_path" && ! -L "$socket_path" ]] || return 1
|
|
|
|
/usr/bin/env -i HOME=/nonexistent LC_ALL=C.UTF-8 PATH=/usr/bin:/bin \
|
|
/usr/bin/python3 "$helper" --serve --runtime-root "$runtime_root" \
|
|
--webhook-file "$webhook_file" &
|
|
child_pid=$!
|
|
_swr_wait_vault_ready "$socket_path" "$child_pid" 18000 || readiness_rc=$?
|
|
if (( readiness_rc != 0 || SWR_PENDING_SIGNAL != 0 )); then
|
|
case "$SWR_PENDING_SIGNAL" in
|
|
129) _swr_terminate_vault_child "$child_pid" HUP || true ;;
|
|
130) _swr_terminate_vault_child "$child_pid" INT || true ;;
|
|
143) _swr_terminate_vault_child "$child_pid" TERM || true ;;
|
|
*) _swr_terminate_vault_child "$child_pid" TERM || true ;;
|
|
esac
|
|
return 1
|
|
fi
|
|
builtin printf -v "$pid_name" '%s' "$child_pid"
|
|
builtin printf -v "$socket_name" '%s' "$socket_path"
|
|
return 0
|
|
}
|
|
|
|
_swr_vault_request() {
|
|
local result_name="${1-}" socket_path="${2-}" command="${3-}" reply
|
|
[[ $# -eq 3 ]] || return 1
|
|
_swr_output_name_is_safe "$result_name" _swr_ || return 1
|
|
[[ "$socket_path" == /* && -S "$socket_path" && ! -L "$socket_path" ]] || return 1
|
|
case "$command" in
|
|
PREPARE|'BUILD ADD'|'BUILD UPDATE'|VERIFY_CANDIDATE|SHUTDOWN)
|
|
;;
|
|
'VERIFY_COMMITTED '/*)
|
|
[[ "${command#VERIFY_COMMITTED }" != *' '* ]] || return 1
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
if [[ -z "$_SWR_VAULT_CLIENT_SOCKET" ]]; then
|
|
coproc SWR_VAULT_CLIENT {
|
|
/usr/bin/timeout --signal=TERM --kill-after=1s 900s \
|
|
/usr/bin/socat - "UNIX-CONNECT:${socket_path}" 2>/dev/null
|
|
}
|
|
_SWR_VAULT_CLIENT_PID="$SWR_VAULT_CLIENT_PID"
|
|
exec {_SWR_VAULT_CLIENT_READ_FD}<&"${SWR_VAULT_CLIENT[0]}" || return 1
|
|
exec {_SWR_VAULT_CLIENT_WRITE_FD}>&"${SWR_VAULT_CLIENT[1]}" || return 1
|
|
_SWR_VAULT_CLIENT_SOCKET="$socket_path"
|
|
fi
|
|
[[ "$_SWR_VAULT_CLIENT_SOCKET" == "$socket_path" ]] || return 1
|
|
builtin printf '%s\n' "$command" >&"$_SWR_VAULT_CLIENT_WRITE_FD" || return 1
|
|
IFS= builtin read -r -t 20 -u "$_SWR_VAULT_CLIENT_READ_FD" reply || return 1
|
|
case "$reply" in
|
|
absent|exact-noop|mismatch|ambiguous|failed|candidate-ready|verified|stopped)
|
|
builtin printf -v "$result_name" '%s' "$reply"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_swr_stop_vault() {
|
|
local socket_path="${1-}" child_pid="${2-}" _swr_stop_reply=''
|
|
local shutdown_ok=1
|
|
[[ $# -eq 2 && "$child_pid" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
if [[ -S "$socket_path" && ! -L "$socket_path" ]]; then
|
|
_swr_vault_request _swr_stop_reply "$socket_path" SHUTDOWN || shutdown_ok=0
|
|
[[ "$_swr_stop_reply" == stopped ]] || shutdown_ok=0
|
|
fi
|
|
if [[ -n "$_SWR_VAULT_CLIENT_WRITE_FD" ]]; then
|
|
exec {_SWR_VAULT_CLIENT_WRITE_FD}>&-
|
|
fi
|
|
if [[ -n "$_SWR_VAULT_CLIENT_READ_FD" ]]; then
|
|
exec {_SWR_VAULT_CLIENT_READ_FD}<&-
|
|
fi
|
|
if [[ -n "$_SWR_VAULT_CLIENT_PID" ]]; then
|
|
(( shutdown_ok != 0 )) || kill -TERM "$_SWR_VAULT_CLIENT_PID" 2>/dev/null || true
|
|
wait "$_SWR_VAULT_CLIENT_PID" 2>/dev/null || true
|
|
fi
|
|
_SWR_VAULT_CLIENT_SOCKET=''
|
|
_SWR_VAULT_CLIENT_READ_FD=''
|
|
_SWR_VAULT_CLIENT_WRITE_FD=''
|
|
_SWR_VAULT_CLIENT_PID=''
|
|
if (( shutdown_ok == 0 )); then
|
|
kill -TERM "$child_pid" 2>/dev/null || true
|
|
wait "$child_pid" 2>/dev/null || true
|
|
return 1
|
|
fi
|
|
/usr/bin/timeout --signal=TERM --kill-after=1s 10s \
|
|
/usr/bin/tail --pid="$child_pid" -f /dev/null >/dev/null 2>&1 || {
|
|
kill -TERM "$child_pid" 2>/dev/null || true
|
|
wait "$child_pid" 2>/dev/null || true
|
|
return 1
|
|
}
|
|
wait "$child_pid"
|
|
}
|
|
|
|
defer_signal() {
|
|
case "${1-}" in
|
|
HUP) SWR_PENDING_SIGNAL=129 ;;
|
|
INT) SWR_PENDING_SIGNAL=130 ;;
|
|
TERM) SWR_PENDING_SIGNAL=143 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
_swr_work_root_is_safe() {
|
|
local work_root="${1-}" _swr_metadata='' _swr_filesystem=''
|
|
[[ $# -eq 1 && "$work_root" == /run/user/1000/platform-slack-keepass.* &&
|
|
-d "$work_root" && ! -L "$work_root" && -O "$work_root" ]] || return 1
|
|
_swr_capture_single_line _swr_metadata /usr/bin/stat --format=%u:%g:%a -- "$work_root" || return 1
|
|
_swr_capture_single_line _swr_filesystem /usr/bin/stat --file-system --format=%T -- "$work_root" || return 1
|
|
[[ "$_swr_metadata" == 1000:1000:700 && "$_swr_filesystem" == tmpfs ]]
|
|
}
|
|
|
|
_swr_prepare_transaction() {
|
|
local work_name="${1-}" device_name="${2-}" mount_name="${3-}"
|
|
local webhook_file="${4-}" contract_path="${5-}"
|
|
local _swr_prepared_device='' _swr_prepared_work_root='' _swr_prepared_mountpoint=''
|
|
local _swr_runtime_metadata='' _swr_runtime_filesystem=''
|
|
[[ $# -eq 5 && "$webhook_file" == /* && "$contract_path" == /* ]] || return 1
|
|
_swr_output_name_is_safe "$work_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$device_name" _swr_ || return 1
|
|
_swr_output_name_is_safe "$mount_name" _swr_ || return 1
|
|
_swr_load_recovery_contract "$contract_path" || return 1
|
|
_swr_resolve_device _swr_prepared_device || return 1
|
|
builtin printf -v "$device_name" '%s' "$_swr_prepared_device"
|
|
_swr_require_unmounted "$_swr_prepared_device" || return 1
|
|
[[ "$(_swr_runtime_root)" == /run/user/1000 ]] || return 1
|
|
_swr_capture_single_line _swr_runtime_metadata /usr/bin/stat --format=%u:%g:%a -- /run/user/1000 || return 1
|
|
_swr_capture_single_line _swr_runtime_filesystem /usr/bin/stat --file-system --format=%T -- /run/user/1000 || return 1
|
|
[[ "$_swr_runtime_metadata" == 1000:1000:700 &&
|
|
"$_swr_runtime_filesystem" == tmpfs ]] || return 1
|
|
_swr_capture_single_line _swr_prepared_work_root /usr/bin/mktemp -d --tmpdir=/run/user/1000 \
|
|
platform-slack-keepass.XXXXXXXX || return 1
|
|
builtin printf -v "$work_name" '%s' "$_swr_prepared_work_root"
|
|
if ! _swr_command /usr/bin/chmod 0700 -- "$_swr_prepared_work_root" ||
|
|
! _swr_work_root_is_safe "$_swr_prepared_work_root"; then
|
|
return 1
|
|
fi
|
|
if _swr_create_mountpoint _swr_prepared_mountpoint; then
|
|
builtin printf -v "$mount_name" '%s' "$_swr_prepared_mountpoint"
|
|
else
|
|
builtin printf -v "$mount_name" '%s' "$_swr_prepared_mountpoint"
|
|
return 1
|
|
fi
|
|
_swr_require_mountpoint_unused "$_swr_prepared_mountpoint" || return 1
|
|
}
|
|
|
|
_swr_refresh_sudo() {
|
|
_swr_command /usr/bin/sudo -v
|
|
}
|
|
|
|
_swr_dirfd_helper() {
|
|
local repository_root="${SWR_REPOSITORY_ROOT-}" helper
|
|
helper="${repository_root}/scripts/libexec/slack-webhook-recovery-dirfd.py"
|
|
[[ "$repository_root" == /* && -f "$helper" && ! -L "$helper" ]] || return 1
|
|
_swr_command /usr/bin/env -i LC_ALL=C.UTF-8 PATH=/usr/bin:/bin \
|
|
/usr/bin/python3 "$helper" "$@"
|
|
}
|
|
|
|
_swr_dirfd_snapshot() {
|
|
local mountpoint="${1-}" work_root="${2-}"
|
|
[[ $# -eq 2 ]] || return 1
|
|
_swr_dirfd_helper snapshot --mount-root "$mountpoint" \
|
|
--destination "$work_root/baseline.kdbx" --result "$work_root/baseline.json"
|
|
}
|
|
|
|
_swr_audit_runtime_processes() {
|
|
local work_root="${1-}"
|
|
[[ $# -eq 1 && "$work_root" == /* ]] || return 1
|
|
_swr_dirfd_helper audit-processes --runtime-root "$work_root"
|
|
}
|
|
|
|
_swr_dirfd_snapshot_current() {
|
|
local mountpoint="${1-}" work_root="${2-}"
|
|
[[ $# -eq 2 ]] || return 1
|
|
_swr_dirfd_helper snapshot-current --mount-root "$mountpoint" \
|
|
--candidate "$work_root/candidate.kdbx" --destination "$work_root/current.kdbx" \
|
|
--result "$work_root/current.json"
|
|
}
|
|
|
|
_swr_dirfd_commit() {
|
|
local result_name="${1-}" mountpoint="${2-}" work_root="${3-}"
|
|
local backup_name="${4-}" _swr_fs_status='' command_rc=0
|
|
[[ $# -eq 4 ]] || return 1
|
|
_swr_output_name_is_safe "$result_name" _swr_ || return 1
|
|
if _swr_dirfd_helper commit --mount-root "$mountpoint" \
|
|
--baseline "$work_root/baseline.json" --candidate "$work_root/candidate.kdbx" \
|
|
--backup-name "$backup_name" --result "$work_root/commit.json"; then
|
|
command_rc=0
|
|
else
|
|
command_rc=$?
|
|
fi
|
|
if _swr_fs_status="$(_swr_dirfd_helper status --result "$work_root/commit.json")"; then
|
|
[[ -n "$_swr_fs_status" && "$_swr_fs_status" != *$'\n'* &&
|
|
"$_swr_fs_status" != *$'\r'* ]] || return 1
|
|
case "$_swr_fs_status" in
|
|
precommit-failure|rename-attempted|verified-commit|committed-but-uncertain) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
builtin printf -v "$result_name" '%s' "$_swr_fs_status"
|
|
else
|
|
builtin printf -v "$result_name" '%s' missing
|
|
fi
|
|
[[ "$_swr_fs_status" == verified-commit ]] && command_rc=0
|
|
return "$command_rc"
|
|
}
|
|
|
|
_swr_confirm_token() {
|
|
local expected="${1-}" prompt tty_fd reply=''
|
|
case "$expected" in
|
|
UPDATE) prompt='Type UPDATE SLACK KDBX default: ' ;;
|
|
WRITE) prompt='Type WRITE SLACK KDBX default: ' ;;
|
|
*) return 1 ;;
|
|
esac
|
|
exec {tty_fd}<>/dev/tty || return 1
|
|
builtin printf '%s' "$prompt" >&"$tty_fd"
|
|
IFS= builtin read -r -u "$tty_fd" reply || {
|
|
exec {tty_fd}>&-
|
|
return 1
|
|
}
|
|
exec {tty_fd}>&-
|
|
[[ "$reply" == "$expected SLACK KDBX default" ]]
|
|
}
|
|
|
|
_swr_inventory_directory() {
|
|
local output_name="${1-}" directory="${2-}" inventory_fd inventory_pid
|
|
local original_read_fd original_write_fd record='' producer_rc=0 framing_rc=0
|
|
local record_bytes=0 total_bytes=0 next_total=0 LC_ALL=C
|
|
local -n output_ref="$output_name"
|
|
[[ $# -eq 2 && "$directory" == /* ]] || return 1
|
|
_swr_output_name_is_safe "$output_name" _swr_ || return 1
|
|
output_ref=()
|
|
coproc SWR_INVENTORY_PROCESS {
|
|
_swr_command /usr/bin/find "$directory" -mindepth 1 -maxdepth 1 -printf '%f\0'
|
|
}
|
|
inventory_pid="$SWR_INVENTORY_PROCESS_PID"
|
|
original_read_fd="${SWR_INVENTORY_PROCESS[0]}"
|
|
original_write_fd="${SWR_INVENTORY_PROCESS[1]}"
|
|
if ! exec {inventory_fd}<&"$original_read_fd"; then
|
|
kill -TERM "$inventory_pid" 2>/dev/null || true
|
|
wait "$inventory_pid" 2>/dev/null || true
|
|
unset SWR_INVENTORY_PROCESS SWR_INVENTORY_PROCESS_PID
|
|
return 1
|
|
fi
|
|
exec {original_read_fd}<&-
|
|
exec {original_write_fd}>&-
|
|
while true; do
|
|
record=''
|
|
if IFS= builtin read -r -d '' -n 256 -u "$inventory_fd" record; then
|
|
record_bytes=${#record}
|
|
next_total=$((total_bytes + record_bytes))
|
|
if (( record_bytes < 1 || record_bytes > 255 || next_total > 4096 ||
|
|
${#output_ref[@]} >= 16 )) ||
|
|
[[ "$record" == */* || "$record" == . || "$record" == .. ]]; then
|
|
framing_rc=1
|
|
break
|
|
fi
|
|
output_ref+=("$record")
|
|
total_bytes=$next_total
|
|
else
|
|
if [[ -n "$record" ]]; then
|
|
framing_rc=1
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
exec {inventory_fd}<&-
|
|
if (( framing_rc != 0 )); then
|
|
kill -TERM "$inventory_pid" 2>/dev/null || true
|
|
fi
|
|
if wait "$inventory_pid"; then
|
|
producer_rc=0
|
|
else
|
|
producer_rc=$?
|
|
fi
|
|
unset SWR_INVENTORY_PROCESS SWR_INVENTORY_PROCESS_PID
|
|
(( producer_rc == 0 && framing_rc == 0 ))
|
|
}
|
|
|
|
_swr_remove_work_root() {
|
|
local work_root="${1-}" path name _swr_metadata=''
|
|
local -a _swr_inventory=()
|
|
local -a owned_files=(
|
|
baseline.kdbx baseline.json candidate.kdbx current.kdbx current.json commit.json
|
|
)
|
|
[[ $# -eq 1 ]] || return 1
|
|
_swr_work_root_is_safe "$work_root" || return 1
|
|
[[ ! -e "$work_root/vault.sock" && ! -L "$work_root/vault.sock" ]] || return 1
|
|
local -A expected=()
|
|
for name in "${owned_files[@]}"; do expected["$name"]=1; done
|
|
_swr_inventory_directory _swr_inventory "$work_root" || return 1
|
|
for name in "${_swr_inventory[@]}"; do
|
|
[[ -n "${expected[$name]+x}" ]] || return 1
|
|
done
|
|
for name in "${owned_files[@]}"; do
|
|
path="$work_root/$name"
|
|
[[ -e "$path" || -L "$path" ]] || continue
|
|
[[ -f "$path" && ! -L "$path" && -O "$path" ]] || return 1
|
|
_swr_capture_single_line _swr_metadata /usr/bin/stat --format=%u:%g:%a:%h -- "$path" || return 1
|
|
[[ "$_swr_metadata" == 1000:1000:600:1 ]] || return 1
|
|
done
|
|
for path in "${owned_files[@]}"; do
|
|
path="$work_root/$path"
|
|
[[ -e "$path" || -L "$path" ]] || continue
|
|
[[ -f "$path" && ! -L "$path" && -O "$path" ]] || return 1
|
|
_swr_capture_single_line _swr_metadata /usr/bin/stat --format=%u:%g:%a:%h -- "$path" || return 1
|
|
[[ "$_swr_metadata" == 1000:1000:600:1 ]] || return 1
|
|
_swr_command /usr/bin/rm -f -- "$path" || return 1
|
|
done
|
|
_swr_command /usr/bin/rmdir -- "$work_root"
|
|
}
|
|
|
|
cleanup_transaction() {
|
|
local work_root="${1-}" device="${2-}" mountpoint="${3-}"
|
|
local socket_path="${4-}" child_pid="${5-}" rc=0 unmounted=0
|
|
[[ $# -eq 5 ]] || return 1
|
|
if [[ -n "$child_pid" ]]; then
|
|
_swr_stop_vault "$socket_path" "$child_pid" || rc=1
|
|
fi
|
|
if [[ -n "$work_root" ]]; then
|
|
_swr_audit_runtime_processes "$work_root" || rc=1
|
|
fi
|
|
if [[ -n "$device" ]] && _swr_stably_unmounted "$device"; then
|
|
unmounted=1
|
|
elif [[ -n "$device" ]]; then
|
|
rc=1
|
|
fi
|
|
if (( unmounted != 0 )) && [[ -n "$mountpoint" && -d "$mountpoint" ]]; then
|
|
_swr_require_mountpoint_unused "$mountpoint" && _swr_remove_mountpoint "$mountpoint" || rc=1
|
|
fi
|
|
if (( unmounted != 0 )) && [[ -n "$work_root" ]]; then
|
|
_swr_remove_work_root "$work_root" || rc=1
|
|
fi
|
|
return "$rc"
|
|
}
|
|
|
|
_swr_restore_transaction_traps() {
|
|
_swr_restore_trap "$1" HUP
|
|
_swr_restore_trap "$2" INT
|
|
_swr_restore_trap "$3" TERM
|
|
}
|
|
|
|
_swr_unmount_transaction_once() {
|
|
local mounted_name="${1-}" device="${2-}" mountpoint="${3-}"
|
|
[[ $# -eq 3 && "$mounted_name" == mounted ]] || return 1
|
|
# Prearm the no-retry state before the potentially ambiguous unmount call.
|
|
builtin printf -v "$mounted_name" '%s' 0
|
|
_swr_unmount_volume "$device" "$mountpoint"
|
|
}
|
|
|
|
execute_transaction() {
|
|
local webhook_file="${1-}" contract_path="${2-}"
|
|
local _swr_work_root='' _swr_device='' _swr_mountpoint=''
|
|
local _swr_vault_pid='' _swr_vault_socket='' _swr_reply='' _swr_fs_commit=''
|
|
local backup_name='' mounted=0 cleanup_rc=0 final_rc=1 TZ=UTC0
|
|
local old_hup old_int old_term
|
|
[[ $# -eq 2 && "$webhook_file" == /* && "$contract_path" == /* ]] || return 1
|
|
SWR_TRANSACTION_STATE=PRECOMMIT_FAILURE
|
|
SWR_PENDING_SIGNAL=0
|
|
old_hup="$(builtin trap -p HUP)"
|
|
old_int="$(builtin trap -p INT)"
|
|
old_term="$(builtin trap -p TERM)"
|
|
builtin trap 'defer_signal HUP' HUP
|
|
builtin trap 'defer_signal INT' INT
|
|
builtin trap 'defer_signal TERM' TERM
|
|
|
|
if _swr_prepare_transaction _swr_work_root _swr_device _swr_mountpoint \
|
|
"$webhook_file" "$contract_path" && (( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_start_vault _swr_vault_pid _swr_vault_socket "$_swr_work_root" "$webhook_file" &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) && _swr_refresh_sudo &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) && _swr_probe_rw "$_swr_device" &&
|
|
(( SWR_PENDING_SIGNAL == 0 )); then
|
|
mounted=1
|
|
if _swr_mount_volume_command "$_swr_device" "$_swr_mountpoint" ro &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_validate_mount "$_swr_device" "$_swr_mountpoint" ro &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_dirfd_snapshot "$_swr_mountpoint" "$_swr_work_root" &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_unmount_transaction_once mounted "$_swr_device" "$_swr_mountpoint" &&
|
|
(( SWR_PENDING_SIGNAL == 0 )); then
|
|
if _swr_vault_request _swr_reply "$_swr_vault_socket" PREPARE; then
|
|
(( SWR_PENDING_SIGNAL == 0 )) || _swr_reply=signal-pending
|
|
case "$_swr_reply" in
|
|
exact-noop)
|
|
if (( SWR_PENDING_SIGNAL == 0 )); then
|
|
SWR_TRANSACTION_STATE=NOOP
|
|
final_rc=0
|
|
fi
|
|
;;
|
|
absent|mismatch)
|
|
if [[ "$_swr_reply" == mismatch ]] && ! _swr_confirm_token UPDATE; then
|
|
final_rc=1
|
|
elif (( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_vault_request _swr_reply "$_swr_vault_socket" \
|
|
"BUILD $([[ "$_swr_reply" == absent ]] && builtin printf ADD || builtin printf UPDATE)" &&
|
|
[[ "$_swr_reply" == candidate-ready ]] &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_vault_request _swr_reply "$_swr_vault_socket" VERIFY_CANDIDATE &&
|
|
[[ "$_swr_reply" == verified ]] &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_confirm_token WRITE && (( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_probe_rw "$_swr_device" && (( SWR_PENDING_SIGNAL == 0 )); then
|
|
mounted=1
|
|
if _swr_mount_volume_command "$_swr_device" "$_swr_mountpoint" rw &&
|
|
(( SWR_PENDING_SIGNAL == 0 )) &&
|
|
_swr_validate_mount "$_swr_device" "$_swr_mountpoint" rw &&
|
|
(( SWR_PENDING_SIGNAL == 0 )); then
|
|
builtin printf -v backup_name 'hyeonworks-recovery.pre-slack-%(%Y%m%dT%H%M%SZ)T.kdbx' -1
|
|
if _swr_dirfd_commit _swr_fs_commit "$_swr_mountpoint" "$_swr_work_root" "$backup_name"; then
|
|
if [[ "$_swr_fs_commit" == verified-commit ]]; then
|
|
if (( SWR_PENDING_SIGNAL != 0 )); then
|
|
SWR_TRANSACTION_STATE=COMMITTED_BUT_UNCERTAIN
|
|
elif _swr_dirfd_snapshot_current "$_swr_mountpoint" "$_swr_work_root" &&
|
|
_swr_vault_request _swr_reply "$_swr_vault_socket" \
|
|
"VERIFY_COMMITTED $_swr_work_root/current.kdbx" &&
|
|
[[ "$_swr_reply" == verified ]] &&
|
|
(( SWR_PENDING_SIGNAL == 0 )); then
|
|
SWR_TRANSACTION_STATE=VERIFIED_COMMIT
|
|
else
|
|
SWR_TRANSACTION_STATE=COMMITTED_BUT_UNCERTAIN
|
|
fi
|
|
else
|
|
SWR_TRANSACTION_STATE=COMMITTED_BUT_UNCERTAIN
|
|
fi
|
|
else
|
|
case "$_swr_fs_commit" in
|
|
precommit-failure) SWR_TRANSACTION_STATE=PRECOMMIT_FAILURE ;;
|
|
rename-attempted|committed-but-uncertain|missing)
|
|
SWR_TRANSACTION_STATE=COMMITTED_BUT_UNCERTAIN
|
|
;;
|
|
*) SWR_TRANSACTION_STATE=COMMITTED_BUT_UNCERTAIN ;;
|
|
esac
|
|
fi
|
|
if _swr_unmount_transaction_once mounted "$_swr_device" "$_swr_mountpoint"; then
|
|
[[ "$SWR_TRANSACTION_STATE" == VERIFIED_COMMIT ]] && final_rc=0 || final_rc=1
|
|
else
|
|
SWR_TRANSACTION_STATE=MANUAL_RECOVERY_REQUIRED
|
|
final_rc=1
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
ambiguous|failed) final_rc=1 ;;
|
|
signal-pending) final_rc=1 ;;
|
|
*) final_rc=1 ;;
|
|
esac
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if (( mounted != 0 )); then
|
|
if _swr_unmount_volume "$_swr_device" "$_swr_mountpoint"; then
|
|
mounted=0
|
|
else
|
|
SWR_TRANSACTION_STATE=MANUAL_RECOVERY_REQUIRED
|
|
fi
|
|
fi
|
|
cleanup_transaction "$_swr_work_root" "$_swr_device" "$_swr_mountpoint" \
|
|
"$_swr_vault_socket" "$_swr_vault_pid" || cleanup_rc=$?
|
|
if (( cleanup_rc != 0 )); then
|
|
SWR_TRANSACTION_STATE=MANUAL_RECOVERY_REQUIRED
|
|
final_rc=1
|
|
fi
|
|
|
|
if (( SWR_PENDING_SIGNAL != 0 )); then
|
|
final_rc="$SWR_PENDING_SIGNAL"
|
|
fi
|
|
|
|
case "$SWR_TRANSACTION_STATE" in
|
|
NOOP)
|
|
(( final_rc == 0 && SWR_PENDING_SIGNAL == 0 )) && builtin printf '%s\n' \
|
|
'SLACK_KEEPASS_RECOVERY=NOOP' \
|
|
'WINDOWS_SSD_UNMOUNTED=PASS' \
|
|
'OFF_HOST_RECOVERY_SATISFIED=NO'
|
|
;;
|
|
VERIFIED_COMMIT)
|
|
(( final_rc == 0 && SWR_PENDING_SIGNAL == 0 )) && builtin printf '%s\n' \
|
|
'SLACK_KEEPASS_RECOVERY=COMMITTED' \
|
|
'KDBX_PRECHANGE_BACKUP=CREATED' \
|
|
'WINDOWS_SSD_UNMOUNTED=PASS' \
|
|
'OFF_HOST_RECOVERY_SATISFIED=NO'
|
|
;;
|
|
COMMITTED_BUT_UNCERTAIN|MANUAL_RECOVERY_REQUIRED)
|
|
builtin printf '%s\n' 'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED'
|
|
final_rc=1
|
|
;;
|
|
esac
|
|
(( SWR_PENDING_SIGNAL == 0 )) || final_rc="$SWR_PENDING_SIGNAL"
|
|
_swr_restore_transaction_traps "$old_hup" "$old_int" "$old_term"
|
|
return "$final_rc"
|
|
}
|
|
|
|
_swr_main() {
|
|
local script_path="$0" _swr_mode _swr_webhook_path
|
|
|
|
if [[ "$script_path" == 'scripts/bootstrap/backup-slack-webhook-recovery.sh' ]]; then
|
|
script_path="${SWR_REPOSITORY_ROOT}/scripts/bootstrap/backup-slack-webhook-recovery.sh"
|
|
fi
|
|
|
|
_swr_parse_cli _swr_mode _swr_webhook_path "$@" || {
|
|
plain_fail 'expected no arguments or exactly --execute --slack-webhook-file ABSOLUTE_PATH'
|
|
return 1
|
|
}
|
|
_swr_validate_runtime "$_swr_mode" "$script_path" || {
|
|
plain_fail 'runtime guard rejected the invocation'
|
|
return 1
|
|
}
|
|
|
|
case "$_swr_mode" in
|
|
DRY_RUN)
|
|
_swr_print_contract
|
|
;;
|
|
EXECUTE)
|
|
[[ -n "$_swr_webhook_path" ]] || {
|
|
plain_fail 'absolute webhook path is required'
|
|
return 1
|
|
}
|
|
execute_transaction "$_swr_webhook_path" "${SWR_RECOVERY_CONTRACT-}"
|
|
;;
|
|
*)
|
|
plain_fail 'unsupported mode'
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
|
|
return 0
|
|
fi
|
|
_swr_main "$@"
|