499 lines
18 KiB
Bash
Executable File
499 lines
18 KiB
Bash
Executable File
#!/bin/bash
|
||
set -euo pipefail
|
||
|
||
# ─── Color helpers ────────────────────────────────────────────────────────────
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
||
|
||
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||
error() { echo -e "${RED}[ERR]${NC} $*" >&2; }
|
||
step() { echo -e "\n${BOLD}${BLUE}══ $* ${NC}"; }
|
||
|
||
# Read from terminal even when script is run via `curl ... | bash`.
|
||
prompt_read() {
|
||
local __var_name="$1"
|
||
local __prompt="$2"
|
||
local __input=""
|
||
if [ -r /dev/tty ]; then
|
||
read -r -p "$__prompt" __input < /dev/tty
|
||
else
|
||
read -r -p "$__prompt" __input
|
||
fi
|
||
printf -v "$__var_name" '%s' "$__input"
|
||
}
|
||
|
||
# ─── OS Detection ─────────────────────────────────────────────────────────────
|
||
detect_os() {
|
||
if [ -f /etc/os-release ]; then
|
||
. /etc/os-release
|
||
OS_ID="${ID:-unknown}"
|
||
OS_ID_LIKE="${ID_LIKE:-}"
|
||
else
|
||
error "Cannot detect OS (no /etc/os-release found)"
|
||
exit 1
|
||
fi
|
||
|
||
case "$OS_ID" in
|
||
aosc) DISTRO="aosc" ;;
|
||
debian) DISTRO="debian" ;;
|
||
ubuntu) DISTRO="ubuntu" ;;
|
||
fedora) DISTRO="fedora" ;;
|
||
*)
|
||
# fallback via ID_LIKE
|
||
case "$OS_ID_LIKE" in
|
||
*debian*) DISTRO="debian" ;;
|
||
*fedora*|*rhel*) DISTRO="fedora" ;;
|
||
*) error "Unsupported distro: $OS_ID"; exit 1 ;;
|
||
esac
|
||
;;
|
||
esac
|
||
info "Detected distro: ${BOLD}$DISTRO${NC} (ID=$OS_ID)"
|
||
}
|
||
|
||
# ─── HTTP Proxy ───────────────────────────────────────────────────────────────
|
||
setup_proxy() {
|
||
step "HTTP Proxy"
|
||
echo -e "Do you want to configure an HTTP proxy for this session? ${YELLOW}(helps with Homebrew downloads)${NC}"
|
||
prompt_read ans "Configure proxy? [y/N] "
|
||
case "$ans" in
|
||
[Yy]*)
|
||
while true; do
|
||
prompt_read proxy_url "Enter proxy URL (e.g. http://192.168.1.1:7890): "
|
||
if [[ "$proxy_url" =~ ^https?://[^:]+:[0-9]+$ ]]; then
|
||
break
|
||
fi
|
||
warn "Invalid format. Expected: http://<IP>:<PORT> or https://<IP>:<PORT>"
|
||
done
|
||
export http_proxy="$proxy_url"
|
||
export https_proxy="$proxy_url"
|
||
export HTTP_PROXY="$proxy_url"
|
||
export HTTPS_PROXY="$proxy_url"
|
||
success "Proxy set to $proxy_url for this session"
|
||
;;
|
||
*)
|
||
info "Skipping proxy configuration"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# ─── Proxy-aware sudo ─────────────────────────────────────────────────────────
|
||
# sudo's default env_reset policy strips proxy variables.
|
||
# This wrapper forwards them so that package managers / curl under sudo
|
||
# can reach the network through the configured proxy.
|
||
psudo() {
|
||
local -a env_args=()
|
||
[ -n "${http_proxy:-}" ] && env_args+=("http_proxy=$http_proxy")
|
||
[ -n "${https_proxy:-}" ] && env_args+=("https_proxy=$https_proxy")
|
||
[ -n "${HTTP_PROXY:-}" ] && env_args+=("HTTP_PROXY=$HTTP_PROXY")
|
||
[ -n "${HTTPS_PROXY:-}" ] && env_args+=("HTTPS_PROXY=$HTTPS_PROXY")
|
||
[ -n "${no_proxy:-}" ] && env_args+=("no_proxy=$no_proxy")
|
||
[ -n "${NO_PROXY:-}" ] && env_args+=("NO_PROXY=$NO_PROXY")
|
||
|
||
if [ ${#env_args[@]} -gt 0 ]; then
|
||
sudo env "${env_args[@]}" "$@"
|
||
else
|
||
sudo "$@"
|
||
fi
|
||
}
|
||
|
||
# ─── SSH Key Setup ────────────────────────────────────────────────────────────
|
||
setup_ssh_key() {
|
||
step "SSH Key Configuration"
|
||
|
||
# Create ~/.ssh
|
||
mkdir -p ~/.ssh
|
||
chmod 700 ~/.ssh
|
||
touch ~/.ssh/authorized_keys
|
||
chmod 600 ~/.ssh/authorized_keys
|
||
|
||
# Show already-present keys
|
||
existing_count=$(grep -c . ~/.ssh/authorized_keys 2>/dev/null || true)
|
||
if [ "$existing_count" -gt 0 ]; then
|
||
info "Existing authorized keys ($existing_count):"
|
||
grep -v '^\s*$' ~/.ssh/authorized_keys | while IFS= read -r line; do
|
||
echo " ${line:0:60}..."
|
||
done
|
||
fi
|
||
|
||
# Collect public key(s) interactively
|
||
echo ""
|
||
echo -e "Paste ${BOLD}new${NC} SSH public key(s) to add (enter blank line to finish):"
|
||
echo ""
|
||
|
||
local added=0
|
||
while true; do
|
||
prompt_read pubkey "Public key (or blank to finish): "
|
||
[[ -z "$pubkey" ]] && break
|
||
if [[ "$pubkey" =~ ^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|sk-ssh-ed25519) ]]; then
|
||
if grep -qF "$pubkey" ~/.ssh/authorized_keys 2>/dev/null; then
|
||
info "Key already present, skipping (${pubkey:0:30}...)"
|
||
else
|
||
echo "$pubkey" >> ~/.ssh/authorized_keys
|
||
success "Key added (${pubkey:0:30}...)"
|
||
(( added++ )) || true
|
||
fi
|
||
else
|
||
warn "Does not look like a valid public key, skipping"
|
||
fi
|
||
done
|
||
|
||
if [ "$added" -eq 0 ]; then
|
||
info "No new keys added"
|
||
else
|
||
success "authorized_keys updated (+$added key(s))"
|
||
fi
|
||
|
||
# Configure sshd_config
|
||
info "Configuring /etc/ssh/sshd_config ..."
|
||
SSHD_CONF="/etc/ssh/sshd_config"
|
||
|
||
sudo_set_sshd() {
|
||
local key="$1" val="$2"
|
||
# Uncomment or add the line
|
||
if psudo grep -qE "^\s*#?\s*${key}\s" "$SSHD_CONF"; then
|
||
psudo sed -i -E "s|^\s*#?\s*(${key})\s+.*|\1 ${val}|" "$SSHD_CONF"
|
||
else
|
||
echo "${key} ${val}" | psudo tee -a "$SSHD_CONF" > /dev/null
|
||
fi
|
||
}
|
||
|
||
sudo_set_sshd "PubkeyAuthentication" "yes"
|
||
sudo_set_sshd "AuthorizedKeysFile" ".ssh/authorized_keys"
|
||
|
||
# Only disable password auth when at least one key is present — avoid lockout
|
||
if grep -qv '^\s*$' ~/.ssh/authorized_keys 2>/dev/null; then
|
||
sudo_set_sshd "PasswordAuthentication" "no"
|
||
success "sshd_config updated (password login disabled)"
|
||
else
|
||
warn "No authorized keys found — keeping PasswordAuthentication unchanged to avoid lockout"
|
||
fi
|
||
|
||
# Restart SSH
|
||
if psudo systemctl restart ssh 2>/dev/null || psudo systemctl restart sshd 2>/dev/null; then
|
||
success "SSH service restarted"
|
||
else
|
||
warn "Could not restart SSH service automatically — please restart it manually"
|
||
fi
|
||
}
|
||
|
||
# ─── Install git via system package manager ───────────────────────────────────
|
||
install_git() {
|
||
if command -v git &>/dev/null; then
|
||
success "git already installed ($(git --version))"
|
||
return
|
||
fi
|
||
info "Installing git via system package manager ..."
|
||
case "$DISTRO" in
|
||
aosc) psudo oma install -y git ;;
|
||
debian|ubuntu) psudo apt-get update -qq && psudo apt-get install -y git ;;
|
||
fedora) psudo dnf install -y git ;;
|
||
esac
|
||
success "git installed"
|
||
}
|
||
|
||
# ─── Homebrew ─────────────────────────────────────────────────────────────────
|
||
install_homebrew() {
|
||
if command -v brew &>/dev/null; then
|
||
success "Homebrew already installed"
|
||
return
|
||
fi
|
||
info "Installing Homebrew ..."
|
||
local install_script
|
||
install_script="$(mktemp)"
|
||
if ! curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh -o "$install_script"; then
|
||
rm -f "$install_script"
|
||
error "Failed to download Homebrew install script (network error)"
|
||
exit 1
|
||
fi
|
||
/bin/bash "$install_script"
|
||
rm -f "$install_script"
|
||
|
||
# Add brew to PATH for the rest of this script
|
||
if [ -f /home/linuxbrew/.linuxbrew/bin/brew ]; then
|
||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||
fi
|
||
|
||
if ! command -v brew &>/dev/null; then
|
||
error "Homebrew installation failed (brew not found after install)"
|
||
exit 1
|
||
fi
|
||
success "Homebrew installed"
|
||
}
|
||
|
||
# brew install,跳过已安装的包,忽略依赖 post-install 失败引起的非零退出,最后校验每个包
|
||
brew_install_packages() {
|
||
local to_install=()
|
||
for pkg in "$@"; do
|
||
if brew list --formula "$pkg" &>/dev/null; then
|
||
info "Homebrew: $pkg already installed, skipping"
|
||
else
|
||
to_install+=("$pkg")
|
||
fi
|
||
done
|
||
|
||
if [ ${#to_install[@]} -eq 0 ]; then
|
||
success "All Homebrew packages already installed"
|
||
return
|
||
fi
|
||
|
||
info "Installing via Homebrew: ${to_install[*]}"
|
||
brew install "${to_install[@]}" || true
|
||
|
||
local failed=()
|
||
for pkg in "${to_install[@]}"; do
|
||
if ! brew list --formula "$pkg" &>/dev/null; then
|
||
failed+=("$pkg")
|
||
fi
|
||
done
|
||
if [ ${#failed[@]} -gt 0 ]; then
|
||
error "The following packages failed to install: ${failed[*]}"
|
||
exit 1
|
||
fi
|
||
success "Homebrew packages installed: ${to_install[*]}"
|
||
}
|
||
|
||
# ─── Software Installation ────────────────────────────────────────────────────
|
||
install_packages() {
|
||
step "Software Installation"
|
||
case "$DISTRO" in
|
||
aosc)
|
||
info "Installing packages via oma ..."
|
||
psudo oma install -y git fish eza fastfetch btop docker docker-compose docker-buildx
|
||
success "All packages installed via oma"
|
||
;;
|
||
debian|ubuntu)
|
||
install_git
|
||
install_homebrew
|
||
brew_install_packages fish eza fastfetch btop
|
||
;;
|
||
fedora)
|
||
install_git
|
||
install_homebrew
|
||
brew_install_packages fish eza fastfetch btop
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# ─── Fish Shell Setup ─────────────────────────────────────────────────────────
|
||
setup_fish() {
|
||
step "Fish Shell Configuration"
|
||
|
||
FISH_PATH="$(command -v fish)"
|
||
if [ -z "$FISH_PATH" ]; then
|
||
error "fish not found in PATH"
|
||
return 1
|
||
fi
|
||
|
||
# Add fish to /etc/shells if not already present
|
||
if ! grep -qF "$FISH_PATH" /etc/shells; then
|
||
echo "$FISH_PATH" | psudo tee -a /etc/shells > /dev/null
|
||
success "Added $FISH_PATH to /etc/shells"
|
||
else
|
||
info "$FISH_PATH already in /etc/shells"
|
||
fi
|
||
|
||
# Change default shell only if not already fish
|
||
current_shell="$(getent passwd "$USER" | cut -d: -f7)"
|
||
if [ "$current_shell" = "$FISH_PATH" ]; then
|
||
info "fish is already the default shell"
|
||
else
|
||
psudo chsh -s "$FISH_PATH" "$USER"
|
||
success "Default shell changed to fish ($FISH_PATH)"
|
||
fi
|
||
|
||
# For brew-based systems: add brew shellenv to fish config
|
||
if [ "$DISTRO" != "aosc" ] && [ -f /home/linuxbrew/.linuxbrew/bin/brew ]; then
|
||
mkdir -p ~/.config/fish
|
||
FISH_CONF="$HOME/.config/fish/config.fish"
|
||
BREW_LINE='eval (/home/linuxbrew/.linuxbrew/bin/brew shellenv)'
|
||
if ! grep -qF "$BREW_LINE" "$FISH_CONF" 2>/dev/null; then
|
||
echo "$BREW_LINE" >> "$FISH_CONF"
|
||
success "brew shellenv added to fish config"
|
||
else
|
||
info "brew shellenv already in fish config"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ─── Docker Installation ──────────────────────────────────────────────────────
|
||
install_docker() {
|
||
step "Docker Installation"
|
||
|
||
case "$DISTRO" in
|
||
aosc)
|
||
info "Docker already installed via oma, skipping"
|
||
;;
|
||
debian|ubuntu)
|
||
if command -v docker &>/dev/null; then
|
||
info "Docker already installed ($(docker --version)), skipping"
|
||
else
|
||
curl -fsSL https://git.mitsea.com/FlintyLemming/scripts-public/raw/branch/main/linux-managements/install-docker.sh \
|
||
-o /tmp/install-docker.sh
|
||
psudo sh /tmp/install-docker.sh
|
||
success "Docker installed"
|
||
fi
|
||
;;
|
||
fedora)
|
||
if command -v docker &>/dev/null; then
|
||
info "Docker already installed ($(docker --version)), skipping"
|
||
else
|
||
info "Setting up Docker CE repository ..."
|
||
psudo curl -fsSL https://download.docker.com/linux/fedora/docker-ce.repo \
|
||
-o /etc/yum.repos.d/docker-ce.repo
|
||
psudo dnf install -y docker-ce docker-ce-cli containerd.io \
|
||
docker-compose-plugin docker-buildx-plugin
|
||
success "Docker installed"
|
||
fi
|
||
;;
|
||
esac
|
||
|
||
docker_no_root
|
||
}
|
||
|
||
# Allow current user to run docker without sudo
|
||
docker_no_root() {
|
||
info "Configuring Docker for non-root usage ..."
|
||
|
||
if ! getent group docker > /dev/null 2>&1; then
|
||
psudo groupadd docker
|
||
fi
|
||
|
||
if id -nG "$USER" | grep -qw docker; then
|
||
info "User '$USER' is already in the docker group"
|
||
else
|
||
psudo usermod -aG docker "$USER"
|
||
success "User '$USER' added to the docker group"
|
||
warn "Log out and back in for the group change to take effect"
|
||
fi
|
||
|
||
if ! psudo systemctl is-enabled --quiet docker 2>/dev/null; then
|
||
psudo systemctl enable docker
|
||
fi
|
||
psudo systemctl start docker
|
||
success "Docker service running"
|
||
}
|
||
|
||
# ─── Dotfiles ─────────────────────────────────────────────────────────────────
|
||
clone_dotfiles() {
|
||
step "Dotfiles"
|
||
DOTFILES_DIR="$HOME/.flinty"
|
||
if [ -d "$DOTFILES_DIR" ]; then
|
||
info "Dotfiles directory already exists, pulling latest ..."
|
||
git -C "$DOTFILES_DIR" pull
|
||
else
|
||
info "Cloning dotfiles ..."
|
||
git clone https://git.mitsea.com/FlintyLemming/dotfiles.git "$DOTFILES_DIR"
|
||
fi
|
||
success "Dotfiles ready at $DOTFILES_DIR"
|
||
}
|
||
|
||
# ─── Fish Config (source dotfiles) ───────────────────────────────────────────
|
||
configure_fish_dotfiles() {
|
||
step "Fish Config — Dotfiles Integration"
|
||
mkdir -p ~/.config/fish
|
||
FISH_CONF="$HOME/.config/fish/config.fish"
|
||
SOURCE_LINE="source ~/.flinty/fish/add-on.fish"
|
||
if ! grep -qF "$SOURCE_LINE" "$FISH_CONF" 2>/dev/null; then
|
||
echo "$SOURCE_LINE" >> "$FISH_CONF"
|
||
success "Added dotfiles source to fish config"
|
||
else
|
||
info "Dotfiles source line already in fish config"
|
||
fi
|
||
}
|
||
|
||
# ─── SSH Config ───────────────────────────────────────────────────────────────
|
||
configure_ssh_config() {
|
||
step "SSH Config — Dotfiles Integration"
|
||
mkdir -p ~/.ssh
|
||
SSH_CONF="$HOME/.ssh/config"
|
||
INCLUDE_LINE="Include ~/.flinty/ssh/config"
|
||
if ! grep -qF "$INCLUDE_LINE" "$SSH_CONF" 2>/dev/null; then
|
||
# Include must be at the top of ssh config
|
||
if [ -f "$SSH_CONF" ]; then
|
||
tmp="$(mktemp)"
|
||
{ echo "$INCLUDE_LINE"; echo ""; cat "$SSH_CONF"; } > "$tmp"
|
||
mv "$tmp" "$SSH_CONF"
|
||
else
|
||
echo "$INCLUDE_LINE" > "$SSH_CONF"
|
||
fi
|
||
chmod 600 "$SSH_CONF"
|
||
success "Include line added to ~/.ssh/config"
|
||
else
|
||
info "Include line already in ~/.ssh/config"
|
||
fi
|
||
}
|
||
|
||
# ─── Sudo Privilege Check ─────────────────────────────────────────────────────
|
||
ensure_sudo() {
|
||
step "Sudo Privilege Check"
|
||
|
||
if sudo -v 2>/dev/null; then
|
||
success "User '$USER' has sudo access"
|
||
# Keep sudo ticket alive in the background for the duration of the script
|
||
( while true; do sudo -n true 2>/dev/null; sleep 50; done ) &
|
||
SUDO_KEEPALIVE_PID=$!
|
||
# Note: ensure_sudo uses raw sudo intentionally — psudo is not defined yet
|
||
return
|
||
fi
|
||
|
||
warn "User '$USER' does not have sudo access"
|
||
|
||
case "$DISTRO" in
|
||
aosc|fedora) local sudo_group="wheel" ;;
|
||
debian|ubuntu) local sudo_group="sudo" ;;
|
||
esac
|
||
|
||
info "Enter the ${BOLD}root password${NC} to grant sudo access (adds '$USER' to '$sudo_group' group):"
|
||
if su -c "usermod -aG $sudo_group $USER" root; then
|
||
success "User '$USER' added to '$sudo_group' group"
|
||
echo ""
|
||
warn "Group change requires re-login to take effect."
|
||
warn "Please log out, log back in, and re-run this script."
|
||
exit 0
|
||
else
|
||
error "Failed to grant sudo. Please run as root: usermod -aG $sudo_group $USER"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# ─── Main ─────────────────────────────────────────────────────────────────────
|
||
main() {
|
||
echo -e "${BOLD}${CYAN}"
|
||
echo "╔══════════════════════════════════════════╗"
|
||
echo "║ FlintyLemming Server Setup ║"
|
||
echo "╚══════════════════════════════════════════╝"
|
||
echo -e "${NC}"
|
||
|
||
detect_os
|
||
ensure_sudo
|
||
|
||
setup_proxy
|
||
setup_ssh_key
|
||
install_packages
|
||
setup_fish
|
||
install_docker
|
||
clone_dotfiles
|
||
configure_fish_dotfiles
|
||
configure_ssh_config
|
||
|
||
step "Starting Docker"
|
||
if psudo systemctl start docker; then
|
||
success "Docker started"
|
||
else
|
||
warn "Could not start Docker — please start it manually"
|
||
fi
|
||
|
||
# Stop sudo keepalive
|
||
if [ -n "${SUDO_KEEPALIVE_PID:-}" ]; then
|
||
kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BOLD}${GREEN}══ Setup complete! ══${NC}"
|
||
echo -e "Please ${BOLD}log out and back in${NC} (or open a new shell) for all changes to take effect."
|
||
}
|
||
|
||
main "$@"
|