From e57f2d977bbff9debd9a3cacea747ad2e4fc4907 Mon Sep 17 00:00:00 2001 From: phg Date: Mon, 11 May 2026 17:50:49 +0200 Subject: [PATCH] Add new template for debian trixie without luks encryption; fix disk resizing on first boot; set default user and ssh keys for cloud init --- .gitignore | 2 +- README.md | 12 - debian/13-trixie-luks/debian-trixie.pkr.hcl | 2 +- debian/13-trixie-luks/files/initial-setup.sh | 26 + debian/13-trixie/credentials.auto.pkrvars.hcl | 1 + debian/13-trixie/debian-trixie.pkr.hcl | 233 ++++++ .../13-trixie/files/90-initial-login-setup.sh | 25 + debian/13-trixie/files/99-pve.cfg | 19 + debian/13-trixie/files/debian.sources | 38 + debian/13-trixie/files/initial-setup.sh | 660 ++++++++++++++++ debian/13-trixie/http/preseed.cfg | 161 ++++ .../scripts/crowdsec-configuration.sh | 8 + .../13-trixie/scripts/crowdsec-repo-setup.sh | 370 +++++++++ debian/13-trixie/scripts/tailscale.sh | 726 ++++++++++++++++++ debian/13-trixie/variables-common.pkr.hcl | 1 + debian/13-trixie/variables.pkr.hcl | 67 ++ favicon.svg | 1 + mise.toml | 4 +- variables-common.pkr.hcl | 2 +- 19 files changed, 2341 insertions(+), 17 deletions(-) create mode 120000 debian/13-trixie/credentials.auto.pkrvars.hcl create mode 100644 debian/13-trixie/debian-trixie.pkr.hcl create mode 100644 debian/13-trixie/files/90-initial-login-setup.sh create mode 100644 debian/13-trixie/files/99-pve.cfg create mode 100644 debian/13-trixie/files/debian.sources create mode 100644 debian/13-trixie/files/initial-setup.sh create mode 100644 debian/13-trixie/http/preseed.cfg create mode 100644 debian/13-trixie/scripts/crowdsec-configuration.sh create mode 100644 debian/13-trixie/scripts/crowdsec-repo-setup.sh create mode 100644 debian/13-trixie/scripts/tailscale.sh create mode 120000 debian/13-trixie/variables-common.pkr.hcl create mode 100644 debian/13-trixie/variables.pkr.hcl create mode 100644 favicon.svg diff --git a/.gitignore b/.gitignore index ca81eef..2f7f285 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Project /credentials.auto.pkrvars.hcl /downloaded_iso_path -overwrite-if.auto.pkrvars.hcl +overwrite*.auto.pkrvars.hcl # Linux *~ diff --git a/README.md b/README.md index 2e6542b..3c7a2f4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ - [Build](#build) - [Build LUKS encrypted Templates](#build-luks-encrypted-templates) - [Setup new templates](#setup-new-templates) - - [ToDo](#todo) ## Templates @@ -106,14 +105,3 @@ mise run setup ``` E.g. `mise run setup debian 13-trixie` or `mise run setup debian 13-trixie-luks`. - -## ToDo - -- [ ] Debian 13 with LUKS - - [x] Setup image with LUKS - - [ ] Automated unlock from packer on 1st boot after installation - - [ ] Setup dropbear - - [ ] Setup Clevis/Tang - - [x] Copy initial setup script, to expand the disk and do various setup steps - - [x] Lock down root user (remove password, prohibit all logins) - - [x] Lock down SSH Server diff --git a/debian/13-trixie-luks/debian-trixie.pkr.hcl b/debian/13-trixie-luks/debian-trixie.pkr.hcl index 7fb60a1..a4fa2ae 100644 --- a/debian/13-trixie-luks/debian-trixie.pkr.hcl +++ b/debian/13-trixie-luks/debian-trixie.pkr.hcl @@ -114,7 +114,7 @@ build { inline = [ "export DEBIAN_FRONTEND=noninteractive", "apt-get update", - "apt-get install -y age apt-transport-https aria2 bat bc bmon btop ca-certificates curl duf eza fastfetch fzf git gnupg htop iftop iotop iperf jq lsof magic-wormhole mosh mtr ncdu parted progress pv ripgrep rsync smartmontools socat sudo tmux usbutils vim wget yq zsh zstd" + "apt-get install -y age apt-transport-https aria2 bat bc bmon btop ca-certificates curl duf eza fastfetch fzf gdisk git gnupg htop iftop iotop iperf jq lsof magic-wormhole mosh mtr ncdu parted progress pv ripgrep rsync smartmontools socat sudo tmux usbutils vim wget yq zsh zstd" ] } diff --git a/debian/13-trixie-luks/files/initial-setup.sh b/debian/13-trixie-luks/files/initial-setup.sh index bc7fc2f..1a2c426 100644 --- a/debian/13-trixie-luks/files/initial-setup.sh +++ b/debian/13-trixie-luks/files/initial-setup.sh @@ -279,6 +279,28 @@ refresh_partition_table() { fi } +repair_gpt_backup_header() { + local disk="$1" + + if command -v sgdisk >/dev/null 2>&1; then + if sgdisk -e "$disk" >/dev/null 2>&1; then + refresh_partition_table "$disk" + return 0 + fi + fi + + if parted -s "$disk" print >/dev/null 2>&1; then + return 0 + fi + + if printf 'Fix\n' | parted ---pretend-input-tty "$disk" print >/dev/null 2>&1; then + refresh_partition_table "$disk" + return 0 + fi + + return 1 +} + wait_for_partition_growth() { local part="$1" local old_bytes="$2" @@ -436,6 +458,10 @@ resize_lvm_on_luks() { log_info "Disk: ${DISK_DEV} | Partition number: ${PART_NUM}" [ -n "$LUKS_NAME" ] && log_info "LUKS mapper name: ${LUKS_NAME}" + if ! repair_gpt_backup_header "$DISK_DEV"; then + log_warn "Unable to repair or verify GPT backup header on ${DISK_DEV}; resize may fail." + fi + if ! is_last_partition_on_disk "$DISK_DEV" "$PART_NUM"; then log_warn "Partition ${PART_NUM} is not the last partition on ${DISK_DEV}. Automatic growth is skipped." return 0 diff --git a/debian/13-trixie/credentials.auto.pkrvars.hcl b/debian/13-trixie/credentials.auto.pkrvars.hcl new file mode 120000 index 0000000..2704ed7 --- /dev/null +++ b/debian/13-trixie/credentials.auto.pkrvars.hcl @@ -0,0 +1 @@ +../../credentials.auto.pkrvars.hcl \ No newline at end of file diff --git a/debian/13-trixie/debian-trixie.pkr.hcl b/debian/13-trixie/debian-trixie.pkr.hcl new file mode 100644 index 0000000..0e278b8 --- /dev/null +++ b/debian/13-trixie/debian-trixie.pkr.hcl @@ -0,0 +1,233 @@ +packer { + required_plugins { + proxmox = { + version = "~> 1" + source = "github.com/hashicorp/proxmox" + } + } +} + +source "proxmox-iso" "debian-13-trixie" { + # Proxmox Connection Settings + proxmox_url = "${var.proxmox_api_url}" + username = "${var.proxmox_api_token_id}" + token = "${var.proxmox_api_token_secret}" + + # Skip TLS Verification + insecure_skip_tls_verify = "${var.proxmox_skip_tls_verify}" + + # VM General Settings + node = "${var.proxmox_node}" + vm_id = "${var.template_vm_id}" + vm_name = "debian-13-trixie-${local.timestamp}" + template_description = "Debian 13 Trixie, built with Packer on ${local.timestamp}" + os = "l26" + qemu_agent = true + + # VM Hardware Settings + machine = "q35" + cpu_type = "${var.template_cpu_type}" + cores = 2 + memory = 2048 + ballooning_minimum = 2048 + bios = "ovmf" + scsi_controller = "virtio-scsi-single" + disks { + disk_size = "30G" + format = "raw" + storage_pool = "${var.disk_storage_pool}" + type = "scsi" # VirtIO-SCSI better maintained as virtio-blk has been deprecated in Proxmox VE 7.4+ and may cause issues with newer Linux kernels + io_thread = true + ssd = true + } + + efi_config { + efi_storage_pool = "${var.disk_storage_pool}" + pre_enrolled_keys = true + efi_format = "raw" + efi_type = "4m" + } + + serials = [ + "socket" + ] + + # Download ISO + boot_iso { + type = "scsi" + iso_url = "${var.iso_url}" + unmount = true + iso_storage_pool = "${var.iso_storage_pool}" + iso_checksum = "${var.iso_checksum}" + } + + # VM Network Settings + network_adapters { + model = "virtio" + mac_address = "${var.mac_address}" + bridge = "${var.network_bridge}" + firewall = "true" + } + + # VM Cloud-Init Settings + cloud_init = true + cloud_init_storage_pool = "${var.disk_storage_pool}" + cloud_init_disk_type = "scsi" + + # PACKER Boot Commands + boot = "order=scsi0;scsi1" + boot_wait = "10s" + communicator = "ssh" + boot_command = [ + "c", + "linux /install.amd/vmlinuz auto-install/enable=true priority=critical ", + "DEBIAN_FRONTEND=text ", + "console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 ", + "passwd/root-password='${var.default_root_passphrase}' ", + "passwd/root-password-again='${var.default_root_passphrase}' ", + "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg noprompt", + "initrd /install.amd/initrd.gz", + "DEBCONF_DEBUG=5", + "boot" + ] + # Static IP + # boot_command = [ + # "c", + # "linux /install.amd/vmlinuz auto-install/enable=true priority=critical ", + # "DEBIAN_FRONTEND=text ", + # "console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 ", + # "passwd/root-password='${var.default_root_passphrase}' ", + # "passwd/root-password-again='${var.default_root_passphrase}' ", + # "netcfg/disable_autoconfig=true ", + # "netcfg/get_ipaddress=172.16.2.254 ", + # "netcfg/get_netmask=255.255.255.0 ", + # "netcfg/get_gateway=172.16.2.3 ", + # "netcfg/get_nameservers=172.16.2.3 ", + # "netcfg/confirm_static=true ", + # "netcfg/get_hostname=debian-installer ", + # "netcfg/get_domain=local ", + # "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg noprompt", + # "initrd /install.amd/initrd.gz", + # "DEBCONF_DEBUG=5", + # "boot" + # ] + + # PACKER Autoinstall Settings + http_directory = "debian/13-trixie/http" + http_interface = "${var.source_proxmox_http_interface}" + + # SSH Settings + ssh_username = "root" + ssh_password = "${var.default_root_passphrase}" + ssh_timeout = "20m" + ssh_pty = true +} + +build { + name = "debian-13-trixie-image" + sources = ["source.proxmox-iso.debian-13-trixie"] + + # Install dependencies and default packages + provisioner "shell" { + inline = [ + "export DEBIAN_FRONTEND=noninteractive", + "apt-get update", + "apt-get install -y age apt-transport-https aria2 bat bc bmon btop ca-certificates curl duf eza fastfetch fzf gdisk git gnupg htop iftop iotop iperf jq lsof magic-wormhole mosh mtr ncdu parted progress pv ripgrep rsync smartmontools socat sudo tmux usbutils vim wget yq zsh zstd" + ] + } + + # Install Tailscale + provisioner "shell" { + script = "debian/13-trixie/scripts/tailscale.sh" + } + + # Setup CrowdSec Repo + provisioner "shell" { + script = "debian/13-trixie/scripts/crowdsec-repo-setup.sh" + } + + # Install CrowdSec + provisioner "shell" { + inline = [ + "apt-get install -y crowdsec", + "apt-get install -y crowdsec-firewall-bouncer-iptables" + ] + } + + # Configure CrowdSec + provisioner "shell" { + script = "debian/13-trixie/scripts/crowdsec-configuration.sh" + } + + # Provisioning the VM Template for Cloud-Init Integration in Proxmox #2 + provisioner "file" { + source = "debian/13-trixie/files/99-pve.cfg" + destination = "/tmp/99-pve.cfg" + } + + # Provisioning the VM Template for Cloud-Init Integration in Proxmox #3 + provisioner "shell" { + inline = ["sudo cp /tmp/99-pve.cfg /etc/cloud/cloud.cfg.d/99-pve.cfg"] + } + + # Remove APT sources.list + provisioner "shell" { + inline = ["rm -f /etc/apt/sources.list /etc/apt/sources.list~"] + } + + # Add custom APT sources + provisioner "file" { + source = "debian/13-trixie/files/debian.sources" + destination = "/etc/apt/sources.list.d/debian.sources" + } + + provisioner "file" { + source = "debian/13-trixie/files/90-initial-login-setup.sh" + destination = "/etc/profile.d/90-initial-login-setup.sh" + } + + provisioner "file" { + source = "debian/13-trixie/files/initial-setup.sh" + destination = "/usr/local/bin/initial-setup.sh" + } + + provisioner "shell" { + inline = [ + "chmod +x /usr/local/bin/initial-setup.sh" + ] + } + + # Setup Serial Console for xterm.js in Proxmox VE + provisioner "shell" { + inline = [ + "sed -i 's/#\\?GRUB_CMDLINE_LINUX=.*\"/GRUB_CMDLINE_LINUX=\"console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0\"/' /etc/default/grub", + "sed -i 's/#\\?GRUB_TERMINAL=.*/GRUB_TERMINAL=\"serial console\"/' /etc/default/grub", + "sed -i 's/#\\?GRUB_SERIAL_COMMAND=.*/GRUB_SERIAL_COMMAND=\"serial --speed=115200\"/' /etc/default/grub", + "update-grub" + ] + } + + # Provisioning the VM Template for Cloud-Init Integration in Proxmox #1 + provisioner "shell" { + inline = [ + "rm /etc/ssh/ssh_host_*", + "truncate -s 0 /etc/machine-id", + "apt -y autoremove --purge 2> /dev/null", + "apt -y clean 2> /dev/null", + "apt -y autoclean 2> /dev/null", + "rm -rf /var/cache/apt/archives /var/lib/apt/lists/*", + "cloud-init clean", + "rm -f /etc/cloud/cloud.cfg.d/subiquity-disable-cloudinit-networking.cfg", + "sync" + ] + } + + # Remove temporary settings and configuration for packer build + provisioner "shell" { + inline = [ + "sed -i 's/^#\\?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config", + "sed -i 's/^#\\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config", + "passwd -dl root" + ] + } +} diff --git a/debian/13-trixie/files/90-initial-login-setup.sh b/debian/13-trixie/files/90-initial-login-setup.sh new file mode 100644 index 0000000..0ec65be --- /dev/null +++ b/debian/13-trixie/files/90-initial-login-setup.sh @@ -0,0 +1,25 @@ +#! /bin/sed 2,5!d;s/^#.// +# This script must be sourced from within a shell +# and not executed. For instance with: +# +# . /usr/local/bin/initial-setup.sh + +# Only run in interactive shells +case $- in + *i*) ;; + *) return ;; +esac + +if [ "$EUID" -ne 0 ]; then + if ! command -v sudo >/dev/null 2>&1 || ! sudo -n true >/dev/null 2>&1; then + echo "Error: must be root or have sudo privileges to run initial login setup." >&2 + return + fi +fi + +SENTINEL="/var/lib/initial-login-setup.done" + +if [ ! -f "$SENTINEL" ] && [ -x /usr/local/bin/initial-setup.sh ]; then + sudo touch "$SENTINEL" + sudo /usr/local/bin/initial-setup.sh +fi diff --git a/debian/13-trixie/files/99-pve.cfg b/debian/13-trixie/files/99-pve.cfg new file mode 100644 index 0000000..c583923 --- /dev/null +++ b/debian/13-trixie/files/99-pve.cfg @@ -0,0 +1,19 @@ +datasource_list: [ConfigDrive, NoCloud] + +users: + - default + +system_info: + default_user: + name: b23 + gecos: base23 default user + groups: [adm, audio, cdrom, dialout, dip, floppy, netdev, plugdev, sudo, video] + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + shell: /bin/bash + lock_passwd: true + +ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBiiCLKXIHS639MGp/5ckFO0cpTIBX6WehDKBbL7zLse Philip Henning + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGh/6DRjKCZHByW4/Inig9LXKI9c3M395fjpv7Ox4Jkz Dennis Michaelis + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILFK9liU5igL7cZBuEzPD/k/YngMJ4YGwT53SvFFnPyy Simon Stumpe + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGi0nRU/9QgbMoRfU27L5GNLqosKiLm8zsGmQhG7vz5J Maik Tirock diff --git a/debian/13-trixie/files/debian.sources b/debian/13-trixie/files/debian.sources new file mode 100644 index 0000000..60dea7b --- /dev/null +++ b/debian/13-trixie/files/debian.sources @@ -0,0 +1,38 @@ +Types: deb +URIs: http://ftp.de.debian.org/debian/ +Suites: trixie +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb-src +URIs: http://ftp.de.debian.org/debian/ +Suites: trixie +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + + +Types: deb +URIs: http://ftp.de.debian.org/debian/ +Suites: trixie-updates +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb-src +URIs: http://ftp.de.debian.org/debian/ +Suites: trixie-updates +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + + +Types: deb +URIs: http://security.debian.org/ +Suites: trixie-security +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb-src +URIs: http://security.debian.org/ +Suites: trixie-security +Components: main contrib non-free non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + diff --git a/debian/13-trixie/files/initial-setup.sh b/debian/13-trixie/files/initial-setup.sh new file mode 100644 index 0000000..b78d695 --- /dev/null +++ b/debian/13-trixie/files/initial-setup.sh @@ -0,0 +1,660 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + +SCRIPT_NAME="$(basename "$0")" +TASK_INDEX=0 +TASK_TOTAL=0 +STORAGE_DETECTED=0 + +ROOT_SOURCE="" +VG_NAME="" +LV_NAME="" +PV_NAME="" +DISK_DEV="" +PART_NUM="" + +if [ -t 1 ]; then + BOLD="$(tput bold 2>/dev/null || true)" + DIM="$(tput dim 2>/dev/null || true)" + RED="$(tput setaf 1 2>/dev/null || true)" + GREEN="$(tput setaf 2 2>/dev/null || true)" + YELLOW="$(tput setaf 3 2>/dev/null || true)" + BLUE="$(tput setaf 4 2>/dev/null || true)" + RESET="$(tput sgr0 2>/dev/null || true)" +else + BOLD="" + DIM="" + RED="" + GREEN="" + YELLOW="" + BLUE="" + RESET="" +fi + +die() { + echo "${RED}Error:${RESET} $*" >&2 + exit 1 +} + +log_info() { + echo "${BLUE}INFO:${RESET} $*" +} + +log_ok() { + echo "${GREEN}OK:${RESET} $*" +} + +log_warn() { + echo "${YELLOW}WARN:${RESET} $*" +} + +section() { + local title="$1" + echo + echo "${BOLD}${title}${RESET}" + echo "${DIM}------------------------------------------------------------${RESET}" +} + +ensure_tty() { + if [ ! -t 0 ] || [ ! -t 1 ]; then + die "This setup must run interactively in a TTY." + fi +} + +ensure_root() { + if [ "${EUID:-$(id -u)}" -ne 0 ]; then + if command -v sudo >/dev/null 2>&1; then + log_info "Re-running with sudo..." + exec sudo -E "$0" "$@" + fi + die "Must be root or have sudo privileges to run this setup." + fi +} + +require_cmd() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + log_warn "Missing command: $cmd" + return 1 + fi + return 0 +} + +prompt_input() { + local label="$1" + local default="${2:-}" + local value="" + if [ -n "$default" ]; then + read -r -p "${label} [${default}]: " value /dev/tty + if [ -n "$value" ]; then + printf '%s' "$value" + return 0 + fi + log_warn "Value cannot be empty." + done +} + +prompt_secret_confirm() { + local label="$1" + local confirm_label="$2" + local a="" + local b="" + while true; do + read -r -s -p "${label}: " a /dev/tty + read -r -s -p "${confirm_label}: " b /dev/tty + if [ -z "$a" ]; then + log_warn "Value cannot be empty." + continue + fi + if [ "$a" != "$b" ]; then + log_warn "Values do not match. Please try again." + continue + fi + printf '%s' "$a" + return 0 + done +} + +confirm() { + local label="$1" + local default="${2:-yes}" + local prompt="" + local answer="" + + if [ "$default" = "yes" ]; then + prompt="[Y/n]" + else + prompt="[y/N]" + fi + + while true; do + read -r -p "${label} ${prompt} " answer /dev/null 2>&1; then + numfmt --to=iec --suffix=B "$bytes" + else + awk -v b="$bytes" 'BEGIN { + split("B KiB MiB GiB TiB", u, " "); + i=1; + while (b>=1024 && i<5) { b/=1024; i++; } + printf "%.1f %s", b, u[i]; + }' + fi +} + +mib_to_human() { + local mib="$1" + awk -v m="$mib" 'BEGIN { printf "%.1f GiB", m/1024 }' +} + +add_task() { + TASK_TITLES+=("$1") + TASK_FUNCS+=("$2") + TASK_TOTAL=$((TASK_TOTAL + 1)) +} + +run_tasks() { + local i + local title + local func + for i in "${!TASK_FUNCS[@]}"; do + TASK_INDEX=$((TASK_INDEX + 1)) + title="${TASK_TITLES[$i]}" + func="${TASK_FUNCS[$i]}" + section "Task ${TASK_INDEX}/${TASK_TOTAL}: ${title}" + "$func" + done +} + +lsblk_attr() { + local path="$1" + local attr="$2" + lsblk -dn -o "$attr" "$path" 2>/dev/null | head -n1 | xargs || true +} + +infer_disk_part_from_partition() { + local part_path="$1" + local resolved + local base + + resolved="$(readlink -f "$part_path" 2>/dev/null || printf '%s' "$part_path")" + base="$(basename "$resolved")" + + if [[ "$base" =~ ^(nvme[0-9]+n[0-9]+)p([0-9]+)$ ]]; then + printf '/dev/%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + return 0 + fi + if [[ "$base" =~ ^(mmcblk[0-9]+)p([0-9]+)$ ]]; then + printf '/dev/%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + return 0 + fi + if [[ "$base" =~ ^(md[0-9]+)p([0-9]+)$ ]]; then + printf '/dev/%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + return 0 + fi + if [[ "$base" =~ ^((sd|vd|xvd|hd)[a-z]+)([0-9]+)$ ]]; then + printf '/dev/%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]}" + return 0 + fi + return 1 +} + +refresh_partition_table() { + local disk="$1" + if command -v partprobe >/dev/null 2>&1; then + partprobe "$disk" || true + fi + if command -v partx >/dev/null 2>&1; then + partx -u "$disk" || true + fi + if command -v udevadm >/dev/null 2>&1; then + udevadm settle || true + fi +} + +repair_gpt_backup_header() { + local disk="$1" + + if command -v sgdisk >/dev/null 2>&1; then + if sgdisk -e "$disk" >/dev/null 2>&1; then + refresh_partition_table "$disk" + return 0 + fi + fi + + if parted -s "$disk" print >/dev/null 2>&1; then + return 0 + fi + + if printf 'Fix\n' | parted ---pretend-input-tty "$disk" print >/dev/null 2>&1; then + refresh_partition_table "$disk" + return 0 + fi + + return 1 +} + +wait_for_partition_growth() { + local part="$1" + local old_bytes="$2" + local new_bytes=0 + local i=0 + + for ((i = 0; i < 12; i++)); do + new_bytes="$(blockdev --getsize64 "$part" 2>/dev/null || echo 0)" + if [ "$new_bytes" -gt "$old_bytes" ]; then + return 0 + fi + sleep 1 + done + return 1 +} + +is_last_partition_on_disk() { + local disk="$1" + local part_num="$2" + local last_part + + last_part="$(parted -ms "$disk" unit s print 2>/dev/null | awk -F: '$1 ~ /^[0-9]+$/ {last=$1} END {print last}')" + [ -n "$last_part" ] && [ "$part_num" = "$last_part" ] +} + +get_trailing_free_bytes() { + local disk="$1" + local part_num="$2" + local disk_bytes + local part_end_bytes + local free_bytes + + disk_bytes="$(blockdev --getsize64 "$disk" 2>/dev/null || true)" + part_end_bytes="$(parted -ms "$disk" unit B print 2>/dev/null | awk -F: -v p="$part_num" '$1==p {gsub("B","",$3); print $3; exit}')" + if [ -z "$disk_bytes" ] || [ -z "$part_end_bytes" ]; then + return 1 + fi + + free_bytes=$((disk_bytes - part_end_bytes - 1)) + if [ "$free_bytes" -lt 0 ]; then + free_bytes=0 + fi + echo "$free_bytes" +} + +show_disk_layout() { + local disk="$1" + local disk_bytes + + disk_bytes="$(blockdev --getsize64 "$disk" 2>/dev/null || true)" + if [ -n "$disk_bytes" ]; then + log_info "Full disk size: $(human_bytes "$disk_bytes")" + else + log_warn "Unable to determine full disk size for ${disk}." + fi + + echo "Current disk layout:" + if ! lsblk -i -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS "$disk" 2>/dev/null | sed 's/^/ /'; then + log_warn "Unable to show current partition layout for ${disk}." + fi +} + +detect_storage_stack() { + STORAGE_DETECTED=0 + DISK_DEV="" + PART_NUM="" + + require_cmd findmnt || return 1 + require_cmd lvs || return 1 + require_cmd pvs || return 1 + require_cmd lsblk || return 1 + + ROOT_SOURCE="$(findmnt -n -o SOURCE / || true)" + if [ -z "$ROOT_SOURCE" ] || [ ! -e "$ROOT_SOURCE" ]; then + log_warn "Unable to detect root device." + return 1 + fi + + VG_NAME="$(lvs --noheadings -o vg_name "$ROOT_SOURCE" 2>/dev/null | xargs || true)" + LV_NAME="$(lvs --noheadings -o lv_name "$ROOT_SOURCE" 2>/dev/null | xargs || true)" + if [ -z "$VG_NAME" ]; then + log_warn "Root does not appear to be on LVM." + return 1 + fi + + PV_NAME="$(pvs --noheadings -o pv_name --select "vg_name=${VG_NAME}" 2>/dev/null | head -n1 | xargs || true)" + if [ -z "$PV_NAME" ]; then + log_warn "Unable to detect LVM physical volume." + return 1 + fi + + if [ ! -b "$PV_NAME" ]; then + log_warn "Detected LVM physical volume is not a block device: ${PV_NAME}" + return 1 + fi + + local inferred + local inferred_disk="" + local inferred_part="" + inferred="$(infer_disk_part_from_partition "$PV_NAME" || true)" + if [ -n "$inferred" ]; then + inferred_disk="$(printf '%s\n' "$inferred" | sed -n '1p')" + inferred_part="$(printf '%s\n' "$inferred" | sed -n '2p')" + fi + + DISK_DEV="$inferred_disk" + PART_NUM="$inferred_part" + + if [ -z "$DISK_DEV" ]; then + local disk_parent + disk_parent="$(lsblk_attr "$PV_NAME" PKNAME)" + if [ -n "$disk_parent" ]; then + DISK_DEV="/dev/${disk_parent}" + else + DISK_DEV="$(lsblk -nro PATH,TYPE -s "$PV_NAME" 2>/dev/null | awk '$2=="disk" {print $1; exit}' | xargs || true)" + fi + fi + + if [ -z "$PART_NUM" ]; then + PART_NUM="$(lsblk_attr "$PV_NAME" PARTNUM)" + fi + + if [ -z "$DISK_DEV" ] || [ ! -b "$DISK_DEV" ] || [ -z "$PART_NUM" ] || ! [[ "$PART_NUM" =~ ^[0-9]+$ ]]; then + log_warn "Unable to detect disk device or partition number." + log_warn "Detected values: PV_NAME=${PV_NAME:-} DISK_DEV=${DISK_DEV:-} PART_NUM=${PART_NUM:-}" + return 1 + fi + + STORAGE_DETECTED=1 + return 0 +} + +resize_lvm() { + if [ "$STORAGE_DETECTED" -ne 1 ]; then + log_warn "Storage layout not detected; skipping resize." + return 0 + fi + + require_cmd parted || return 0 + require_cmd blockdev || return 0 + require_cmd pvresize || return 0 + require_cmd lvextend || return 0 + + log_info "Root LV: ${ROOT_SOURCE}" + log_info "VG: ${VG_NAME} | LV: ${LV_NAME}" + log_info "LVM PV: ${PV_NAME}" + log_info "Disk: ${DISK_DEV} | Partition number: ${PART_NUM}" + + if ! repair_gpt_backup_header "$DISK_DEV"; then + log_warn "Unable to repair or verify GPT backup header on ${DISK_DEV}; resize may fail." + fi + + if ! is_last_partition_on_disk "$DISK_DEV" "$PART_NUM"; then + log_warn "Partition ${PART_NUM} is not the last partition on ${DISK_DEV}. Automatic growth is skipped." + return 0 + fi + + local part_size_before + local free_bytes + + part_size_before="$(blockdev --getsize64 "$PV_NAME" 2>/dev/null || echo 0)" + free_bytes="$(get_trailing_free_bytes "$DISK_DEV" "$PART_NUM" || true)" + if [ -z "$free_bytes" ]; then + log_warn "Unable to determine free disk space." + return 0 + fi + + if [ "$free_bytes" -lt $((1024 * 1024)) ]; then + log_ok "No significant free space detected after the root partition." + return 0 + fi + + log_info "Unallocated space available: $(human_bytes "$free_bytes")" + show_disk_layout "$DISK_DEV" + if ! confirm "Extend partition, VG, and root LV now?" "yes"; then + log_warn "Skipped resize." + return 0 + fi + + log_info "Extending partition ${PV_NAME} to 100% of disk..." + if ! parted -s "$DISK_DEV" resizepart "$PART_NUM" 100%; then + log_warn "Partition resize failed." + return 0 + fi + + refresh_partition_table "$DISK_DEV" + if [ "$part_size_before" -gt 0 ] && ! wait_for_partition_growth "$PV_NAME" "$part_size_before"; then + log_warn "Kernel has not reported the new partition size yet; continuing anyway." + fi + + log_info "Resizing LVM physical volume..." + if ! pvresize "$PV_NAME"; then + log_warn "pvresize failed." + return 0 + fi + + log_info "Extending root LV to use all free space..." + if ! lvextend -l +100%FREE -r "$ROOT_SOURCE"; then + log_warn "lvextend failed." + return 0 + fi + + log_ok "Resize complete." +} + +parse_size_to_mib() { + local input="$1" + local normalized + normalized="$(echo "$input" | tr '[:upper:]' '[:lower:]' | xargs)" + if [[ "$normalized" =~ ^[0-9]+$ ]]; then + echo "$normalized" + return 0 + fi + if [[ "$normalized" =~ ^([0-9]+)(g|gb)$ ]]; then + echo $((BASH_REMATCH[1] * 1024)) + return 0 + fi + if [[ "$normalized" =~ ^([0-9]+)(m|mb)$ ]]; then + echo "${BASH_REMATCH[1]}" + return 0 + fi + return 1 +} + +setup_swap() { + require_cmd awk || return 0 + require_cmd fallocate || return 0 + require_cmd mkswap || return 0 + require_cmd swapon || return 0 + + local mem_kib + local mem_mib + local swap_mib + + mem_kib="$(awk '/MemTotal/ {print $2}' /proc/meminfo)" + mem_mib=$((mem_kib / 1024)) + + if [ "$mem_mib" -lt 2048 ]; then + swap_mib=$((mem_mib * 2)) + elif [ "$mem_mib" -lt 4096 ]; then + swap_mib=$mem_mib + else + swap_mib=$((mem_mib / 5)) + fi + + log_info "Detected RAM: $(mib_to_human "$mem_mib")" + log_info "Recommended swap size: $(mib_to_human "$swap_mib")" + + if ! confirm "Use recommended swap size?" "yes"; then + local custom + while true; do + custom="$(prompt_input "Enter custom swap size (MiB or GiB, e.g. 2048 or 4G)")" + if swap_mib="$(parse_size_to_mib "$custom")"; then + if [ "$swap_mib" -gt 0 ]; then + break + fi + fi + log_warn "Invalid size. Try again." + done + fi + + local swapfile="/swapfile" + if [ -e "$swapfile" ]; then + log_warn "Swap file already exists at ${swapfile}." + if ! confirm "Replace existing swap file?" "no"; then + log_warn "Skipped swap setup." + return 0 + fi + if swapon --show=NAME --noheadings | grep -qx "$swapfile"; then + swapoff "$swapfile" || true + fi + rm -f "$swapfile" + fi + + log_info "Creating swap file (${swap_mib} MiB) at ${swapfile}..." + fallocate -l "${swap_mib}M" "$swapfile" + chmod 600 "$swapfile" + mkswap "$swapfile" >/dev/null + swapon "$swapfile" + + if ! grep -qE "^[[:space:]]*${swapfile}[[:space:]]" /etc/fstab; then + echo "${swapfile} none swap sw 0 0" >> /etc/fstab + fi + + log_ok "Swap enabled." +} + +setup_tailscale() { + if ! require_cmd tailscale; then + log_warn "Tailscale is not installed; skipping." + return 0 + fi + + if ! confirm "Set up Tailscale now?" "yes"; then + log_warn "Skipped Tailscale setup." + return 0 + fi + + local server + local tags + local key + local tag_list="" + local t + + server="$(prompt_input "Tailscale/Headscale server URL" "https://vpn.s1q.dev")" + tags="$(prompt_input "Client tags (comma-separated)" "server")" + key="$(prompt_secret "Pre-authentication key")" + + if [ -n "$tags" ]; then + IFS=',' read -r -a tag_array <<<"$tags" + for t in "${tag_array[@]:-}"; do + t="$(echo "$t" | xargs)" + if [ -n "$t" ]; then + if [ -n "$tag_list" ]; then + tag_list+="," + fi + tag_list+="tag:${t}" + fi + done + fi + + log_info "Bringing up Tailscale..." + if [ -n "$tag_list" ]; then + tailscale up --login-server "$server" --authkey "$key" --ssh --advertise-tags "$tag_list" + else + tailscale up --login-server "$server" --authkey "$key" --ssh + fi + + log_ok "Tailscale setup complete." +} + +setup_crowdsec() { + if ! require_cmd cscli; then + log_warn "CrowdSec (cscli) not installed; skipping." + return 0 + fi + + if ! confirm "Set up CrowdSec now?" "yes"; then + log_warn "Skipped CrowdSec setup." + return 0 + fi + + local key + key="$(prompt_secret "Enrollment key")" + + log_info "Enrolling CrowdSec..." + if cscli console enroll -e "$key"; then + log_info "Restarting CrowdSec service..." + systemctl restart crowdsec || log_warn "Failed to restart crowdsec service." + log_ok "CrowdSec enrollment complete." + else + log_warn "CrowdSec enrollment failed." + fi +} + +prompt_reboot() { + log_warn "A reboot is strongly recommended after partition or swap changes." + if confirm "Reboot now?" "yes"; then + log_info "Rebooting..." + reboot + else + log_info "Please reboot later to ensure changes take effect." + fi +} + +welcome() { + section "Initial VM Setup" + log_info "This setup runs once and is fully interactive." + log_info "Hostname: $(hostname)" +} + +main() { + ensure_tty + ensure_root "$@" + welcome + + if detect_storage_stack; then + log_ok "Detected LVM storage layout." + else + log_warn "Storage layout detection incomplete; some steps may be skipped." + fi + + TASK_TITLES=() + TASK_FUNCS=() + + add_task "Resize LVM (if free space exists)" resize_lvm + add_task "Configure swap file" setup_swap + add_task "Configure Tailscale" setup_tailscale + add_task "Configure CrowdSec" setup_crowdsec + add_task "Reboot recommendation" prompt_reboot + + run_tasks + log_ok "Initial setup finished." +} + +main "$@" diff --git a/debian/13-trixie/http/preseed.cfg b/debian/13-trixie/http/preseed.cfg new file mode 100644 index 0000000..39a08dd --- /dev/null +++ b/debian/13-trixie/http/preseed.cfg @@ -0,0 +1,161 @@ +#_preseed_V1 + +### Localization +d-i debian-installer/locale string en_US.UTF-8 +d-i keyboard-configuration/xkb-keymap select us + +### Unattended +d-i auto-install/enable boolean true +d-i debconf/priority select critical +d-i debian-installer/framebuffer boolean false + +### Network +d-i netcfg/choose_interface select ens18 +d-i netcfg/get_hostname string debian-13-template +d-i netcfg/get_domain string +d-i netcfg/wireless_wep string +d-i netcfg/disable_dhcp boolean false + +### Root Password (no user) +d-i passwd/make-user boolean false +# Root password is set via kernel cmdline in debian-trixie.pkr.hcl; these lines are ignored but left here for reference: +# d-i passwd/root-password password "$PACKER_ROOT_PASS" +# d-i passwd/root-password-again password "$PACKER_ROOT_PASS" + +### Mirror / APT +d-i apt-setup/cdrom/set-first boolean false +d-i apt-setup/cdrom/set-next boolean false +d-i apt-setup/cdrom/set-failed boolean false + +d-i mirror/country string manual +d-i mirror/http/hostname string ftp.de.debian.org +d-i mirror/http/directory string /debian +d-i mirror/http/proxy string + +# If you want an explicit suite: +# d-i mirror/suite string trixie + +# Your extra repo line (updated to also include non-free-firmware for trixie) +d-i apt-setup/local0/repository string http://ftp.de.debian.org/debian/ trixie main contrib non-free non-free-firmware +popularity-contest popularity-contest/participate boolean false + +d-i apt-setup/contrib boolean true +d-i apt-setup/non-free boolean true +d-i apt-setup/non-free-firmware boolean true +d-i apt-setup/security_host string security.debian.org +d-i apt-setup/services-select multiselect security, updates + +### Timezone +d-i clock-setup/utc boolean true +d-i time/zone string UTC +d-i clock-setup/ntp boolean true + +### Storage (UEFI + /boot + LUKS/LVM) +# Pick first detected disk automatically +d-i preseed/early_command string \ + DISK="$(list-devices disk | head -n1)"; \ + debconf-set partman-auto/disk "$DISK"; + +# "crypto" = LVM within an encrypted partition [oai_citation:3‡Debian](https://www.debian.org/releases/stable/amd64/apbs04.en.html) +# "lvm" = LVM without encryption +d-i partman-auto/method string lvm +d-i partman-auto-lvm/guided_size string max +d-i partman-auto-lvm/new_vg_name string vg0 + +# Cleanup old metadata if present +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-md/device_remove_md boolean true + +# Confirmations +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-md/confirm boolean true +d-i partman-md/confirm_nooverwrite boolean true + +# # LUKS password +# # LUKS passphrase is set via kernel cmdline in debian-trixie.pkr.hcl; these lines are ignored but left here for reference: +# # d-i partman-crypto/passphrase password "$PACKER_LUKS_PASS" +# # d-i partman-crypto/passphrase-again password "$PACKER_LUKS_PASS" +# d-i partman-crypto/weak_passphrase boolean true +# d-i partman-crypto/confirm boolean true +# d-i partman-auto-crypto/erase_disks boolean false + +# Ensure GPT +d-i partman-partitioning/choose_label select gpt +d-i partman-partitioning/default_label string gpt + +# Force UEFI (if needed in your environment) +d-i partman-efi/non_efi_system boolean true + +# Do NOT go back to partitioning menu if a partition/LV has no filesystem +d-i partman-basicmethods/method_only boolean false + +# Accept installing without swap (answer "No" to the warning) +d-i partman-basicfilesystems/no_swap boolean false +d-i partman-basicfilesystems/no_swap seen true + +# Custom recipe +# NOTE: Avoid putting comments inside this expert_recipe block; d-i can ignore it. [oai_citation:4‡Unix & Linux Stack Exchange](https://unix.stackexchange.com/questions/796185/debian-preseed-install-auto-creates-swap) +d-i partman-auto/choose_recipe select luks-lvm +d-i partman-auto/expert_recipe string \ + luks-lvm :: \ + 1075 1075 1075 fat32 \ + $primary{ } \ + $iflabel{ gpt } \ + $reusemethod{ } \ + method{ efi } \ + format{ } \ + filesystem{ fat32 } \ + mountpoint{ /boot/efi } \ + . \ + 1075 1075 1075 ext4 \ + $primary{ } \ + $defaultignore{ } \ + method{ format } \ + format{ } \ + use_filesystem{ } \ + filesystem{ ext4 } \ + mountpoint{ /boot } \ + . \ + 25770 25770 -1 ext4 \ + $lvmok{ } \ + lv_name{ root } \ + method{ format } \ + format{ } \ + use_filesystem{ } \ + filesystem{ ext4 } \ + mountpoint{ / } \ + . + +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true + +### Software +d-i debconf/frontend select noninteractive +tasksel tasksel/first multiselect standard, ssh-server + +d-i pkgsel/include string cloud-init curl qemu-guest-agent sudo vim +d-i pkgsel/upgrade select full-upgrade +d-i pkgsel/update-policy select none +d-i pkgsel/updatedb boolean true + +### Bootloader +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i grub-installer/bootdev string default + +### Late command (single declaration; multiple commands chained) +# 1) Remove filler LV so vg0 has free space after install +# 2) Enable root ssh login (same intent as your original) +d-i preseed/late_command string \ + lvremove -f /dev/vg0/reserved || true; \ + in-target sed -i 's/^#PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config || true + +# Eject the installation media before rebooting +d-i cdrom-detect/eject boolean true +d-i cdrom-detect/eject seen true + +### Finish +d-i finish-install/reboot_in_progress note diff --git a/debian/13-trixie/scripts/crowdsec-configuration.sh b/debian/13-trixie/scripts/crowdsec-configuration.sh new file mode 100644 index 0000000..6ac3f7a --- /dev/null +++ b/debian/13-trixie/scripts/crowdsec-configuration.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euf -o pipefail + +export DEBIAN_FRONTEND=noninteractive + +# Enable write-ahead-logging (wal -- allowing more concurrency in SQLite that will improve performances in most scenarios.) +sed -i -E '/^db_config:/,/^[^[:space:]]/{s/^([[:space:]]*)type:[[:space:]]*sqlite$/&\ +\1use_wal: true/}' /etc/crowdsec/config.yaml diff --git a/debian/13-trixie/scripts/crowdsec-repo-setup.sh b/debian/13-trixie/scripts/crowdsec-repo-setup.sh new file mode 100644 index 0000000..7b2e815 --- /dev/null +++ b/debian/13-trixie/scripts/crowdsec-repo-setup.sh @@ -0,0 +1,370 @@ +#!/bin/sh +# +# Inspired from packagecloud installation scripts +# +# #MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Crowdsec repositories installation script +# +# This script: +# - Requires `root` or `sudo` privileges to run +# - Attempts to detect your Linux distribution and version and configure your +# package management system for you. +# - Installs dependencies and recommendations without asking for confirmation. +# - Is POSIX compliant and can be run using bash or any POSIX-compliant shell + + +unknown_os() { + echo "Unfortunately, your operating system distribution and version are not supported by this script." + echo + echo "You can override the OS detection by setting os= and dist= prior to running this script." + echo "You can find a list of supported OSes and distributions on our website: https://packagecloud.io/docs#os_distro_version" + echo + echo "For example, to force Ubuntu Trusty: os=ubuntu dist=trusty ./script.sh" + echo + echo "Please file an issue at https://github.com/crowdsecurity/crowdsec" + exit 1 +} + +detect_os() { + if [ -z "$os" ] && [ -z "$dist" ]; then + if [ -e /etc/os-release ]; then + . /etc/os-release + os=$ID + if [ "$os" = "poky" ]; then + dist="$VERSION_ID" + elif [ "$os" = "sles" ]; then + dist="$VERSION_ID" + os=opensuse + elif [ "$os" = "opensuse" ]; then + dist="$VERSION_ID" + elif [ "$os" = "opensuse-leap" ]; then + os=opensuse + dist="$VERSION_ID" + elif [ "$os" = "amzn" ]; then + dist="$VERSION_ID" + else + dist=$(echo "$VERSION_ID" | awk -F '.' '{ print $1 }') + fi + + elif command -v lsb_release >/dev/null; then + # get major version (e.g. '5' or '6') + dist=$(lsb_release -r | cut -f2 | awk -F '.' '{ print $1 }') + + # get os (e.g. 'centos', 'redhatenterpriseserver', etc) + os=$(lsb_release -i | cut -f2 | awk '{ print tolower($1) }') + + elif [ -e /etc/oracle-release ]; then + dist=$(cut -f5 --delimiter=' ' /etc/oracle-release | awk -F '.' '{ print $1 }') + os='ol' + + elif [ -e /etc/fedora-release ]; then + dist=$(cut -f3 --delimiter=' ' /etc/fedora-release) + os='fedora' + + elif [ -e /etc/redhat-release ]; then + os_hint=$(awk '{ print tolower($1) }' /etc/redhat-release) + if [ "$os_hint" = "centos" ]; then + dist=$(awk '{ print $3 }' /etc/redhat-release | awk -F '.' '{ print $1 }') + os='centos' + elif [ "$os_hint" = "scientific" ]; then + dist=$(awk '{ print $4 }' /etc/redhat-release | awk -F '.' '{ print $1 }') + os='scientific' + else + dist=$(awk '{ print tolower($7) }' /etc/redhat-release | cut -f1 --delimiter='.') + os='redhatenterpriseserver' + fi + + elif grep -q Amazon /etc/issue; then + dist='6' + os='aws' + else + unknown_os + fi + fi + + # remove whitespace from OS and dist name and transform to lowercase + os=$(echo "$os" | tr -d ' ' | tr '[:upper:]' '[:lower:]') + dist=$(echo "$dist" | tr -d ' ' | tr '[:upper:]' '[:lower:]') + + if [ -z "$dist" ]; then + echo "Detected operating system as $os." + else + echo "Detected operating system as $os/$dist." + fi + + if [ "$os" = "ol" ] || [ "$os" = "el" ] && [ "$dist" -gt 7 ]; then + _skip_pygpgme=1 + else + _skip_pygpgme=0 + fi + +} + +gpg_check_deb() { + echo "Checking for gpg..." + if command -v gpg >/dev/null; then + echo "Detected gpg..." + else + echo "Installing gnupg for GPG verification..." + if ! apt-get install -y gnupg; then + echo "Unable to install GPG! Your base system has a problem; please check your default OS's package repositories because GPG should work." + echo "Repository installation aborted." + echo + echo "Please file an issue at https://github.com/crowdsecurity/crowdsec" + exit 1 + fi + fi +} + +curl_check_deb() { + echo "Checking for curl..." + if command -v curl >/dev/null; then + echo "Detected curl..." + else + echo "Installing curl..." + + if apt-get install -q -y curl; then + echo "Unable to install curl! Your base system has a problem; please check your default OS's package repositories because curl should work." + echo "Repository installation aborted." + echo + echo "Please file an issue at https://github.com/crowdsecurity/crowdsec" + exit 1 + fi + fi +} + +curl_check_rpm() { + echo "Checking for curl..." + if command -v curl >/dev/null; then + echo "Detected curl..." + else + echo "Installing curl..." + yum install -d0 -e0 -y curl + fi +} + +curl_check_zypper() { + echo "Checking for curl..." + if command -v curl >/dev/null; then + echo "Detected curl..." + else + echo "Installing curl..." + zypper install curl + fi +} + +finalize_yum_repo() { + if [ "$_skip_pygpgme" = 0 ]; then + echo "Installing pygpgme to verify GPG signatures..." + yum install -y pygpgme --disablerepo="crowdsec_${repo}" + if ! rpm -qa | grep -qw pygpgme; then + echo + echo "WARNING: " + echo "The pygpgme package could not be installed. This means GPG verification is not possible for any RPM installed on your system. " + echo "To fix this, add a repository with pygpgme. Usually, the EPEL repository for your system will have this. " + echo "More information: https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F" + echo + + # set the repo_gpgcheck option to 0 + sed -i'' 's/repo_gpgcheck=1/repo_gpgcheck=0/' "/etc/yum.repos.d/crowdsec_${repo}.repo" + fi + fi + + echo "Installing yum-utils..." + yum install -y yum-utils --disablerepo="crowdsec_${repo}" + if ! rpm -qa | grep -qw yum-utils; then + echo + echo "WARNING: " + echo "The yum-utils package could not be installed. This means you may not be able to install source RPMs or use other yum features." + echo + fi + + echo "Generating yum cache for crowdsec..." + yum -q makecache -y --disablerepo='*' --enablerepo="crowdsec_${repo}" +} + +install_debian_keyring() { + if [ "$os" = "debian" ]; then + echo "Installing debian-archive-keyring which is needed for installing " + echo "apt-transport-https on many Debian systems." + apt-get install -y debian-archive-keyring >/dev/null 2>&1 + fi +} + +detect_apt_version() { + apt_version_full=$(apt-get -v | head -1 | awk '{ print $2 }') + apt_version_major=$(echo "$apt_version_full" | cut -d. -f1) + apt_version_minor=$(echo "$apt_version_full" | cut -d. -f2) + apt_version_modified="${apt_version_major}${apt_version_minor}0" + + echo "Detected apt version as $apt_version_full" +} + +main() { + if [ -z "$repo" ]; then + repo="crowdsec" + fi + + detect_os + case $os in + ubuntu | debian | raspbian | linuxmint) + detect_apt_version + gpg_check_deb + curl_check_deb + apt_source_path="/etc/apt/sources.list.d/crowdsec_${repo}.list" + pre_reqs="apt-transport-https ca-certificates curl" + if [ -f "$apt_source_path" ]; then + echo + echo "The file $apt_source_path already exists: overwriting it." + echo + fi + # needed dependencies + apt-get update -qq >/dev/null + #shellcheck disable=SC2086 + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $pre_reqs >/dev/null + # gpg keys + gpg_key_url="https://packagecloud.io/crowdsec/${repo}/gpgkey" + apt_keyrings_dir="/etc/apt/keyrings" + gpg_keyring_path="$apt_keyrings_dir/crowdsec_${repo}-archive-keyring.gpg" + gpg_key_path_old="/etc/apt/trusted.gpg.d/crowdsec_${repo}.gpg" + echo + echo "Importing packagecloud gpg key... " + echo + + # move gpg key to old path if apt version is older than 1.1 + if [ "$apt_version_modified" -lt 110 ]; then + curl -fsSL "$gpg_key_url" | gpg --dearmor >"$gpg_key_path_old" + # grant 644 permisions to gpg key path old + chmod 0644 "$gpg_key_path_old" + + # deletes the keyrings directory if it is empty + echo "Packagecloud gpg key imported to $gpg_key_path_old" + else + if [ ! -d "$apt_keyrings_dir" ]; then + install -d -m 0755 "$apt_keyrings_dir" + fi + # import the gpg key + curl -fsSL "$gpg_key_url" | gpg --dearmor >"$gpg_keyring_path" + # grant 644 permisions to gpg keyring path + chmod 0644 "$gpg_keyring_path" + + echo "Packagecloud gpg key imported to $gpg_keyring_path" + fi + echo + echo "Installing ${apt_source_path}..." + echo + echo "deb [signed-by=/etc/apt/keyrings/crowdsec_${repo}-archive-keyring.gpg] https://packagecloud.io/crowdsec/${repo}/any/ any main" >"$apt_source_path" + echo "deb-src [signed-by=/etc/apt/keyrings/crowdsec_${repo}-archive-keyring.gpg] https://packagecloud.io/crowdsec/${repo}/any/ any main" >>"$apt_source_path" + apt-get update -qq >/dev/null + + ;; + centos | rhel | fedora | redhatentrepriseserver | amzn | cloudlinux | almalinux | rocky | opensuse | ol) + if [ "$os" = "ol" ] && [ "$dist" = "7" ] || [ "$os" = "amzn" ] && [ "$dist" = "2" ]; then + rpm_repo_config_url="https://packagecloud.io/install/repositories/crowdsec/${repo}/config_file.repo?os=${os}&dist=${dist}&source=script" + else + rpm_repo_config_url="https://packagecloud.io/install/repositories/crowdsec/${repo}/config_file.repo?os=rpm_any&dist=rpm_any&source=script" + fi + if [ "$os" = "opensuse" ]; then + curl_check_zypper + rpm_repo_path=/etc/zypp/repos.d/crowdsec_${repo}.repo + else + curl_check_rpm + rpm_repo_path=/etc/yum.repos.d/crowdsec_${repo}.repo + fi + + echo "Downloading repository file: $rpm_repo_config_url" + + curl -sSf "$rpm_repo_config_url" >"$rpm_repo_path" + curl_exit_code=$? + if [ "$curl_exit_code" = "22" ]; then + echo + echo + echo "Unable to download repo config from: " + echo "$rpm_repo_config_url" + echo + echo "This usually happens if your operating system is not supported by " + echo "packagecloud.io, or this script's OS detection failed." + echo + echo "You can override the OS detection by setting os= and dist= prior to running this script." + echo "You can find a list of supported OSes and distributions on our website: https://packagecloud.io/docs#os_distro_version" + echo + echo "For example, to force CentOS 6: os=el dist=6 ./script.sh" + echo + echo "If you are running a supported OS, please file an issue at https://github.com/crowdsecurity/crowdsec." + [ -e "$rpm_repo_path" ] && rm "$rpm_repo_path" + exit 1 + elif [ "$curl_exit_code" = "35" ] || [ "$curl_exit_code" = "60" ]; then + echo + echo "curl is unable to connect to packagecloud.io over TLS when running: " + echo " curl $rpm_repo_config_url" + echo + echo "This is usually due to one of two things:" + echo + echo " 1.) Missing CA root certificates (make sure the ca-certificates package is installed)" + echo " 2.) An old version of libssl. Try upgrading libssl on your system to a more recent version" + echo + echo "Contact support@crowdsec.net with information about your system for help." + [ -e "$rpm_repo_path" ] && rm "$rpm_repo_path" + exit 1 + elif [ "$curl_exit_code" -gt "0" ]; then + echo + echo "Unable to run: " + echo " curl $rpm_repo_config_url" + echo + echo "Double check your curl installation and try again." + echo + echo "Please file an issue at https://github.com/crowdsecurity/crowdsec if you think the behavior is not intended" + [ -e "$rpm_repo_path" ] && rm "$rpm_repo_path" + exit 1 + else + echo "done." + fi + if [ "$os" = "opensuse" ]; then + zypper --gpg-auto-import-keys refresh "crowdsec_${repo}" + zypper --gpg-auto-import-keys refresh "crowdsec_${repo}-source" + else + echo "$os" + finalize_yum_repo + fi + ;; + *) + echo "Error This system is not supported (yet) by this script." + echo "Please have a look at documentation https://docs.crowdsec.net/ or" + echo "file an issue at https://github.com/crowdsecurity/crowdsec if you think" + echo "the behavior is not intended" + + exit 1 + ;; + esac + + echo +} + +if [ "$(id -u)" -ne 0 ]; then + echo "This script must be run as root" + echo + echo "file an issue at https://github.com/crowdsecurity/crowdsec if you think" + echo "the behavior is not intended" + exit 1 +fi + +main diff --git a/debian/13-trixie/scripts/tailscale.sh b/debian/13-trixie/scripts/tailscale.sh new file mode 100644 index 0000000..8ffd3f5 --- /dev/null +++ b/debian/13-trixie/scripts/tailscale.sh @@ -0,0 +1,726 @@ +#!/bin/sh +# Copyright (c) Tailscale Inc & contributors +# SPDX-License-Identifier: BSD-3-Clause +# +# This script detects the current operating system, and installs +# Tailscale according to that OS's conventions. +# +# Environment variables: +# TRACK: Set to "stable" or "unstable" (default: stable) +# TAILSCALE_VERSION: Pin to a specific version (e.g., "1.88.4") +# +# Examples: +# curl -fsSL https://tailscale.com/install.sh | sh +# curl -fsSL https://tailscale.com/install.sh | TAILSCALE_VERSION=1.88.4 sh +# curl -fsSL https://tailscale.com/install.sh | TRACK=unstable sh + +set -eu + +# All the code is wrapped in a main function that gets called at the +# bottom of the file, so that a truncated partial download doesn't end +# up executing half a script. +main() { + # Step 1: detect the current linux distro, version, and packaging system. + # + # We rely on a combination of 'uname' and /etc/os-release to find + # an OS name and version, and from there work out what + # installation method we should be using. + # + # The end result of this step is that the following three + # variables are populated, if detection was successful. + OS="" + VERSION="" + PACKAGETYPE="" + APT_KEY_TYPE="" # Only for apt-based distros + APT_SYSTEMCTL_START=false # Only needs to be true for Kali + TRACK="${TRACK:-stable}" + TAILSCALE_VERSION="${TAILSCALE_VERSION:-}" + + case "$TRACK" in + stable|unstable) + ;; + *) + echo "unsupported track $TRACK" + exit 1 + ;; + esac + + if [ -f /etc/os-release ]; then + # /etc/os-release populates a number of shell variables. We care about the following: + # - ID: the short name of the OS (e.g. "debian", "freebsd") + # - VERSION_ID: the numeric release version for the OS, if any (e.g. "18.04") + # - VERSION_CODENAME: the codename of the OS release, if any (e.g. "buster") + # - UBUNTU_CODENAME: if it exists, use instead of VERSION_CODENAME + . /etc/os-release + VERSION_MAJOR="${VERSION_ID:-}" + VERSION_MAJOR="${VERSION_MAJOR%%.*}" + case "$ID" in + ubuntu|pop|neon|zorin|tuxedo) + OS="ubuntu" + if [ "${UBUNTU_CODENAME:-}" != "" ]; then + VERSION="$UBUNTU_CODENAME" + else + VERSION="$VERSION_CODENAME" + fi + PACKAGETYPE="apt" + # Third-party keyrings became the preferred method of + # installation in Ubuntu 20.04. + if [ "$VERSION_MAJOR" -lt 20 ]; then + APT_KEY_TYPE="legacy" + else + APT_KEY_TYPE="keyring" + fi + ;; + debian) + OS="$ID" + VERSION="$VERSION_CODENAME" + PACKAGETYPE="apt" + # Third-party keyrings became the preferred method of + # installation in Debian 11 (Bullseye). + if [ -z "${VERSION_ID:-}" ]; then + # rolling release. If you haven't kept current, that's on you. + APT_KEY_TYPE="keyring" + # Parrot Security is a special case that uses ID=debian + elif [ "$NAME" = "Parrot Security" ]; then + # All versions new enough to have this behaviour prefer keyring + # and their VERSION_ID is not consistent with Debian. + APT_KEY_TYPE="keyring" + # They don't specify the Debian version they're based off in os-release + # but Parrot 6 is based on Debian 12 Bookworm. + VERSION=bookworm + elif [ "$VERSION_MAJOR" -lt 11 ]; then + APT_KEY_TYPE="legacy" + else + APT_KEY_TYPE="keyring" + fi + ;; + linuxmint) + if [ "${UBUNTU_CODENAME:-}" != "" ]; then + OS="ubuntu" + VERSION="$UBUNTU_CODENAME" + elif [ "${DEBIAN_CODENAME:-}" != "" ]; then + OS="debian" + VERSION="$DEBIAN_CODENAME" + else + OS="ubuntu" + VERSION="$VERSION_CODENAME" + fi + PACKAGETYPE="apt" + if [ "$VERSION_MAJOR" -lt 5 ]; then + APT_KEY_TYPE="legacy" + else + APT_KEY_TYPE="keyring" + fi + ;; + elementary) + OS="ubuntu" + VERSION="$UBUNTU_CODENAME" + PACKAGETYPE="apt" + if [ "$VERSION_MAJOR" -lt 6 ]; then + APT_KEY_TYPE="legacy" + else + APT_KEY_TYPE="keyring" + fi + ;; + industrial-os) + OS="debian" + PACKAGETYPE="apt" + if [ "$VERSION_MAJOR" -lt 5 ]; then + VERSION="buster" + APT_KEY_TYPE="legacy" + else + VERSION="bullseye" + APT_KEY_TYPE="keyring" + fi + ;; + parrot|mendel) + OS="debian" + PACKAGETYPE="apt" + if [ "$VERSION_MAJOR" -lt 5 ]; then + VERSION="buster" + APT_KEY_TYPE="legacy" + else + VERSION="bullseye" + APT_KEY_TYPE="keyring" + fi + ;; + galliumos) + OS="ubuntu" + PACKAGETYPE="apt" + VERSION="bionic" + APT_KEY_TYPE="legacy" + ;; + pureos|kaisen) + OS="debian" + PACKAGETYPE="apt" + VERSION="bullseye" + APT_KEY_TYPE="keyring" + ;; + raspbian) + OS="$ID" + VERSION="$VERSION_CODENAME" + PACKAGETYPE="apt" + # Third-party keyrings became the preferred method of + # installation in Raspbian 11 (Bullseye). + if [ "$VERSION_MAJOR" -lt 11 ]; then + APT_KEY_TYPE="legacy" + else + APT_KEY_TYPE="keyring" + fi + ;; + kali) + OS="debian" + PACKAGETYPE="apt" + APT_SYSTEMCTL_START=true + # Third-party keyrings became the preferred method of + # installation in Debian 11 (Bullseye), which Kali switched + # to in roughly 2021.x releases + if [ "$VERSION_MAJOR" -lt 2021 ]; then + # Kali VERSION_ID is "kali-rolling", which isn't distinguishing + VERSION="buster" + APT_KEY_TYPE="legacy" + else + VERSION="bullseye" + APT_KEY_TYPE="keyring" + fi + ;; + Deepin|deepin) # https://github.com/tailscale/tailscale/issues/7862 + OS="debian" + PACKAGETYPE="apt" + if [ "$VERSION_MAJOR" -lt 20 ]; then + APT_KEY_TYPE="legacy" + VERSION="buster" + else + APT_KEY_TYPE="keyring" + VERSION="bullseye" + fi + ;; + pika) + PACKAGETYPE="apt" + # All versions of PikaOS are new enough to prefer keyring + APT_KEY_TYPE="keyring" + # Older versions of PikaOS are based on Ubuntu rather than Debian + if [ "$VERSION_MAJOR" -lt 4 ]; then + OS="ubuntu" + VERSION="$UBUNTU_CODENAME" + else + OS="debian" + VERSION="$DEBIAN_CODENAME" + fi + ;; + sparky) + OS="debian" + PACKAGETYPE="apt" + VERSION="$DEBIAN_CODENAME" + APT_KEY_TYPE="keyring" + ;; + centos) + OS="$ID" + VERSION="$VERSION_MAJOR" + PACKAGETYPE="dnf" + if [ "$VERSION" = "7" ]; then + PACKAGETYPE="yum" + fi + ;; + ol) + OS="oracle" + VERSION="$VERSION_MAJOR" + PACKAGETYPE="dnf" + if [ "$VERSION" = "7" ]; then + PACKAGETYPE="yum" + fi + ;; + rhel|miraclelinux) + OS="$ID" + if [ "$ID" = "miraclelinux" ]; then + OS="rhel" + fi + VERSION="$VERSION_MAJOR" + PACKAGETYPE="dnf" + if [ "$VERSION" = "7" ]; then + PACKAGETYPE="yum" + fi + ;; + fedora) + OS="$ID" + VERSION="" + PACKAGETYPE="dnf" + ;; + rocky|almalinux|nobara|openmandriva|sangoma|risios|cloudlinux|alinux|fedora-asahi-remix|ultramarine) + OS="fedora" + VERSION="" + PACKAGETYPE="dnf" + ;; + amzn) + OS="amazon-linux" + VERSION="$VERSION_ID" + PACKAGETYPE="yum" + ;; + xenenterprise) + OS="centos" + VERSION="$VERSION_MAJOR" + PACKAGETYPE="yum" + ;; + opensuse-leap|sles) + OS="opensuse" + VERSION="leap/$VERSION_ID" + PACKAGETYPE="zypper" + ;; + opensuse-tumbleweed) + OS="opensuse" + VERSION="tumbleweed" + PACKAGETYPE="zypper" + ;; + sle-micro-rancher) + OS="opensuse" + VERSION="leap/15.4" + PACKAGETYPE="zypper" + ;; + arch|archarm|endeavouros|blendos|garuda|archcraft|cachyos) + OS="arch" + VERSION="" # rolling release + PACKAGETYPE="pacman" + ;; + manjaro|manjaro-arm|biglinux) + OS="manjaro" + VERSION="" # rolling release + PACKAGETYPE="pacman" + ;; + alpine) + OS="$ID" + VERSION="$VERSION_ID" + PACKAGETYPE="apk" + ;; + postmarketos) + OS="alpine" + VERSION="$VERSION_ID" + PACKAGETYPE="apk" + ;; + nixos) + echo "Please add Tailscale to your NixOS configuration directly:" + echo + echo "services.tailscale.enable = true;" + exit 1 + ;; + bazzite) + echo "Bazzite comes with Tailscale installed by default." + echo "Please enable Tailscale by running the following commands as root:" + echo + echo "ujust enable-tailscale" + echo "tailscale up" + exit 1 + ;; + void) + OS="$ID" + VERSION="" # rolling release + PACKAGETYPE="xbps" + ;; + gentoo) + OS="$ID" + VERSION="" # rolling release + PACKAGETYPE="emerge" + ;; + freebsd) + OS="$ID" + VERSION="$VERSION_MAJOR" + PACKAGETYPE="pkg" + ;; + osmc) + OS="debian" + PACKAGETYPE="apt" + VERSION="bullseye" + APT_KEY_TYPE="keyring" + ;; + photon) + OS="photon" + VERSION="$VERSION_MAJOR" + PACKAGETYPE="tdnf" + ;; + steamos) + echo "To install Tailscale on SteamOS, please follow the instructions here:" + echo "https://github.com/tailscale-dev/deck-tailscale" + exit 1 + ;; + + # TODO: wsl? + # TODO: synology? qnap? + esac + fi + + # If we failed to detect something through os-release, consult + # uname and try to infer things from that. + if [ -z "$OS" ]; then + if type uname >/dev/null 2>&1; then + case "$(uname)" in + FreeBSD) + # FreeBSD before 12.2 doesn't have + # /etc/os-release, so we wouldn't have found it in + # the os-release probing above. + OS="freebsd" + VERSION="$(freebsd-version | cut -f1 -d.)" + PACKAGETYPE="pkg" + ;; + OpenBSD) + OS="openbsd" + VERSION="$(uname -r)" + PACKAGETYPE="" + ;; + Darwin) + OS="macos" + VERSION="$(sw_vers -productVersion | cut -f1-2 -d.)" + PACKAGETYPE="appstore" + ;; + Linux) + OS="other-linux" + VERSION="" + PACKAGETYPE="" + ;; + esac + fi + fi + + # Ideally we want to use curl, but on some installs we + # only have wget. Detect and use what's available. + CURL= + if type curl >/dev/null; then + CURL="curl -fsSL" + elif type wget >/dev/null; then + CURL="wget -q -O-" + fi + if [ -z "$CURL" ]; then + echo "The installer needs either curl or wget to download files." + echo "Please install either curl or wget to proceed." + exit 1 + fi + + TEST_URL="https://pkgs.tailscale.com/" + RC=0 + TEST_OUT=$($CURL "$TEST_URL" 2>&1) || RC=$? + if [ $RC != 0 ]; then + echo "The installer cannot reach $TEST_URL" + echo "Please make sure that your machine has internet access." + echo "Test output:" + echo $TEST_OUT + exit 1 + fi + + # Step 2: having detected an OS we support, is it one of the + # versions we support? + OS_UNSUPPORTED= + case "$OS" in + ubuntu|debian|raspbian|centos|oracle|rhel|amazon-linux|opensuse|photon) + # Check with the package server whether a given version is supported. + URL="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/installer-supported" + $CURL "$URL" 2> /dev/null | grep -q OK || OS_UNSUPPORTED=1 + ;; + fedora) + # All versions supported, no version checking required. + ;; + arch) + # Rolling release, no version checking needed. + ;; + manjaro) + # Rolling release, no version checking needed. + ;; + alpine) + # All versions supported, no version checking needed. + # TODO: is that true? When was tailscale packaged? + ;; + void) + # Rolling release, no version checking needed. + ;; + gentoo) + # Rolling release, no version checking needed. + ;; + freebsd) + if [ "$VERSION" != "12" ] && \ + [ "$VERSION" != "13" ] && \ + [ "$VERSION" != "14" ] && \ + [ "$VERSION" != "15" ] + then + OS_UNSUPPORTED=1 + fi + ;; + openbsd) + OS_UNSUPPORTED=1 + ;; + macos) + # We delegate macOS installation to the app store, it will + # perform version checks for us. + ;; + other-linux) + OS_UNSUPPORTED=1 + ;; + *) + OS_UNSUPPORTED=1 + ;; + esac + if [ "$OS_UNSUPPORTED" = "1" ]; then + case "$OS" in + other-linux) + echo "Couldn't determine what kind of Linux is running." + echo "You could try the static binaries at:" + echo "https://pkgs.tailscale.com/$TRACK/#static" + ;; + "") + echo "Couldn't determine what operating system you're running." + ;; + *) + echo "$OS $VERSION isn't supported by this script yet." + ;; + esac + echo + echo "If you'd like us to support your system better, please email support@tailscale.com" + echo "and tell us what OS you're running." + echo + echo "Please include the following information we gathered from your system:" + echo + echo "OS=$OS" + echo "VERSION=$VERSION" + echo "PACKAGETYPE=$PACKAGETYPE" + if type uname >/dev/null 2>&1; then + echo "UNAME=$(uname -a)" + else + echo "UNAME=" + fi + echo + if [ -f /etc/os-release ]; then + cat /etc/os-release + else + echo "No /etc/os-release" + fi + exit 1 + fi + + # Step 3: work out if we can run privileged commands, and if so, + # how. + CAN_ROOT= + SUDO= + if [ "$(id -u)" = 0 ]; then + CAN_ROOT=1 + SUDO="" + elif type sudo >/dev/null; then + CAN_ROOT=1 + SUDO="sudo" + elif type doas >/dev/null; then + CAN_ROOT=1 + SUDO="doas" + fi + if [ "$CAN_ROOT" != "1" ]; then + echo "This installer needs to run commands as root." + echo "We tried looking for 'sudo' and 'doas', but couldn't find them." + echo "Either re-run this script as root, or set up sudo/doas." + exit 1 + fi + + + # Step 4: run the installation. + OSVERSION="$OS" + [ "$VERSION" != "" ] && OSVERSION="$OSVERSION $VERSION" + + # Prepare package name with optional version + PACKAGE_NAME="tailscale" + if [ -n "$TAILSCALE_VERSION" ]; then + echo "Installing Tailscale $TAILSCALE_VERSION for $OSVERSION, using method $PACKAGETYPE" + else + echo "Installing Tailscale for $OSVERSION, using method $PACKAGETYPE" + fi + case "$PACKAGETYPE" in + apt) + export DEBIAN_FRONTEND=noninteractive + if [ "$APT_KEY_TYPE" = "legacy" ] && ! type gpg >/dev/null; then + $SUDO apt-get update + $SUDO apt-get install -y gnupg + fi + + set -x + $SUDO mkdir -p --mode=0755 /usr/share/keyrings + case "$APT_KEY_TYPE" in + legacy) + $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.asc" | $SUDO apt-key add - + $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list + $SUDO chmod 0644 /etc/apt/sources.list.d/tailscale.list + ;; + keyring) + $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.noarmor.gpg" | $SUDO tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null + $SUDO chmod 0644 /usr/share/keyrings/tailscale-archive-keyring.gpg + $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.tailscale-keyring.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list + $SUDO chmod 0644 /etc/apt/sources.list.d/tailscale.list + ;; + esac + $SUDO apt-get update + if [ -n "$TAILSCALE_VERSION" ]; then + $SUDO apt-get install -y "tailscale=$TAILSCALE_VERSION" tailscale-archive-keyring + else + $SUDO apt-get install -y tailscale tailscale-archive-keyring + fi + if [ "$APT_SYSTEMCTL_START" = "true" ]; then + $SUDO systemctl enable --now tailscaled + $SUDO systemctl start tailscaled + fi + set +x + ;; + yum) + set -x + $SUDO yum install yum-utils -y + $SUDO yum-config-manager -y --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" + if [ -n "$TAILSCALE_VERSION" ]; then + $SUDO yum install "tailscale-$TAILSCALE_VERSION" -y + else + $SUDO yum install tailscale -y + fi + $SUDO systemctl enable --now tailscaled + set +x + ;; + dnf) + # DNF 5 has a different argument format; determine which one we have. + DNF_VERSION="3" + if LANG=C.UTF-8 dnf --version | grep -q '^dnf5 version'; then + DNF_VERSION="5" + fi + + # The 'config-manager' plugin wasn't implemented when + # DNF5 was released; detect that and use the old + # version if necessary. + if [ "$DNF_VERSION" = "5" ]; then + set -x + $SUDO dnf install -y 'dnf-command(config-manager)' && DNF_HAVE_CONFIG_MANAGER=1 || DNF_HAVE_CONFIG_MANAGER=0 + set +x + + if [ "$DNF_HAVE_CONFIG_MANAGER" != "1" ]; then + if type dnf-3 >/dev/null; then + DNF_VERSION="3" + else + echo "dnf 5 detected, but 'dnf-command(config-manager)' not available and dnf-3 not found" + exit 1 + fi + fi + fi + + set -x + if [ "$DNF_VERSION" = "3" ]; then + $SUDO dnf install -y 'dnf-command(config-manager)' + $SUDO dnf config-manager --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" + elif [ "$DNF_VERSION" = "5" ]; then + # Already installed config-manager, above. + $SUDO dnf config-manager addrepo --overwrite --from-repofile="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" + else + echo "unexpected: unknown dnf version $DNF_VERSION" + exit 1 + fi + if [ -n "$TAILSCALE_VERSION" ]; then + $SUDO dnf install -y "tailscale-$TAILSCALE_VERSION" + else + $SUDO dnf install -y tailscale + fi + $SUDO systemctl enable --now tailscaled + set +x + ;; + tdnf) + set -x + curl -fsSL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" > /etc/yum.repos.d/tailscale.repo + if [ -n "$TAILSCALE_VERSION" ]; then + $SUDO tdnf install -y "tailscale-$TAILSCALE_VERSION" + else + $SUDO tdnf install -y tailscale + fi + $SUDO systemctl enable --now tailscaled + set +x + ;; + zypper) + set -x + $SUDO rpm --import "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/repo.gpg" + $SUDO zypper --non-interactive ar -g -r "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" + $SUDO zypper --non-interactive --gpg-auto-import-keys refresh + if [ -n "$TAILSCALE_VERSION" ]; then + $SUDO zypper --non-interactive install "tailscale=$TAILSCALE_VERSION" + else + $SUDO zypper --non-interactive install tailscale + fi + $SUDO systemctl enable --now tailscaled + set +x + ;; + pacman) + set -x + if [ -n "$TAILSCALE_VERSION" ]; then + echo "Warning: Arch Linux maintains their own Tailscale package. Version pinning may not work as expected, as the target version may no longer be available." + $SUDO pacman -S "tailscale=$TAILSCALE_VERSION" --noconfirm + else + $SUDO pacman -S tailscale --noconfirm + fi + $SUDO systemctl enable --now tailscaled + set +x + ;; + pkg) + set -x + if [ -n "$TAILSCALE_VERSION" ]; then + echo "Warning: FreeBSD maintains their own Tailscale package. Version pinning may not work as expected, as the target version may no longer be available." + $SUDO pkg install --yes "tailscale-$TAILSCALE_VERSION" + else + $SUDO pkg install --yes tailscale + fi + $SUDO service tailscaled enable + $SUDO service tailscaled start + set +x + ;; + apk) + set -x + if ! grep -Eq '^http.*/community$' /etc/apk/repositories; then + if type setup-apkrepos >/dev/null; then + $SUDO setup-apkrepos -c -1 + else + echo "installing tailscale requires the community repo to be enabled in /etc/apk/repositories" + exit 1 + fi + fi + if [ -n "$TAILSCALE_VERSION" ]; then + echo "Warning: Alpine Linux maintains their own Tailscale package. Version pinning may not work as expected, as the target version may no longer be available." + $SUDO apk add "tailscale=$TAILSCALE_VERSION" + else + $SUDO apk add tailscale + fi + $SUDO rc-update add tailscale + $SUDO rc-service tailscale start + set +x + ;; + xbps) + set -x + if [ -n "$TAILSCALE_VERSION" ]; then + echo "Warning: Void Linux maintains their own Tailscale package. Version pinning may not work as expected, as the target version may no longer be available." + $SUDO xbps-install "tailscale-$TAILSCALE_VERSION" -y + else + $SUDO xbps-install tailscale -y + fi + set +x + ;; + emerge) + set -x + if [ -n "$TAILSCALE_VERSION" ]; then + echo "Warning: Gentoo maintains their own Tailscale package. Version pinning may not work as expected, as the target version may no longer be available." + $SUDO emerge --ask=n "=net-vpn/tailscale-$TAILSCALE_VERSION" + else + $SUDO emerge --ask=n net-vpn/tailscale + fi + set +x + ;; + appstore) + set -x + open "https://apps.apple.com/us/app/tailscale/id1475387142" + set +x + ;; + *) + echo "unexpected: unknown package type $PACKAGETYPE" + exit 1 + ;; + esac + + echo "Installation complete! Log in to start using Tailscale by running:" + echo + if [ -z "$SUDO" ]; then + echo "tailscale up" + else + echo "$SUDO tailscale up" + fi +} + +main diff --git a/debian/13-trixie/variables-common.pkr.hcl b/debian/13-trixie/variables-common.pkr.hcl new file mode 120000 index 0000000..f97f3d6 --- /dev/null +++ b/debian/13-trixie/variables-common.pkr.hcl @@ -0,0 +1 @@ +../../variables-common.pkr.hcl \ No newline at end of file diff --git a/debian/13-trixie/variables.pkr.hcl b/debian/13-trixie/variables.pkr.hcl new file mode 100644 index 0000000..ede3f32 --- /dev/null +++ b/debian/13-trixie/variables.pkr.hcl @@ -0,0 +1,67 @@ +# Variables +variable "template_vm_id" { + type = string + default = "65000" + description = "The VM ID of the Proxmox template to use for building the image" +} + +variable "mac_address" { + type = string + default = "BC:24:11:00:13:37" + description = "The MAC address to assign to the VM's network adapter" +} + +variable "iso_url" { + type = string + default = "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.4.0-amd64-netinst.iso" + description = "The URL to download the Debian 13 Trixie ISO" +} + +variable "iso_checksum" { + type = string + default = "file:https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA256SUMS" + description = "ISO checksum (SHA256 or checksum file URL)" +} + +variable "disk_storage_pool" { + type = string + default = "ceph-pool" + description = "The Proxmox storage pool to use for the VM disk" +} + +variable "iso_storage_pool" { + type = string + default = "cephfs" + description = "The Proxmox storage pool to use for the ISO" +} + +variable "template_cpu_type" { + type = string + default = "host" + description = "The CPU type for the Proxmox template" +} + +variable "network_bridge" { + type = string + default = "vmbr108" + description = "The network bridge to attach the VM to" +} + +variable "proxmox_node" { + type = string + default = "pve1" + description = "The Proxmox node to use for building the image" +} + +variable "install_finished_inform_port" { + type = string + default = "10000" + description = "The server port to inform when installation is finished" +} + + + +# local values +local "timestamp" { + expression = formatdate("YYYYMMDD-hhMMss-ZZZ", timestamp()) +} diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..ddf0875 --- /dev/null +++ b/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mise.toml b/mise.toml index fddb401..9f90a58 100644 --- a/mise.toml +++ b/mise.toml @@ -21,10 +21,10 @@ flag "-i --install-finished-inform-port " help="Server port to inform when ''' run = ''' mise run lint ${usage_dir?} -[[ -z "${usage_install_finished_inform_port}" ]] && packer build ${usage_dir?} +[[ -z "${usage_install_finished_inform_port}" ]] && packer build ${usage_dir?} || true [[ -n "${usage_install_finished_inform_port}" ]] && packer build \ -var "install_finished_inform_port=${usage_install_finished_inform_port?}" \ - ${usage_dir?} + ${usage_dir?} || true ''' [tasks.build-luks] diff --git a/variables-common.pkr.hcl b/variables-common.pkr.hcl index 22aab38..a6791e4 100644 --- a/variables-common.pkr.hcl +++ b/variables-common.pkr.hcl @@ -12,7 +12,7 @@ variable "proxmox_skip_tls_verify" { variable "source_proxmox_http_interface" { type = string - default = "en18" + default = "en17" description = "The network interface to use for the Proxmox HTTP source" }