• Bash
    • ramdisk.sh

      Creates and mounts a proper RAMdisk of custom size. (tmpfs IS NOT A REAL RAMDISK AND CAN SWAP!)


wget -q https://kps.makz.net/run/ramdisk.sh

#!/bin/bash
#
# v3.0 — Create, inspect, or destroy a RAM disk at /srv/ramdisk.
#
# Backend is chosen automatically:
#   - kernel >= 6.4 : tmpfs with noswap (lazy alloc, no module, no format)
#   - kernel <  6.4 : brd block device + ext4 (real pinned RAM)
#
set -euo pipefail

readonly MOUNT="/srv/ramdisk"
readonly DEV="/dev/ram0"
readonly LABEL="RAMDISK"
readonly STATE_FILE="/run/ramdisk.backend"   # remembers which backend was used
readonly DEFAULT_SIZE_GB=1

# ---------- output helpers ----------
if [[ -t 1 ]]; then
    readonly BOLD=$(tput bold)   NORMAL=$(tput sgr0)
    readonly RED=$(tput setaf 1) GREEN=$(tput setaf 2)
else
    readonly BOLD="" NORMAL="" RED="" GREEN=""
fi

die()  { echo "${RED}Error:${NORMAL} $*" >&2; exit 1; }
info() { echo "$*"; }

usage() {
    cat <<EOF
Usage: $0 [-s SIZE_GB] [-i] [-r] [-f] [-b BACKEND] [-h]

Without arguments, creates a ${DEFAULT_SIZE_GB}G RAM disk at ${MOUNT}.

  -s N         Create a RAM disk of N gigabytes at ${MOUNT}.
  -i           Show RAM disk status.
  -r           Unmount and remove the RAM disk.
  -f           With -r: forcefully kill processes using ${MOUNT} first.
  -b BACKEND   Force a backend: 'tmpfs' or 'brd'. Default: auto by kernel version.
  -h           Show this help.
EOF
}

# ---------- backend selection ----------
# Returns "tmpfs" if running kernel >= 6.4, else "brd".
detect_backend() {
    local kver major minor
    kver=$(uname -r)
    major=${kver%%.*}
    minor=${kver#*.}; minor=${minor%%.*}
    # Numeric guard in case uname -r returns something weird.
    [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]] || { echo "brd"; return; }
    if (( major > 6 )) || (( major == 6 && minor >= 4 )); then
        echo "tmpfs"
    else
        echo "brd"
    fi
}

# ---------- args ----------
size_gb=$DEFAULT_SIZE_GB
action="create"
force=0
backend=""

while getopts "fhirs:b:" opt; do
    case "$opt" in
        f) force=1 ;;
        h) usage; exit 0 ;;
        i) action="info" ;;
        r) action="remove" ;;
        s) size_gb="$OPTARG" ;;
        b) backend="$OPTARG" ;;
        *) usage >&2; exit 2 ;;
    esac
done

[[ "$size_gb" =~ ^[0-9]+$ ]] && (( size_gb > 0 )) \
    || die "Size must be a positive integer (GB). Got: $size_gb"

if [[ -n "$backend" ]]; then
    [[ "$backend" == "tmpfs" || "$backend" == "brd" ]] \
        || die "Backend must be 'tmpfs' or 'brd'. Got: $backend"
fi

# ---------- backend implementations ----------
create_tmpfs() {
    info "Backend: ${BOLD}tmpfs${NORMAL} (kernel $(uname -r), noswap supported)"

    mkdir -p "$MOUNT"
    mount -t tmpfs -o "size=${size_gb}G,mode=1777,noswap" tmpfs "$MOUNT"

    # Confirm noswap actually took effect (older /proc/mounts won't list it
    # if mount silently dropped it on an unsupported kernel).
    if ! grep -qE "[[:space:]]${MOUNT}[[:space:]].*\bnoswap\b" /proc/mounts; then
        umount "$MOUNT"
        die "tmpfs noswap option not honored by kernel. Use '-b brd' instead."
    fi
}

create_brd() {
    info "Backend: ${BOLD}brd${NORMAL} (kernel $(uname -r), tmpfs noswap unavailable)"

    local size_kib=$(( size_gb * 1024 * 1024 ))

    if [[ -d /sys/module/brd ]]; then
        local existing_kib
        existing_kib=$(blockdev --getsize64 "$DEV" 2>/dev/null | awk '{print int($1/1024)}')
        if [[ -z "$existing_kib" ]] || (( existing_kib != size_kib )); then
            die "brd already loaded with a different size (${existing_kib} KiB vs ${size_kib} KiB requested). Unload it first: 'modprobe -r brd' (after unmounting users)."
        fi
        info "brd already loaded at requested size."
    else
        info "Loading brd module (rd_size=${size_kib} KiB)..."
        modprobe brd rd_nr=1 max_part=1 "rd_size=${size_kib}"
    fi

    info "Formatting ${DEV} as ext4..."
    mkfs.ext4 -q -m 0 -L "$LABEL" -E lazy_itable_init=0,lazy_journal_init=0 "$DEV"

    mkdir -p "$MOUNT"
    mount -o noatime "$DEV" "$MOUNT"
    chmod 1777 "$MOUNT"
}

remove_tmpfs() {
    umount "$MOUNT"
    rmdir "$MOUNT" 2>/dev/null || true
}

remove_brd() {
    umount "$MOUNT"
    blockdev --flushbufs "$DEV" 2>/dev/null || true
    rmdir "$MOUNT" 2>/dev/null || true

    if [[ -r /sys/module/brd/refcnt ]] && [[ $(< /sys/module/brd/refcnt) -eq 0 ]]; then
        modprobe -r brd 2>/dev/null || true
    fi
}

# ---------- actions ----------
case "$action" in
info)
    if mountpoint -q "$MOUNT"; then
        if [[ -f "$STATE_FILE" ]]; then
            info "Backend: ${BOLD}$(<"$STATE_FILE")${NORMAL}"
        fi
        df -h "$MOUNT"
        exit 0
    else
        info "No RAM disk at ${MOUNT}"
        exit 1
    fi
    ;;

remove)
    [[ $EUID -eq 0 ]] || die "Must be run as root."
    mountpoint -q "$MOUNT" || die "No RAM disk mounted at ${MOUNT}"

    if (( force )); then
        info "Killing processes using ${MOUNT}..."
        fuser -km "$MOUNT" 2>/dev/null || true
        sleep 1
    fi

    # Trust the state file over the CLI flag — we need to clean up the actual backend.
    used_backend=$(<"$STATE_FILE" 2>/dev/null || echo "")
    if [[ -z "$used_backend" ]]; then
        # No state file (older mount, or someone deleted /run state).
        # Guess from /proc/mounts.
        if grep -qE "[[:space:]]${MOUNT}[[:space:]]tmpfs[[:space:]]" /proc/mounts; then
            used_backend="tmpfs"
        else
            used_backend="brd"
        fi
    fi

    info "Removing RAM disk (backend: ${used_backend})..."
    if [[ "$used_backend" == "tmpfs" ]]; then
        remove_tmpfs
    else
        remove_brd
    fi

    rm -f "$STATE_FILE"
    info "${GREEN}RAM disk removed.${NORMAL}"
    exit 0
    ;;

create)
    [[ $EUID -eq 0 ]] || die "Must be run as root."

    if mountpoint -q "$MOUNT"; then
        info "RAM disk already mounted at ${MOUNT}. Exiting."
        df -h "$MOUNT"
        exit 0
    fi

    avail_kib=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo)
    need_kib=$(( size_gb * 1024 * 1024 ))
    (( avail_kib >= need_kib )) \
        || die "Not enough available RAM: need ${need_kib} KiB, have ${avail_kib} KiB."

    [[ -z "$backend" ]] && backend=$(detect_backend)

    if [[ "$backend" == "tmpfs" ]]; then
        create_tmpfs
    else
        create_brd
    fi

    # Remember which backend was used so -r can clean up correctly.
    echo "$backend" > "$STATE_FILE"

    info "${GREEN}Done.${NORMAL} ${size_gb}G RAM disk mounted at ${BOLD}${MOUNT}${NORMAL}"
    df -h "$MOUNT"
    ;;
esac