日志、系统状态与基础排错
本节目标
建立一条可重复的基础排错顺序:先读错误与 stderr,再确认影响范围,依次检查路径、权限、进程和端口,只做最小修复,最后重新验证。完成后生成固定健康报告,并得到当前可用的 linux checkpoint。
开始前状态
从完整完成 LINUX-06 的 dev-lab 开始:固定访问日志、隔离配置、权限演示脚本和前六节 Linux 笔记都必须保持规范字节与执行位类别,且没有遗留监听进程、端口或临时网络目录。本节使用课程自带的 data/health-events.log 样例,不读取主机上的日志或状态数据。
必要原理
一条有效的排错记录需要区分事实与推测。INFO 表示样例中的正常观察,WARN 表示需要核对的条件;它们都是固定教学输入,不代表当前电脑发生过对应事件。命令失败时,先保留 stderr 和明确路径,再判断问题属于文件不存在、权限不足、进程退出还是端口不可用。
修复范围越小,越容易证明没有引入新问题。路径错误就修正路径,执行位错误就只处理明确的普通文件,进程问题只针对已保存的准确 PID,端口问题先核对监听范围。每次最小修复后都重新运行同一检查,而不是同时改动多个条件。
scripts/health-check.sh 是确定性报告生成器:它只接受一个显式练习目录,先验证完整输入,再通过自有同目录草稿无覆盖地原子发布最终文件。check-linux.sh 是另一项只读验证,它不会加载配置,也不会执行任何 checkpoint 脚本。
动手完成
下面的状态迁移先验证完整 LINUX-06 路径集合、真实目录、普通文件、精确字节和 Git 可移植的执行位类别,再从仓库内的权威 checkpoint 复制六个固定草稿。每个草稿都通过 noclobber 独占 FD 在目标目录建立并立即绑定身份,写入和执行位规范化均在这个 FD 关闭前完成,再复核 FD、路径身份与权限类别后逐项原子发布;raced-in 链接或替换会被拒绝,失败清理只处理仍保持原身份的自有文件,并按原模式恢复已替换的 README、笔记与权限演示脚本。
运行位置: 仓库根目录,且 DEVLAB_DIR 指向待转换的 LINUX-06 dev-lab。预期输出: 无 stdout 或 stderr;新增固定事件、生成器与报告,并原子更新 README 和最终笔记。失败处理: 任一前置、草稿或发布检查失败都应保持原 snapshot 与外部 sentinel 不变;根据具体路径修复后重新验证,不要手动绕过 guard。
advance_linux_health_checkpoint() (
set -euo pipefail
devlab_input="${DEVLAB_DIR:-}"
[[ -n "$devlab_input" ]] || { printf 'DEVLAB_DIR 必须是明确的练习目录。\n'; exit 1; }
[[ -d "$devlab_input" ]] || { printf 'DEVLAB_DIR 必须是真实目录。\n'; exit 1; }
devlab_logical="$(cd -- "$devlab_input" && pwd -L)" || exit 1
devlab_dir="$(cd -- "$devlab_input" && pwd -P)" || exit 1
[[ "$devlab_logical" == "$devlab_dir" && ! -L "$devlab_input" ]] || {
printf 'DEVLAB_DIR 不得经过符号链接。\n'
exit 1
}
[[ "$devlab_dir" != / ]] || { printf 'DEVLAB_DIR 必须是明确的练习目录。\n'; exit 1; }
if [[ -n "${HOME:-}" && -d "$HOME" ]]; then
home_dir="$(cd -- "$HOME" && pwd -P)" || exit 1
[[ "$devlab_dir" != "$home_dir" ]] || { printf 'DEVLAB_DIR 不得是用户主目录。\n'; exit 1; }
fi
mode_digits() {
local mode
if mode="$(LC_ALL=C stat -c '%a' "$1" 2>/dev/null)"; then :; else
mode="$(LC_ALL=C stat -f '%Lp' "$1")" || return 1
fi
printf '%s' "$mode"
}
file_identity() {
local identity
if identity="$(LC_ALL=C stat -c '%d:%i' "$1" 2>/dev/null)"; then :; else
identity="$(LC_ALL=C stat -f '%d:%i' "$1")" || return 1
fi
printf '%s' "$identity"
}
file_inode() {
local inode
if inode="$(LC_ALL=C stat -L -c '%i' "$1" 2>/dev/null)"; then :; else
inode="$(LC_ALL=C stat -L -f '%i' "$1")" || return 1
fi
printf '%s' "$inode"
}
fd_matches_identity() {
local fd_path="$1"
local expected_identity="$2"
local fd_identity=''
local system_name=''
if fd_identity="$(LC_ALL=C stat -L -c '%d:%i' "$fd_path" 2>/dev/null)"; then :; else
fd_identity="$(LC_ALL=C stat -L -f '%d:%i' "$fd_path")" || return 1
fi
if [[ "$fd_identity" == "$expected_identity" ]]; then
return 0
fi
if [[ -x /usr/bin/uname ]]; then
system_name="$(/usr/bin/uname -s)" || return 1
else
system_name="$(/bin/uname -s)" || return 1
fi
[[ "$system_name" == Darwin && "${fd_identity##*:}" == "${expected_identity##*:}" ]]
}
file_hash() {
local output
if command -v sha256sum >/dev/null 2>&1; then
output="$(sha256sum -- "$1")" || return 1
elif command -v shasum >/dev/null 2>&1; then
output="$(shasum -a 256 -- "$1")" || return 1
else
return 1
fi
printf '%s' "${output%% *}"
}
has_any_execute_bit() {
local mode_text
mode_text="$(LC_ALL=C ls -ld "$1")"
mode_text="${mode_text%% *}"
[[ "$mode_text" == *[xstST]* ]]
}
require_directory() {
local relative_path="$1"
[[ -d "$devlab_dir/$relative_path" && ! -L "$devlab_dir/$relative_path" ]] || exit 1
}
require_predecessor_file() {
local expected_hash="$1"
local relative_path="$2"
local expected_class="$3"
[[ -f "$devlab_dir/$relative_path" && ! -L "$devlab_dir/$relative_path" ]] || exit 1
case "$expected_class" in
x) [[ -x "$devlab_dir/$relative_path" ]] || exit 1 ;;
-) ! has_any_execute_bit "$devlab_dir/$relative_path" || exit 1 ;;
esac
[[ "$(file_hash "$devlab_dir/$relative_path")" == "$expected_hash" ]] || exit 1
}
for relative_path in config data notes output scripts; do require_directory "$relative_path"; done
expected_paths='README.md
config
config/bashrc.dev-lab
config/env.dev-lab
data
data/access.log
notes
notes/linux-notes.md
notes/shell-notes.md
output
output/summary.txt
scripts
scripts/permission-demo.sh
scripts/report.sh'
actual_paths="$(cd "$devlab_dir" && find . -mindepth 1 -print | sed 's#^\./##' | LC_ALL=C sort)"
[[ "$actual_paths" == "$expected_paths" ]] || { printf 'LINUX-06 路径集合不匹配。\n'; exit 1; }
while read -r expected_hash relative_path expected_class; do
require_predecessor_file "$expected_hash" "$relative_path" "$expected_class"
done <<'EXPECTED_PREDECESSOR'
f007cc31c8178ea0c1338c4b4e505d389b798e0b4b1ceec7159c4af9b3ff40ea README.md -
882ef7d9502cc5d8145c5f8b0321dbd17b0c7d431db9421cbe390f1f86e94e12 config/bashrc.dev-lab -
00aec1c6d894bb334bef59a03fad5d6392f0a2c1e5517cb89b788c4ad72b390a config/env.dev-lab -
1df6e34d28da0483cc1e62523acc777597ce3178a77d089bf7ffcabb4b0ab254 data/access.log -
d937ab49e62af7d2f5bbb92f457476afa2a9632a9d1c68135a30a98ffe9aed7d notes/shell-notes.md -
57c722d8618350451763615ce0899cb6608e852cc889dc82997f77d7a5f93144 notes/linux-notes.md -
fd45eca2af0f073a58b8a6291f64804b6e5eade93aa773c6d5e49832edd65591 output/summary.txt -
eb8bf1b36e7bd043f64665680b71d086a0a9aeb3c5b5a045fe2fb3e40bb52e37 scripts/report.sh x
3f54de46ae71a2b3af65bd3aeaf9c28bbf91409abc67c87f718c16c4961445e8 scripts/permission-demo.sh x
EXPECTED_PREDECESSOR
BASH_ENV= ENV= /bin/bash --noprofile --norc -n "$devlab_dir/config/bashrc.dev-lab"
BASH_ENV= ENV= /bin/bash --noprofile --norc -n "$devlab_dir/scripts/report.sh"
BASH_ENV= ENV= /bin/bash --noprofile --norc -n "$devlab_dir/scripts/permission-demo.sh"
candidate_input="$PWD/code/devenv/dev-lab/checkpoints/linux"
[[ -d "$candidate_input" && ! -L "$candidate_input" ]] || {
printf '找不到明确的 linux checkpoint。\n'
exit 1
}
candidate_dir="$(cd -- "$candidate_input" && pwd -P)" || exit 1
[[ "$candidate_dir" != "$devlab_dir" ]] || exit 1
while read -r expected_hash relative_path expected_class; do
[[ -f "$candidate_dir/$relative_path" && ! -L "$candidate_dir/$relative_path" ]] || exit 1
case "$expected_class" in
x) [[ -x "$candidate_dir/$relative_path" ]] || exit 1 ;;
-) ! has_any_execute_bit "$candidate_dir/$relative_path" || exit 1 ;;
*) exit 1 ;;
esac
[[ "$(file_hash "$candidate_dir/$relative_path")" == "$expected_hash" ]] || exit 1
done <<'EXPECTED_CANDIDATE'
f35c009dec20278b5df50693da43c54ae4b8e228a173fcb9f59e1b3aea5a2dc5 README.md -
fe0119bfe33a541d816c33740f38b32280896ffce54bd7841985c7b1e6bd06e5 data/health-events.log -
e54276c7c8a2c2818aa48891030e31f7712fde6f5f42b70257c1a3e9582086dc notes/linux-notes.md -
32c0404e72a9692ecac0a2877a98b8ae09fa90090c2af9dbdbc3c9da18a1cf1e output/health-report.txt -
5fa78e594202c38e9004b65fe69c0281c509b00ad7c10b0c48cfd2b383720e4f scripts/health-check.sh x
3f54de46ae71a2b3af65bd3aeaf9c28bbf91409abc67c87f718c16c4961445e8 scripts/permission-demo.sh x
EXPECTED_CANDIDATE
BASH_ENV= ENV= /bin/bash --noprofile --norc -n "$candidate_dir/scripts/health-check.sh"
readme_draft="$devlab_dir/.README.md.next"
notes_draft="$devlab_dir/notes/.linux-notes.md.next"
events_draft="$devlab_dir/data/.health-events.log.next"
permission_draft="$devlab_dir/scripts/.permission-demo.sh.next"
health_draft="$devlab_dir/scripts/.health-check.sh.next"
report_draft="$devlab_dir/output/.health-report.txt.next"
readme_rollback="$devlab_dir/.README.md.rollback"
notes_rollback="$devlab_dir/notes/.linux-notes.md.rollback"
permission_rollback="$devlab_dir/scripts/.permission-demo.sh.rollback"
readme_recovery="$devlab_dir/.README.md.recovery"
notes_recovery="$devlab_dir/notes/.linux-notes.md.recovery"
permission_recovery="$devlab_dir/scripts/.permission-demo.sh.recovery"
readme_target="$devlab_dir/README.md"
notes_target="$devlab_dir/notes/linux-notes.md"
events_target="$devlab_dir/data/health-events.log"
permission_target="$devlab_dir/scripts/permission-demo.sh"
health_target="$devlab_dir/scripts/health-check.sh"
report_target="$devlab_dir/output/health-report.txt"
for target_path in "$events_target" "$health_target" "$report_target"; do
[[ ! -e "$target_path" && ! -L "$target_path" ]] || { printf '新目标已存在,停止并检查:%s\n' "$target_path"; exit 1; }
done
for draft_path in \
"$readme_draft" "$notes_draft" "$events_draft" "$permission_draft" "$health_draft" "$report_draft" \
"$readme_rollback" "$notes_rollback" "$permission_rollback" \
"$readme_recovery" "$notes_recovery" "$permission_recovery"; do
[[ ! -e "$draft_path" && ! -L "$draft_path" ]] || { printf '草稿路径已存在,停止并检查:%s\n' "$draft_path"; exit 1; }
done
readme_original_identity="$(file_identity "$readme_target")"
notes_original_identity="$(file_identity "$notes_target")"
permission_original_identity="$(file_identity "$permission_target")"
readme_original_mode="$(mode_digits "$readme_target")"
notes_original_mode="$(mode_digits "$notes_target")"
permission_original_mode="$(mode_digits "$permission_target")"
[[ "$readme_original_mode" =~ ^[0-7]{3,4}$ && "$notes_original_mode" =~ ^[0-7]{3,4}$ \
&& "$permission_original_mode" =~ ^[0-7]{3,4}$ ]] || exit 1
devlab_identity="$(file_identity "$devlab_dir")" || exit 1
devlab_parent_identity="$(file_identity "${devlab_dir%/*}")" || exit 1
notes_dir_identity="$(file_identity "$devlab_dir/notes")" || exit 1
data_dir_identity="$(file_identity "$devlab_dir/data")" || exit 1
scripts_dir_identity="$(file_identity "$devlab_dir/scripts")" || exit 1
output_dir_identity="$(file_identity "$devlab_dir/output")" || exit 1
readme_draft_identity=''
notes_draft_identity=''
events_draft_identity=''
permission_draft_identity=''
health_draft_identity=''
report_draft_identity=''
readme_rollback_identity=''
notes_rollback_identity=''
permission_rollback_identity=''
readme_recovery_identity=''
notes_recovery_identity=''
permission_recovery_identity=''
bind_parent() {
local parent_path="$1"
case "$parent_path" in
"$devlab_dir") parent_identity="$devlab_identity"; ancestor_identity="$devlab_parent_identity" ;;
"$devlab_dir/notes") parent_identity="$notes_dir_identity"; ancestor_identity="$devlab_identity" ;;
"$devlab_dir/data") parent_identity="$data_dir_identity"; ancestor_identity="$devlab_identity" ;;
"$devlab_dir/scripts") parent_identity="$scripts_dir_identity"; ancestor_identity="$devlab_identity" ;;
"$devlab_dir/output") parent_identity="$output_dir_identity"; ancestor_identity="$devlab_identity" ;;
*) return 1 ;;
esac
}
remove_if_owned() {
local owned_path="$1"
local owned_identity="$2"
local parent_path="${owned_path%/*}"
local owned_name="${owned_path##*/}"
local parent_identity=''
local ancestor_identity=''
[[ -n "$owned_identity" ]] || return 0
bind_parent "$parent_path" || return 1
(
cd -P -- "$parent_path" || exit 1
cleanup_owned_boundary="$owned_name"
[[ "$(file_identity .)" == "$parent_identity" \
&& "$(file_identity ..)" == "$ancestor_identity" \
&& ! -L "$parent_path" ]] || exit 1
[[ ! -e "./$owned_name" && ! -L "./$owned_name" ]] && exit 0
[[ -f "./$owned_name" && ! -L "./$owned_name" \
&& "$(file_identity "./$owned_name" 2>/dev/null)" == "$owned_identity" ]] || exit 1
/bin/rm -f -- "./$owned_name"
)
}
verify_owned_file() {
local owned_path="$1"
local owned_identity="$2"
local expected_hash="$3"
local expected_class="$4"
local expected_mode="${5:-}"
[[ -f "$owned_path" && ! -L "$owned_path" ]] || return 1
[[ "$(file_identity "$owned_path")" == "$owned_identity" ]] || return 1
[[ "$(file_hash "$owned_path")" == "$expected_hash" ]] || return 1
case "$expected_class" in
x) [[ -x "$owned_path" ]] || return 1 ;;
-) ! has_any_execute_bit "$owned_path" || return 1 ;;
*) return 1 ;;
esac
[[ -z "$expected_mode" || "$(mode_digits "$owned_path")" == "$expected_mode" ]] || return 1
}
copy_to_new_owned_file() {
local source_path="$1"
local owned_path="$2"
local identity_variable="$3"
local expected_class="$4"
local expected_mode="${5:-}"
local created_identity=''
local parent_path="${owned_path%/*}"
local owned_name="${owned_path##*/}"
local parent_identity=''
local ancestor_identity=''
local expected_hash=''
local mode_argument=''
local caller_directory="$PWD"
[[ -f "$source_path" && ! -L "$source_path" ]] || return 1
expected_hash="$(file_hash "$source_path")" || return 1
if [[ -n "$expected_mode" ]]; then
[[ "$expected_mode" =~ ^[0-7]{3,4}$ ]] || return 1
mode_argument="$expected_mode"
elif [[ "$expected_class" == - ]]; then
mode_argument=u=rw,go=r
elif [[ "$expected_class" == x ]]; then
mode_argument=u=rw,go=r,a+x
else
return 1
fi
bind_parent "$parent_path" || return 1
cd -P -- "$parent_path" || return 1
[[ "$(file_identity .)" == "$parent_identity" \
&& "$(file_identity ..)" == "$ancestor_identity" \
&& ! -e "./$owned_name" && ! -L "./$owned_name" ]] || return 1
set -o noclobber
exec 6> "./$owned_name" || { set +o noclobber; return 1; }
set +o noclobber
created_identity="$(file_identity "./$owned_name")" || { exec 6>&-; return 1; }
printf -v "$identity_variable" '%s' "$created_identity"
owned_post_create_boundary="$owned_name"
fd_matches_identity /dev/fd/6 "$created_identity" || { exec 6>&-; return 1; }
/bin/cat "$source_path" >&6 || { exec 6>&-; return 1; }
owned_post_write_boundary="$owned_name"
/bin/chmod "$mode_argument" /dev/fd/6 || { exec 6>&-; return 1; }
owned_post_mode_boundary="$owned_name"
fd_matches_identity /dev/fd/6 "$created_identity" || { exec 6>&-; return 1; }
[[ -f "./$owned_name" && ! -L "./$owned_name" \
&& "$(file_identity "./$owned_name")" == "$created_identity" \
&& "$(file_identity .)" == "$parent_identity" \
&& "$(file_identity ..)" == "$ancestor_identity" ]] || { exec 6>&-; return 1; }
exec 6>&-
cd -P -- "$caller_directory" || return 1
verify_owned_file "$owned_path" "$created_identity" "$expected_hash" "$expected_class" "$expected_mode" || return 1
[[ "$(file_hash "$source_path")" == "$expected_hash" ]] || return 1
}
publish_owned_file() {
local label="$1"
local owned_path="$2"
local owned_identity="$3"
local final_path="$4"
local expected_final_identity="$5"
local trusted_move="${6:-0}"
local parent_path="${owned_path%/*}"
local owned_name="${owned_path##*/}"
local final_name="${final_path##*/}"
local parent_identity=''
local ancestor_identity=''
local replace_mode=0
bind_parent "$parent_path" || return 1
[[ "${final_path%/*}" == "$parent_path" ]] || return 1
[[ -n "$expected_final_identity" ]] && replace_mode=1
(
cd -P -- "$parent_path" || exit 1
case "$label" in
readme) linux07_publication_boundary=readme ;;
notes) linux07_publication_boundary=notes ;;
permission) linux07_publication_boundary=permission ;;
events) linux07_publication_boundary=events ;;
health) linux07_publication_boundary=health ;;
report) linux07_publication_boundary=report ;;
*) linux07_publication_boundary="$label" ;;
esac
[[ "$(file_identity .)" == "$parent_identity" \
&& "$(file_identity ..)" == "$ancestor_identity" \
&& ! -L "$parent_path" \
&& -f "./$owned_name" && ! -L "./$owned_name" \
&& "$(file_identity "./$owned_name")" == "$owned_identity" ]] || exit 1
if [[ "$replace_mode" -eq 1 ]]; then
[[ -f "./$final_name" && ! -L "./$final_name" \
&& "$(file_identity "./$final_name")" == "$expected_final_identity" ]] || exit 1
else
[[ ! -e "./$final_name" && ! -L "./$final_name" ]] || exit 1
fi
case "$label" in
readme) linux07_publication_commit_boundary=readme ;;
notes) linux07_publication_commit_boundary=notes ;;
permission) linux07_publication_commit_boundary=permission ;;
events) linux07_publication_commit_boundary=events ;;
health) linux07_publication_commit_boundary=health ;;
report) linux07_publication_commit_boundary=report ;;
*) linux07_publication_commit_boundary="$label" ;;
esac
[[ "$(file_identity .)" == "$parent_identity" \
&& "$(file_identity ..)" == "$ancestor_identity" \
&& ! -L "$parent_path" \
&& -f "./$owned_name" && ! -L "./$owned_name" \
&& "$(file_identity "./$owned_name")" == "$owned_identity" ]] || exit 1
if [[ "$replace_mode" -eq 1 ]]; then
[[ -f "./$final_name" && ! -L "./$final_name" \
&& "$(file_identity "./$final_name")" == "$expected_final_identity" ]] || exit 1
else
[[ ! -e "./$final_name" && ! -L "./$final_name" ]] || exit 1
fi
if [[ "$trusted_move" -eq 1 ]]; then
if [[ "$(/usr/bin/uname -s 2>/dev/null || /bin/uname -s)" == Darwin ]]; then
if [[ "$replace_mode" -eq 1 ]]; then /bin/mv -f -h -- "./$owned_name" "./$final_name"; else /bin/mv -n -h -- "./$owned_name" "./$final_name"; fi
else
if [[ "$replace_mode" -eq 1 ]]; then /bin/mv -f -T -- "./$owned_name" "./$final_name"; else /bin/mv -n -T -- "./$owned_name" "./$final_name"; fi
fi
elif [[ "$(/usr/bin/uname -s 2>/dev/null || /bin/uname -s)" == Darwin ]]; then
if [[ "$replace_mode" -eq 1 ]]; then mv -f -h -- "./$owned_name" "./$final_name"; else mv -n -h -- "./$owned_name" "./$final_name"; fi
else
if [[ "$replace_mode" -eq 1 ]]; then mv -f -T -- "./$owned_name" "./$final_name"; else mv -n -T -- "./$owned_name" "./$final_name"; fi
fi || exit 1
[[ ! -e "./$owned_name" && ! -L "./$owned_name" \
&& -f "./$final_name" && ! -L "./$final_name" \
&& "$(file_identity "./$final_name")" == "$owned_identity" \
&& "$(file_identity .)" == "$parent_identity" \
&& "$(file_identity ..)" == "$ancestor_identity" ]]
)
}
transition_success=0
readme_installed=''
notes_installed=''
events_installed=''
permission_installed=''
health_installed=''
report_installed=''
restore_replaced_file() {
local target_path="$1"
local installed_identity="$2"
local rollback_path="$3"
local rollback_identity="$4"
local recovery_path="$5"
local recovery_variable="$6"
local expected_hash="$7"
local expected_class="$8"
local expected_mode="$9"
[[ -n "$installed_identity" ]] || return 0
verify_owned_file "$rollback_path" "$rollback_identity" "$expected_hash" "$expected_class" "$expected_mode" || return 1
if publish_owned_file "rollback-${target_path##*/}" "$rollback_path" "$rollback_identity" "$target_path" "$installed_identity"; then
verify_owned_file "$target_path" "$rollback_identity" "$expected_hash" "$expected_class" "$expected_mode"
return
fi
verify_owned_file "$rollback_path" "$rollback_identity" "$expected_hash" "$expected_class" "$expected_mode" || return 1
[[ -f "$target_path" && ! -L "$target_path" \
&& "$(file_identity "$target_path")" == "$installed_identity" ]] || return 1
if copy_to_new_owned_file "$rollback_path" "$recovery_path" "$recovery_variable" "$expected_class" "$expected_mode"; then
local recovery_identity="${!recovery_variable}"
if publish_owned_file "recovery-${target_path##*/}" "$recovery_path" "$recovery_identity" "$target_path" "$installed_identity" 1; then
verify_owned_file "$target_path" "$recovery_identity" "$expected_hash" "$expected_class" "$expected_mode" || return 1
printf -v "$recovery_variable" '%s' ''
remove_if_owned "$rollback_path" "$rollback_identity"
return
fi
return 1
fi
publish_owned_file "trusted-rollback-${target_path##*/}" "$rollback_path" "$rollback_identity" "$target_path" "$installed_identity" 1 \
&& verify_owned_file "$target_path" "$rollback_identity" "$expected_hash" "$expected_class" "$expected_mode"
}
cleanup_linux_health_transition() {
local cleanup_status="$?"
local cleanup_failed=0
trap - EXIT
set +e
if [[ "$transition_success" -ne 1 ]]; then
remove_if_owned "$report_target" "$report_installed" || cleanup_failed=1
remove_if_owned "$health_target" "$health_installed" || cleanup_failed=1
remove_if_owned "$events_target" "$events_installed" || cleanup_failed=1
restore_replaced_file "$permission_target" "$permission_installed" "$permission_rollback" "$permission_rollback_identity" "$permission_recovery" permission_recovery_identity 3f54de46ae71a2b3af65bd3aeaf9c28bbf91409abc67c87f718c16c4961445e8 x "$permission_original_mode" || cleanup_failed=1
restore_replaced_file "$notes_target" "$notes_installed" "$notes_rollback" "$notes_rollback_identity" "$notes_recovery" notes_recovery_identity 57c722d8618350451763615ce0899cb6608e852cc889dc82997f77d7a5f93144 - "$notes_original_mode" || cleanup_failed=1
restore_replaced_file "$readme_target" "$readme_installed" "$readme_rollback" "$readme_rollback_identity" "$readme_recovery" readme_recovery_identity f007cc31c8178ea0c1338c4b4e505d389b798e0b4b1ceec7159c4af9b3ff40ea - "$readme_original_mode" || cleanup_failed=1
fi
remove_if_owned "$readme_draft" "$readme_draft_identity" || cleanup_failed=1
remove_if_owned "$notes_draft" "$notes_draft_identity" || cleanup_failed=1
remove_if_owned "$events_draft" "$events_draft_identity" || cleanup_failed=1
remove_if_owned "$permission_draft" "$permission_draft_identity" || cleanup_failed=1
remove_if_owned "$health_draft" "$health_draft_identity" || cleanup_failed=1
remove_if_owned "$report_draft" "$report_draft_identity" || cleanup_failed=1
remove_if_owned "$readme_rollback" "$readme_rollback_identity" || cleanup_failed=1
remove_if_owned "$notes_rollback" "$notes_rollback_identity" || cleanup_failed=1
remove_if_owned "$permission_rollback" "$permission_rollback_identity" || cleanup_failed=1
remove_if_owned "$readme_recovery" "$readme_recovery_identity" || cleanup_failed=1
remove_if_owned "$notes_recovery" "$notes_recovery_identity" || cleanup_failed=1
remove_if_owned "$permission_recovery" "$permission_recovery_identity" || cleanup_failed=1
if [[ "$cleanup_failed" -ne 0 ]]; then
printf 'LINUX-07 回滚或清理失败;已保留外来替换,请检查精确路径。\n' >&2
exit 1
fi
exit "$cleanup_status"
}
trap cleanup_linux_health_transition EXIT
copy_to_new_owned_file "$candidate_dir/README.md" "$readme_draft" readme_draft_identity -
copy_to_new_owned_file "$candidate_dir/notes/linux-notes.md" "$notes_draft" notes_draft_identity -
copy_to_new_owned_file "$candidate_dir/data/health-events.log" "$events_draft" events_draft_identity -
copy_to_new_owned_file "$candidate_dir/scripts/permission-demo.sh" "$permission_draft" permission_draft_identity x
copy_to_new_owned_file "$candidate_dir/scripts/health-check.sh" "$health_draft" health_draft_identity x
copy_to_new_owned_file "$candidate_dir/output/health-report.txt" "$report_draft" report_draft_identity -
verify_owned_file "$readme_draft" "$readme_draft_identity" f35c009dec20278b5df50693da43c54ae4b8e228a173fcb9f59e1b3aea5a2dc5 -
verify_owned_file "$notes_draft" "$notes_draft_identity" e54276c7c8a2c2818aa48891030e31f7712fde6f5f42b70257c1a3e9582086dc -
verify_owned_file "$events_draft" "$events_draft_identity" fe0119bfe33a541d816c33740f38b32280896ffce54bd7841985c7b1e6bd06e5 -
verify_owned_file "$permission_draft" "$permission_draft_identity" 3f54de46ae71a2b3af65bd3aeaf9c28bbf91409abc67c87f718c16c4961445e8 x
verify_owned_file "$health_draft" "$health_draft_identity" 5fa78e594202c38e9004b65fe69c0281c509b00ad7c10b0c48cfd2b383720e4f x
verify_owned_file "$report_draft" "$report_draft_identity" 32c0404e72a9692ecac0a2877a98b8ae09fa90090c2af9dbdbc3c9da18a1cf1e -
[[ "$(file_identity "$readme_target")" == "$readme_original_identity" ]] || exit 1
[[ "$(file_identity "$notes_target")" == "$notes_original_identity" ]] || exit 1
[[ "$(file_identity "$permission_target")" == "$permission_original_identity" ]] || exit 1
copy_to_new_owned_file "$readme_target" "$readme_rollback" readme_rollback_identity - "$readme_original_mode"
copy_to_new_owned_file "$notes_target" "$notes_rollback" notes_rollback_identity - "$notes_original_mode"
copy_to_new_owned_file "$permission_target" "$permission_rollback" permission_rollback_identity x "$permission_original_mode"
verify_owned_file "$readme_rollback" "$readme_rollback_identity" f007cc31c8178ea0c1338c4b4e505d389b798e0b4b1ceec7159c4af9b3ff40ea - "$readme_original_mode"
verify_owned_file "$notes_rollback" "$notes_rollback_identity" 57c722d8618350451763615ce0899cb6608e852cc889dc82997f77d7a5f93144 - "$notes_original_mode"
verify_owned_file "$permission_rollback" "$permission_rollback_identity" 3f54de46ae71a2b3af65bd3aeaf9c28bbf91409abc67c87f718c16c4961445e8 x "$permission_original_mode"
[[ "$(file_identity "$readme_target")" == "$readme_original_identity" ]] || exit 1
[[ "$(file_identity "$notes_target")" == "$notes_original_identity" ]] || exit 1
[[ "$(file_identity "$permission_target")" == "$permission_original_identity" ]] || exit 1
publish_owned_file readme "$readme_draft" "$readme_draft_identity" "$readme_target" "$readme_original_identity"
readme_installed="$readme_draft_identity"
publish_owned_file notes "$notes_draft" "$notes_draft_identity" "$notes_target" "$notes_original_identity"
notes_installed="$notes_draft_identity"
publish_owned_file permission "$permission_draft" "$permission_draft_identity" "$permission_target" "$permission_original_identity"
permission_installed="$permission_draft_identity"
publish_owned_file events "$events_draft" "$events_draft_identity" "$events_target" ''
events_installed="$events_draft_identity"
publish_owned_file health "$health_draft" "$health_draft_identity" "$health_target" ''
health_installed="$health_draft_identity"
publish_owned_file report "$report_draft" "$report_draft_identity" "$report_target" ''
report_installed="$report_draft_identity"
verify_owned_file "$readme_target" "$readme_installed" f35c009dec20278b5df50693da43c54ae4b8e228a173fcb9f59e1b3aea5a2dc5 -
verify_owned_file "$notes_target" "$notes_installed" e54276c7c8a2c2818aa48891030e31f7712fde6f5f42b70257c1a3e9582086dc -
verify_owned_file "$permission_target" "$permission_installed" 3f54de46ae71a2b3af65bd3aeaf9c28bbf91409abc67c87f718c16c4961445e8 x
verify_owned_file "$events_target" "$events_installed" fe0119bfe33a541d816c33740f38b32280896ffce54bd7841985c7b1e6bd06e5 -
verify_owned_file "$health_target" "$health_installed" 5fa78e594202c38e9004b65fe69c0281c509b00ad7c10b0c48cfd2b383720e4f x
verify_owned_file "$report_target" "$report_installed" 32c0404e72a9692ecac0a2877a98b8ae09fa90090c2af9dbdbc3c9da18a1cf1e -
transition_success=1
remove_if_owned "$readme_rollback" "$readme_rollback_identity"
readme_rollback_identity=''
remove_if_owned "$notes_rollback" "$notes_rollback_identity"
notes_rollback_identity=''
remove_if_owned "$permission_rollback" "$permission_rollback_identity"
permission_rollback_identity=''
trap - EXIT
)
advance_linux_health_checkpoint
transition_status="$?"
unset -f advance_linux_health_checkpoint
[[ "$transition_status" -eq 0 ]] || exit "$transition_status"
迁移成功后再只读查看它刚创建的固定事件,并把 INFO 与 WARN 分开。不要把样例时间戳当作当前系统时间。
运行位置: 完成 LINUX-07 迁移的 dev-lab 根目录。预期输出: 四行固定事件,其中两行 INFO、两行 WARN;stderr 为空。失败处理: 若路径、字节或行数不同,恢复课程提供的固定输入,不要用当前系统输出替换它。
grep ' INFO ' data/health-events.log
grep ' WARN ' data/health-events.log
迁移完成后,在 dev-lab 的父目录放置下载的 checker,并把练习目录作为唯一参数。
运行位置: 包含 check-linux.sh 与 dev-lab/ 的父目录。预期输出: checker stdout 只有 linux checkpoint: ok;报告内容仍在文件中,不会混入 checker stdout。失败处理: 按 stderr 定位相对路径,先恢复规范内容或执行位类别,再重新运行;checker 不会替你修改文件。
/usr/bin/env -i PATH=/usr/bin:/bin /bin/bash check-linux.sh ./dev-lab
观察结果
固定 health-events.log 恰有两条 INFO 和两条 WARN。生成的 health-report.txt 只包含边界、权限、配置档位、HTTP 请求与告警计数,不包含用户名、主机名、PID、实际端口或当前时间。checker 成功行与报告六行保持两个独立区域。
常见问题
为什么不直接执行权限演示脚本来验证? 脚本的类型、字节、执行位类别与语法已经足以证明规范状态;只读 checker 不应通过执行脚本制造额外行为。
为什么报告生成器和 checker 分开? 生成器拥有一个明确写入职责,checker 则需要保持完全只读。分离后可以用检查前后 snapshot 直接证明 checker 没有改动目标。
出现 WARN 就表示练习失败吗? 不是。这里的两条 WARN 是固定样例,用来练习从证据到最小修复的流程;完成条件是固定输入和报告均符合契约。
完成检查
- 固定事件包含两条 INFO 与两条 WARN;
- 排错顺序覆盖 stderr、路径、权限、进程、端口、最小修复与重新验证;
health-check.sh只写自有报告草稿并无覆盖地原子发布最终报告;check-linux.sh成功 stdout 只有linux checkpoint: ok,stderr 为空;- linux checkpoint 与 checker 已发布可用,且两者保持同一固定状态契约。
下一步
Linux 基础序列到此完成。GIT-01 仍是后续计划项,本页不提供尚未公开的入口。