Skip to content

root-ubuntu: give a box with no swap 2G of it - #36

Merged
ralyodio merged 1 commit into
masterfrom
worktree-swapfile-setup
Aug 30, 2026
Merged

root-ubuntu: give a box with no swap 2G of it#36
ralyodio merged 1 commit into
masterfrom
worktree-swapfile-setup

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Every box this script provisions was running with zero swap. Checked before writing anything:

box ram swap before swap now
dev (dev.profullstack.com) 15.6G none 2G
seed2 = dev.chovy.com (152.53.47.37, one box) 15.6G none 2G
seed1 (104.152.209.195) none not applied, see below

With no swap the kernel's only answer to a memory spike is the OOM killer, and what it picks is whatever was biggest — on a dev box that is the build, the language server, or the editor somebody was working in.

What the step does

A swapfile, not a partition: provider images arrive with the whole disk given to /, so there is no partition to make, and a file can be resized or removed on a live box.

It converges like the rest of the script:

  • only acts on a box with no swap at all — a machine with a swap partition or zram is left alone rather than gaining a second, forgotten swapfile
  • fstab entry matched on the path — a re-run adds nothing, and a hand-edited line survives
  • declines where a plain swapfile is wrong or dangerous — btrfs needs chattr +C and no compression; a swapfile on zfs can deadlock the box under exactly the pressure it was added to survive
  • declines inside a container — the kernel and its swap belong to the host
  • declines when the disk cannot spare it — a full / breaks things a memory spike never would have

vm.swappiness goes to 10 alongside it. The default of 60 treats swap as another tier of memory and pages out pages that are still in use, which is how swap earned its reputation.

Two details worth keeping:

  • the mode goes on before the chown and on its own. Chained behind a failing chown the file would stay readable and mkswap would still accept it — and swap is every secret the machine has ever paged out.
  • a failed mkswap or swapon removes the file, so a later run cannot mistake 2G of dead disk for working swap.

SWAP_SIZE=0 turns the whole thing off. SWAP_SIZE / SWAP_FILE / SWAPPINESS are documented in --help and server.conf.example.

Verification

Applied to dev and seed2 by running the committed code, then checked on the boxes:

swap on:    /swapfile 2G file -1
fstab:      /swapfile  none  swap  sw  0 0   (1 line)
perms:      600 root:root 2147483648 bytes
live value: vm.swappiness=10
swapon -a:  re-reads /etc/fstab cleanly (a reboot will too)
findmnt --verify passes

A second run on dev reported swap already active: /swapfile (file, 2G) and changed nothing.

17 new tests in test/root-ubuntu.test.ts (100 in that file, 560 across the suite, all passing). They stub swapon/mkswap/df/systemd-detect-virt and rewrite the two absolute paths into a temp dir with declare -f, so the code under test is the code in the file and the test cannot touch the machine it runs on. bash -n and shellcheck are clean; the two tsc errors in src/free-names.ts are pre-existing on master.

Not done: seed1

seed1.h4kr.com still has no swap. Root SSH is permitted there but neither key on the dev box is in root's authorized_keys, and the ubuntu account's sudo wants a password. Needs either that password or our key added to root — then the same step applies.

🤖 Generated with Claude Code

https://claude.ai/code/session_013TerE4nvNU3jvS51nRR6Pd

Every box this script provisions was running with zero swap, so the
kernel's only answer to a memory spike was the OOM killer -- and what it
picks is whatever was biggest, which on a dev box is the build, the
language server, or the editor somebody was working in. 2G does not make
a small box a big one; it turns "the process died" into "that got slow
for a moment".

A swapfile rather than a partition: provider images arrive with the whole
disk given to /, so there is no partition to make, and a file can be
resized or removed on a live box.

The step converges like the rest of the script. It only ever acts on a
box with NO swap at all, so a machine with a swap partition or zram is
left alone rather than gaining a second, forgotten swapfile; the fstab
entry is matched on the path, so a re-run adds nothing and a hand-edited
line survives. It declines where a plain swapfile is wrong (btrfs needs
chattr +C and no compression) or dangerous (a swapfile on zfs can
deadlock the box under exactly the pressure it was added to survive),
inside a container, where the kernel and its swap belong to the host, and
when the disk cannot spare the space -- a full / breaks things a memory
spike never would have.

vm.swappiness goes to 10 with it. The default of 60 treats swap as
another tier of memory and pages out pages that are still in use, which
is how swap earns its reputation; 10 keeps it as the safety net.

Two details worth keeping: the mode goes on before the chown and on its
own, because chained behind a failing chown the file would stay readable
and mkswap would still accept it, and swap is every secret the machine
has ever paged out. And a failed mkswap or swapon removes the file, so a
later run cannot mistake 2G of dead disk for working swap.

SWAP_SIZE=0 turns the whole thing off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TerE4nvNU3jvS51nRR6Pd
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

16 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 2 | LOW: 9

Severity Rule Location
HIGH sh-remote-script-execution root-ubuntu.sh:139
HIGH sh-remote-script-execution root-ubuntu.sh:2792
HIGH sh-remote-script-execution root-ubuntu.sh:2796
HIGH sh-remote-script-execution root-ubuntu.sh:2945
HIGH sh-remote-script-execution root-ubuntu.sh:3974
MEDIUM redos-nested-quantifier src/domain-free.ts:56
MEDIUM redos-nested-quantifier src/porkbun.ts:506
LOW secret-generic-credential src/credentials.ts:36
LOW insecure-temp-file test/blog.test.ts:73
LOW insecure-temp-file test/blog.test.ts:74
LOW insecure-temp-file test/credentials.test.ts:43
LOW insecure-temp-file test/credentials.test.ts:44
LOW secret-generic-api-key test/credentials.test.ts:208
LOW insecure-temp-file test/download.test.ts:99
LOW insecure-temp-file test/download.test.ts:100
LOW secret-generic-credential test/shorten.test.ts:36

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit e810b6d into master Aug 30, 2026
5 checks passed
@ralyodio
ralyodio deleted the worktree-swapfile-setup branch August 30, 2026 06:17
ralyodio added a commit that referenced this pull request Aug 30, 2026
Eight commits since 0.15.0, one of them a new command, so the minor moves.

- sysupdate: apt lists, apt packages and snaps in one command, reachable
  from the pit as /update (#37)
- root-ubuntu.sh: a 2G swapfile on a box with no swap at all, plus
  vm.swappiness=10 (#36)
- root-ubuntu.sh: one ssh-agent per user under systemd (#35)
- root-ubuntu.sh: lm-sensors, smartmontools and the rest of the hardware
  inventory set, with sensor detection on bare metal
- companions: diskpush, and installers that are not npm (#34)
- porkbun: Porkbun's own pre-flight, structured refusals, and the fact that
  registration spends prepaid credit rather than a card (#32, #33)


Claude-Session: https://claude.ai/code/session_013TerE4nvNU3jvS51nRR6Pd

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant