feat: add alpine init script
Signed-off-by: Ivan Li <ivanli2048@gmail.com>
This commit is contained in:
347
alpine/init.sh
Executable file
347
alpine/init.sh
Executable file
@@ -0,0 +1,347 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Initialize an Alpine Linux server:
|
||||
# - Set root password
|
||||
# - Create a new user
|
||||
# - Configure timezone (UTC+8, Asia/Shanghai)
|
||||
# - Install and configure OpenSSH
|
||||
# - Set up SSH key-based login from a fixed authorized_keys URL
|
||||
# - Configure sshd keepalive
|
||||
# - Install and initialize zsh for the new user
|
||||
|
||||
set -u
|
||||
|
||||
SSH_AUTH_KEYS_URL="https://webdav-syncthing.ivanli.cc/Ivan-Personal/Credentials/Public/authorized_keys-uys8y1bkrxi55v0gOJWtrKJ2uM9TLsUq"
|
||||
|
||||
info() {
|
||||
printf '[INFO] %s\n' "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '[WARN] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
error() {
|
||||
printf '[ERROR] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
error "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_alpine() {
|
||||
if ! grep -q '^ID=alpine' /etc/os-release 2>/dev/null; then
|
||||
warn "This script is designed for Alpine Linux, but this system does not report ID=alpine."
|
||||
warn "Continuing anyway, but commands may fail."
|
||||
fi
|
||||
}
|
||||
|
||||
install_base_packages() {
|
||||
info "Installing base packages (tzdata, openssh, zsh, git, curl, zoxide)..."
|
||||
if ! command -v apk >/dev/null 2>&1; then
|
||||
error "apk command not found. Are you sure this is Alpine Linux?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
apk update || {
|
||||
error "apk update failed; check network or APK repositories."
|
||||
exit 1
|
||||
}
|
||||
|
||||
apk add --no-cache tzdata openssh zsh git curl zoxide || {
|
||||
error "Failed to install base packages."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
configure_timezone() {
|
||||
local zone="Asia/Shanghai"
|
||||
|
||||
if [ ! -f "/usr/share/zoneinfo/$zone" ]; then
|
||||
warn "Timezone data for $zone not found; skipping timezone configuration."
|
||||
return 1
|
||||
fi
|
||||
|
||||
info "Setting timezone to $zone (UTC+8)..."
|
||||
ln -sf "/usr/share/zoneinfo/$zone" /etc/localtime || {
|
||||
warn "Failed to update /etc/localtime."
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "$zone" > /etc/timezone || {
|
||||
warn "Failed to write /etc/timezone."
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
set_root_password() {
|
||||
info "Now setting root password (you will be prompted by passwd)."
|
||||
passwd root || {
|
||||
error "Failed to set root password."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
prompt_for_username() {
|
||||
local username
|
||||
|
||||
while :; do
|
||||
printf 'Enter username to create: '
|
||||
read -r username
|
||||
|
||||
case "$username" in
|
||||
""|root)
|
||||
warn "Username cannot be empty or 'root'."
|
||||
;;
|
||||
*[!a-z0-9_-]*)
|
||||
warn "Username should only contain lowercase letters, digits, '-', '_' ."
|
||||
;;
|
||||
*)
|
||||
echo "$username"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
create_user_if_needed() {
|
||||
local username="$1"
|
||||
|
||||
if id "$username" >/dev/null 2>&1; then
|
||||
info "User '$username' already exists; will reuse it."
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Creating user '$username' with default shell /bin/zsh..."
|
||||
adduser -D -s /bin/zsh "$username" || {
|
||||
error "Failed to create user '$username'."
|
||||
exit 1
|
||||
}
|
||||
|
||||
info "Now setting password for user '$username' (you will be prompted by passwd)."
|
||||
passwd "$username" || {
|
||||
error "Failed to set password for user '$username'."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
setup_zsh_for_user() {
|
||||
local username="$1"
|
||||
local home="/home/$username"
|
||||
local zshrc="$home/.zshrc"
|
||||
|
||||
if [ ! -d "$home" ]; then
|
||||
warn "Home directory $home does not exist; skipping zsh configuration."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -f "$zshrc" ]; then
|
||||
warn "$zshrc already exists; leaving it unchanged."
|
||||
else
|
||||
info "Creating $zshrc with zsh plugins and prompt configuration..."
|
||||
cat > "$zshrc" <<'EOF'
|
||||
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
|
||||
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||
fi
|
||||
|
||||
# Zinit plugin manager
|
||||
if [[ ! -f $HOME/.local/share/zinit/zinit.git/zinit.zsh ]]; then
|
||||
print -P "%F{33} %F{220}Installing %F{33}Zinit%F{220} plugin manager…%f"
|
||||
command mkdir -p "$HOME/.local/share/zinit" && command chmod g-rwX "$HOME/.local/share/zinit"
|
||||
command git clone https://github.com/zdharma-continuum/zinit "$HOME/.local/share/zinit/zinit.git" && \
|
||||
print -P "%F{33} %F{34}Installation successful.%f%b" || \
|
||||
print -P "%F{160} The clone has failed.%f%b"
|
||||
fi
|
||||
|
||||
source "$HOME/.local/share/zinit/zinit.git/zinit.zsh"
|
||||
autoload -Uz _zinit
|
||||
(( ${+_comps} )) && _comps[zinit]=_zinit
|
||||
|
||||
# Basic plugins
|
||||
zinit load zsh-users/zsh-syntax-highlighting
|
||||
zinit load zsh-users/zsh-autosuggestions
|
||||
zinit load ael-code/zsh-colored-man-pages
|
||||
|
||||
# Directory jumping
|
||||
eval "$(zoxide init zsh)"
|
||||
|
||||
# Prompt theme
|
||||
zinit ice depth=1
|
||||
zinit light romkatv/powerlevel10k
|
||||
|
||||
# History
|
||||
HISTFILE=~/.zsh_history
|
||||
HISTSIZE=100000
|
||||
HISTFILESIZE=300000
|
||||
SAVEHIST=10000
|
||||
setopt INC_APPEND_HISTORY_TIME
|
||||
setopt EXTENDED_HISTORY
|
||||
|
||||
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
|
||||
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
|
||||
EOF
|
||||
chown "$username:$username" "$zshrc" || warn "Failed to change owner of $zshrc."
|
||||
fi
|
||||
|
||||
if command -v chsh >/dev/null 2>&1; then
|
||||
chsh -s /bin/zsh "$username" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
set_sshd_option() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="/etc/ssh/sshd_config"
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
warn "$file not found; sshd may not be installed correctly."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if grep -Eq "^[#[:space:]]*$key\\b" "$file"; then
|
||||
sed -i "s|^[#[:space:]]*$key\\b.*|$key $value|" "$file"
|
||||
else
|
||||
printf '\n%s %s\n' "$key" "$value" >> "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_authorized_keys() {
|
||||
local username="$1"
|
||||
local url="$2"
|
||||
local home="/home/$username"
|
||||
local ssh_dir="$home/.ssh"
|
||||
local auth_file="$ssh_dir/authorized_keys"
|
||||
local tmp_file="$auth_file.tmp"
|
||||
local i
|
||||
|
||||
if [ -z "$url" ]; then
|
||||
warn "No SSH key URL provided; skipping SSH key setup."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$home" ]; then
|
||||
warn "Home directory $home does not exist; cannot configure authorized_keys."
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$ssh_dir"
|
||||
chmod 700 "$ssh_dir"
|
||||
|
||||
info "Fetching SSH public keys for user '$username' from:"
|
||||
info " $url"
|
||||
|
||||
i=1
|
||||
while [ "$i" -le 10 ]; do
|
||||
info "Download attempt $i/10..."
|
||||
if curl -fsS "$url" -o "$tmp_file"; then
|
||||
if [ -s "$tmp_file" ]; then
|
||||
mv "$tmp_file" "$auth_file"
|
||||
chown "$username:$username" "$auth_file"
|
||||
chmod 600 "$auth_file"
|
||||
info "SSH public keys installed at $auth_file."
|
||||
return 0
|
||||
else
|
||||
warn "Downloaded file is empty; retrying..."
|
||||
fi
|
||||
else
|
||||
warn "Failed to download SSH keys (attempt $i)."
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 3
|
||||
done
|
||||
|
||||
rm -f "$tmp_file"
|
||||
warn "Unable to fetch SSH keys from $url after 10 attempts."
|
||||
warn "SSH password authentication will remain enabled to avoid locking you out."
|
||||
return 1
|
||||
}
|
||||
|
||||
configure_sshd() {
|
||||
local disable_password="$1"
|
||||
|
||||
info "Configuring sshd..."
|
||||
|
||||
if [ ! -f /etc/ssh/sshd_config ]; then
|
||||
warn "/etc/ssh/sshd_config not found, starting sshd once to generate default config..."
|
||||
rc-service sshd start >/dev/null 2>&1 || service sshd start >/dev/null 2>&1 || true
|
||||
rc-service sshd stop >/dev/null 2>&1 || service sshd stop >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
set_sshd_option "PubkeyAuthentication" "yes"
|
||||
|
||||
if [ "$disable_password" -eq 1 ]; then
|
||||
set_sshd_option "PasswordAuthentication" "no"
|
||||
else
|
||||
set_sshd_option "PasswordAuthentication" "yes"
|
||||
fi
|
||||
|
||||
# Keepalive settings to avoid frequent disconnects
|
||||
set_sshd_option "ClientAliveInterval" "60"
|
||||
set_sshd_option "ClientAliveCountMax" "3"
|
||||
set_sshd_option "TCPKeepAlive" "yes"
|
||||
|
||||
# Do not change PermitRootLogin here to avoid surprises; keep distro defaults.
|
||||
}
|
||||
|
||||
enable_sshd_service() {
|
||||
info "Enabling and starting sshd service..."
|
||||
rc-update add sshd default >/dev/null 2>&1 || rc-update add sshd default || true
|
||||
|
||||
if ! rc-service sshd restart >/dev/null 2>&1 && \
|
||||
! service sshd restart >/dev/null 2>&1; then
|
||||
warn "Failed to restart sshd; please check manually with 'rc-service sshd status'."
|
||||
else
|
||||
info "sshd is running."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
check_alpine
|
||||
|
||||
install_base_packages
|
||||
configure_timezone
|
||||
|
||||
set_root_password
|
||||
|
||||
local username
|
||||
username="$(prompt_for_username)"
|
||||
create_user_if_needed "$username"
|
||||
setup_zsh_for_user "$username"
|
||||
|
||||
local setup_keys_choice
|
||||
local ssh_disable_password=0
|
||||
|
||||
printf 'Do you want to configure SSH key-based login for this user now? [y/N]: '
|
||||
read -r setup_keys_choice || setup_keys_choice=""
|
||||
case "$setup_keys_choice" in
|
||||
y|Y)
|
||||
if setup_authorized_keys "$username" "$SSH_AUTH_KEYS_URL"; then
|
||||
ssh_disable_password=1
|
||||
else
|
||||
ssh_disable_password=0
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
info "Skipping SSH key-based login configuration."
|
||||
;;
|
||||
esac
|
||||
|
||||
configure_sshd "$ssh_disable_password"
|
||||
enable_sshd_service
|
||||
|
||||
info "Initialization complete."
|
||||
if [ "$ssh_disable_password" -eq 1 ]; then
|
||||
info "SSH password authentication is disabled; key-based login is enabled."
|
||||
else
|
||||
info "SSH password authentication is still enabled."
|
||||
fi
|
||||
info "New user: $username"
|
||||
info "Timezone: $(cat /etc/timezone 2>/dev/null || echo 'unknown')"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user