From 6eddb7e2c5774947866727718192982a91cf12b3 Mon Sep 17 00:00:00 2001 From: Bart Sjerps Date: Fri, 17 Jul 2026 09:40:12 +0200 Subject: [PATCH] update --- bin/resize_filesystem | 131 ++++++++++++++++++++++++++++++++++++++++++ inventory.ini | 20 ++++++- postgres.yml | 29 ++++++++++ 3 files changed, 177 insertions(+), 3 deletions(-) create mode 100755 bin/resize_filesystem diff --git a/bin/resize_filesystem b/bin/resize_filesystem new file mode 100755 index 0000000..ccc2b96 --- /dev/null +++ b/bin/resize_filesystem @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Resize an LVM logical volume and its filesystem. + +Author: Bart Sjerps +Date: 2026-07-17 +Generated by opencode +""" +import os +import sys +import subprocess +import argparse +from typing import Optional + + +def die(msg: str) -> None: + """Print an error message to stderr and exit with code 2.""" + print(f"Error: {msg}", file=sys.stderr) + sys.exit(2) + + +def run(cmd: list[str], dryrun: bool = False) -> str: + """Execute a shell command, returning stdout. Exits on non-zero status.""" + if dryrun: + print(f"[DRY-RUN] {' '.join(cmd)}") + return "" + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as e: + err = (e.stderr or e.stdout or "").strip() + die(f"{' '.join(cmd)} failed: {err or f'exit code {e.returncode}'}") + return result.stdout.strip() + + +def resolve_lv(mount: Optional[str] = None, lvname: Optional[str] = None) -> str: + """Resolve a mountpoint or LV name to an absolute LVM device path.""" + if mount: + dev = run(["findmnt", "-no", "SOURCE", "--target", mount]) + if not dev: + die(f"No device mounted at {mount}") + else: + dev = lvname + + if not dev.startswith("/dev/"): + dev = f"/dev/{dev}" + + if not run(["lvs", dev]): + die(f"{dev} is not an LVM logical volume") + + return dev + + +def detect_fs(dev: str) -> str: + """Detect the filesystem type on a block device via blkid.""" + fstype = run(["blkid", "-o", "value", "-s", "TYPE", dev]) + if not fstype: + die(f"Cannot detect filesystem on {dev}") + return fstype + + +def resolve_mount(dev: str, mount: Optional[str] = None) -> str: + """Return a mountpoint for a device, using the provided one or looking it up.""" + if mount: + return mount + + mountpoint = run(["findmnt", "-no", "TARGET", "--source", dev]) + if not mountpoint: + die(f"No mountpoint found for {dev}") + return mountpoint + + +def lvextend_cmd(size: str, dev: str) -> list[str]: + """Build an lvextend command, using -l for extent/percent sizes.""" + if "%" in size: + return ["lvextend", "-l", size, dev] + return ["lvextend", "-L", size, dev] + + +def btrfs_resize_size(size: str) -> str: + """Map an LVM size spec to a btrfs filesystem resize argument.""" + if "%" in size: + return "max" + return size + + +def validate_resize(dev: str, fstype: str, mount: Optional[str] = None) -> Optional[str]: + """Validate resize is possible before mutating the LV. Returns mountpoint when needed.""" + if fstype in ("ext2", "ext3", "ext4"): + return None + if fstype in ("xfs", "btrfs"): + return resolve_mount(dev, mount) + die(f"Unsupported filesystem: {fstype}") + + +def do_resize(dev: str, fstype: str, size: str, mount: Optional[str] = None, dryrun: bool = False) -> None: + """Extend the LV with lvextend, then resize the filesystem accordingly.""" + fs_mount = validate_resize(dev, fstype, mount) + + run(lvextend_cmd(size, dev), dryrun=dryrun) + + if fstype in ("ext2", "ext3", "ext4"): + run(["resize2fs", dev], dryrun=dryrun) + elif fstype == "xfs": + run(["xfs_growfs", fs_mount], dryrun=dryrun) + elif fstype == "btrfs": + run(["btrfs", "filesystem", "resize", btrfs_resize_size(size), fs_mount], dryrun=dryrun) + + if not dryrun: + print("Done.") + + +def main() -> None: + """Parse args, validate root, then resize LV and filesystem.""" + parser = argparse.ArgumentParser(description="Resize an LVM logical volume and its filesystem") + parser.add_argument("-s", dest="size", required=True, help="New size (e.g. +10G, -5G, 20G, 100%FREE)") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("-m", dest="mount", metavar="MOUNTPOINT", help="Select LV by mountpoint") + group.add_argument("-l", dest="lvname", metavar="LVNAME", help="Select LV by name or path (e.g. vg0/data)") + parser.add_argument("-n", dest="dryrun", action="store_true", help="Dry-run – print what would be done without executing") + + args = parser.parse_args() + + if os.geteuid() != 0: + die("This script must be run as root") + + lvdev = resolve_lv(mount=args.mount, lvname=args.lvname) + fstype = detect_fs(lvdev) + do_resize(lvdev, fstype, args.size, mount=args.mount, dryrun=args.dryrun) + + +if __name__ == "__main__": + main() diff --git a/inventory.ini b/inventory.ini index ebd5230..b085765 100644 --- a/inventory.ini +++ b/inventory.ini @@ -3,24 +3,28 @@ atc-docker01.dell-atc.lan atc-docker02.dell-atc.lan atc-db01.dell-atc.lan atc-dns1.dell-atc.lan -# atc-db02.dell-atc.lan atc-lake01.dell-atc.lan atc-elastic01.dell-atc.lan atc-kafka01.dell-atc.lan atc-portal01.dell-atc.lan atc-grafana.dell-atc.lan -# atc-test01.dell-atc.lan atc-mgt01.dell-atc.lan atc-gpu-bart.dell-atc.lan atc-gpu-dev.dell-atc.lan atc-gpu-mo1.dell-atc.lan atc-gpu-prod.dell-atc.lan atc-nas.dell-atc.lan +dns1.dell-fde.lan +dns2.dell-fde.lan +db01.dell-fde.lan +haproxy.dell-fde.lan [proxmox] dcp-pve01.dell-atc.lan DSS-GPU.dell-atc.lan pve2.dell-atc.lan +proxmox01.dell-fde.lan +proxmox02.dell-fde.lan [docker] atc-mgt01.dell-atc.lan @@ -32,7 +36,8 @@ atc-gpu-prod.dell-atc.lan atc-db02.dell-atc.lan atc-dns1.dell-atc.lan atc-dns1.lan -# atc-dns2.dell-atc.lan +dns1.dell-fde.lan +dns2.dell-fde.lan [rhel] atc-docker01.dell-atc.lan @@ -45,6 +50,10 @@ atc-gpu-prod.dell-atc.lan atc-nas.dell-atc.lan atc-backup.dell-atc.lan atc-db01.dell-atc.lan +dns1.dell-fde.lan +haproxy.dell-fde.lan +dns2.dell-fde.lan +db01.dell-fde.lan [borg_servers] atc-backup.dell-atc.lan @@ -62,10 +71,15 @@ dcp-pve01.dell-atc.lan pve2.dell-atc.lan dss-gpu.dell-atc.lan atc-dns1.lan +dns1.dell-fde.lan +haproxy.dell-fde.lan +dns2.dell-fde.lan +db01.dell-fde.lan [postgres] atc-db01.dell-atc.lan +db01.dell-fde.lan [nvidia] atc-gpu-bart.dell-atc.lan diff --git a/postgres.yml b/postgres.yml index ccdcd92..418c3b4 100644 --- a/postgres.yml +++ b/postgres.yml @@ -6,6 +6,7 @@ ansible.builtin.dnf: name: 'https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm' state: present + disable_gpg_check: true - name: Postgres RPMs ansible.builtin.dnf: @@ -15,6 +16,34 @@ state: latest tasks: + - name: Create Database Logical Volume + community.general.lvol: + vg: data + lv: pgdata + size: 32G + + - name: Create Database Filesystem + community.general.filesystem: + fstype: xfs + dev: /dev/data/pgdata + + - name: Mount filesystems + ansible.posix.mount: + path: '/var/lib/pgsql' + src: '/dev/data/pgdata' + opts: 'noatime' + state: mounted + fstype: "xfs" + + - name: Open Firewall port + firewalld: + # service: docker-registry + service: postgresql + permanent: true + state: enabled + immediate: yes + when: ansible_facts['os_family'] == "RedHat" + - name: PG Backup directory file: path: '/var/backups/postgres'