Make swapfile a default for every install, not just Asterisk droplets
Extracts the swapfile logic out of services/asterisk.sh (previously DigitalOcean-droplet-gated) into lib/common.sh's ensure_swapfile() — provider detection was never really the point, the actual condition that matters is "modest RAM, no swap yet," which applies just as much to a non-DO VPS running several Docker services at once as it did to a single-purpose droplet. - lib/common.sh: new ensure_swapfile(), same fallocate/mkswap/fstab/ swappiness logic as before, threshold raised from 2048MB to 4096MB (a 4GB box running a full service stack is exactly the case that motivated this change — the old threshold would have skipped it). - services/base.sh: calls it unconditionally so every install gets the same check regardless of which other services get chosen. - services/asterisk.sh: swapfile call is no longer gated behind IS_DO — calls the shared helper directly. Kept a standalone-mode stub (same pattern as this file's other stubbed helpers) so `sudo bash asterisk.sh` with no base.sh in the picture still gets it. Idempotent either way: a box that already has swap, or already got it from base.sh earlier in the same run, no-ops immediately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TBtExJcqxnokyZZKmphdug
This commit is contained in:
@@ -268,6 +268,47 @@ ensure_ufw_enabled() {
|
||||
log_success "UFW enabled (SSH on port ${_ssh_port} allowed first, so this won't lock you out)."
|
||||
}
|
||||
|
||||
# Adds a swapfile on any box with modest RAM and no swap already active —
|
||||
# no cloud-provider detection, just the actual condition that matters. Used
|
||||
# to be DigitalOcean-droplet-gated logic living only in services/asterisk.sh;
|
||||
# generalized here so every install gets the same safety net regardless of
|
||||
# which services get chosen or which provider the box is on — a small VPS
|
||||
# running several Docker services at once needs this just as much as a
|
||||
# single-purpose Asterisk droplet did. Idempotent and safe to call from
|
||||
# multiple places in the same run (services/base.sh calls it for every
|
||||
# install; services/asterisk.sh also calls it directly so the standalone
|
||||
# `sudo bash asterisk.sh` path — no base.sh involved — still gets it): a
|
||||
# box that already has swap, or already got it from an earlier call in the
|
||||
# same session, just returns immediately.
|
||||
ensure_swapfile() {
|
||||
local TOTAL_RAM_MB
|
||||
TOTAL_RAM_MB="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
[[ "$TOTAL_RAM_MB" -gt 0 && "$TOTAL_RAM_MB" -le 4096 ]] || return 0
|
||||
swapon --show | grep -q . && return 0
|
||||
[ "$DRY_RUN" = true ] && { echo "[DRY-RUN] Would add a swapfile (${TOTAL_RAM_MB}MB RAM, no swap detected)"; return 0; }
|
||||
|
||||
local FREE_DISK_MB SWAP_MB=2048
|
||||
FREE_DISK_MB="$(df -Pm / | awk 'NR==2 {print $4}')"
|
||||
if [[ "$FREE_DISK_MB" -le $((SWAP_MB + 2048)) ]]; then
|
||||
log_warning "Not enough free disk for a safe swapfile (${FREE_DISK_MB}MB free) — skipping."
|
||||
log_warning "Consider a bigger box, or free up disk before installing."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ADD_SWAP=""
|
||||
prompt_yn "No swap detected on this ${TOTAL_RAM_MB}MB-RAM box — add a ${SWAP_MB}MB swapfile? (recommended) (y/n):" "y" ADD_SWAP
|
||||
[[ "$ADD_SWAP" =~ ^[Yy]$ ]] || return 0
|
||||
|
||||
fallocate -l "${SWAP_MB}M" /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count="$SWAP_MB" status=none
|
||||
chmod 600 /swapfile
|
||||
mkswap /swapfile >/dev/null
|
||||
swapon /swapfile
|
||||
grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
||||
grep -q '^vm.swappiness' /etc/sysctl.conf 2>/dev/null || echo 'vm.swappiness=10' >> /etc/sysctl.conf
|
||||
sysctl -w vm.swappiness=10 >/dev/null 2>&1
|
||||
log_success "Swapfile enabled (${SWAP_MB}MB, swappiness=10, persists across reboots)."
|
||||
}
|
||||
|
||||
# Scopes a UFW allow rule to just the caddy_net bridge subnet instead of
|
||||
# every interface. Needed for any port that only needs to be reachable from
|
||||
# a *locally* Caddy-fronted service (via host.docker.internal) — a plain
|
||||
|
||||
+57
-51
@@ -4,12 +4,13 @@
|
||||
#
|
||||
# One installer for both deployment shapes. It detects a DigitalOcean droplet
|
||||
# (via the link-local metadata service, with a y/n fallback if that's blocked)
|
||||
# and, in droplet mode, swaps in the public-cloud specifics: a swapfile for
|
||||
# low-RAM plans, a public-FQDN-only flow with no LAN/VLAN prompts, a Caddy
|
||||
# site block pinned to that one FQDN, an optional remote Authelia, and a
|
||||
# DigitalOcean Cloud Firewall via doctl. Everything else — vendor files,
|
||||
# compose, messaging dialplan, presence alerts, UFW, log rotation — is
|
||||
# identical either way.
|
||||
# and, in droplet mode, swaps in the public-cloud specifics: a public-FQDN-only
|
||||
# flow with no LAN/VLAN prompts, a Caddy site block pinned to that one FQDN,
|
||||
# an optional remote Authelia, and a DigitalOcean Cloud Firewall via doctl.
|
||||
# The swapfile (lib/common.sh's ensure_swapfile) is NOT droplet-gated — every
|
||||
# box gets that same low-RAM safety net regardless of provider or deployment
|
||||
# shape. Everything else — vendor files, compose, messaging dialplan,
|
||||
# presence alerts, UFW, log rotation — is identical either way.
|
||||
#
|
||||
# This used to be two services (services/asterisk-digital-ocean.sh held a
|
||||
# near-duplicate copy of the whole file). An existing droplet install at
|
||||
@@ -92,6 +93,36 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
esac
|
||||
}
|
||||
|
||||
# Standalone-mode copy of lib/common.sh's ensure_swapfile() — kept in
|
||||
# sync by hand, same as every other helper stubbed in this block.
|
||||
ensure_swapfile() {
|
||||
local TOTAL_RAM_MB
|
||||
TOTAL_RAM_MB="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
[[ "$TOTAL_RAM_MB" -gt 0 && "$TOTAL_RAM_MB" -le 4096 ]] || return 0
|
||||
swapon --show | grep -q . && return 0
|
||||
[ "${DRY_RUN:-false}" = true ] && { echo "[DRY-RUN] Would add a swapfile (${TOTAL_RAM_MB}MB RAM, no swap detected)"; return 0; }
|
||||
|
||||
local FREE_DISK_MB SWAP_MB=2048
|
||||
FREE_DISK_MB="$(df -Pm / | awk 'NR==2 {print $4}')"
|
||||
if [[ "$FREE_DISK_MB" -le $((SWAP_MB + 2048)) ]]; then
|
||||
log_warning "Not enough free disk for a safe swapfile (${FREE_DISK_MB}MB free) — skipping."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ADD_SWAP=""
|
||||
prompt_yn "No swap detected on this ${TOTAL_RAM_MB}MB-RAM box — add a ${SWAP_MB}MB swapfile? (recommended) (y/n):" "y" ADD_SWAP
|
||||
[[ "$ADD_SWAP" =~ ^[Yy]$ ]] || return 0
|
||||
|
||||
fallocate -l "${SWAP_MB}M" /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count="$SWAP_MB" status=none
|
||||
chmod 600 /swapfile
|
||||
mkswap /swapfile >/dev/null
|
||||
swapon /swapfile
|
||||
grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
||||
grep -q '^vm.swappiness' /etc/sysctl.conf 2>/dev/null || echo 'vm.swappiness=10' >> /etc/sysctl.conf
|
||||
sysctl -w vm.swappiness=10 >/dev/null 2>&1
|
||||
log_success "Swapfile enabled (${SWAP_MB}MB, swappiness=10, persists across reboots)."
|
||||
}
|
||||
|
||||
configure_caddy_for_service() {
|
||||
local _name="$1" _upstream="$2" _subdomain="$3" _extra="${4:-}"
|
||||
local _caddy_dir="$DOCKER_DIR/caddy"
|
||||
@@ -272,8 +303,9 @@ _asterisk_resolve_layout() {
|
||||
# metadata service isn't always reachable (a container, a firewalled
|
||||
# 169.254.0.0/16, a non-DO cloud that still wants the same public-IP
|
||||
# treatment), so a miss falls back to asking rather than silently deciding
|
||||
# for the user. Droplet mode is what gates the swapfile, the public-FQDN-only
|
||||
# flow, and the Cloud Firewall step further down.
|
||||
# for the user. Droplet mode is what gates the public-FQDN-only flow and the
|
||||
# Cloud Firewall step further down (the swapfile is NOT droplet-gated — see
|
||||
# ensure_swapfile in lib/common.sh, called unconditionally further down).
|
||||
_asterisk_detect_digitalocean() {
|
||||
local _meta="http://169.254.169.254/metadata/v1"
|
||||
DROPLET_ID="$(curl -fsS --max-time 2 "$_meta/id" 2>/dev/null || true)"
|
||||
@@ -284,8 +316,8 @@ _asterisk_detect_digitalocean() {
|
||||
if [[ -n "$DROPLET_ID" ]]; then
|
||||
[[ -z "$PUBLIC_IP" ]] && PUBLIC_IP="$(curl -fsS --max-time 3 https://ifconfig.me 2>/dev/null || true)"
|
||||
log_success "DigitalOcean droplet detected (id $DROPLET_ID, public IP ${PUBLIC_IP:-unknown})."
|
||||
log_info "Droplet mode adds: swapfile for low-RAM plans, public-FQDN-only setup (no"
|
||||
log_info "LAN/VLAN prompts), a Cloud Firewall via doctl, and a remote-Authelia option."
|
||||
log_info "Droplet mode adds: public-FQDN-only setup (no LAN/VLAN prompts), a Cloud"
|
||||
log_info "Firewall via doctl, and a remote-Authelia option."
|
||||
prompt_yn "Set this up as a public droplet? (n = treat it as a home/LAN box) (y/n):" "y" _answer
|
||||
else
|
||||
log_info "No DigitalOcean metadata service reachable — assuming a home/LAN box."
|
||||
@@ -883,39 +915,6 @@ EOF
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Shared: swapfile for low-RAM public cloud boxes ────────────────────────
|
||||
# DigitalOcean doesn't provision swap by default. Docker + Asterisk + coturn
|
||||
# fit in 512MB-1GB at idle with little headroom; a swapfile absorbs spikes
|
||||
# (apt/image pulls, log bursts, a few concurrent calls) instead of the
|
||||
# kernel OOM-killing a container or the box going unresponsive over SSH.
|
||||
_asterisk_offer_swapfile() {
|
||||
local TOTAL_RAM_MB
|
||||
TOTAL_RAM_MB="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
[[ "$TOTAL_RAM_MB" -gt 0 && "$TOTAL_RAM_MB" -le 2048 ]] || return 0
|
||||
swapon --show | grep -q . && return 0
|
||||
|
||||
local FREE_DISK_MB SWAP_MB=2048
|
||||
FREE_DISK_MB="$(df -Pm / | awk 'NR==2 {print $4}')"
|
||||
if [[ "$FREE_DISK_MB" -le $((SWAP_MB + 2048)) ]]; then
|
||||
log_warning "Not enough free disk for a safe swapfile (${FREE_DISK_MB}MB free) — skipping."
|
||||
log_warning "Consider a bigger box, or free up disk before installing."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ADD_SWAP=""
|
||||
prompt_yn "No swap detected on this ${TOTAL_RAM_MB}MB-RAM box — add a ${SWAP_MB}MB swapfile? (y/n):" "y" ADD_SWAP
|
||||
[[ "$ADD_SWAP" =~ ^[Yy]$ ]] || return 0
|
||||
|
||||
fallocate -l "${SWAP_MB}M" /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count="$SWAP_MB" status=none
|
||||
chmod 600 /swapfile
|
||||
mkswap /swapfile >/dev/null
|
||||
swapon /swapfile
|
||||
grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
||||
grep -q '^vm.swappiness' /etc/sysctl.conf 2>/dev/null || echo 'vm.swappiness=10' >> /etc/sysctl.conf
|
||||
sysctl -w vm.swappiness=10 >/dev/null 2>&1
|
||||
log_success "Swapfile enabled (${SWAP_MB}MB, swappiness=10, persists across reboots)."
|
||||
}
|
||||
|
||||
# ── Droplet-mode Caddy: web admin on the SAME FQDN used for SIP ────────────
|
||||
# Deliberately NOT using configure_caddy_for_service in this mode. Caddy only
|
||||
# holds a cert for domains it's actively serving, and Asterisk never does ACME
|
||||
@@ -1316,8 +1315,9 @@ MD
|
||||
## DigitalOcean droplet notes
|
||||
|
||||
This install is in public-cloud mode: the installer read the droplet's public
|
||||
IP from the metadata service, set up a swapfile, offered a Cloud Firewall, and
|
||||
reverse-proxied the web admin on the same FQDN used for SIP.
|
||||
IP from the metadata service, offered a Cloud Firewall, and reverse-proxied
|
||||
the web admin on the same FQDN used for SIP. (The swapfile below isn't
|
||||
droplet-specific — every install on this box gets the same check.)
|
||||
|
||||
### Droplet sizing
|
||||
|
||||
@@ -1336,10 +1336,13 @@ phones actually are is fine; SIP/RTP care about latency more than raw
|
||||
bandwidth.
|
||||
|
||||
**Swap:** DigitalOcean doesn't provision swap by default, and Docker +
|
||||
Asterisk + coturn leave little headroom at 512MB–1GB RAM. The installer
|
||||
detects RAM ≤2GB with no existing swap and offers to add a 2GB swapfile
|
||||
(persisted in \`/etc/fstab\`) — it's what makes the \$4/mo plan viable instead
|
||||
of risking an OOM kill under load.
|
||||
Asterisk + coturn leave little headroom at 512MB–1GB RAM. This isn't
|
||||
Asterisk- or droplet-specific — \`base.sh\` (and this installer, for the
|
||||
standalone-run case) checks RAM ≤4GB with no existing swap and offers a 2GB
|
||||
swapfile (persisted in \`/etc/fstab\`) on every install, since a box running
|
||||
several Docker services at once needs the same insurance a single-purpose
|
||||
droplet does. It's what makes the \$4/mo plan viable instead of risking an
|
||||
OOM kill under load — and it's why nothing above 4GB gets asked at all.
|
||||
|
||||
**OS image:** Ubuntu 24.04 LTS (supported through April 2029) is the safe,
|
||||
battle-tested choice for Docker + coturn. Ubuntu 26.04 LTS is also available
|
||||
@@ -1417,9 +1420,9 @@ install_asterisk() {
|
||||
echo "[DRY-RUN] Would copy/download vendor files from easy-asterisk, patching Asterisk to"
|
||||
echo "[DRY-RUN] log security events to logs/full (what CrowdSec + the Security Dashboard read)"
|
||||
echo "[DRY-RUN] Would rotate logs/full at 100MB via /etc/logrotate.d/asterisk"
|
||||
echo "[DRY-RUN] Would add a swapfile if RAM <= 4096MB and none exists (any deployment shape)"
|
||||
echo "[DRY-RUN] Would detect a DigitalOcean droplet via its metadata service (asking either way)"
|
||||
echo "[DRY-RUN] and, in droplet mode, additionally:"
|
||||
echo "[DRY-RUN] - add a swapfile if RAM <= 2048MB and none exists"
|
||||
echo "[DRY-RUN] - skip the LAN/VLAN prompts and set up one public FQDN for SIP + web admin"
|
||||
echo "[DRY-RUN] - reverse-proxy the web admin on that SAME FQDN (needed for SIP cert sync)"
|
||||
echo "[DRY-RUN] - offer local OR remote Authelia to protect the web admin"
|
||||
@@ -1525,7 +1528,10 @@ install_asterisk() {
|
||||
local IS_DO=false DROPLET_ID="" PUBLIC_IP=""
|
||||
_asterisk_detect_digitalocean
|
||||
|
||||
[[ "$IS_DO" == true ]] && _asterisk_offer_swapfile
|
||||
# Not droplet-gated — every box gets the same low-RAM safety net now
|
||||
# (see lib/common.sh's ensure_swapfile). Idempotent: a no-op if base.sh
|
||||
# already added swap earlier in this run, or if swap already exists.
|
||||
ensure_swapfile
|
||||
|
||||
mkdir -p "$EA_DIR"
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
|
||||
@@ -13,6 +13,7 @@ install_base() {
|
||||
echo "[DRY-RUN] Would install Docker CE + Compose plugin"
|
||||
echo "[DRY-RUN] Would detect an NVIDIA GPU and offer to install the driver"
|
||||
echo " + NVIDIA Container Toolkit (for GPU-accelerated Docker services)"
|
||||
echo "[DRY-RUN] Would add a swapfile if RAM <= 4096MB and none exists"
|
||||
echo "[DRY-RUN] Would install/configure openssh-server"
|
||||
echo "[DRY-RUN] Would offer SSH key import from GitHub/Launchpad"
|
||||
echo "[DRY-RUN] Would offer to disable SSH password auth"
|
||||
@@ -38,6 +39,9 @@ install_base() {
|
||||
# ── Docker ───────────────────────────────────────────────────────────────
|
||||
require_docker || log_warning "Docker install failed — will retry after base setup"
|
||||
|
||||
# ── Swapfile — default for every install, not just Asterisk droplets ────
|
||||
ensure_swapfile
|
||||
|
||||
# ── NVIDIA GPU (driver + container toolkit) ─────────────────────────────
|
||||
_base_setup_nvidia_gpu
|
||||
|
||||
|
||||
Reference in New Issue
Block a user