Add scripts
This commit is contained in:
43
01-banner.sh
Normal file
43
01-banner.sh
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# Dynamic login banner. Installed to /etc/update-motd.d/ (executable) or
|
||||
# rendered once into /etc/motd as a fallback. Keep it fast & dependency-free.
|
||||
|
||||
# --- colors (only if stdout is a tty; motd.d output is captured, so keep plain)
|
||||
B=$'\e[1m'; D=$'\e[2m'; G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; C=$'\e[36m'; N=$'\e[0m'
|
||||
|
||||
host="$(hostname -f 2>/dev/null || hostname)"
|
||||
os="$(. /etc/os-release 2>/dev/null && echo "${PRETTY_NAME:-Linux}")"
|
||||
kern="$(uname -r)"
|
||||
up="$(uptime -p 2>/dev/null | sed 's/^up //')"
|
||||
load="$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null)"
|
||||
ipaddr="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')"
|
||||
[ -z "$ipaddr" ] && ipaddr="n/a"
|
||||
|
||||
# memory + disk (root fs)
|
||||
mem="$(free -h 2>/dev/null | awk '/^Mem:/ {print $3 "/" $2}')"
|
||||
disk="$(df -h / 2>/dev/null | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}')"
|
||||
|
||||
# pending updates (apt) + reboot flag - cheap checks, ignore errors
|
||||
updates=""
|
||||
if [ -r /var/lib/update-notifier/updates-available ]; then
|
||||
updates="$(awk 'NR==1{print}' /var/lib/update-notifier/updates-available 2>/dev/null)"
|
||||
fi
|
||||
reboot_flag=""
|
||||
[ -f /var/run/reboot-required ] && reboot_flag="${R} reboot required${N}"
|
||||
|
||||
# load colorization
|
||||
load1="${load%% *}"
|
||||
lc="$G"
|
||||
awk "BEGIN{exit !($load1 > 2.0)}" && lc="$Y"
|
||||
awk "BEGIN{exit !($load1 > 4.0)}" && lc="$R"
|
||||
|
||||
printf '\n'
|
||||
printf ' %s%s%s\n' "$B$C" "$host" "$N"
|
||||
printf ' %s%s%s\n' "$D" "$os · kernel $kern" "$N"
|
||||
printf '\n'
|
||||
printf ' %sIP%s %s\n' "$D" "$N" "$ipaddr"
|
||||
printf ' %sUptime%s %s %sload%s %s%s%s\n' "$D" "$N" "${up:-n/a}" "$D" "$N" "$lc" "$load" "$N"
|
||||
printf ' %sMem%s %s %sdisk /%s %s\n' "$D" "$N" "${mem:-n/a}" "$D" "$N" "${disk:-n/a}"
|
||||
[ -n "$updates" ] && printf ' %sUpdates%s %s\n' "$D" "$N" "$updates"
|
||||
[ -n "$reboot_flag" ] && printf ' %s\n' "$reboot_flag"
|
||||
printf '\n'
|
||||
66
README.md
Normal file
66
README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# linux-bootstrap
|
||||
|
||||
## Oneliner
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/CHANGE_ME/linux-bootstrap/main/install.sh | bash
|
||||
```
|
||||
|
||||
Forward flags to the bootstrapper after `--`:
|
||||
|
||||
```bash
|
||||
# skip hardening, set hostname
|
||||
curl -fsSL https://raw.githubusercontent.com/CHANGE_ME/linux-bootstrap/main/install.sh \
|
||||
| bash -s -- --skip hardening --hostname web01
|
||||
|
||||
# everything including hardening
|
||||
curl -fsSL .../install.sh | bash -s -- --only base,cli,neovim,motd,shell,hardening
|
||||
```
|
||||
|
||||
Override repo/ref/dest via env:
|
||||
|
||||
```bash
|
||||
REF=dev DEST=/srv/bootstrap curl -fsSL .../install.sh | bash
|
||||
```
|
||||
|
||||
### Pure-git alternative (if git is already present)
|
||||
|
||||
```bash
|
||||
git clone --depth=1 https://github.com/CHANGE_ME/linux-bootstrap.git /opt/linux-bootstrap \
|
||||
&& /opt/linux-bootstrap/bootstrap.sh
|
||||
```
|
||||
|
||||
## Modules
|
||||
| module | default | description |
|
||||
|------------|:------:|-------------|
|
||||
| `base` | yes | apt update/upgrade + essentials: git, curl, tmux, htop, tree, rsync, jq, dnsutils, mtr, build-essential, … |
|
||||
| `cli` | yes | modern CLI: ripgrep, fd, bat, fzf, btop |
|
||||
| `neovim` | yes | neovim + the lua config in `config/nvim/` |
|
||||
| `motd` | yes | dynamic login banner (host, IP, uptime, load, mem, disk, updates) |
|
||||
| `shell` | yes | `fd`/`bat` symlinks + system-wide aliases in `/etc/profile.d`, `EDITOR=nvim` |
|
||||
| `hardening`| **no** | opt-in: unattended-upgrades, fail2ban sshd jail |
|
||||
|
||||
```bash
|
||||
./bootstrap.sh --list # show modules
|
||||
./bootstrap.sh --only nvim # just (re)deploy nvim config
|
||||
./bootstrap.sh --skip motd # run everything except motd
|
||||
```
|
||||
|
||||
## Customizing
|
||||
|
||||
- nvim: edit `config/nvim/lua/core/*.lua` and `config/nvim/lua/core/plugins.lua`
|
||||
- banner: edit `config/motd/01-banner.sh`
|
||||
- aliases / packages: edit the `mod_*` functions in `bootstrap.sh`
|
||||
|
||||
## Optional: SSH hardening (do this manually, with care)
|
||||
|
||||
After confirming key-based login works:
|
||||
|
||||
```bash
|
||||
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'
|
||||
PasswordAuthentication no
|
||||
PermitRootLogin prohibit-password
|
||||
KbdInteractiveAuthentication no
|
||||
EOF
|
||||
sudo systemctl reload ssh # or sshd, depending on distro
|
||||
```
|
||||
247
bootstrap.sh
Normal file
247
bootstrap.sh
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# bootstrap.sh - standardize a fresh Debian/Ubuntu host.
|
||||
#
|
||||
# Usage:
|
||||
# ./bootstrap.sh # run default modules
|
||||
# ./bootstrap.sh --only nvim # run a single module
|
||||
# ./bootstrap.sh --skip hardening
|
||||
# ./bootstrap.sh --list # show modules
|
||||
# ./bootstrap.sh --hostname web01
|
||||
#
|
||||
set -Eeuo pipefail
|
||||
|
||||
# resolve repo dir even when invoked via symlink / different cwd
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
. "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
trap 'err "failed at line $LINENO (exit $?)"' ERR
|
||||
|
||||
# ---- module registry --------------------------------------------------------
|
||||
# order matters; "default" controls whether it runs without an explicit --only
|
||||
MODULES=(base cli neovim motd shell hardening)
|
||||
declare -A MOD_DEFAULT=(
|
||||
[base]=1 [cli]=1 [neovim]=1 [motd]=1 [shell]=1 [hardening]=0
|
||||
)
|
||||
declare -A MOD_DESC=(
|
||||
[base]="apt update/upgrade + essential packages (git, curl, tmux, ...)"
|
||||
[cli]="modern CLI quality-of-life tools (ripgrep, fd, bat, fzf, btop, ...)"
|
||||
[neovim]="neovim + standardized lua config"
|
||||
[motd]="dynamic welcome message / login banner"
|
||||
[shell]="shared aliases, sane bash defaults, tool symlinks"
|
||||
[hardening]="OPT-IN: unattended-upgrades, fail2ban, sshd defaults"
|
||||
)
|
||||
|
||||
# ---- args -------------------------------------------------------------------
|
||||
ONLY=""; SKIP=""; SET_HOSTNAME=""
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${C_BOLD}linux-bootstrap${C_RESET}
|
||||
|
||||
--only <m> run only module <m> (repeatable, comma-ok)
|
||||
--skip <m> skip module <m> (repeatable, comma-ok)
|
||||
--hostname <h> set the system hostname
|
||||
--list list modules and exit
|
||||
-h | --help this help
|
||||
|
||||
Modules (default = runs unless --only given):
|
||||
EOF
|
||||
for m in "${MODULES[@]}"; do
|
||||
local mark=" "; [[ ${MOD_DEFAULT[$m]} -eq 1 ]] && mark="${C_GRN}*${C_RESET} "
|
||||
printf ' %s%-10s %s\n' "$mark" "$m" "${MOD_DESC[$m]}"
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--only) ONLY="${ONLY}${ONLY:+,}$2"; shift 2 ;;
|
||||
--skip) SKIP="${SKIP}${SKIP:+,}$2"; shift 2 ;;
|
||||
--hostname) SET_HOSTNAME="$2"; shift 2 ;;
|
||||
--list) usage; exit 0 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
in_csv() { [[ ",$1," == *",$2,"* ]]; }
|
||||
|
||||
should_run() {
|
||||
local m="$1"
|
||||
in_csv "$SKIP" "$m" && return 1
|
||||
if [[ -n "$ONLY" ]]; then in_csv "$ONLY" "$m"; return; fi
|
||||
[[ ${MOD_DEFAULT[$m]} -eq 1 ]]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MODULES
|
||||
# =============================================================================
|
||||
|
||||
mod_base() {
|
||||
step "base packages"
|
||||
pkg_refresh
|
||||
if [[ -z "$ONLY" || "$ONLY" == *base* ]]; then
|
||||
log "apt-get upgrade (security + bugfixes)"
|
||||
$SUDO apt-get -y -qq upgrade || \
|
||||
warn "upgrade had non-fatal issues"
|
||||
fi
|
||||
# one shot install of the essentials
|
||||
pkg_install \
|
||||
ca-certificates curl wget git gnupg \
|
||||
tmux htop tree unzip zip rsync \
|
||||
build-essential \
|
||||
dnsutils mtr-tiny net-tools iproute2 \
|
||||
jq ncdu lsof
|
||||
ok "base packages installed"
|
||||
|
||||
if [[ -n "$SET_HOSTNAME" ]]; then
|
||||
log "setting hostname -> ${SET_HOSTNAME}"
|
||||
$SUDO hostnamectl set-hostname "$SET_HOSTNAME" 2>/dev/null || \
|
||||
echo "$SET_HOSTNAME" | $SUDO tee /etc/hostname >/dev/null
|
||||
ok "hostname set"
|
||||
fi
|
||||
}
|
||||
|
||||
mod_cli() {
|
||||
step "modern CLI tools"
|
||||
# these package names are stable on Debian bookworm+ / Ubuntu 22.04+
|
||||
pkg_install ripgrep fd-find bat fzf || warn "some CLI pkgs unavailable on this release"
|
||||
# btop fell back to htop if missing in old repos
|
||||
ensure_cmd btop btop || warn "btop not in repo (htop already installed)"
|
||||
ok "CLI tools installed (symlinks handled by 'shell' module)"
|
||||
}
|
||||
|
||||
mod_neovim() {
|
||||
step "neovim + config"
|
||||
ensure_cmd nvim neovim
|
||||
ensure_cmd rsync rsync # module is self-contained even with --only neovim
|
||||
|
||||
# config goes to the *invoking* user's home, not root's, when run via sudo
|
||||
local target_user target_home
|
||||
target_user="${SUDO_USER:-$(id -un)}"
|
||||
target_home="$(getent passwd "$target_user" | cut -d: -f6)"
|
||||
[[ -d "$target_home" ]] || die "cannot resolve home for ${target_user}"
|
||||
|
||||
local nvim_dst="${target_home}/.config/nvim"
|
||||
log "deploying nvim config -> ${nvim_dst}"
|
||||
install -d -m 0755 "${target_home}/.config"
|
||||
# mirror repo config; remove stale lua we own, keep user additions elsewhere
|
||||
rsync -a --delete "${SCRIPT_DIR}/config/nvim/" "${nvim_dst}/"
|
||||
# fix ownership if we wrote as root
|
||||
if [[ -n "${SUDO_USER:-}" ]]; then
|
||||
chown -R "${target_user}:$(id -gn "$target_user")" "${target_home}/.config/nvim"
|
||||
fi
|
||||
|
||||
# make nvim the default editor system-wide where possible
|
||||
if have update-alternatives; then
|
||||
$SUDO update-alternatives --install /usr/bin/editor editor "$(command -v nvim)" 100 \
|
||||
>/dev/null 2>&1 || true
|
||||
fi
|
||||
ok "neovim configured for user ${target_user}"
|
||||
}
|
||||
|
||||
mod_motd() {
|
||||
step "welcome message / motd"
|
||||
# disable distro default motd noise where safe
|
||||
if [[ -d /etc/update-motd.d ]]; then
|
||||
for f in /etc/update-motd.d/10-help-text /etc/update-motd.d/50-motd-news \
|
||||
/etc/update-motd.d/00-header; do
|
||||
if [[ -e "$f" ]]; then
|
||||
$SUDO chmod -x "$f" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
if write_if_changed /etc/update-motd.d/01-bootstrap-banner 0755 \
|
||||
< "${SCRIPT_DIR}/config/motd/01-banner.sh"; then
|
||||
ok "dynamic motd installed"
|
||||
else
|
||||
log "motd already up to date"
|
||||
fi
|
||||
else
|
||||
# fallback: static /etc/motd
|
||||
"${SCRIPT_DIR}/config/motd/01-banner.sh" 2>/dev/null | $SUDO tee /etc/motd >/dev/null
|
||||
ok "static /etc/motd written"
|
||||
fi
|
||||
}
|
||||
|
||||
mod_shell() {
|
||||
step "shell defaults + aliases"
|
||||
# Debian ships fd as 'fdfind' and bat as 'batcat'. add friendly symlinks.
|
||||
local bindir="/usr/local/bin"
|
||||
have fdfind && [[ ! -e "$bindir/fd" ]] && $SUDO ln -s "$(command -v fdfind)" "$bindir/fd" && ok "symlink fd"
|
||||
have batcat && [[ ! -e "$bindir/bat" ]] && $SUDO ln -s "$(command -v batcat)" "$bindir/bat" && ok "symlink bat"
|
||||
|
||||
# drop a managed aliases file sourced from /etc/profile.d (system-wide)
|
||||
if write_if_changed /etc/profile.d/zz-bootstrap-aliases.sh 0644 <<'EOF'
|
||||
# managed by linux-bootstrap - do not edit by hand
|
||||
alias ll='ls -alh --color=auto'
|
||||
alias la='ls -A --color=auto'
|
||||
alias l='ls -CF --color=auto'
|
||||
alias ..='cd ..'
|
||||
alias ...='cd ../..'
|
||||
alias grep='grep --color=auto'
|
||||
alias df='df -h'
|
||||
alias du='du -h'
|
||||
alias ip='ip -color=auto'
|
||||
command -v bat >/dev/null 2>&1 && alias cat='bat --paging=never --style=plain'
|
||||
command -v btop >/dev/null 2>&1 && alias top='btop'
|
||||
export EDITOR=nvim
|
||||
export VISUAL=nvim
|
||||
alias vi='nvim'
|
||||
alias vim='nvim'
|
||||
EOF
|
||||
then ok "system-wide aliases installed"; else log "aliases already up to date"; fi
|
||||
}
|
||||
|
||||
mod_hardening() {
|
||||
step "hardening (opt-in)"
|
||||
warn "hardening enabled - review before using on hosts you SSH into"
|
||||
|
||||
# automatic security updates
|
||||
pkg_install unattended-upgrades apt-listchanges
|
||||
$SUDO dpkg-reconfigure -f noninteractive unattended-upgrades >/dev/null 2>&1 || true
|
||||
ok "unattended-upgrades enabled"
|
||||
|
||||
# fail2ban with sane sshd defaults
|
||||
pkg_install fail2ban
|
||||
if write_if_changed /etc/fail2ban/jail.d/sshd-bootstrap.conf 0644 <<'EOF'
|
||||
[sshd]
|
||||
enabled = true
|
||||
backend = systemd
|
||||
maxretry = 4
|
||||
bantime = 1h
|
||||
findtime = 10m
|
||||
EOF
|
||||
then $SUDO systemctl restart fail2ban 2>/dev/null || true; fi
|
||||
ok "fail2ban configured for sshd"
|
||||
|
||||
# NOTE: deliberately NOT touching PasswordAuthentication / PermitRootLogin /
|
||||
# firewall here. Locking yourself out of a remote box is too easy. Add those
|
||||
# as a separate, explicit step once key-based access is verified.
|
||||
log "sshd/firewall changes left to a deliberate manual step (see README)"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# RUN
|
||||
# =============================================================================
|
||||
main() {
|
||||
detect_sudo
|
||||
detect_distro
|
||||
case "$DISTRO_ID:$DISTRO_LIKE" in
|
||||
*debian*|*ubuntu*) : ;; # supported
|
||||
*) [[ "$PKG_MGR" == "apt" ]] || warn "non-apt distro: best-effort only" ;;
|
||||
esac
|
||||
|
||||
local ran=0
|
||||
for m in "${MODULES[@]}"; do
|
||||
if should_run "$m"; then
|
||||
"mod_${m}"
|
||||
ran=$((ran+1))
|
||||
fi
|
||||
done
|
||||
[[ $ran -gt 0 ]] || { warn "no modules ran (check --only/--skip)"; exit 0; }
|
||||
|
||||
printf '\n%s== done: %d module(s) ==%s\n' "$C_BOLD" "$ran" "$C_RESET"
|
||||
ok "re-login or 'source /etc/profile' to pick up aliases"
|
||||
}
|
||||
main "$@"
|
||||
117
common.sh
Normal file
117
common.sh
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# common.sh - shared helpers. Sourced by bootstrap.sh, not run directly.
|
||||
|
||||
# Exported once so we never rely on the `$SUDO VAR=val cmd` pattern, which
|
||||
# breaks when $SUDO is empty (running as root): the var assignment gets parsed
|
||||
# before $SUDO expands, so `DEBIAN_FRONTEND=...` ends up treated as a command.
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# ---- colors / logging -------------------------------------------------------
|
||||
if [[ -t 1 ]]; then
|
||||
C_RESET=$'\e[0m'; C_DIM=$'\e[2m'; C_RED=$'\e[31m'; C_GRN=$'\e[32m'
|
||||
C_YLW=$'\e[33m'; C_BLU=$'\e[34m'; C_BOLD=$'\e[1m'
|
||||
else
|
||||
C_RESET=""; C_DIM=""; C_RED=""; C_GRN=""; C_YLW=""; C_BLU=""; C_BOLD=""
|
||||
fi
|
||||
|
||||
log() { printf '%s[*]%s %s\n' "$C_BLU" "$C_RESET" "$*"; }
|
||||
ok() { printf '%s[+]%s %s\n' "$C_GRN" "$C_RESET" "$*"; }
|
||||
warn() { printf '%s[!]%s %s\n' "$C_YLW" "$C_RESET" "$*" >&2; }
|
||||
err() { printf '%s[x]%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; }
|
||||
die() { err "$*"; exit 1; }
|
||||
step() { printf '\n%s== %s ==%s\n' "$C_BOLD" "$*" "$C_RESET"; }
|
||||
|
||||
# ---- privilege --------------------------------------------------------------
|
||||
# Sets $SUDO to "sudo" if not root, "" if root. Fails if neither works.
|
||||
detect_sudo() {
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
SUDO=""
|
||||
elif command -v sudo >/dev/null 2>&1; then
|
||||
SUDO="sudo"
|
||||
# prime the credential cache early so prompts don't appear mid-run
|
||||
$SUDO -v || die "sudo authentication failed"
|
||||
else
|
||||
die "not root and sudo not available"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- distro detection -------------------------------------------------------
|
||||
# Sets DISTRO_ID (debian/ubuntu/...), DISTRO_LIKE, DISTRO_CODENAME, PKG_MGR.
|
||||
detect_distro() {
|
||||
[[ -r /etc/os-release ]] || die "/etc/os-release missing - unsupported system"
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
DISTRO_ID="${ID:-unknown}"
|
||||
# shellcheck disable=SC2034 # used in bootstrap.sh main()
|
||||
DISTRO_LIKE="${ID_LIKE:-}"
|
||||
DISTRO_CODENAME="${VERSION_CODENAME:-}"
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
PKG_MGR="apt"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
PKG_MGR="dnf"
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
PKG_MGR="pacman"
|
||||
else
|
||||
die "no supported package manager found (apt/dnf/pacman)"
|
||||
fi
|
||||
ok "detected ${DISTRO_ID} ${DISTRO_CODENAME:-} (pkg: ${PKG_MGR})"
|
||||
}
|
||||
|
||||
# ---- package helpers (apt-focused, dnf/pacman best-effort) ------------------
|
||||
APT_UPDATED=0
|
||||
pkg_refresh() {
|
||||
case "$PKG_MGR" in
|
||||
apt)
|
||||
if [[ $APT_UPDATED -eq 0 ]]; then
|
||||
log "apt-get update"
|
||||
# a single broken third-party repo shouldn't abort the whole bootstrap;
|
||||
# downstream installs will fail loudly if a needed pkg is truly missing.
|
||||
$SUDO apt-get update -qq || warn "apt-get update reported errors (broken repo?) - continuing"
|
||||
APT_UPDATED=1
|
||||
fi
|
||||
;;
|
||||
dnf) $SUDO dnf -q makecache ;;
|
||||
pacman) $SUDO pacman -Sy --noconfirm >/dev/null ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# is a binary already on PATH?
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# install one or more packages, skipping any already-present *packages*
|
||||
pkg_install() {
|
||||
pkg_refresh
|
||||
case "$PKG_MGR" in
|
||||
apt)
|
||||
$SUDO apt-get install -y -qq \
|
||||
--no-install-recommends "$@" ;;
|
||||
dnf) $SUDO dnf install -y "$@" ;;
|
||||
pacman) $SUDO pacman -S --needed --noconfirm "$@" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# install a package only if a given command is missing. usage: ensure_cmd <cmd> <pkg...>
|
||||
ensure_cmd() {
|
||||
local cmd="$1"; shift
|
||||
if have "$cmd"; then
|
||||
printf '%s - %s already present%s\n' "$C_DIM" "$cmd" "$C_RESET"
|
||||
return 0
|
||||
fi
|
||||
log "installing ${cmd} (pkg: $*)"
|
||||
pkg_install "$@"
|
||||
}
|
||||
|
||||
# write file from stdin only if content differs (idempotent). usage: write_if_changed <path>
|
||||
write_if_changed() {
|
||||
local target="$1" tmp
|
||||
tmp="$(mktemp)"
|
||||
cat > "$tmp"
|
||||
if [[ -f "$target" ]] && cmp -s "$tmp" "$target"; then
|
||||
rm -f "$tmp"
|
||||
return 1 # unchanged
|
||||
fi
|
||||
$SUDO install -D -m "${2:-0644}" "$tmp" "$target"
|
||||
rm -f "$tmp"
|
||||
return 0 # changed
|
||||
}
|
||||
16
init.lua
Normal file
16
init.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
-- init.lua - standardized neovim config for managed hosts.
|
||||
-- Design goals: fast, works offline (pure-lua core), plugins are optional.
|
||||
|
||||
-- core: zero external dependencies, always loads
|
||||
require("core.options")
|
||||
require("core.keymaps")
|
||||
require("core.autocmds")
|
||||
|
||||
-- plugins: best-effort. if bootstrap fails (no network on a fresh box),
|
||||
-- the editor still works perfectly with the core config above.
|
||||
local ok, err = pcall(require, "core.plugins")
|
||||
if not ok then
|
||||
vim.schedule(function()
|
||||
vim.notify("plugins not loaded (offline?): " .. tostring(err), vim.log.levels.WARN)
|
||||
end)
|
||||
end
|
||||
59
install.sh
Normal file
59
install.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# install.sh - tiny entrypoint for the curl|bash oneliner.
|
||||
# Ensures git, clones the repo, hands off to bootstrap.sh.
|
||||
#
|
||||
# Configurable via env:
|
||||
# REPO_URL git url (default: this public repo)
|
||||
# REF branch/tag/commit to check out (default: main)
|
||||
# DEST clone destination (default: /opt/linux-bootstrap)
|
||||
# Any extra args are forwarded to bootstrap.sh, e.g.:
|
||||
# curl -fsSL .../install.sh | bash -s -- --skip hardening
|
||||
#
|
||||
set -Eeuo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
REPO_URL="${REPO_URL:-https://github.com/CHANGE_ME/linux-bootstrap.git}"
|
||||
REF="${REF:-main}"
|
||||
DEST="${DEST:-/opt/linux-bootstrap}"
|
||||
|
||||
say() { printf '\e[34m[install]\e[0m %s\n' "$*"; }
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
command -v sudo >/dev/null 2>&1 || { echo "need root or sudo"; exit 1; }
|
||||
SUDO="sudo"
|
||||
else
|
||||
SUDO=""
|
||||
fi
|
||||
|
||||
# 1. ensure git
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
say "installing git"
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
$SUDO apt-get update -qq
|
||||
$SUDO apt-get install -y -qq git
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
$SUDO dnf install -y git
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
$SUDO pacman -Sy --noconfirm git
|
||||
else
|
||||
echo "no supported package manager to install git"; exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. clone or update
|
||||
if [[ -d "$DEST/.git" ]]; then
|
||||
say "updating existing clone at $DEST"
|
||||
$SUDO git -C "$DEST" fetch --depth=1 origin "$REF"
|
||||
$SUDO git -C "$DEST" checkout -f "$REF"
|
||||
$SUDO git -C "$DEST" reset --hard "origin/$REF" 2>/dev/null || true
|
||||
else
|
||||
say "cloning $REPO_URL -> $DEST"
|
||||
$SUDO git clone --depth=1 --branch "$REF" "$REPO_URL" "$DEST" 2>/dev/null \
|
||||
|| $SUDO git clone --depth=1 "$REPO_URL" "$DEST"
|
||||
fi
|
||||
|
||||
# 3. run bootstrap, forwarding any extra args
|
||||
say "running bootstrap.sh $*"
|
||||
$SUDO chmod +x "$DEST/bootstrap.sh" "$DEST/config/motd/01-banner.sh"
|
||||
exec "$DEST/bootstrap.sh" "$@"
|
||||
Reference in New Issue
Block a user