commit 8fbcb5cdc6f8e890328c2753cc0c65e0d6dfffb5
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-06-28T02:16:13Z |
| subject | Sign rbashrc |
commit 8fbcb5cdc6f8e890328c2753cc0c65e0d6dfffb5
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-06-28T02:16:13Z
Sign rbashrc
---
bash/core/00_env.bash | 2 +
bash/core/50_upgrade.bash | 204 ++++++++++++++++++++++++++++------------
bash/dist/rbash-signing-key.asc | 11 +++
bash/dist/rbashrc | 180 ++++++++++++++++++++++++++---------
bash/dist/rbashrc.asc | 8 ++
bash/tools/sign-rbashrc.sh | 144 ++++++++++++++++++++++++++++
6 files changed, 446 insertions(+), 103 deletions(-)
diff --git a/bash/core/00_env.bash b/bash/core/00_env.bash
index 0ab2e86..ef57cc6 100755
--- a/bash/core/00_env.bash
+++ b/bash/core/00_env.bash
@@ -9,4 +9,6 @@ if [ "$RHOSTNAME" = "<HOSTNAME>" ]; then
RCOLOR=$(( RANDOM * 6 / 32767 + 1 ))
fi
+RBASH_SIGNING_FPR="7C1E948D841254C569EDFBDED5CF5FB1CFAD27C3"
+
_RBASH_LOADED=
diff --git a/bash/core/50_upgrade.bash b/bash/core/50_upgrade.bash
index 5b7a52f..4401076 100755
--- a/bash/core/50_upgrade.bash
+++ b/bash/core/50_upgrade.bash
@@ -1,60 +1,148 @@
+rbash_verify_upgrade() {
+ local new_rbashrc="$1"
+ local sig_file="$2"
+ local key_file="$3"
+
+ local gnupg_home key_fpr rc
+
+ command -v gpg >/dev/null 2>&1 || {
+ echo "gpg is required for rbash upgrade verification"
+ return 1
+ }
+
+ [ -n "${RBASH_SIGNING_FPR:-}" ] || {
+ echo "RBASH_SIGNING_FPR is not set"
+ echo "Refusing to upgrade without a pinned signing key fingerprint"
+ return 1
+ }
+
+ [ -f "$new_rbashrc" ] || {
+ echo "missing rbashrc: $new_rbashrc"
+ return 1
+ }
+
+ [ -f "$sig_file" ] || {
+ echo "missing signature: $sig_file"
+ return 1
+ }
+
+ [ -f "$key_file" ] || {
+ echo "missing public key: $key_file"
+ return 1
+ }
+
+ gnupg_home="$(mktemp -d)"
+ chmod 700 "$gnupg_home"
+
+ GNUPGHOME="$gnupg_home" gpg --quiet --batch --import "$key_file" >/dev/null 2>&1 || {
+ rc=$?
+ rm -rf "$gnupg_home"
+ echo "failed to import rbash signing key"
+ return "$rc"
+ }
+
+ key_fpr="$(
+ GNUPGHOME="$gnupg_home" gpg --with-colons --fingerprint 2>/dev/null |
+ awk -F: '$1 == "fpr" { print $10; exit }'
+ )"
+
+ if [ "$key_fpr" != "$RBASH_SIGNING_FPR" ]; then
+ rm -rf "$gnupg_home"
+
+ echo "rbash signing key fingerprint mismatch"
+ echo "expected: $RBASH_SIGNING_FPR"
+ echo "got: ${key_fpr:-<none>}"
+ return 1
+ fi
+
+ GNUPGHOME="$gnupg_home" gpg \
+ --quiet \
+ --batch \
+ --verify "$sig_file" "$new_rbashrc" >/dev/null 2>&1
+
+ rc=$?
+ rm -rf "$gnupg_home"
+
+ if [ "$rc" -ne 0 ]; then
+ echo "rbash signature verification failed"
+ return "$rc"
+ fi
+
+ return 0
+}
+
rbash-upgrade() {
- local target url tmp backup
-
- target="$__RBASH_SCRIPT"
- url="$(rbash_url "dist/rbashrc")"
-
- echo "Updating $target"
-
- tmp="$(mktemp "${TMPDIR:-/tmp}/rbashrc.download.XXXXXX")" || return 1
-
- if ! rbash_download "$url" > "$tmp"; then
- echo "rbash: upgrade download failed: $url" >&2
- rm -f "$tmp"
- return 1
- fi
-
- if [ ! -s "$tmp" ]; then
- echo "rbash: downloaded rbashrc is empty; refusing to install" >&2
- rm -f "$tmp"
- return 1
- fi
-
- if ! grep -q 'RBASH_HOME' "$tmp"; then
- echo "rbash: downloaded file does not look like rbashrc; refusing to install" >&2
- rm -f "$tmp"
- return 1
- fi
-
- if ! bash -n "$tmp"; then
- echo "rbash: downloaded rbashrc has syntax errors; refusing to install" >&2
- rm -f "$tmp"
- return 1
- fi
-
- backup="$target.bak.$(date +%Y%m%d%H%M%S)"
-
- if [ -f "$target" ]; then
- cp "$target" "$backup" || {
- echo "rbash: failed to create backup: $backup" >&2
- rm -f "$tmp"
- return 1
- }
- echo "Backup: $backup"
- fi
-
- chmod 700 "$tmp"
-
- if ! mv "$tmp" "$target"; then
- echo "rbash: failed to replace $target" >&2
- echo "rbash: backup remains at $backup" >&2
- rm -f "$tmp"
- return 1
- fi
-
- rm -rf "$RBASH_CACHE_DIR"
- mkdir -p "$RBASH_CACHE_DIR"
-
- # shellcheck source=/dev/null
- source "$target"
+ local target backup backup_suffix
+ local tmp_dir tmp_rbashrc tmp_sig tmp_key
+ local base_url rbashrc_url sig_url key_url
+ local rc
+
+ target="${__RBASH_SCRIPT:-$HOME/.bashrc}"
+
+ base_url="$(rbash_url "dist")"
+
+ rbashrc_url="$base_url/rbashrc"
+ sig_url="$base_url/rbashrc.asc"
+ key_url="$base_url/rbash-signing-key.asc"
+
+ command -v gpg >/dev/null 2>&1 || {
+ echo "gpg is required"
+ return 1
+ }
+
+ tmp_dir="$(mktemp -d)"
+ tmp_rbashrc="$tmp_dir/rbashrc"
+ tmp_sig="$tmp_dir/rbashrc.asc"
+ tmp_key="$tmp_dir/rbash-signing-key.asc"
+
+ __rbash_upgrade_cleanup() {
+ rm -rf "$tmp_dir"
+ }
+
+ trap __rbash_upgrade_cleanup RETURN
+
+ echo "Downloading rbashrc..."
+ rbash_download "$rbashrc_url" > "$tmp_rbashrc" || return 1
+
+ echo "Downloading rbashrc signature..."
+ rbash_download "$sig_url" > "$tmp_sig" || return 1
+
+ echo "Downloading rbash signing public key..."
+ rbash_download "$key_url" > "$tmp_key" || return 1
+
+ echo "Verifying rbashrc signature..."
+ rbash_verify_upgrade "$tmp_rbashrc" "$tmp_sig" "$tmp_key" || {
+ echo "rbash upgrade aborted"
+ return 1
+ }
+
+ backup_suffix="$(date +%Y%m%d%H%M%S)"
+ backup="$target.bak.$backup_suffix"
+
+ echo "Updating $target"
+ echo "Backup: $backup"
+
+ if [ -f "$target" ]; then
+ cp "$target" "$backup" || {
+ echo "failed to create backup: $backup"
+ return 1
+ }
+ fi
+
+ cp "$tmp_rbashrc" "$target" || {
+ rc=$?
+
+ echo "failed to install new rbashrc"
+
+ if [ -f "$backup" ]; then
+ echo "restoring backup"
+ cp "$backup" "$target" || true
+ fi
+
+ return "$rc"
+ }
+
+ chmod 600 "$target" 2>/dev/null || true
+
+ echo "rbash upgrade complete"
}
diff --git a/bash/dist/rbash-signing-key.asc b/bash/dist/rbash-signing-key.asc
new file mode 100644
index 0000000..a1ee3f9
--- /dev/null
+++ b/bash/dist/rbash-signing-key.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEakCA9RYJKwYBBAHaRw8BAQdABA3i7/ksB6wkBBNv8aDG77uCMctElvLffD2+
+mah0zeK0THJiYXNoIHJlbGVhc2Ugc2lnbmluZyAocmJhc2ggcmVsZWFzZSBzaWdu
+aW5nIGtleSkgPHBlbmd1aW5AYXN0cm9wZW5ndWluLm5ldD6ItQQTFgoAXRYhBHwe
+lI2EElTFae373tXPX7HPrSfDBQJqQID1GxSAAAAAAAQADm1hbnUyLDIuNSsxLjEy
+LDAsMwIbAwUJA8JnAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDVz1+x
+z60nw63rAP9IlxolGkJT8No57cWoVOtyRR3qFaGV0/6JBE+e8DvxBgEA2ejTyqYt
+Jejb/OZ3jZCN7vLPMQKfG6XMCNncjCezLAw=
+=arV7
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/bash/dist/rbashrc b/bash/dist/rbashrc
index aa40cc6..613f121 100755
--- a/bash/dist/rbashrc
+++ b/bash/dist/rbashrc
@@ -34,6 +34,8 @@ if [ "$RHOSTNAME" = "<HOSTNAME>" ]; then
RCOLOR=$(( RANDOM * 6 / 32767 + 1 ))
fi
+RBASH_SIGNING_FPR="7C1E948D841254C569EDFBDED5CF5FB1CFAD27C3"
+
_RBASH_LOADED=
@@ -403,65 +405,153 @@ r2dissource() {
# ---- rbash: bash/core/50_upgrade.bash ----
+rbash_verify_upgrade() {
+ local new_rbashrc="$1"
+ local sig_file="$2"
+ local key_file="$3"
+
+ local gnupg_home key_fpr rc
+
+ command -v gpg >/dev/null 2>&1 || {
+ echo "gpg is required for rbash upgrade verification"
+ return 1
+ }
+
+ [ -n "${RBASH_SIGNING_FPR:-}" ] || {
+ echo "RBASH_SIGNING_FPR is not set"
+ echo "Refusing to upgrade without a pinned signing key fingerprint"
+ return 1
+ }
+
+ [ -f "$new_rbashrc" ] || {
+ echo "missing rbashrc: $new_rbashrc"
+ return 1
+ }
+
+ [ -f "$sig_file" ] || {
+ echo "missing signature: $sig_file"
+ return 1
+ }
+
+ [ -f "$key_file" ] || {
+ echo "missing public key: $key_file"
+ return 1
+ }
+
+ gnupg_home="$(mktemp -d)"
+ chmod 700 "$gnupg_home"
+
+ GNUPGHOME="$gnupg_home" gpg --quiet --batch --import "$key_file" >/dev/null 2>&1 || {
+ rc=$?
+ rm -rf "$gnupg_home"
+ echo "failed to import rbash signing key"
+ return "$rc"
+ }
+
+ key_fpr="$(
+ GNUPGHOME="$gnupg_home" gpg --with-colons --fingerprint 2>/dev/null |
+ awk -F: '$1 == "fpr" { print $10; exit }'
+ )"
+
+ if [ "$key_fpr" != "$RBASH_SIGNING_FPR" ]; then
+ rm -rf "$gnupg_home"
+
+ echo "rbash signing key fingerprint mismatch"
+ echo "expected: $RBASH_SIGNING_FPR"
+ echo "got: ${key_fpr:-<none>}"
+ return 1
+ fi
+
+ GNUPGHOME="$gnupg_home" gpg \
+ --quiet \
+ --batch \
+ --verify "$sig_file" "$new_rbashrc" >/dev/null 2>&1
+
+ rc=$?
+ rm -rf "$gnupg_home"
+
+ if [ "$rc" -ne 0 ]; then
+ echo "rbash signature verification failed"
+ return "$rc"
+ fi
+
+ return 0
+}
+
rbash-upgrade() {
- local target url tmp backup
+ local target backup backup_suffix
+ local tmp_dir tmp_rbashrc tmp_sig tmp_key
+ local base_url rbashrc_url sig_url key_url
+ local rc
- target="$__RBASH_SCRIPT"
- url="$(rbash_url "dist/rbashrc")"
+ target="${__RBASH_SCRIPT:-$HOME/.bashrc}"
- echo "Updating $target"
+ base_url="$(rbash_url "dist")"
- tmp="$(mktemp "${TMPDIR:-/tmp}/rbashrc.download.XXXXXX")" || return 1
+ rbashrc_url="$base_url/rbashrc"
+ sig_url="$base_url/rbashrc.asc"
+ key_url="$base_url/rbash-signing-key.asc"
- if ! rbash_download "$url" > "$tmp"; then
- echo "rbash: upgrade download failed: $url" >&2
- rm -f "$tmp"
- return 1
- fi
+ command -v gpg >/dev/null 2>&1 || {
+ echo "gpg is required"
+ return 1
+ }
- if [ ! -s "$tmp" ]; then
- echo "rbash: downloaded rbashrc is empty; refusing to install" >&2
- rm -f "$tmp"
- return 1
- fi
+ tmp_dir="$(mktemp -d)"
+ tmp_rbashrc="$tmp_dir/rbashrc"
+ tmp_sig="$tmp_dir/rbashrc.asc"
+ tmp_key="$tmp_dir/rbash-signing-key.asc"
- if ! grep -q 'RBASH_HOME' "$tmp"; then
- echo "rbash: downloaded file does not look like rbashrc; refusing to install" >&2
- rm -f "$tmp"
- return 1
- fi
+ __rbash_upgrade_cleanup() {
+ rm -rf "$tmp_dir"
+ }
- if ! bash -n "$tmp"; then
- echo "rbash: downloaded rbashrc has syntax errors; refusing to install" >&2
- rm -f "$tmp"
- return 1
- fi
+ trap __rbash_upgrade_cleanup RETURN
- backup="$target.bak.$(date +%Y%m%d%H%M%S)"
+ echo "Downloading rbashrc..."
+ rbash_download "$rbashrc_url" > "$tmp_rbashrc" || return 1
- if [ -f "$target" ]; then
- cp "$target" "$backup" || {
- echo "rbash: failed to create backup: $backup" >&2
- rm -f "$tmp"
- return 1
- }
- echo "Backup: $backup"
- fi
+ echo "Downloading rbashrc signature..."
+ rbash_download "$sig_url" > "$tmp_sig" || return 1
- chmod 700 "$tmp"
+ echo "Downloading rbash signing public key..."
+ rbash_download "$key_url" > "$tmp_key" || return 1
- if ! mv "$tmp" "$target"; then
- echo "rbash: failed to replace $target" >&2
- echo "rbash: backup remains at $backup" >&2
- rm -f "$tmp"
- return 1
- fi
+ echo "Verifying rbashrc signature..."
+ rbash_verify_upgrade "$tmp_rbashrc" "$tmp_sig" "$tmp_key" || {
+ echo "rbash upgrade aborted"
+ return 1
+ }
- rm -rf "$RBASH_CACHE_DIR"
- mkdir -p "$RBASH_CACHE_DIR"
+ backup_suffix="$(date +%Y%m%d%H%M%S)"
+ backup="$target.bak.$backup_suffix"
- # shellcheck source=/dev/null
- source "$target"
+ echo "Updating $target"
+ echo "Backup: $backup"
+
+ if [ -f "$target" ]; then
+ cp "$target" "$backup" || {
+ echo "failed to create backup: $backup"
+ return 1
+ }
+ fi
+
+ cp "$tmp_rbashrc" "$target" || {
+ rc=$?
+
+ echo "failed to install new rbashrc"
+
+ if [ -f "$backup" ]; then
+ echo "restoring backup"
+ cp "$backup" "$target" || true
+ fi
+
+ return "$rc"
+ }
+
+ chmod 600 "$target" 2>/dev/null || true
+
+ echo "rbash upgrade complete"
}
diff --git a/bash/dist/rbashrc.asc b/bash/dist/rbashrc.asc
new file mode 100644
index 0000000..0a1295a
--- /dev/null
+++ b/bash/dist/rbashrc.asc
@@ -0,0 +1,8 @@
+-----BEGIN PGP SIGNATURE-----
+
+iJEEABYKADkWIQR8HpSNhBJUxWnt+97Vz1+xz60nwwUCakCD2BsUgAAAAAAEAA5t
+YW51MiwyLjUrMS4xMiwwLDMACgkQ1c9fsc+tJ8MfWwEAx539cnhecaZdpc4pj0gQ
+ekUURDkVLf4654Ot6il0TbQA/iWaIOPgSOgDYwB8+83cjqlNrXRiq/Uj0Nu5vWZm
+V/AO
+=LRnk
+-----END PGP SIGNATURE-----
diff --git a/bash/tools/sign-rbashrc.sh b/bash/tools/sign-rbashrc.sh
new file mode 100755
index 0000000..76c6412
--- /dev/null
+++ b/bash/tools/sign-rbashrc.sh
@@ -0,0 +1,144 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")/../.."
+
+DIST="bash/dist"
+TARGET="$DIST/rbashrc"
+SIG="$DIST/rbashrc.asc"
+PUBKEY="$DIST/rbash-signing-key.asc"
+
+KEY_NAME_DEFAULT="rbash release signing"
+KEY_EMAIL_DEFAULT="rbash@astropenguin.net"
+KEY_COMMENT_DEFAULT="rbash release signing key"
+
+die() {
+ echo "error: $*" >&2
+ exit 1
+}
+
+need() {
+ command -v "$1" >/dev/null 2>&1 || die "$1 is required"
+}
+
+prompt() {
+ local var_name="$1"
+ local label="$2"
+ local default_value="${3:-}"
+ local value
+
+ if [ -n "$default_value" ]; then
+ read -r -p "$label [$default_value]: " value
+ value="${value:-$default_value}"
+ else
+ read -r -p "$label: " value
+ fi
+
+ printf -v "$var_name" '%s' "$value"
+}
+
+gpg_key_exists() {
+ local email="$1"
+
+ gpg --list-secret-keys --with-colons "$email" 2>/dev/null |
+ awk -F: '$1 == "sec" { found = 1 } END { exit !found }'
+}
+
+gpg_key_fpr() {
+ local email="$1"
+
+ gpg --list-secret-keys --with-colons --fingerprint "$email" |
+ awk -F: '$1 == "fpr" { print $10; exit }'
+}
+
+create_key() {
+ local name email comment expire batch_file
+
+ echo "No rbash signing key found."
+ echo
+
+ prompt name "Name" "$KEY_NAME_DEFAULT"
+ prompt email "Email" "$KEY_EMAIL_DEFAULT"
+ prompt comment "Comment" "$KEY_COMMENT_DEFAULT"
+ prompt expire "Expire-Date, e.g. 2y, 5y, 0 for never" "5y"
+
+ [ -n "$name" ] || die "name is required"
+ [ -n "$email" ] || die "email is required"
+
+ batch_file="$(mktemp)"
+ trap 'rm -f "$batch_file"' RETURN
+
+ cat > "$batch_file" <<EOF
+%ask-passphrase
+Key-Type: eddsa
+Key-Curve: ed25519
+Key-Usage: sign
+Name-Real: $name
+Name-Email: $email
+Name-Comment: $comment
+Expire-Date: $expire
+%commit
+EOF
+
+ echo
+ echo "Creating GPG signing key..."
+ gpg --batch --generate-key "$batch_file"
+
+ echo "$email"
+}
+
+pick_or_create_key() {
+ local configured_email
+
+ configured_email="${RBASH_SIGNING_KEY_EMAIL:-$KEY_EMAIL_DEFAULT}"
+
+ if gpg_key_exists "$configured_email"; then
+ echo "$configured_email"
+ return 0
+ fi
+
+ create_key
+}
+
+main() {
+ local key_email fpr
+
+ need gpg
+ [ -f "$TARGET" ] || die "$TARGET does not exist"
+
+ key_email="$(pick_or_create_key)"
+ fpr="$(gpg_key_fpr "$key_email")"
+
+ [ -n "$fpr" ] || die "failed to resolve signing key fingerprint"
+
+ echo "Signing key:"
+ echo " email: $key_email"
+ echo " fingerprint: $fpr"
+ echo
+
+ echo "Signing $TARGET -> $SIG"
+ gpg \
+ --batch \
+ --yes \
+ --armor \
+ --detach-sign \
+ --local-user "$fpr" \
+ --output "$SIG" \
+ "$TARGET"
+
+ echo "Exporting public key -> $PUBKEY"
+ gpg \
+ --batch \
+ --yes \
+ --armor \
+ --export "$fpr" > "$PUBKEY"
+
+ echo
+ echo "Done."
+ echo
+ echo "Add/pin this fingerprint in rbashrc:"
+ echo
+ echo "RBASH_SIGNING_FPR=\"$fpr\""
+}
+
+main "$@"