feat: add update script and operations docs/templates
This commit is contained in:
+21
-47
@@ -11,10 +11,8 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_linux() {
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
fail "This bootstrap script supports Linux only."
|
||||
fi
|
||||
current_os() {
|
||||
uname -s
|
||||
}
|
||||
|
||||
HOST_ROOT="${1:-$(pwd)}"
|
||||
@@ -51,55 +49,31 @@ report_github_structure() {
|
||||
fi
|
||||
}
|
||||
|
||||
has_python_311_or_higher() {
|
||||
command -v python3 >/dev/null 2>&1 \
|
||||
&& python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)'
|
||||
}
|
||||
|
||||
install_python_311() {
|
||||
if has_python_311_or_higher; then
|
||||
return 0
|
||||
fi
|
||||
if command -v apt-get >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1; then
|
||||
log "Installing Python 3.11 via apt-get (minimum required: 3.11)."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3.11
|
||||
else
|
||||
fail "Python 3.11+ is required. Install it manually or provide apt-get + sudo."
|
||||
fi
|
||||
has_python_311_or_higher || fail "Python 3.11+ installation check failed."
|
||||
}
|
||||
|
||||
has_node_20() {
|
||||
command -v node >/dev/null 2>&1 \
|
||||
&& node -e 'const [major] = process.versions.node.split(".").map(Number); process.exit(major >= 20 ? 0 : 1);'
|
||||
}
|
||||
|
||||
install_node_20() {
|
||||
if has_node_20; then
|
||||
return 0
|
||||
fi
|
||||
if command -v apt-get >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1 && command -v curl >/dev/null 2>&1; then
|
||||
log "Installing Node.js 20 LTS via NodeSource (minimum required: v20)."
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
else
|
||||
fail "Node.js v20+ is required. Install it manually or provide apt-get + sudo + curl."
|
||||
fi
|
||||
has_node_20 || fail "Node.js v20+ installation check failed."
|
||||
}
|
||||
|
||||
main() {
|
||||
require_linux
|
||||
os_name="$(current_os)"
|
||||
[ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}"
|
||||
|
||||
log "Host root: ${HOST_ROOT}"
|
||||
ensure_host_structure
|
||||
install_python_311
|
||||
install_node_20
|
||||
|
||||
# Intentionally do not install dependencies in bootstrap.
|
||||
# This script only prepares the host directory layout.
|
||||
log "${os_name} detected. Skipping Python/Node installation by design."
|
||||
|
||||
report_github_structure
|
||||
log "python3: $(python3 --version 2>&1)"
|
||||
log "node: $(node --version 2>&1)"
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
log "python3: $(python3 --version 2>&1)"
|
||||
else
|
||||
log "python3: not found"
|
||||
fi
|
||||
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
log "node: $(node --version 2>&1)"
|
||||
else
|
||||
log "node: not found"
|
||||
fi
|
||||
|
||||
log "Bootstrap completed successfully."
|
||||
}
|
||||
|
||||
|
||||
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: update.sh [options] [HOST_ROOT]
|
||||
|
||||
Options:
|
||||
--dry-run Show planned commands without executing them.
|
||||
--remote <name> Git remote name to use when available (default: stage-pilot)
|
||||
--repo-url <url> Fallback repository URL when remote is missing.
|
||||
(default: https://github.com/genichin/stage-pilot.git)
|
||||
--branch <name> Upstream branch to sync (default: main)
|
||||
--prefix <path> Subtree prefix path (default: .vendor/stage-pilot)
|
||||
--policy <name> install.sh conflict policy: replace|preserve|fail
|
||||
(default: preserve)
|
||||
--skip-install Only update subtree. Do not run install.sh.
|
||||
-h, --help Show this help message.
|
||||
|
||||
Arguments:
|
||||
HOST_ROOT Host repository root (default: current directory)
|
||||
EOF
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[update] %s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '[update] ERROR: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
if [ "${DRY_RUN}" -eq 1 ]; then
|
||||
log "[dry-run] $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
DRY_RUN=0
|
||||
REMOTE_NAME="stage-pilot"
|
||||
REPO_URL="https://github.com/genichin/stage-pilot.git"
|
||||
BRANCH="main"
|
||||
PREFIX=".vendor/stage-pilot"
|
||||
POLICY="preserve"
|
||||
SKIP_INSTALL=0
|
||||
ARGS=()
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
;;
|
||||
--remote)
|
||||
[ "$#" -ge 2 ] || fail "--remote requires a value"
|
||||
REMOTE_NAME="$2"
|
||||
shift
|
||||
;;
|
||||
--repo-url)
|
||||
[ "$#" -ge 2 ] || fail "--repo-url requires a value"
|
||||
REPO_URL="$2"
|
||||
shift
|
||||
;;
|
||||
--branch)
|
||||
[ "$#" -ge 2 ] || fail "--branch requires a value"
|
||||
BRANCH="$2"
|
||||
shift
|
||||
;;
|
||||
--prefix)
|
||||
[ "$#" -ge 2 ] || fail "--prefix requires a value"
|
||||
PREFIX="$2"
|
||||
shift
|
||||
;;
|
||||
--policy)
|
||||
[ "$#" -ge 2 ] || fail "--policy requires a value"
|
||||
POLICY="$2"
|
||||
shift
|
||||
;;
|
||||
--skip-install)
|
||||
SKIP_INSTALL=1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--*)
|
||||
fail "Unknown option: $1"
|
||||
;;
|
||||
*)
|
||||
ARGS+=("$1")
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
HOST_ROOT="${ARGS[0]:-$(pwd)}"
|
||||
|
||||
case "${POLICY}" in
|
||||
replace|preserve|fail)
|
||||
;;
|
||||
*)
|
||||
fail "Invalid --policy '${POLICY}'. Allowed: replace|preserve|fail"
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}"
|
||||
|
||||
if ! git -C "${HOST_ROOT}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
fail "Host root is not a git repository: ${HOST_ROOT}"
|
||||
fi
|
||||
|
||||
if git -C "${HOST_ROOT}" remote get-url "${REMOTE_NAME}" >/dev/null 2>&1; then
|
||||
SOURCE_REF="${REMOTE_NAME}"
|
||||
log "Using git remote '${REMOTE_NAME}'"
|
||||
else
|
||||
SOURCE_REF="${REPO_URL}"
|
||||
log "Remote '${REMOTE_NAME}' not found; using URL '${REPO_URL}'"
|
||||
fi
|
||||
|
||||
if [ -d "${HOST_ROOT}/${PREFIX}" ]; then
|
||||
run_cmd git -C "${HOST_ROOT}" subtree pull --prefix="${PREFIX}" "${SOURCE_REF}" "${BRANCH}" --squash
|
||||
else
|
||||
log "Subtree prefix not found; adding subtree at ${PREFIX}"
|
||||
run_cmd git -C "${HOST_ROOT}" subtree add --prefix="${PREFIX}" "${SOURCE_REF}" "${BRANCH}" --squash
|
||||
fi
|
||||
|
||||
if [ "${SKIP_INSTALL}" -eq 1 ]; then
|
||||
log "Skip install requested. Update completed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
INSTALL_SCRIPT="${HOST_ROOT}/${PREFIX}/bootstrap/install.sh"
|
||||
[ -f "${INSTALL_SCRIPT}" ] || fail "Install script not found after update: ${INSTALL_SCRIPT}"
|
||||
|
||||
if [ "${DRY_RUN}" -eq 1 ]; then
|
||||
run_cmd /bin/bash "${INSTALL_SCRIPT}" --dry-run "${HOST_ROOT}" "${POLICY}"
|
||||
else
|
||||
/bin/bash "${INSTALL_SCRIPT}" "${HOST_ROOT}" "${POLICY}"
|
||||
fi
|
||||
|
||||
log "Update completed successfully."
|
||||
Reference in New Issue
Block a user