Add bootstrap-vps-ssh and full server install README

- Move SSH bootstrap from rubix; wget from public rubix-deploy on PC
- README steps 0–5 and updates match former rubix install flow
This commit is contained in:
Sinisa Madzar
2026-06-01 07:37:36 +02:00
parent 48a920c0db
commit 0d27eb7463
2 changed files with 568 additions and 5 deletions
+78 -5
View File
@@ -1,11 +1,21 @@
# rubix-deploy # rubix-deploy
Bootstrap **step 1** for new Rubix servers (install app from Gitea release). New Rubix server — full install flow.
**Repository:** https://gitea.dialer.work/swissdatabase/rubix-deploy **Repository:** https://gitea.dialer.work/swissdatabase/rubix-deploy
**Clone:** `git clone https://gitea.dialer.work/swissdatabase/rubix-deploy.git` App releases: https://gitea.dialer.work/swissdatabase/rubix/releases
## On a new VPS ## 0) SSH on a new VPS (from your PC)
```bash
wget -O bootstrap-vps-ssh.sh \
https://gitea.dialer.work/swissdatabase/rubix-deploy/raw/branch/main/bootstrap-vps-ssh.sh
chmod +x bootstrap-vps-ssh.sh
./bootstrap-vps-ssh.sh
ssh <your-host-alias>
```
## 1) Download Rubix (on the VPS)
```bash ```bash
wget -O install-rubix.sh \ wget -O install-rubix.sh \
@@ -14,6 +24,69 @@ chmod +x install-rubix.sh
sudo ./install-rubix.sh sudo ./install-rubix.sh
``` ```
The script: `apt update` + `apt upgrade`, installs tools, asks for token, creates `/home/www/callcenter`, downloads **rubix**. The script will:
**Step 2+** (`.env`, Docker, `up.sh`): [swissdatabase/rubix](https://gitea.dialer.work/swissdatabase/rubix) README. 1. Ask for your **Gitea token** (if `GITEA_TOKEN` is not already exported)
2. Create **`/home/www/callcenter`**
3. Download and unpack the latest **rubix** release there
Optional: specific version `sudo ./install-rubix.sh v1.1.10`
Optional: other path `sudo RUBIX_INSTALL_PATH=/opt/rubix ./install-rubix.sh`
## 2) Configure `.env` and start stack
```bash
cd /home/www/callcenter/deploy/docker
cp .env.example .env
nano .env
```
Set at least: `GITEA_REGISTRY_PULL_TOKEN` (same token as step 1), `MYSQL_ROOT_PASSWORD`, DB users/passwords, `COMPOSE_PROFILES`, domains, `RUBIX_STORAGE_ROOT`.
Install Docker if not present:
```bash
apt-get install -y docker.io docker-compose-plugin
systemctl enable --now docker
echo '<token>' | docker login gitea.dialer.work -u <user> --password-stdin
```
Start:
```bash
cd /home/www/callcenter/deploy/docker
sudo ./up.sh
```
Single service: `sudo ./up.sh mysql` / `cms` / `asterisk`
## 3) SQL migrations (fresh database)
```bash
cd /home/www/callcenter/deploy
sudo RUBIX_MYSQL_VIA_DOCKER=1 MYSQL_DOCKER_CONTAINER=rubix-mysql \
./apply_sql_migrations.sh
```
Use your `RUBIX_MYSQL_CONTAINER` from `.env`. Existing server: restore MySQL datadir under `RUBIX_MYSQL_DATA` instead.
## 4) CMS HTTPS (when DNS is ready)
```bash
cd /home/www/callcenter/deploy/docker
docker-cms-certbot
```
## 5) Dialer
```bash
docker-asterisk
/etc/init.d/callcenter2 restart
```
## Updates (app already installed)
```bash
cd /home/www/callcenter/deploy
./rubix_deploy_from_release.sh vX.Y.Z
```
+490
View File
@@ -0,0 +1,490 @@
#!/bin/bash
# First-time VPS bootstrap from your workstation:
# password login → deploy user + SSH key → test key → disable password auth → apt upgrade
# → optional ~/.ssh/config alias on this machine
#
# Run locally (not on the VPS):
# wget -O bootstrap-vps-ssh.sh \
# https://gitea.dialer.work/swissdatabase/rubix-deploy/raw/branch/main/bootstrap-vps-ssh.sh
# chmod +x bootstrap-vps-ssh.sh
# ./bootstrap-vps-ssh.sh
#
# Needs: ssh, optional sshpass (apt install sshpass)
set -euo pipefail
SSH_CONFIG="${HOME}/.ssh/config"
SSH_DIR="${HOME}/.ssh"
MARKER_BEGIN="# --- RUBIX-VPS-BEGIN"
MARKER_END="# --- RUBIX-VPS-END"
prompt() {
local var_name="$1"
local text="$2"
local default="${3:-}"
local value=""
if [[ -n "${default}" ]]; then
read -r -p "${text} [${default}]: " value
value="${value:-${default}}"
else
read -r -p "${text}: " value
fi
printf -v "${var_name}" '%s' "${value}"
}
prompt_secret() {
local var_name="$1"
local text="$2"
local value=""
read -r -s -p "${text}: " value
echo ""
printf -v "${var_name}" '%s' "${value}"
}
prompt_yes_no() {
local var_name="$1"
local text="$2"
local default="${3:-y}"
local hint="Y/n"
[[ "${default}" == "y" ]] && hint="Y/n" || hint="y/N"
local answer=""
read -r -p "${text} [${hint}]: " answer
answer="${answer:-${default}}"
case "${answer}" in
y|Y|yes|YES) printf -v "${var_name}" '%s' "yes" ;;
*) printf -v "${var_name}" '%s' "no" ;;
esac
}
have_sshpass() {
command -v sshpass >/dev/null 2>&1
}
# Install sshpass on the machine running this script (your PC), not on the VPS.
ensure_local_sshpass() {
if have_sshpass; then
return 0
fi
echo "[rubix-vps] sshpass not found — installing locally (one sudo prompt) ..."
local run_as=(bash -c)
if [[ "$(id -u)" -ne 0 ]]; then
if ! command -v sudo >/dev/null 2>&1; then
echo "[rubix-vps] WARN: need sudo to install sshpass (run: sudo apt install sshpass)." >&2
return 1
fi
run_as=(sudo bash -c)
fi
if command -v apt-get >/dev/null 2>&1; then
"${run_as[@]}" 'apt-get update -qq && apt-get install -y sshpass'
elif command -v apt >/dev/null 2>&1; then
"${run_as[@]}" 'apt update -qq && apt install -y sshpass'
elif command -v dnf >/dev/null 2>&1; then
"${run_as[@]}" 'dnf install -y sshpass'
elif command -v yum >/dev/null 2>&1; then
"${run_as[@]}" 'yum install -y sshpass'
elif command -v pacman >/dev/null 2>&1; then
"${run_as[@]}" 'pacman -Sy --noconfirm sshpass'
elif command -v zypper >/dev/null 2>&1; then
"${run_as[@]}" 'zypper install -y sshpass'
else
echo "[rubix-vps] WARN: no supported package manager for auto-install of sshpass." >&2
return 1
fi
if have_sshpass; then
echo "[rubix-vps] sshpass installed."
return 0
fi
echo "[rubix-vps] WARN: sshpass install failed." >&2
return 1
}
# Stop ssh-agent from offering keys (avoids "Too many authentication failures" on password login).
password_ssh_common_opts() {
printf '%s\n' \
StrictHostKeyChecking=accept-new \
IdentitiesOnly=yes \
IdentityAgent=none \
PubkeyAuthentication=no \
PreferredAuthentications=password,keyboard-interactive \
NumberOfPasswordPrompts=3
}
ssh_password() {
local target="$1"
shift
local -a base=(ssh -p "${VPS_PORT}")
local opt
while IFS= read -r opt; do
base+=(-o "${opt}")
done < <(password_ssh_common_opts)
if [[ -n "${ROOT_PASSWORD:-}" ]] && have_sshpass; then
sshpass -p "${ROOT_PASSWORD}" "${base[@]}" "${target}" "$@"
else
"${base[@]}" "${target}" "$@"
fi
}
scp_password() {
local src="$1"
local dest="$2"
local -a base=(scp -P "${VPS_PORT}")
local opt
while IFS= read -r opt; do
base+=(-o "${opt}")
done < <(password_ssh_common_opts)
if [[ -n "${ROOT_PASSWORD:-}" ]] && have_sshpass; then
sshpass -p "${ROOT_PASSWORD}" "${base[@]}" "${src}" "${dest}"
else
"${base[@]}" "${src}" "${dest}"
fi
}
ssh_key() {
local user="$1"
shift
ssh -o BatchMode=yes \
-o StrictHostKeyChecking=accept-new \
-o PasswordAuthentication=no \
-o IdentitiesOnly=yes \
-i "${PRIVATE_KEY_FILE}" \
-p "${VPS_PORT}" \
"${user}@${VPS_HOST}" \
"$@"
}
test_key_login() {
local user="$1"
ssh_key "${user}" "echo ok" >/dev/null 2>&1
}
remote_env() {
printf 'DEPLOY_USER=%q KEEP_ROOT=%q INSTALL_ROOT_KEY=%q' \
"${DEPLOY_USER}" "${KEEP_ROOT}" "${INSTALL_ROOT_KEY}"
}
update_local_ssh_config() {
local alias="$1"
local host="$2"
local port="$3"
local user="$4"
local key_file="$5"
mkdir -p "${SSH_DIR}"
chmod 700 "${SSH_DIR}"
touch "${SSH_CONFIG}"
chmod 600 "${SSH_CONFIG}"
local block
block="$(cat <<EOF
${MARKER_BEGIN} ${alias} ---
Host ${alias}
HostName ${host}
User ${user}
Port ${port}
IdentityFile ${key_file}
IdentitiesOnly yes
${MARKER_END} ${alias} ---
EOF
)"
local tmp
tmp="$(mktemp)"
if [[ -f "${SSH_CONFIG}" ]]; then
awk -v begin="${MARKER_BEGIN} ${alias} ---" -v end="${MARKER_END} ${alias} ---" '
$0 == begin { skip=1; next }
$0 == end { skip=0; next }
!skip { print }
' "${SSH_CONFIG}" > "${tmp}" || true
fi
{
cat "${tmp}"
echo ""
printf '%s\n' "${block}"
} > "${SSH_CONFIG}.new"
mv "${SSH_CONFIG}.new" "${SSH_CONFIG}"
rm -f "${tmp}"
echo "[rubix-vps] wrote Host ${alias}${SSH_CONFIG}"
}
print_key_summary() {
local alias="$1"
echo ""
echo "[rubix-vps] SSH key for Host alias \"${alias}\":"
echo " Private key : ${PRIVATE_KEY_FILE}"
echo " Public key : ${PUBLIC_KEY_FILE}"
echo " ssh config : Host ${alias} → IdentityFile ${PRIVATE_KEY_FILE}"
echo ""
echo " authorized_keys line:"
sed 's/^/ /' "${PUBLIC_KEY_FILE}"
echo ""
}
pick_ssh_key() {
local alias="$1"
local choice=""
local key_path="${SSH_DIR}/${alias}.ed25519"
echo ""
echo "SSH key for ~/.ssh/config Host \"${alias}\":"
echo " Naming: ${key_path} (+ ${key_path}.pub)"
echo ""
echo " 1) Use existing public key"
echo " 2) Generate new ed25519 key pair at ${key_path}"
read -r -p "Choice [2]: " choice
choice="${choice:-2}"
case "${choice}" in
1)
local default_pub="${SSH_DIR}/id_ed25519.pub"
prompt PUBLIC_KEY_FILE "Path to public key (.pub)" "${default_pub}"
if [[ ! -f "${PUBLIC_KEY_FILE}" ]]; then
echo "Missing: ${PUBLIC_KEY_FILE}" >&2
exit 1
fi
local priv="${PUBLIC_KEY_FILE%.pub}"
if [[ -f "${priv}" ]]; then
PRIVATE_KEY_FILE="${priv}"
else
prompt PRIVATE_KEY_FILE "Path to matching private key" "${SSH_DIR}/id_ed25519"
fi
;;
*)
if [[ -f "${key_path}" ]]; then
prompt_yes_no OVERWRITE "Key ${key_path} exists. Overwrite?" "n"
if [[ "${OVERWRITE}" != "yes" ]]; then
echo "Aborted." >&2
exit 1
fi
rm -f "${key_path}" "${key_path}.pub"
fi
local comment="rubix-vps-${alias}-$(date +%Y%m%d)"
echo ""
echo "[rubix-vps] Generating ${key_path} (comment: ${comment}) ..."
ssh-keygen -t ed25519 -f "${key_path}" -C "${comment}" -N ""
PRIVATE_KEY_FILE="${key_path}"
PUBLIC_KEY_FILE="${key_path}.pub"
;;
esac
if [[ ! -f "${PRIVATE_KEY_FILE}" ]]; then
echo "Private key not found: ${PRIVATE_KEY_FILE}" >&2
exit 1
fi
chmod 600 "${PRIVATE_KEY_FILE}" 2>/dev/null || true
print_key_summary "${alias}"
}
remote_install_users_and_keys() {
ssh_password "${INITIAL_USER}@${VPS_HOST}" \
"$(remote_env)" bash -s <<'REMOTE'
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
echo "Remote: must run as root." >&2
exit 1
fi
PUBKEY="$(tr -d '\r\n' < /tmp/rubix-bootstrap.pub)"
if ! id "${DEPLOY_USER}" >/dev/null 2>&1; then
useradd -m -s /bin/bash "${DEPLOY_USER}"
echo "Created user ${DEPLOY_USER}"
else
echo "User ${DEPLOY_USER} already exists"
fi
if getent group sudo >/dev/null 2>&1; then
usermod -aG sudo "${DEPLOY_USER}"
elif getent group wheel >/dev/null 2>&1; then
usermod -aG wheel "${DEPLOY_USER}"
fi
install_key_for_user() {
local u="$1"
local home_dir
home_dir="$(getent passwd "${u}" | cut -d: -f6)"
local ssh_dir="${home_dir}/.ssh"
mkdir -p "${ssh_dir}"
chmod 700 "${ssh_dir}"
touch "${ssh_dir}/authorized_keys"
if ! grep -qxF "${PUBKEY}" "${ssh_dir}/authorized_keys" 2>/dev/null; then
echo "${PUBKEY}" >> "${ssh_dir}/authorized_keys"
fi
chown -R "${u}:${u}" "${ssh_dir}"
chmod 600 "${ssh_dir}/authorized_keys"
}
install_key_for_user "${DEPLOY_USER}"
if [[ "${INSTALL_ROOT_KEY}" == "yes" ]]; then
install_key_for_user root
fi
echo "Keys installed for ${DEPLOY_USER} (and root if requested)."
REMOTE
}
remote_harden_and_upgrade() {
ssh_password "${INITIAL_USER}@${VPS_HOST}" \
"$(remote_env)" bash -s <<'REMOTE'
set -euo pipefail
SSHD="/etc/ssh/sshd_config"
cp -a "${SSHD}" "${SSHD}.rubix-backup-$(date +%Y%m%d%H%M%S)"
set_sshd_option() {
local key="$1"
local value="$2"
if grep -qE "^[#[:space:]]*${key}[[:space:]]" "${SSHD}"; then
sed -i "s/^[#[:space:]]*${key}[[:space:]].*/${key} ${value}/" "${SSHD}"
else
echo "${key} ${value}" >> "${SSHD}"
fi
}
set_sshd_option PasswordAuthentication no
set_sshd_option KbdInteractiveAuthentication no
set_sshd_option ChallengeResponseAuthentication no
set_sshd_option PubkeyAuthentication yes
set_sshd_option UsePAM no
if [[ "${KEEP_ROOT}" == "yes" ]]; then
set_sshd_option PermitRootLogin prohibit-password
else
set_sshd_option PermitRootLogin no
fi
if sshd -t 2>/dev/null || /usr/sbin/sshd -t 2>/dev/null; then
:
else
latest="$(ls -t ${SSHD}.rubix-backup-* 2>/dev/null | head -1)"
[[ -n "${latest}" ]] && cp -a "${latest}" "${SSHD}"
echo "sshd config test failed — restored backup" >&2
exit 1
fi
if systemctl list-units --type=service --all 2>/dev/null | grep -q 'sshd\.service'; then
systemctl reload sshd
elif systemctl list-units --type=service --all 2>/dev/null | grep -q 'ssh\.service'; then
systemctl reload ssh
else
service ssh reload 2>/dev/null || service sshd reload
fi
if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get upgrade -y
apt-get install -y locales
if [[ -f /etc/locale.gen ]]; then
sed -i '/en_US.UTF-8 UTF-8/s/^# *//' /etc/locale.gen
grep -q '^en_US.UTF-8 UTF-8' /etc/locale.gen || echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen
locale-gen en_US.UTF-8 2>/dev/null || locale-gen
fi
update-locale LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 2>/dev/null || true
mkdir -p /var/lib/cloud/instance
touch /var/lib/cloud/instance/locale-check.skip 2>/dev/null || true
elif command -v dnf >/dev/null 2>&1; then
dnf -y upgrade
elif command -v yum >/dev/null 2>&1; then
yum -y update
fi
rm -f /tmp/rubix-bootstrap.pub
echo "SSH hardened and packages upgraded."
REMOTE
}
main() {
echo "=== Rubix VPS first-time SSH bootstrap (run on your PC) ==="
echo ""
ensure_local_sshpass || true
prompt VPS_HOST "VPS hostname or IP"
prompt VPS_PORT "SSH port" "22"
prompt INITIAL_USER "First-login user (usually root)" "root"
prompt DEPLOY_USER "Deploy/sudo user to create" "deployer"
if [[ "${DEPLOY_USER}" == "root" ]]; then
echo "WARN: Using root as deploy user — prefer a sudo user (e.g. deployer) for daily SSH." >&2
fi
prompt SSH_ALIAS "Local ~/.ssh/config Host alias (e.g. rubix-server-5)" "${DEPLOY_USER}-${VPS_HOST}"
prompt_yes_no KEEP_ROOT "Keep root SSH login (key-only, no password)" "yes"
prompt_yes_no INSTALL_ROOT_KEY "Install same public key for root" "${KEEP_ROOT}"
if have_sshpass; then
prompt_secret ROOT_PASSWORD "Initial ${INITIAL_USER} password (entered once via sshpass)"
else
echo ""
echo "[rubix-vps] sshpass unavailable — ssh/scp will prompt for the VPS password each time."
echo ""
prompt_secret ROOT_PASSWORD "Initial ${INITIAL_USER} password (empty = type when ssh asks)"
fi
mkdir -p "${SSH_DIR}"
chmod 700 "${SSH_DIR}"
pick_ssh_key "${SSH_ALIAS}"
if [[ ! -f "${PUBLIC_KEY_FILE}" ]]; then
PUBLIC_KEY_FILE="${PRIVATE_KEY_FILE}.pub"
fi
if [[ ! -f "${PUBLIC_KEY_FILE}" ]]; then
echo "Public key not found: ${PUBLIC_KEY_FILE}" >&2
exit 1
fi
echo ""
echo "[rubix-vps] Testing password login ..."
ssh_password "${INITIAL_USER}@${VPS_HOST}" "echo connected && uname -a"
echo "[rubix-vps] Uploading public key ..."
scp_password "${PUBLIC_KEY_FILE}" "${INITIAL_USER}@${VPS_HOST}:/tmp/rubix-bootstrap.pub"
echo "[rubix-vps] Creating ${DEPLOY_USER} and installing authorized_keys ..."
remote_install_users_and_keys
echo "[rubix-vps] Testing key login (before disabling passwords) ..."
if ! test_key_login "${DEPLOY_USER}"; then
echo "ERROR: Key login as ${DEPLOY_USER} failed. Password auth still enabled — fix keys and retry." >&2
exit 1
fi
echo "[rubix-vps] Key login OK for ${DEPLOY_USER}"
if [[ "${INSTALL_ROOT_KEY}" == "yes" && "${DEPLOY_USER}" != "root" ]]; then
if test_key_login root; then
echo "[rubix-vps] Key login OK for root"
else
echo "[rubix-vps] WARN: root key login failed (deploy user works)" >&2
fi
fi
echo "[rubix-vps] Disabling password auth, reloading sshd, running apt upgrade ..."
remote_harden_and_upgrade
echo "[rubix-vps] Testing key login after hardening ..."
if ! test_key_login "${DEPLOY_USER}"; then
echo "ERROR: Key login failed after hardening. Use provider console; restore /etc/ssh/sshd_config.rubix-backup-*" >&2
exit 1
fi
prompt_yes_no WRITE_CONFIG "Add Host ${SSH_ALIAS} to ${SSH_CONFIG}" "yes"
if [[ "${WRITE_CONFIG}" == "yes" ]]; then
update_local_ssh_config "${SSH_ALIAS}" "${VPS_HOST}" "${VPS_PORT}" "${DEPLOY_USER}" "${PRIVATE_KEY_FILE}"
echo ""
echo "Next login:"
echo " ssh ${SSH_ALIAS}"
else
echo ""
echo "Manual login:"
echo " ssh -i ${PRIVATE_KEY_FILE} -p ${VPS_PORT} ${DEPLOY_USER}@${VPS_HOST}"
fi
echo ""
echo "Done."
}
main "$@"