valise-sh 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +152 -2
- package/README.md +99 -146
- package/bin/valise +57 -5376
- package/docs/architecture.md +77 -121
- package/docs/contrib-modules.md +172 -218
- package/docs/installation.md +137 -83
- package/docs/module-abi.md +125 -126
- package/docs/projects.md +249 -0
- package/docs/release-policy.md +68 -93
- package/docs/shared-modules.md +40 -39
- package/examples/README.md +12 -11
- package/examples/e2e/README.md +59 -66
- package/examples/e2e/bundles.tsv +6 -0
- package/examples/e2e/module.d/e2e_aliases.meta +1 -2
- package/examples/e2e/module.d/e2e_completions_bash.meta +0 -1
- package/examples/e2e/module.d/e2e_completions_zsh.meta +0 -1
- package/examples/e2e/module.d/e2e_env_setup.meta +2 -3
- package/examples/e2e/module.d/e2e_fish_features.meta +0 -1
- package/examples/e2e/module.d/e2e_generated_config.meta +1 -2
- package/examples/e2e/module.d/e2e_ksh_features.meta +0 -1
- package/examples/e2e/module.d/e2e_layer_probe.meta +1 -2
- package/examples/e2e/module.d/e2e_optional_network.meta +1 -2
- package/examples/e2e/module.d/e2e_platform_tools.meta +8 -0
- package/examples/e2e/module.d/e2e_platform_tools_bsd.meta +2 -3
- package/examples/e2e/module.d/e2e_platform_tools_darwin.meta +2 -3
- package/examples/e2e/module.d/e2e_platform_tools_linux.meta +2 -3
- package/examples/e2e/valise.conf +4 -0
- package/examples/e2e/variants.tsv +3 -0
- package/examples/local/module.d/hello_valise.meta +1 -2
- package/examples/profile-project/README.md +78 -108
- package/examples/profile-project/bundles.tsv +8 -0
- package/examples/profile-project/{shell/layers → layers}/demo/module.d/contrib_demo_greeting.meta +1 -2
- package/examples/profile-project/{shell/module.d → module.d}/greeting.meta +1 -2
- package/examples/profile-project/{shell/module.d → module.d}/profile_main.meta +0 -1
- package/examples/profile-project/{shell/modules → modules}/greeting.sh +1 -1
- package/examples/profile-project/valise.conf +4 -0
- package/examples/profile-project/variants.tsv +3 -0
- package/examples/shared/README.md +13 -34
- package/examples/shared/module.d/contrib_shared_paths.meta +2 -3
- package/examples/shared/module.d/contrib_shared_umask.meta +1 -2
- package/examples/shared/modules/shared/contrib_shared_paths.sh +2 -2
- package/lib/capabilities.sh +40 -13
- package/lib/cli/commands.sh +278 -0
- package/lib/cli/common.sh +326 -0
- package/lib/cli/devtools.sh +539 -0
- package/lib/cli/fish-hook.sh +986 -0
- package/lib/cli/integration.sh +337 -0
- package/lib/cli/lifecycle.sh +578 -0
- package/lib/cli/project.sh +1807 -0
- package/lib/cli/store.sh +1023 -0
- package/lib/cli/usage.sh +308 -0
- package/lib/compiler.awk +11 -4
- package/lib/fish/loader.fish +69 -9
- package/lib/fish/metadata.fish +49 -5
- package/lib/fish/profile.fish +170 -0
- package/lib/loader.sh +6 -8
- package/lib/metadata.sh +111 -5
- package/manifests/parser-support.tsv +2 -2
- package/module.d/valise_editor.meta +9 -0
- package/module.d/valise_history.meta +9 -0
- package/module.d/valise_pager.meta +9 -0
- package/module.d/valise_path.meta +9 -0
- package/module.d/{platform_bsd.meta → valise_platform_bsd.meta} +3 -4
- package/module.d/{platform_linux.meta → valise_platform_linux.meta} +3 -4
- package/module.d/{platform_macos.meta → valise_platform_macos.meta} +3 -4
- package/module.d/{platform_posix.meta → valise_platform_posix.meta} +2 -3
- package/module.d/valise_xdg.meta +9 -0
- package/modules/system/{contrib_editor.sh → valise_editor.sh} +2 -2
- package/modules/system/{contrib_history.sh → valise_history.sh} +2 -2
- package/modules/system/{contrib_pager.sh → valise_pager.sh} +2 -2
- package/modules/system/{contrib_path.sh → valise_path.sh} +3 -3
- package/modules/system/{contrib_xdg.sh → valise_xdg.sh} +2 -2
- package/package.json +91 -10
- package/docs/variants.md +0 -327
- package/entrypoints/.gitkeep +0 -0
- package/entrypoints/fish/profile.fish +0 -99
- package/examples/e2e/build-manifest.sh +0 -138
- package/examples/local/.gitkeep +0 -0
- package/examples/profile-project/shell/release/bundle-roots +0 -9
- package/examples/profile-project/shell/release/install-variants +0 -3
- package/examples/profile-project/shell/release/module-registry.tsv +0 -4
- package/examples/profile-project/shell/release/project.conf +0 -3
- package/lib/.gitkeep +0 -0
- package/manifests/.gitkeep +0 -0
- package/module.d/.gitkeep +0 -0
- package/module.d/contrib_editor.meta +0 -10
- package/module.d/contrib_history.meta +0 -10
- package/module.d/contrib_pager.meta +0 -10
- package/module.d/contrib_path.meta +0 -10
- package/module.d/contrib_xdg.meta +0 -10
- package/modules/.gitkeep +0 -0
- /package/examples/profile-project/{shell/layers → layers}/demo/modules/shared/contrib_demo_greeting.sh +0 -0
package/lib/capabilities.sh
CHANGED
|
@@ -666,6 +666,12 @@ sp_cap_write_compromise_paths() (
|
|
|
666
666
|
tree:0)
|
|
667
667
|
command -p find "$_spc_path" \( -type l -o ! -user "$_spc_uid" -o \( ! -type l -a \( -perm -020 -o -perm -002 \) \) \) -print
|
|
668
668
|
;;
|
|
669
|
+
tree-owned:1)
|
|
670
|
+
command -p find "$_spc_path" \( ! -user "$_spc_uid" -o \( ! -type l -a \( \( -perm -020 ! -group "$_spc_group" \) -o -perm -002 \) \) \) -print
|
|
671
|
+
;;
|
|
672
|
+
tree-owned:0)
|
|
673
|
+
command -p find "$_spc_path" \( ! -user "$_spc_uid" -o \( ! -type l -a \( -perm -020 -o -perm -002 \) \) \) -print
|
|
674
|
+
;;
|
|
669
675
|
*) exit 1 ;;
|
|
670
676
|
esac
|
|
671
677
|
' sh "$_spc_path" "$_spc_scope"
|
|
@@ -689,71 +695,92 @@ sp_cap_write_compromise_paths() (
|
|
|
689
695
|
tree:0)
|
|
690
696
|
command -p find "$_spc_path" \( -type l -o ! -user "$_spc_uid" -o \( ! -type l -a \( -perm -020 -o -perm -002 \) \) \) -print
|
|
691
697
|
;;
|
|
698
|
+
tree-owned:1)
|
|
699
|
+
command -p find "$_spc_path" \( ! -user "$_spc_uid" -o \( ! -type l -a \( \( -perm -020 ! -group "$_spc_group" \) -o -perm -002 \) \) \) -print
|
|
700
|
+
;;
|
|
701
|
+
tree-owned:0)
|
|
702
|
+
command -p find "$_spc_path" \( ! -user "$_spc_uid" -o \( ! -type l -a \( -perm -020 -o -perm -002 \) \) \) -print
|
|
703
|
+
;;
|
|
692
704
|
*) exit 1 ;;
|
|
693
705
|
esac
|
|
694
706
|
fi
|
|
695
707
|
)
|
|
696
708
|
|
|
709
|
+
# sp_cap_secure_local_bin_check <home> [reject|allow-owned-symlinks]
|
|
710
|
+
# The default rejects every symlink in ~/.local/bin. allow-owned-symlinks
|
|
711
|
+
# accepts a symlink whose link entry is owned by the current user, which is
|
|
712
|
+
# the shape pipx and uv install; unowned entries and group/other-writable
|
|
713
|
+
# regular files are still rejected.
|
|
697
714
|
sp_cap_secure_local_bin_check() {
|
|
698
|
-
[ "$#" -
|
|
715
|
+
[ "$#" -ge 1 ] && [ "$#" -le 2 ] && [ -n "$1" ] || return 1
|
|
699
716
|
_spc_home=$1
|
|
717
|
+
_spc_symlink_policy=${2:-reject}
|
|
718
|
+
case "$_spc_symlink_policy" in
|
|
719
|
+
reject) _spc_bin_scope=tree ;;
|
|
720
|
+
allow-owned-symlinks) _spc_bin_scope=tree-owned ;;
|
|
721
|
+
*)
|
|
722
|
+
sp_cap_error "unknown symlink policy: $_spc_symlink_policy"
|
|
723
|
+
unset _spc_home _spc_symlink_policy
|
|
724
|
+
return 1
|
|
725
|
+
;;
|
|
726
|
+
esac
|
|
700
727
|
case "$_spc_home" in
|
|
701
728
|
/*) ;;
|
|
702
729
|
*)
|
|
703
730
|
sp_cap_error 'HOME is not an absolute path'
|
|
704
|
-
unset _spc_home
|
|
731
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope
|
|
705
732
|
return 1
|
|
706
733
|
;;
|
|
707
734
|
esac
|
|
708
735
|
[ -d "$_spc_home" ] && [ ! -L "$_spc_home" ] || {
|
|
709
736
|
sp_cap_error "HOME is not a real directory: $_spc_home"
|
|
710
|
-
unset _spc_home
|
|
737
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope
|
|
711
738
|
return 1
|
|
712
739
|
}
|
|
713
740
|
_spc_local_dir=$_spc_home/.local
|
|
714
741
|
_spc_local_bin=$_spc_local_dir/bin
|
|
715
742
|
_spc_home_bad=$(sp_cap_write_compromise_paths "$_spc_home" self) || {
|
|
716
743
|
sp_cap_error "failed to inspect HOME permissions: $_spc_home"
|
|
717
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad
|
|
744
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad
|
|
718
745
|
return 1
|
|
719
746
|
}
|
|
720
747
|
if [ -n "$_spc_home_bad" ]; then
|
|
721
748
|
sp_cap_error "HOME violates ownership, symlink, or external-write policy: $_spc_home"
|
|
722
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad
|
|
749
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad
|
|
723
750
|
return 1
|
|
724
751
|
fi
|
|
725
752
|
[ -d "$_spc_local_dir" ] && [ ! -L "$_spc_local_dir" ] || {
|
|
726
753
|
sp_cap_error "local directory is not a real directory: $_spc_local_dir"
|
|
727
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad
|
|
754
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad
|
|
728
755
|
return 1
|
|
729
756
|
}
|
|
730
757
|
[ -d "$_spc_local_bin" ] && [ ! -L "$_spc_local_bin" ] || {
|
|
731
758
|
sp_cap_error "local bin directory is not a real directory: $_spc_local_bin"
|
|
732
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad
|
|
759
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad
|
|
733
760
|
return 1
|
|
734
761
|
}
|
|
735
762
|
_spc_local_bad=$(sp_cap_write_compromise_paths "$_spc_local_dir" self) || {
|
|
736
763
|
sp_cap_error "failed to inspect local directory permissions: $_spc_local_dir"
|
|
737
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad
|
|
764
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad
|
|
738
765
|
return 1
|
|
739
766
|
}
|
|
740
767
|
if [ -n "$_spc_local_bad" ]; then
|
|
741
768
|
sp_cap_error "local directory violates ownership, symlink, or external-write policy: $_spc_local_dir"
|
|
742
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad
|
|
769
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad
|
|
743
770
|
return 1
|
|
744
771
|
fi
|
|
745
|
-
_spc_bin_bad=$(sp_cap_write_compromise_paths "$_spc_local_bin"
|
|
772
|
+
_spc_bin_bad=$(sp_cap_write_compromise_paths "$_spc_local_bin" "$_spc_bin_scope") || {
|
|
746
773
|
sp_cap_error "failed to inspect local bin execution chain: $_spc_local_bin"
|
|
747
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad _spc_bin_bad
|
|
774
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad _spc_bin_bad
|
|
748
775
|
return 1
|
|
749
776
|
}
|
|
750
777
|
if [ -n "$_spc_bin_bad" ]; then
|
|
751
778
|
sp_cap_error "Compromised execution chain. Writable/unowned files or symlinks found in '$_spc_local_bin'."
|
|
752
779
|
sp_cap_print_line "$_spc_bin_bad" >&2
|
|
753
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad _spc_bin_bad
|
|
780
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad _spc_bin_bad
|
|
754
781
|
return 1
|
|
755
782
|
fi
|
|
756
|
-
unset _spc_home _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad _spc_bin_bad
|
|
783
|
+
unset _spc_home _spc_symlink_policy _spc_bin_scope _spc_local_dir _spc_local_bin _spc_home_bad _spc_local_bad _spc_bin_bad
|
|
757
784
|
return 0
|
|
758
785
|
}
|
|
759
786
|
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Copyright (c) 2026 Joseph Cumines
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# valise CLI: published commands: the stubs that put an active release's
|
|
4
|
+
# bin/ commands on PATH, and the ledger that records which stubs valise owns.
|
|
5
|
+
# Sourced by bin/valise; not executable on its own.
|
|
6
|
+
#
|
|
7
|
+
# A stub is a tiny sh script in the command directory that execs the same
|
|
8
|
+
# name under the active release link, so a release switch never rewrites a
|
|
9
|
+
# stub; it only adds and removes them. The ledger lists every stub valise
|
|
10
|
+
# wrote as name, path, and SHA-256. A file valise did not write, or a stub
|
|
11
|
+
# someone edited, is never overwritten or removed.
|
|
12
|
+
|
|
13
|
+
_rc_tmp=""
|
|
14
|
+
_rc_stub=""
|
|
15
|
+
_rc_pub=""
|
|
16
|
+
|
|
17
|
+
# commands_bin_dir: the directory stubs are written to.
|
|
18
|
+
commands_bin_dir() {
|
|
19
|
+
[ -n "${VALISE_BIN_DIR:-}${XDG_BIN_HOME:-}${HOME:-}" ] || return 1
|
|
20
|
+
path_normalize "${VALISE_BIN_DIR:-${XDG_BIN_HOME:-$HOME/.local/bin}}"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
commands_ledger_path() {
|
|
24
|
+
printf '%s\n' "$VALISE_XDG_STATE/commands.tsv"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# commands_stub <name>: print the stub for one command.
|
|
28
|
+
commands_stub() {
|
|
29
|
+
_cs_target=$(shell_quote "$VALISE_ACTIVE_LINK/bin/$1") || return 1
|
|
30
|
+
printf '%s\n' \
|
|
31
|
+
'#!/bin/sh' \
|
|
32
|
+
'# valise command stub; managed by valise; do not edit.' \
|
|
33
|
+
"exec $_cs_target \"\$@\""
|
|
34
|
+
unset _cs_target
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# commands_stub_digest <name>: print the SHA-256 of the stub for one command.
|
|
38
|
+
commands_stub_digest() {
|
|
39
|
+
_csd_tmp=$(trusted_mktemp file "${TMPDIR:-/tmp}/valise-stub.XXXXXX") || return 1
|
|
40
|
+
if commands_stub "$1" >"$_csd_tmp"; then
|
|
41
|
+
file_sha256 "$_csd_tmp"
|
|
42
|
+
_csd_status=$?
|
|
43
|
+
else
|
|
44
|
+
_csd_status=1
|
|
45
|
+
fi
|
|
46
|
+
_csd_rm=$(trusted_utility_path rm) || return 1
|
|
47
|
+
"$_csd_rm" -f "$_csd_tmp" || _csd_status=1
|
|
48
|
+
return "$_csd_status"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# commands_file_is <path> <sha256>: succeed when path is a regular file, not a
|
|
52
|
+
# symlink, with the given digest.
|
|
53
|
+
commands_file_is() {
|
|
54
|
+
[ -f "$1" ] && [ ! -L "$1" ] || return 1
|
|
55
|
+
_cfi_digest=$(file_sha256 "$1") || return 1
|
|
56
|
+
[ "$_cfi_digest" = "$2" ]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# command_name_valid <name>: the build's rule for a published command name.
|
|
60
|
+
# It keeps a stub inside the command directory and free of glob characters.
|
|
61
|
+
command_name_valid() {
|
|
62
|
+
case $1 in
|
|
63
|
+
'' | [!A-Za-z0-9_]* | *[!A-Za-z0-9_.-]*) return 1 ;;
|
|
64
|
+
esac
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# release_commands <release-dir>: print the commands a release publishes.
|
|
69
|
+
# Fails, naming the entry, when an installed release lists a name the build
|
|
70
|
+
# would have rejected, so no caller ever joins such a name to a path.
|
|
71
|
+
release_commands() {
|
|
72
|
+
_rcs_list=$(release_conf_value "$1" commands 2>/dev/null || :)
|
|
73
|
+
_rcs_seen=" "
|
|
74
|
+
_rcs_noglob=0
|
|
75
|
+
case $- in *f*) _rcs_noglob=1 ;; esac
|
|
76
|
+
set -f
|
|
77
|
+
for _rcs_name in $_rcs_list; do
|
|
78
|
+
if ! command_name_valid "$_rcs_name"; then
|
|
79
|
+
[ "$_rcs_noglob" -eq 1 ] || set +f
|
|
80
|
+
printf 'valise: error: release lists an invalid command name: %s\n' "$_rcs_name" >&2
|
|
81
|
+
unset _rcs_list _rcs_seen _rcs_noglob _rcs_name
|
|
82
|
+
return 1
|
|
83
|
+
fi
|
|
84
|
+
case $_rcs_seen in
|
|
85
|
+
*" $_rcs_name "*) ;;
|
|
86
|
+
*) _rcs_seen="$_rcs_seen$_rcs_name " ;;
|
|
87
|
+
esac
|
|
88
|
+
done
|
|
89
|
+
[ "$_rcs_noglob" -eq 1 ] || set +f
|
|
90
|
+
_rcs_seen=${_rcs_seen# }
|
|
91
|
+
printf '%s\n' "${_rcs_seen% }"
|
|
92
|
+
unset _rcs_list _rcs_seen _rcs_noglob _rcs_name
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# commands_publish_ledger: publish the staged ledger rows as the ledger.
|
|
96
|
+
commands_publish_ledger() {
|
|
97
|
+
_rc_pub=$(trusted_mktemp_at "$VALISE_XDG_STATE" file "$VALISE_XDG_STATE/.commands.XXXXXX") ||
|
|
98
|
+
die "cannot create command ledger staging file"
|
|
99
|
+
"$_rcm_cat" "$_rc_tmp" >"$_rc_pub" || die "cannot write command ledger"
|
|
100
|
+
"$_rcm_chmod" 0644 "$_rc_pub" || die "cannot set command ledger mode"
|
|
101
|
+
"$_rcm_mv" -f "$_rc_pub" "$_rcm_ledger" || die "cannot publish command ledger"
|
|
102
|
+
_rc_pub=""
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# reconcile_commands <release-dir>
|
|
106
|
+
# Make the stubs in the command directory match the commands the release
|
|
107
|
+
# publishes. Owned stubs for commands the release no longer has are removed;
|
|
108
|
+
# a missing stub is written unless the path holds a file valise does not own,
|
|
109
|
+
# which is left alone and reported. Call with the store lock held.
|
|
110
|
+
reconcile_commands() {
|
|
111
|
+
_rcm_release=$1
|
|
112
|
+
_rcm_want=$(release_commands "$_rcm_release") || die "refusing to publish the commands of $_rcm_release"
|
|
113
|
+
if ! _rcm_bin=$(commands_bin_dir); then
|
|
114
|
+
[ -z "$_rcm_want" ] || info "warning: HOME is not set; no commands were published"
|
|
115
|
+
unset _rcm_release _rcm_want _rcm_bin
|
|
116
|
+
return 0
|
|
117
|
+
fi
|
|
118
|
+
_rcm_ledger=$(commands_ledger_path)
|
|
119
|
+
_rcm_tab=$(printf '\t')
|
|
120
|
+
_rcm_rm=$(trusted_utility_path rm) || die "no trusted rm utility available"
|
|
121
|
+
_rcm_mkdir=$(trusted_utility_path mkdir) || die "no trusted mkdir utility available"
|
|
122
|
+
_rcm_chmod=$(trusted_utility_path chmod) || die "no trusted chmod utility available"
|
|
123
|
+
_rcm_ln=$(trusted_utility_path ln) || die "no trusted ln utility available"
|
|
124
|
+
_rcm_mv=$(trusted_utility_path mv) || die "no trusted mv utility available"
|
|
125
|
+
_rcm_cat=$(trusted_utility_path cat) || die "no trusted cat utility available"
|
|
126
|
+
_rcm_awk=$(trusted_utility_path awk) || die "no trusted awk utility available"
|
|
127
|
+
[ ! -L "$_rcm_ledger" ] || die "command ledger must not be a symlink: $_rcm_ledger"
|
|
128
|
+
"$_rcm_mkdir" -p "$VALISE_XDG_STATE" || die "cannot create state directory: $VALISE_XDG_STATE"
|
|
129
|
+
_rc_tmp=$(trusted_mktemp_at "$VALISE_XDG_STATE" file "$VALISE_XDG_STATE/.commands.XXXXXX") ||
|
|
130
|
+
die "cannot create command ledger staging file"
|
|
131
|
+
_rcm_owned=
|
|
132
|
+
|
|
133
|
+
# Drop or keep each stub the ledger records.
|
|
134
|
+
if [ -f "$_rcm_ledger" ]; then
|
|
135
|
+
while IFS="$_rcm_tab" read -r _rcm_name _rcm_path _rcm_sha; do
|
|
136
|
+
[ -n "$_rcm_name" ] || continue
|
|
137
|
+
case " $_rcm_want " in
|
|
138
|
+
*" $_rcm_name "*) _rcm_keep=1 ;;
|
|
139
|
+
*) _rcm_keep=0 ;;
|
|
140
|
+
esac
|
|
141
|
+
[ "$_rcm_path" = "$_rcm_bin/$_rcm_name" ] || _rcm_keep=0
|
|
142
|
+
if [ ! -e "$_rcm_path" ] && [ ! -L "$_rcm_path" ]; then
|
|
143
|
+
# The stub is gone; it is written again below if still wanted.
|
|
144
|
+
continue
|
|
145
|
+
fi
|
|
146
|
+
if ! commands_file_is "$_rcm_path" "$_rcm_sha"; then
|
|
147
|
+
info "warning: command stub was changed outside valise; leaving it and no longer managing it: $_rcm_path"
|
|
148
|
+
continue
|
|
149
|
+
fi
|
|
150
|
+
if [ "$_rcm_keep" -eq 1 ]; then
|
|
151
|
+
printf '%s\t%s\t%s\n' "$_rcm_name" "$_rcm_path" "$_rcm_sha" >>"$_rc_tmp" ||
|
|
152
|
+
die "cannot write command ledger"
|
|
153
|
+
_rcm_owned="$_rcm_owned $_rcm_name"
|
|
154
|
+
else
|
|
155
|
+
"$_rcm_rm" -f "$_rcm_path" || die "cannot remove command stub: $_rcm_path"
|
|
156
|
+
info "removed command: $_rcm_path"
|
|
157
|
+
fi
|
|
158
|
+
done <"$_rcm_ledger"
|
|
159
|
+
elif [ -e "$_rcm_ledger" ]; then
|
|
160
|
+
die "command ledger is not a regular file: $_rcm_ledger"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# Write each wanted stub valise does not hold yet.
|
|
164
|
+
for _rcm_name in $_rcm_want; do
|
|
165
|
+
case " $_rcm_owned " in *" $_rcm_name "*) continue ;; esac
|
|
166
|
+
_rcm_path="$_rcm_bin/$_rcm_name"
|
|
167
|
+
if [ -e "$_rcm_path" ] || [ -L "$_rcm_path" ]; then
|
|
168
|
+
info "warning: command $_rcm_name not published: $_rcm_path exists and valise does not own it"
|
|
169
|
+
continue
|
|
170
|
+
fi
|
|
171
|
+
"$_rcm_mkdir" -p "$_rcm_bin" || die "cannot create command directory: $_rcm_bin"
|
|
172
|
+
_rc_stub=$(trusted_mktemp_at "$_rcm_bin" file "$_rcm_bin/.valise-stub.XXXXXX") ||
|
|
173
|
+
die "cannot create command stub staging file"
|
|
174
|
+
commands_stub "$_rcm_name" >"$_rc_stub" || die "cannot write command stub: $_rcm_name"
|
|
175
|
+
"$_rcm_chmod" 0755 "$_rc_stub" || die "cannot set command stub mode"
|
|
176
|
+
_rcm_sha=$(file_sha256 "$_rc_stub") || die "no trusted SHA-256 utility available"
|
|
177
|
+
# The ledger names the stub before the stub exists, so an interrupted run
|
|
178
|
+
# never leaves a stub valise does not know it owns; a recorded stub that
|
|
179
|
+
# is missing is simply written again later.
|
|
180
|
+
printf '%s\t%s\t%s\n' "$_rcm_name" "$_rcm_path" "$_rcm_sha" >>"$_rc_tmp" ||
|
|
181
|
+
die "cannot write command ledger"
|
|
182
|
+
commands_publish_ledger
|
|
183
|
+
# A hard link fails when the name exists, so a file created since the
|
|
184
|
+
# check above is never replaced.
|
|
185
|
+
if "$_rcm_ln" "$_rc_stub" "$_rcm_path" 2>/dev/null; then
|
|
186
|
+
info "published command: $_rcm_path"
|
|
187
|
+
else
|
|
188
|
+
info "warning: command $_rcm_name not published: cannot create $_rcm_path"
|
|
189
|
+
_rc_pub=$(trusted_mktemp_at "$VALISE_XDG_STATE" file "$VALISE_XDG_STATE/.commands.XXXXXX") ||
|
|
190
|
+
die "cannot create command ledger staging file"
|
|
191
|
+
"$_rcm_awk" -F '\t' -v n="$_rcm_name" -v p="$_rcm_path" '!($1 == n && $2 == p)' "$_rc_tmp" >"$_rc_pub" &&
|
|
192
|
+
"$_rcm_mv" -f "$_rc_pub" "$_rc_tmp" || die "cannot write command ledger"
|
|
193
|
+
_rc_pub=""
|
|
194
|
+
commands_publish_ledger
|
|
195
|
+
fi
|
|
196
|
+
"$_rcm_rm" -f "$_rc_stub" || die "cannot remove command stub staging file"
|
|
197
|
+
_rc_stub=""
|
|
198
|
+
done
|
|
199
|
+
|
|
200
|
+
"$_rcm_chmod" 0644 "$_rc_tmp" || die "cannot set command ledger mode"
|
|
201
|
+
"$_rcm_mv" -f "$_rc_tmp" "$_rcm_ledger" || die "cannot publish command ledger"
|
|
202
|
+
_rc_tmp=""
|
|
203
|
+
if [ -n "$_rcm_want" ]; then
|
|
204
|
+
case ":${PATH:-}:" in
|
|
205
|
+
*":$_rcm_bin:"*) ;;
|
|
206
|
+
*) info "warning: $_rcm_bin is not on PATH; add it to use the published commands" ;;
|
|
207
|
+
esac
|
|
208
|
+
fi
|
|
209
|
+
unset _rcm_release _rcm_bin _rcm_ledger _rcm_want _rcm_tab _rcm_rm _rcm_mkdir _rcm_chmod _rcm_ln _rcm_mv \
|
|
210
|
+
_rcm_cat _rcm_awk _rcm_owned _rcm_name _rcm_path _rcm_sha _rcm_keep
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# remove_owned_commands: remove every stub the ledger records that still has
|
|
214
|
+
# the content valise wrote, then the ledger. Call with the store lock held.
|
|
215
|
+
remove_owned_commands() {
|
|
216
|
+
_roc_ledger=$(commands_ledger_path)
|
|
217
|
+
[ -e "$_roc_ledger" ] || [ -L "$_roc_ledger" ] || return 0
|
|
218
|
+
[ -f "$_roc_ledger" ] && [ ! -L "$_roc_ledger" ] || die "command ledger is not a regular file: $_roc_ledger"
|
|
219
|
+
_roc_rm=$(trusted_utility_path rm) || die "no trusted rm utility available"
|
|
220
|
+
_roc_tab=$(printf '\t')
|
|
221
|
+
while IFS="$_roc_tab" read -r _roc_name _roc_path _roc_sha; do
|
|
222
|
+
[ -n "$_roc_name" ] || continue
|
|
223
|
+
[ -e "$_roc_path" ] || [ -L "$_roc_path" ] || continue
|
|
224
|
+
if commands_file_is "$_roc_path" "$_roc_sha"; then
|
|
225
|
+
"$_roc_rm" -f "$_roc_path" || die "cannot remove command stub: $_roc_path"
|
|
226
|
+
info "removed command: $_roc_path"
|
|
227
|
+
else
|
|
228
|
+
info "warning: command stub was changed outside valise; leaving it: $_roc_path"
|
|
229
|
+
fi
|
|
230
|
+
done <"$_roc_ledger"
|
|
231
|
+
"$_roc_rm" -f "$_roc_ledger" || die "cannot remove command ledger: $_roc_ledger"
|
|
232
|
+
unset _roc_ledger _roc_rm _roc_tab _roc_name _roc_path _roc_sha
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# doctor_commands <release-dir>
|
|
236
|
+
# Report the state of each command the release publishes. Prints doctor
|
|
237
|
+
# lines; sets _status=1 when a published command cannot run.
|
|
238
|
+
doctor_commands() {
|
|
239
|
+
if ! _dc_want=$(release_commands "$1"); then
|
|
240
|
+
printf ' FAIL: the active release lists an invalid command name\n'
|
|
241
|
+
_status=1
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
[ -n "$_dc_want" ] || return 0
|
|
245
|
+
_dc_bin=$(commands_bin_dir) || {
|
|
246
|
+
printf ' FAIL: cannot resolve the command directory\n'
|
|
247
|
+
_status=1
|
|
248
|
+
return 0
|
|
249
|
+
}
|
|
250
|
+
_dc_ledger=$(commands_ledger_path)
|
|
251
|
+
if [ ! -f "$_dc_ledger" ] || [ -L "$_dc_ledger" ]; then
|
|
252
|
+
printf ' info: release commands are not published (installed without integration): %s\n' "$_dc_want"
|
|
253
|
+
unset _dc_want _dc_bin _dc_ledger
|
|
254
|
+
return 0
|
|
255
|
+
fi
|
|
256
|
+
_dc_awk=$(trusted_utility_path awk) || die "no trusted awk utility available"
|
|
257
|
+
for _dc_name in $_dc_want; do
|
|
258
|
+
_dc_path="$_dc_bin/$_dc_name"
|
|
259
|
+
_dc_sha=$("$_dc_awk" -F '\t' -v n="$_dc_name" -v p="$_dc_path" '$1 == n && $2 == p { print $3; exit }' "$_dc_ledger")
|
|
260
|
+
if [ -z "$_dc_sha" ]; then
|
|
261
|
+
if [ -e "$_dc_path" ] || [ -L "$_dc_path" ]; then
|
|
262
|
+
printf ' warn: command %s not published: %s exists and valise does not own it\n' "$_dc_name" "$_dc_path"
|
|
263
|
+
else
|
|
264
|
+
printf ' warn: command %s not published (run valise install or valise release use)\n' "$_dc_name"
|
|
265
|
+
fi
|
|
266
|
+
elif commands_file_is "$_dc_path" "$_dc_sha"; then
|
|
267
|
+
printf ' ok: command published: %s\n' "$_dc_path"
|
|
268
|
+
else
|
|
269
|
+
printf ' FAIL: command stub missing or changed: %s\n' "$_dc_path"
|
|
270
|
+
_status=1
|
|
271
|
+
fi
|
|
272
|
+
done
|
|
273
|
+
case ":${PATH:-}:" in
|
|
274
|
+
*":$_dc_bin:"*) printf ' ok: command directory is on PATH: %s\n' "$_dc_bin" ;;
|
|
275
|
+
*) printf ' warn: command directory is not on PATH: %s\n' "$_dc_bin" ;;
|
|
276
|
+
esac
|
|
277
|
+
unset _dc_want _dc_bin _dc_ledger _dc_awk _dc_name _dc_path _dc_sha
|
|
278
|
+
}
|