From 8ba2c5bcce93d439d981a316712774197ef614fd Mon Sep 17 00:00:00 2001 From: FlintyLemming Date: Thu, 26 Feb 2026 18:42:49 +0800 Subject: [PATCH] add linux server setup script --- linux-managements/setup.sh | 458 +++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100755 linux-managements/setup.sh diff --git a/linux-managements/setup.sh b/linux-managements/setup.sh new file mode 100755 index 0000000..ba81428 --- /dev/null +++ b/linux-managements/setup.sh @@ -0,0 +1,458 @@ +#!/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}"; } + +# ─── 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}" + read -rp "Configure proxy? [y/N] " ans + case "$ans" in + [Yy]*) + while true; do + read -rp "Enter proxy URL (e.g. http://192.168.1.1:7890): " proxy_url + if [[ "$proxy_url" =~ ^https?://[^:]+:[0-9]+$ ]]; then + break + fi + warn "Invalid format. Expected: http://: or https://:" + 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 +} + +# ─── 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 + read -rp "Public key (or blank to finish): " pubkey + [[ -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 sudo grep -qE "^\s*#?\s*${key}\s" "$SSHD_CONF"; then + sudo sed -i -E "s|^\s*#?\s*(${key})\s+.*|\1 ${val}|" "$SSHD_CONF" + else + echo "${key} ${val}" | sudo 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 sudo systemctl restart ssh 2>/dev/null || sudo 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) sudo oma install -y git ;; + debian|ubuntu) sudo apt-get update -qq && sudo apt-get install -y git ;; + fedora) sudo 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 ..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + # 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 + 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 ..." + sudo oma install -y git fish eza fastfetch btop docker docker-compose-plugin docker-buildx-plugin + 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" | sudo 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 + sudo 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 + info "Downloading Docker install script ..." + curl -fsSL https://config.mitsea.com/install-docker.sh -o /tmp/install-docker.sh + + echo "" + echo -e "Use a ${BOLD}mirror${NC} for Docker installation? ${YELLOW}(recommended in China)${NC}" + read -rp "Use nyist mirror? [y/N] " use_mirror + case "$use_mirror" in + [Yy]*) sudo sh /tmp/install-docker.sh --mirror nyist ;; + *) sudo sh /tmp/install-docker.sh ;; + esac + 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 ..." + sudo curl -fsSL https://download.docker.com/linux/fedora/docker-ce.repo \ + -o /etc/yum.repos.d/docker-ce.repo + sudo 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 + sudo groupadd docker + fi + + if id -nG "$USER" | grep -qw docker; then + info "User '$USER' is already in the docker group" + else + sudo 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 ! sudo systemctl is-enabled --quiet docker 2>/dev/null; then + sudo systemctl enable docker + fi + sudo 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=$! + 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 sudo 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 "$@"