VPS one-click scripts

swap Create swap

To add swap on a VPS, run the command below as root. It creates a 2G file at /swapfile by default; pass a size with bash -s -- 4G.

Command
$ curl -fsSL xyz.sh/swap | bash
Last updated:
Declared OS support:Debian, Ubuntu, CentOS, Rocky, AlmaLinux
Root required
SHA256:7e28c6b0cc99067ce42395aeee9e7b42d0b898f17600b25d4becaab981c8da97 · Matches the script curl receives. You can also fetch /swap.sh.sha256
Read the tutorial:Add Swap on a VPS (Chosen Size, Persisted in fstab)

Every script passes bash -n and ShellCheck and was tested with curl | bash as root in the official Docker images of Debian 12/11, Ubuntu 24.04/22.04/20.04/18.04, Rocky 9, AlmaLinux 9 and CentOS 7 (see “Container test results” on each page). Containers lack systemd, kernel modules and real public routes, so those parts still need a real VPS.

What it does

Creates a 2G swap file at /swapfile by default (size and path are configurable), sets mode 600, runs mkswap and swapon, and persists the file in /etc/fstab. Creation is refused if there is not enough free disk space (a 512M safety margin is kept). Existing swap files are not replaced without confirmation. --remove disables and deletes the file.

Supported OS

  • Debian
  • Ubuntu
  • CentOS
  • Rocky
  • AlmaLinux

Root required

Flags and examples

$ curl -fsSL xyz.sh/swap | bash
$ curl -fsSL xyz.sh/swap | bash -s -- 4G
$ curl -fsSL xyz.sh/swap | bash -s -- --path /swapfile --size 1G
$ curl -fsSL xyz.sh/swap | bash -s -- --remove

Container test results

Results of curl | bash as root without a terminal in each distribution's official Docker image. partial means the container could not verify everything (for example no systemd, no kernel module, or the test protocol blocked by the test environment's proxy) and a real VPS is needed to confirm. (2026-11-11)

OSResult
debian:12partial
debian:11partial
ubuntu:24.04partial
ubuntu:22.04partial
ubuntu:20.04partial
ubuntu:18.04partial
rockylinux:9partial
almalinux:9partial
centos:7partial

FAQ

Is this script safe?
The xyz.sh wrapper adds no telemetry and does not perform hidden actions. You can read the full source on the page or with curl -fsSL xyz.sh/<name>.sh before running it. Piping a remote script to bash is still a risk — review the source first. It will not replace an existing swap file without confirmation, and it refuses to create one if free space (plus a 512M margin) is insufficient.
Which operating systems are supported?
Declared support: Debian, Ubuntu, CentOS, Rocky Linux, and AlmaLinux. The script detects the distribution and asks before continuing on an unrecognized OS. Syntax was checked with bash -n and ShellCheck; installer scripts have not been end-to-end verified on every listed distribution.
Do I need root?
Yes. Run it as root or via sudo bash. The script exits with a message if it is not root.
How do I uninstall it?
curl -fsSL xyz.sh/swap | bash -s -- --remove. If you used a custom path, add --path /your/path.
What is the default size, and can I change it?
The default is 2G. Pass 4G, --size 1G, or --path /swapfile --size 2048M.

Full source

This is exactly what curl receives (common library + script body). Read it before running.

xyz.sh/swap.sh
#!/usr/bin/env bash
# Served by xyz.sh/swap
# Human-readable page: https://xyz.sh/swap
# This file is the common library followed by the script body.
# No telemetry is added by xyz.sh.
# =============================================================================
# xyz.sh common library
# Shared helpers for every script served by xyz.sh.
#
# Promises:
#   - No telemetry, analytics, or background "phone-home" calls
#   - No hidden installs or config changes beyond what the script announces
#   - Official upstream URLs are printed before any remote code is executed
#   - Destructive or system-changing steps ask for confirmation (or YES=1 / -y)
# =============================================================================

if [[ -n "${XYZ_COMMON_LOADED:-}" ]]; then
  # When sourced a second time, return. When executed as a file, return fails.
  # shellcheck disable=SC2317
  return 0 2>/dev/null || true
fi
# shellcheck disable=SC2034
XYZ_COMMON_LOADED=1

set -euo pipefail

XYZ_VERSION="1.3.0"
XYZ_BASE="${XYZ_BASE:-https://xyz.sh}"
XYZ_NAME="${XYZ_NAME:-unknown}"

# --- language ---------------------------------------------------------------
# Override with XYZ_LANG=zh or XYZ_LANG=en.
if [[ -z "${XYZ_LANG:-}" ]]; then
  case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in
    zh*|ZH*|cmn*|CMN*) XYZ_LANG="zh" ;;
    *) XYZ_LANG="en" ;;
  esac
fi

xyz_t() {
  # xyz_t "English text" "中文文本"
  if [[ "${XYZ_LANG}" == "zh" ]]; then
    printf '%s' "${2:-$1}"
  else
    printf '%s' "$1"
  fi
}

# --- colors (TTY only) ------------------------------------------------------
# XYZ_COLOR=always|never|auto (default auto: TTY and no NO_COLOR); --no-color.
_xyz_color=0
case "${XYZ_COLOR:-auto}" in
  always) _xyz_color=1 ;;
  never) _xyz_color=0 ;;
  *) [[ -t 1 && -z "${NO_COLOR:-}" ]] && _xyz_color=1 ;;
esac
[[ "${XYZ_NO_COLOR:-0}" == "1" ]] && _xyz_color=0
if [[ "${_xyz_color}" == "1" ]]; then
  XYZ_C_RESET=$'\033[0m'
  XYZ_C_BOLD=$'\033[1m'
  XYZ_C_DIM=$'\033[2m'
  XYZ_C_RED=$'\033[31m'
  XYZ_C_GREEN=$'\033[32m'
  XYZ_C_YELLOW=$'\033[33m'
  XYZ_C_CYAN=$'\033[36m'
else
  XYZ_C_RESET=""
  XYZ_C_BOLD=""
  XYZ_C_DIM=""
  XYZ_C_RED=""
  XYZ_C_GREEN=""
  XYZ_C_YELLOW=""
  XYZ_C_CYAN=""
fi

xyz_info()  { printf '%s[INFO]%s %s\n'  "${XYZ_C_GREEN}"  "${XYZ_C_RESET}" "$*"; }
xyz_warn()  { printf '%s[WARN]%s %s\n'  "${XYZ_C_YELLOW}" "${XYZ_C_RESET}" "$*" >&2; }
xyz_error() { printf '%s[ERROR]%s %s\n' "${XYZ_C_RED}"    "${XYZ_C_RESET}" "$*" >&2; }
xyz_step()  { printf '%s==>%s %s\n'     "${XYZ_C_CYAN}"   "${XYZ_C_RESET}" "$*"; }

# --- args / init ------------------------------------------------------------
XYZ_ARGS=()
XYZ_YES=0
XYZ_DRY_RUN=0

xyz_parse_global_args() {
  local filtered=()
  local arg
  for arg in "$@"; do
    case "${arg}" in
      -y|--yes)
        XYZ_YES=1
        ;;
      --lang=zh|zh)
        XYZ_LANG="zh"
        ;;
      --lang=en|en)
        XYZ_LANG="en"
        ;;
      --dry-run)
        XYZ_DRY_RUN=1
        ;;
      --no-color)
        XYZ_NO_COLOR=1
        XYZ_C_RESET=""; XYZ_C_BOLD=""; XYZ_C_DIM=""; XYZ_C_RED=""
        XYZ_C_GREEN=""; XYZ_C_YELLOW=""; XYZ_C_CYAN=""
        ;;
      --help|-h)
        XYZ_HELP=1
        filtered+=("${arg}")
        ;;
      *)
        filtered+=("${arg}")
        ;;
    esac
  done
  XYZ_ARGS=("${filtered[@]+"${filtered[@]}"}")
  if [[ "${YES:-}" =~ ^(1|yes|true|YES)$ || "${XYZ_ASSUME_YES:-}" == "1" ]]; then
    XYZ_YES=1
  fi
  # Exported so individual scripts can read flags set here.
  export XYZ_ARGS XYZ_YES XYZ_LANG XYZ_HELP="${XYZ_HELP:-}" XYZ_DRY_RUN="${XYZ_DRY_RUN:-0}"
  export XYZ_NO_COLOR="${XYZ_NO_COLOR:-0}"
  if declare -F xyz_ui_init >/dev/null; then xyz_ui_init; fi
}

xyz_banner() {
  local title="${XYZ_TITLE:-$XYZ_NAME}"
  printf '\n%s%sxyz.sh/%s%s  v%s\n' "${XYZ_C_BOLD}" "${XYZ_C_CYAN}" "${XYZ_NAME}" "${XYZ_C_RESET}" "${XYZ_VERSION}"
  printf '%s\n' "${XYZ_C_DIM}----------------------------------------${XYZ_C_RESET}"
  printf '  %s: %s/%s\n' "$(xyz_t "Page" "页面")" "${XYZ_BASE}" "${XYZ_NAME}"
  printf '  %s: %s/%s.sh\n' "$(xyz_t "Raw" "源码")" "${XYZ_BASE}" "${XYZ_NAME}"
  if [[ -n "${XYZ_UPSTREAM:-}" ]]; then
    printf '  %s: %s\n' "$(xyz_t "Upstream" "上游来源")" "${XYZ_UPSTREAM}"
  fi
  printf '  %s\n' "$(xyz_t "No telemetry, no hidden actions." "无遥测、无隐藏操作。")"
  printf '%s\n\n' "${XYZ_C_DIM}----------------------------------------${XYZ_C_RESET}"
  if [[ -n "${title}" && "${title}" != "${XYZ_NAME}" ]]; then
    xyz_step "${title}"
  fi
}

xyz_init() {
  xyz_parse_global_args "$@"
  xyz_detect_os
  xyz_banner
}

# --- root / OS --------------------------------------------------------------
xyz_is_root() {
  [[ "${EUID:-$(id -u)}" -eq 0 ]]
}

xyz_require_root() {
  if ! xyz_is_root; then
    xyz_error "$(xyz_t \
      "This script must be run as root. Try: sudo bash" \
      "请使用 root 运行。可尝试:sudo bash")"
    exit 1
  fi
}

xyz_prefer_root() {
  if ! xyz_is_root; then
    xyz_warn "$(xyz_t \
      "Not running as root; some information or tests may be limited." \
      "当前不是 root,部分信息或测试可能不完整。")"
  fi
}

XYZ_OS_ID=""
XYZ_OS_LIKE=""
XYZ_OS_VERSION=""
XYZ_OS_FAMILY=""   # debian | rhel | unknown
XYZ_OS_PRETTY=""

xyz_detect_os() {
  XYZ_OS_ID=""
  XYZ_OS_LIKE=""
  XYZ_OS_VERSION=""
  XYZ_OS_FAMILY="unknown"
  XYZ_OS_PRETTY="unknown"
  if [[ -r /etc/os-release ]]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    XYZ_OS_ID="${ID:-}"
    XYZ_OS_LIKE="${ID_LIKE:-}"
    XYZ_OS_VERSION="${VERSION_ID:-}"
    XYZ_OS_PRETTY="${PRETTY_NAME:-$XYZ_OS_ID}"
    export XYZ_OS_ID XYZ_OS_LIKE XYZ_OS_VERSION XYZ_OS_FAMILY XYZ_OS_PRETTY
  fi
  case "${XYZ_OS_ID}" in
    debian|ubuntu)
      XYZ_OS_FAMILY="debian"
      ;;
    centos|rhel|rocky|almalinux|alma|fedora)
      XYZ_OS_FAMILY="rhel"
      ;;
    *)
      case " ${XYZ_OS_ID} ${XYZ_OS_LIKE} " in
        *debian*|*ubuntu*) XYZ_OS_FAMILY="debian" ;;
        *rhel*|*centos*|*fedora*|*rocky*|*alma*) XYZ_OS_FAMILY="rhel" ;;
        *) : ;;
      esac
      ;;
  esac
}

xyz_os_supported() {
  case "${XYZ_OS_ID}" in
    debian|ubuntu|centos|rocky|almalinux) return 0 ;;
    *) : ;;
  esac
  case " ${XYZ_OS_LIKE} " in
    *debian*|*ubuntu*|*rhel*|*centos*|*rocky*|*alma*) return 0 ;;
    *) : ;;
  esac
  return 1
}

xyz_require_supported_os() {
  xyz_detect_os
  if ! xyz_os_supported; then
    xyz_warn "$(xyz_t \
      "Unrecognized distribution: ${XYZ_OS_ID:-unknown} (${XYZ_OS_PRETTY}). Officially supported: Debian, Ubuntu, CentOS, Rocky, AlmaLinux." \
      "未能识别的发行版:${XYZ_OS_ID:-unknown}(${XYZ_OS_PRETTY})。官方支持:Debian、Ubuntu、CentOS、Rocky、AlmaLinux。")"
    if ! xyz_confirm "$(xyz_t "Continue anyway?" "仍然继续?")"; then
      exit 1
    fi
  else
    xyz_info "$(xyz_t "Detected OS:" "检测到系统:") ${XYZ_OS_PRETTY} (${XYZ_OS_FAMILY})"
  fi
}

# --- packages / commands ----------------------------------------------------
xyz_need_cmd() {
  command -v "$1" >/dev/null 2>&1
}

xyz_pkg_install() {
  xyz_detect_os
  if (( $# == 0 )); then
    return 0
  fi
  case "${XYZ_OS_FAMILY}" in
    debian)
      export DEBIAN_FRONTEND=noninteractive
      apt-get update -y || xyz_warn "$(xyz_t "apt-get update reported errors (a repository may be unreachable); trying to install anyway." "apt-get update 报错(可能有软件源无法访问),仍尝试安装。")"
      apt-get install -y --no-install-recommends "$@"
      ;;
    rhel)
      if xyz_need_cmd dnf; then
        dnf install -y "$@"
      elif xyz_need_cmd yum; then
        yum install -y "$@"
      else
        xyz_error "$(xyz_t "Neither dnf nor yum is available." "找不到 dnf 或 yum。")"
        exit 1
      fi
      ;;
    *)
      xyz_error "$(xyz_t "Cannot install packages on this OS automatically." "无法在此系统上自动安装软件包。")"
      exit 1
      ;;
  esac
}

xyz_ensure_cmds() {
  local pkg
  local missing=()
  for pkg in "$@"; do
    if ! xyz_need_cmd "${pkg}"; then
      missing+=("${pkg}")
    fi
  done
  if ((${#missing[@]} > 0)); then
    xyz_info "$(xyz_t "Installing missing commands:" "正在安装缺失命令:") ${missing[*]}"
    xyz_pkg_install "${missing[@]}"
  fi
}

# --- confirmation (safe with curl | bash) -----------------------------------
# A readable /dev/tty node is not enough: in containers or curl|bash without a
# controlling terminal, open() fails with ENXIO ("No such device or address").
# [[ -r /dev/tty ]] is true in that case and must not be used as the check.
xyz_open_tty() {
  # Opens /dev/tty into XYZ_TTY_FD if it is a real terminal. Caller must close.
  local fd
  XYZ_TTY_FD=""
  # Brace group so a failed open cannot leak "No such device or address".
  if { exec {fd}<>/dev/tty; } 2>/dev/null && [[ -t "${fd}" ]]; then
    XYZ_TTY_FD="${fd}"
    return 0
  fi
  if [[ -n "${fd:-}" ]]; then
    exec {fd}>&- 2>/dev/null || true
  fi
  return 1
}

xyz_close_tty() {
  if [[ -n "${XYZ_TTY_FD:-}" ]]; then
    exec {XYZ_TTY_FD}>&- 2>/dev/null || true
    XYZ_TTY_FD=""
  fi
}

xyz_confirm() {
  local prompt="$1"
  if [[ "${XYZ_YES}" == "1" ]]; then
    xyz_info "$(xyz_t "Auto-confirmed (-y / YES=1):" "已自动确认(-y / YES=1):") ${prompt}"
    return 0
  fi
  local reply=""
  if xyz_open_tty; then
    printf '%s [y/N] ' "${prompt}" >&"${XYZ_TTY_FD}"
    # shellcheck disable=SC2162
    read -r reply <&"${XYZ_TTY_FD}" || true
    xyz_close_tty
  else
    xyz_warn "$(xyz_t \
      "No TTY available for confirmation. Re-run with -y or YES=1 if you accept." \
      "没有可用的终端来确认。若你已了解并同意,请加 -y 或 YES=1 重试。")"
    return 1
  fi
  case "${reply}" in
    y|Y|yes|YES|Yes) return 0 ;;
    *) return 1 ;;
  esac
}

# --- download helpers -------------------------------------------------------
# Create the temp dir in the main shell. xyz_tmpdir is usually called inside
# $(...), a subshell, so creating it (and its EXIT trap) there would delete the
# directory as soon as the subshell returned.
xyz_init_tmpdir() {
  if [[ -z "${XYZ_TMPDIR:-}" || ! -d "${XYZ_TMPDIR}" ]]; then
    XYZ_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/xyzsh.XXXXXX")"
    # shellcheck disable=SC2064
    trap "rm -rf '${XYZ_TMPDIR}'" EXIT
  fi
}

xyz_tmpdir() {
  if [[ -z "${XYZ_TMPDIR:-}" || ! -d "${XYZ_TMPDIR}" ]]; then
    xyz_error "internal: temp dir not initialised"
    exit 1
  fi
  printf '%s' "${XYZ_TMPDIR}"
}

xyz_fetch() {
  # xyz_fetch URL DEST
  local url="$1"
  local dest="$2"
  xyz_info "$(xyz_t "Downloading" "正在下载"): ${url}"
  xyz_info "$(xyz_t "Saving to" "保存到"): ${dest}"
  if xyz_need_cmd curl; then
    curl -fsSL --connect-timeout 20 --max-time 180 --retry 2 --retry-delay 1 \
      -o "${dest}" "${url}"
  elif xyz_need_cmd wget; then
    wget -q --timeout=60 -O "${dest}" "${url}"
  else
    xyz_error "$(xyz_t "Need curl or wget to download files." "需要 curl 或 wget 才能下载文件。")"
    exit 1
  fi
  if [[ ! -s "${dest}" ]]; then
    xyz_error "$(xyz_t "Download failed or file is empty:" "下载失败或文件为空:") ${url}"
    exit 1
  fi
  if xyz_need_cmd sha256sum; then
    xyz_info "SHA256: $(sha256sum "${dest}" | awk '{print $1}')"
  elif xyz_need_cmd shasum; then
    xyz_info "SHA256: $(shasum -a 256 "${dest}" | awk '{print $1}')"
  fi
}

xyz_run_upstream() {
  # xyz_run_upstream URL [args...]
  # Downloads an official installer, shows the URL and a short preview, then
  # runs it only after confirmation (unless -y / YES=1).
  local url="$1"
  shift
  local dest
  xyz_init_tmpdir
  dest="$(xyz_tmpdir)/upstream.sh"
  xyz_fetch "${url}" "${dest}"
  chmod +x "${dest}"
  xyz_info "$(xyz_t "Preview (first 20 lines):" "预览(前 20 行):")"
  sed -n '1,20p' "${dest}" || true
  printf '%s\n' "${XYZ_C_DIM}...${XYZ_C_RESET}"
  # Default --dry-run: verify the download and preview, do not execute.
  # Scripts that forward --dry-run to upstream (e.g. docker) set
  # XYZ_FORWARD_DRY_RUN=1 so the official script still runs with that flag.
  if [[ "${XYZ_DRY_RUN:-0}" == "1" && "${XYZ_FORWARD_DRY_RUN:-0}" != "1" ]]; then
    xyz_info "$(xyz_t \
      "Dry-run: official script downloaded and previewed; not executing it." \
      "演练模式:已下载并预览官方脚本,不会执行。")"
    return 0
  fi
  if ! xyz_confirm "$(xyz_t \
    "Run this official upstream script?" \
    "运行这份官方上游脚本?")"; then
    xyz_error "$(xyz_t "Aborted by user." "已取消。")"
    exit 1
  fi
  xyz_step "$(xyz_t "Executing upstream script" "正在执行上游脚本")"
  # Never let the upstream script inherit our stdin: under `curl | bash` that
  # is the rest of this very script. Give it the terminal if there is one,
  # otherwise /dev/null. Interactive installers set XYZ_UPSTREAM_NEEDS_TTY=1.
  if xyz_open_tty; then
    local tty_fd="${XYZ_TTY_FD}"
    XYZ_TTY_FD=""
    bash "${dest}" "$@" <&"${tty_fd}"
    local rc=$?
    exec {tty_fd}>&- 2>/dev/null || true
    return "${rc}"
  fi
  if [[ "${XYZ_UPSTREAM_NEEDS_TTY:-0}" == "1" ]]; then
    xyz_error "$(xyz_t \
      "This official installer is interactive and needs a terminal. Log in with SSH and run: bash <(curl -fsSL ${XYZ_BASE}/${XYZ_NAME})" \
      "这个官方安装脚本需要交互,必须在终端里运行。请用 SSH 登录后执行:bash <(curl -fsSL ${XYZ_BASE}/${XYZ_NAME})")"
    exit 1
  fi
  bash "${dest}" "$@" </dev/null
}

# --- small shared helpers ----------------------------------------------------
xyz_strip_ansi() {
  sed -e 's/\x1b\[[0-9;?]*[A-Za-z]//g' -e 's/\r//g'
}

# Effective SSH port: sshd -T (what sshd really uses) > config files > env > 22.
xyz_effective_ssh_port() {
  local port=""
  if xyz_need_cmd sshd; then
    port="$(sshd -T 2>/dev/null | awk '$1=="port" {print $2; exit}' || true)"
  fi
  if [[ -z "${port}" && -d /etc/ssh/sshd_config.d ]]; then
    port="$(cat /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk 'tolower($1)=="port" {print $2; exit}' || true)"
  fi
  if [[ -z "${port}" && -f /etc/ssh/sshd_config ]]; then
    port="$(awk 'tolower($1)=="port" {print $2; exit}' /etc/ssh/sshd_config || true)"
  fi
  if [[ -z "${port}" && -n "${SSH_CONNECTION:-}" ]]; then
    port="$(printf '%s' "${SSH_CONNECTION}" | awk '{print $4}')"
  fi
  printf '%s' "${port:-22}"
}

xyz_has_systemd() {
  [[ -d /run/systemd/system ]] && xyz_need_cmd systemctl
}

# Back up a file once per run as FILE.xyz-bak-YYYYmmddHHMMSS and print the path.
xyz_backup_file() {
  local f="$1"
  local bak
  bak="${f}.xyz-bak-$(date +%Y%m%d%H%M%S)"
  if [[ -e "${f}" ]]; then
    cp -a "${f}" "${bak}"
    printf '%s' "${bak}"
  fi
}

# --- misc -------------------------------------------------------------------
xyz_usage_exit() {
  if [[ "${1:-}" != "" ]]; then
    printf '%s\n' "$1"
  fi
  exit 0
}

XYZ_NAME="swap"

# xyz.sh/swap — create and enable a swap file of a chosen size.
#
# Usage:
#   curl -fsSL xyz.sh/swap | bash
#   curl -fsSL xyz.sh/swap | bash -s -- 2G
#   curl -fsSL xyz.sh/swap | bash -s -- --size 4G
#   curl -fsSL xyz.sh/swap | bash -s -- --size 2G -y
#   curl -fsSL xyz.sh/swap | bash -s -- --path /swapfile --size 1G
#   curl -fsSL xyz.sh/swap | bash -s -- --remove
#
# Default size is 2G if the disk has enough free space; otherwise the script
# asks (or, with -y, refuses rather than filling the disk).
# Requires root. No telemetry. Does not touch existing swap without confirmation.

set -euo pipefail

if [[ -z "${XYZ_COMMON_LOADED:-}" ]]; then
  _xyz_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  if [[ -f "${_xyz_dir}/lib/common.sh" ]]; then
    # shellcheck source=lib/common.sh
    source "${_xyz_dir}/lib/common.sh"
  else
    echo "xyz.sh: missing lib/common.sh" >&2
    exit 1
  fi
fi

XYZ_NAME="swap"
XYZ_TITLE="$(xyz_t "Create a swap file" "创建 swap 文件")"
xyz_init "$@"
xyz_require_root
xyz_require_supported_os

SWAP_PATH="/swapfile"
SWAP_SIZE="2G"
DO_REMOVE=0

if [[ "${XYZ_HELP:-}" == "1" ]]; then
  cat <<EOF
$(xyz_t "Usage:" "用法:")
  curl -fsSL xyz.sh/swap | bash
  curl -fsSL xyz.sh/swap | bash -s -- 2G
  curl -fsSL xyz.sh/swap | bash -s -- --size 4G -y
  curl -fsSL xyz.sh/swap | bash -s -- --path /swapfile --size 1G
  curl -fsSL xyz.sh/swap | bash -s -- --remove
  curl -fsSL xyz.sh/swap | bash -s -- --size 2G --dry-run
EOF
  exit 0
fi

i=0
while [[ $i -lt ${#XYZ_ARGS[@]} ]]; do
  arg="${XYZ_ARGS[$i]}"
  case "${arg}" in
    --remove)
      DO_REMOVE=1
      ;;
    --size)
      i=$((i + 1))
      SWAP_SIZE="${XYZ_ARGS[$i]:-}"
      if [[ -z "${SWAP_SIZE}" ]]; then
        xyz_error "$(xyz_t "--size requires a value such as 2G." "--size 需要形如 2G 的值。")"
        exit 1
      fi
      ;;
    --path)
      i=$((i + 1))
      SWAP_PATH="${XYZ_ARGS[$i]:-}"
      if [[ -z "${SWAP_PATH}" ]]; then
        xyz_error "$(xyz_t "--path requires a file path." "--path 需要文件路径。")"
        exit 1
      fi
      ;;
    --size=*)
      SWAP_SIZE="${arg#--size=}"
      ;;
    --path=*)
      SWAP_PATH="${arg#--path=}"
      ;;
    [0-9]*[GgMmKk])
      SWAP_SIZE="${arg}"
      ;;
    *)
      xyz_error "$(xyz_t "Unknown argument:" "未知参数:") ${arg}"
      exit 1
      ;;
  esac
  i=$((i + 1))
done

# Convert 2G / 1024M / 1K style sizes to bytes (integer, 1024-based).
xyz_size_to_bytes() {
  local spec="$1"
  local n unit
  n="$(printf '%s' "${spec}" | tr -d ' ')"
  unit="$(printf '%s' "${n}" | sed -n 's/.*\([KkMmGg]\)$/\1/p')"
  n="$(printf '%s' "${n}" | sed 's/[KkMmGg]$//')"
  if ! [[ "${n}" =~ ^[0-9]+$ ]]; then
    return 1
  fi
  case "${unit}" in
    G|g) printf '%s' $((n * 1024 * 1024 * 1024)) ;;
    M|m) printf '%s' $((n * 1024 * 1024)) ;;
    K|k) printf '%s' $((n * 1024)) ;;
    "")  printf '%s' "${n}" ;;
    *)   return 1 ;;
  esac
}

xyz_show_swap() {
  xyz_step "$(xyz_t "Current swap" "当前 swap")"
  swapon --show || true
  free -h | sed -n '1,2p' || true
}

if [[ "${DO_REMOVE}" == "1" ]]; then
  xyz_show_swap
  if [[ ! -e "${SWAP_PATH}" ]]; then
    xyz_error "$(xyz_t "Swap file not found:" "找不到 swap 文件:") ${SWAP_PATH}"
    exit 1
  fi
  if [[ "${XYZ_DRY_RUN:-0}" == "1" ]]; then
    xyz_info "$(xyz_t "Dry-run: would disable and remove ${SWAP_PATH}." "演练模式:将停用并删除 ${SWAP_PATH}。")"
    exit 0
  fi
  if ! xyz_confirm "$(xyz_t "Disable and remove ${SWAP_PATH}?" "停用并删除 ${SWAP_PATH}?")"; then
    exit 1
  fi
  swapoff "${SWAP_PATH}" 2>/dev/null || true
  if [[ -f /etc/fstab ]]; then
    # Remove only exact path entries added for this swap file.
    xyz_init_tmpdir
    tmp="$(xyz_tmpdir)/fstab"
    awk -v p="${SWAP_PATH}" '$1 != p { print }' /etc/fstab > "${tmp}"
    cat "${tmp}" > /etc/fstab
  fi
  rm -f "${SWAP_PATH}"
  xyz_info "$(xyz_t "Removed." "已删除。")"
  xyz_show_swap
  exit 0
fi

BYTES="$(xyz_size_to_bytes "${SWAP_SIZE}")" || {
  xyz_error "$(xyz_t "Invalid size (use e.g. 1G, 2048M):" "无效大小(请使用 1G、2048M 等):") ${SWAP_SIZE}"
  exit 1
}

if (( BYTES < 64 * 1024 * 1024 )); then
  xyz_error "$(xyz_t "Size is too small (minimum 64M)." "大小太小(至少 64M)。")"
  exit 1
fi

xyz_show_swap

DIR="$(dirname "${SWAP_PATH}")"
AVAIL="$(df -B1 --output=avail "${DIR}" | tail -n 1 | tr -d ' ')"
# Keep at least 512M free after creating the file.
NEED=$((BYTES + 512 * 1024 * 1024))
if (( AVAIL < NEED )); then
  xyz_error "$(xyz_t \
    "Not enough free space on ${DIR} for ${SWAP_SIZE} (plus a 512M safety margin)." \
    "${DIR} 上没有足够空间创建 ${SWAP_SIZE}(需额外保留 512M)。")"
  df -h "${DIR}" || true
  exit 1
fi

if [[ -e "${SWAP_PATH}" ]]; then
  xyz_warn "$(xyz_t "A file already exists at:" "该路径已存在文件:") ${SWAP_PATH}"
  if ! xyz_confirm "$(xyz_t "Replace the existing swap file? This will swapoff it first." "替换已有 swap 文件?会先 swapoff。")"; then
    exit 1
  fi
  swapoff "${SWAP_PATH}" 2>/dev/null || true
  rm -f "${SWAP_PATH}"
fi

xyz_info "$(xyz_t "Planned action:" "计划操作:")"
printf '  path : %s\n' "${SWAP_PATH}"
printf '  size : %s (%s bytes)\n' "${SWAP_SIZE}" "${BYTES}"
printf '  fstab: persist with defaults, sw, pri=10\n'

if [[ "${XYZ_DRY_RUN:-0}" == "1" ]]; then
  xyz_info "$(xyz_t "Dry-run: swap file will not be created." "演练模式:不会创建 swap 文件。")"
  exit 0
fi

if ! xyz_confirm "$(xyz_t "Create and enable this swap file?" "创建并启用该 swap 文件?")"; then
  exit 1
fi

xyz_step "$(xyz_t "Allocating file" "正在分配文件")"
# fallocate is fast on most ext4/xfs/btrfs volumes; dd is the portable fallback.
if xyz_need_cmd fallocate && fallocate -l "${BYTES}" "${SWAP_PATH}" 2>/dev/null; then
  :
else
  xyz_info "$(xyz_t "fallocate unavailable, using dd." "fallocate 不可用,改用 dd。")"
  COUNT=$((BYTES / 1024 / 1024))
  if (( COUNT * 1024 * 1024 != BYTES )); then
    # Odd sizes: write in 1K blocks.
    dd if=/dev/zero of="${SWAP_PATH}" bs=1024 count=$((BYTES / 1024)) status=progress
  else
    dd if=/dev/zero of="${SWAP_PATH}" bs=1M count="${COUNT}" status=progress
  fi
fi

chmod 600 "${SWAP_PATH}"
mkswap "${SWAP_PATH}"
if ! swapon "${SWAP_PATH}"; then
  xyz_error "$(xyz_t \
    "mkswap succeeded but swapon failed. This is common in containers when swap is disabled on the host, or when the process lacks CAP_SYS_ADMIN." \
    "mkswap 成功但 swapon 失败。当宿主机禁用了 swap,或进程缺少 CAP_SYS_ADMIN 时(容器中常见),会出现这种情况。")"
  rm -f "${SWAP_PATH}"
  exit 1
fi

if [[ -f /etc/fstab ]] && ! awk -v p="${SWAP_PATH}" '$1 == p { found=1 } END { exit !found }' /etc/fstab; then
  printf '%s none swap defaults,sw,pri=10 0 0\n' "${SWAP_PATH}" >> /etc/fstab
  xyz_info "$(xyz_t "Added an entry to /etc/fstab." "已写入 /etc/fstab。")"
fi

xyz_info "$(xyz_t "Swap is ready." "swap 已就绪。")"
xyz_show_swap

Recommended VPS

These slots are placeholders. No vendor or affiliate link has been added, and nothing here is a purchase button. Real plans and compliant referral links will be filled in later.

Placeholder · Value

Vendor TBD

Region, specs, and price will be filled in after onboarding. This is not a checkout link.

Placeholder · High RAM / storage

Vendor TBD

Region, specs, and price will be filled in after onboarding. This is not a checkout link.