diff --git a/debian/13-trixie-luks/debian-trixie.pkr.hcl b/debian/13-trixie-luks/debian-trixie.pkr.hcl index 1c60656..5540149 100644 --- a/debian/13-trixie-luks/debian-trixie.pkr.hcl +++ b/debian/13-trixie-luks/debian-trixie.pkr.hcl @@ -20,7 +20,7 @@ source "proxmox-iso" "debian-13-trixie-luks" { node = "${var.proxmox_node}" vm_id = "${var.template_vm_id}" vm_name = "debian-13-trixie-luks-${local.timestamp}" - template_description = "Debian 13 Trixie, LUKS encrypted, built with Packer on ${local.timestamp}\n\nLUKS default passphrase: `${var.default_luks_passphrase}`" + template_description = "Debian 13 Trixie, LUKS encrypted, built with Packer on ${local.timestamp}" os = "l26" qemu_agent = true @@ -45,10 +45,6 @@ source "proxmox-iso" "debian-13-trixie-luks" { efi_type = "4m" } - serials = [ - "socket" - ] - # Download ISO boot_iso { type = "scsi" @@ -79,7 +75,6 @@ source "proxmox-iso" "debian-13-trixie-luks" { "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}' ", "partman-crypto/passphrase='${var.default_luks_passphrase}' ", @@ -106,38 +101,21 @@ build { name = "debian-13-trixie-luks-image" sources = ["source.proxmox-iso.debian-13-trixie-luks"] - # Install dependencies and default packages + # Provisioning the VM Template for Cloud-Init Integration in Proxmox #1 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 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" + "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" ] } - # Install Tailscale - provisioner "shell" { - script = "debian/13-trixie-luks/scripts/tailscale.sh" - } - - # Setup CrowdSec Repo - provisioner "shell" { - script = "debian/13-trixie-luks/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-luks/scripts/crowdsec-configuration.sh" - } - # Provisioning the VM Template for Cloud-Init Integration in Proxmox #2 provisioner "file" { source = "debian/13-trixie-luks/files/99-pve.cfg" @@ -159,62 +137,4 @@ build { source = "debian/13-trixie-luks/files/debian.sources" destination = "/etc/apt/sources.list.d/debian.sources" } - - provisioner "file" { - source = "debian/13-trixie-luks/files/90-initial-login-setup.sh" - destination = "/etc/profile.d/90-initial-login-setup.sh" - } - - provisioner "file" { - source = "debian/13-trixie-luks/files/initial-setup.sh" - destination = "/usr/local/bin/initial-setup.sh" - } - - provisioner "shell" { - inline = [ - "chmod +x /usr/local/bin/initial-setup.sh" - ] - } - - # Install Clevis - provisioner "shell" { - inline = [ - "apt-get update", - "apt-get install -y clevis clevis-luks clevis-initramfs" - ] - } - - # 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-luks/files/90-initial-login-setup.sh b/debian/13-trixie-luks/files/90-initial-login-setup.sh deleted file mode 100644 index 2419845..0000000 --- a/debian/13-trixie-luks/files/90-initial-login-setup.sh +++ /dev/null @@ -1,26 +0,0 @@ -#! /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 - #DEBUG touch SENTINEL before running the setup script to prevent infinite loops during development - sudo /usr/local/bin/initial-setup.sh - sudo touch "$SENTINEL" -fi diff --git a/debian/13-trixie-luks/files/initial-setup.sh b/debian/13-trixie-luks/files/initial-setup.sh deleted file mode 100644 index bc7fc2f..0000000 --- a/debian/13-trixie-luks/files/initial-setup.sh +++ /dev/null @@ -1,740 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -SCRIPT_NAME="$(basename "$0")" -TASK_INDEX=0 -TASK_TOTAL=0 -TEMP_FILES=() -STORAGE_DETECTED=0 - -ROOT_SOURCE="" -VG_NAME="" -LV_NAME="" -PV_NAME="" -LUKS_DEV="" -LUKS_NAME="" -LUKS_PART="" -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}" -} - -add_temp_file() { - TEMP_FILES+=("$1") -} - -cleanup() { - local file - for file in "${TEMP_FILES[@]:-}"; do - [ -f "$file" ] && rm -f "$file" - done -} -trap cleanup EXIT - -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 -} - -resolve_luks_backing_partition() { - local mapper_path="$1" - local mapper_name="$2" - local mapper_name_alt="$3" - local part="" - - if command -v cryptsetup >/dev/null 2>&1; then - part="$(cryptsetup status "$mapper_name" 2>/dev/null | awk '/^[[:space:]]*device:/ {print $2; exit}' | xargs || true)" - if [ -z "$part" ] && [ -n "$mapper_name_alt" ] && [ "$mapper_name_alt" != "$mapper_name" ]; then - part="$(cryptsetup status "$mapper_name_alt" 2>/dev/null | awk '/^[[:space:]]*device:/ {print $2; exit}' | xargs || true)" - fi - if [ -z "$part" ]; then - part="$(cryptsetup status "$mapper_path" 2>/dev/null | awk '/^[[:space:]]*device:/ {print $2; exit}' | xargs || true)" - fi - fi - - if [ -z "$part" ]; then - part="$(lsblk -nro PATH,TYPE -s "$mapper_path" 2>/dev/null | awk '$2=="part" {print $1; exit}' | xargs || true)" - fi - - printf '%s' "$part" -} - -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 -} - -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" -} - -resize_open_luks_mapping() { - if [ -n "$LUKS_NAME" ] && cryptsetup resize "$LUKS_NAME" >/dev/null 2>&1; then - return 0 - fi - cryptsetup resize "$LUKS_DEV" >/dev/null 2>&1 -} - -detect_storage_stack() { - STORAGE_DETECTED=0 - LUKS_NAME="" - LUKS_PART="" - DISK_DEV="" - PART_NUM="" - - require_cmd findmnt || return 1 - require_cmd lvs || return 1 - require_cmd pvs || return 1 - require_cmd lsblk || return 1 - require_cmd cryptsetup || 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 - - LUKS_DEV="$PV_NAME" - LUKS_NAME="$(basename "$LUKS_DEV")" - local mapper_name_alt - mapper_name_alt="$(lsblk_attr "$LUKS_DEV" NAME)" - - local luks_type - luks_type="$(lsblk_attr "$LUKS_DEV" TYPE)" - if [ "$luks_type" != "crypt" ]; then - log_warn "LVM PV is not on a LUKS device (type: ${luks_type:-unknown}). LUKS resize will be skipped." - return 1 - fi - - LUKS_PART="$(resolve_luks_backing_partition "$LUKS_DEV" "$LUKS_NAME" "$mapper_name_alt")" - if [ -z "$LUKS_PART" ] || [ ! -b "$LUKS_PART" ]; then - log_warn "Unable to detect LUKS backing partition." - return 1 - fi - - local inferred - local inferred_disk="" - local inferred_part="" - inferred="$(infer_disk_part_from_partition "$LUKS_PART" || 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 "$LUKS_PART" PKNAME)" - if [ -n "$disk_parent" ]; then - DISK_DEV="/dev/${disk_parent}" - else - DISK_DEV="$(lsblk -nro PATH,TYPE -s "$LUKS_PART" 2>/dev/null | awk '$2=="disk" {print $1; exit}' | xargs || true)" - fi - fi - - if [ -z "$PART_NUM" ]; then - PART_NUM="$(lsblk_attr "$LUKS_PART" 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: LUKS_PART=${LUKS_PART:-} DISK_DEV=${DISK_DEV:-} PART_NUM=${PART_NUM:-}" - return 1 - fi - - STORAGE_DETECTED=1 - return 0 -} - -resize_lvm_on_luks() { - 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 cryptsetup || 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 "LUKS device: ${LUKS_DEV}" - log_info "LUKS partition: ${LUKS_PART}" - log_info "Disk: ${DISK_DEV} | Partition number: ${PART_NUM}" - [ -n "$LUKS_NAME" ] && log_info "LUKS mapper name: ${LUKS_NAME}" - - 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 "$LUKS_PART" 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")" - if ! confirm "Extend partition, LUKS device, VG, and root LV now?" "yes"; then - log_warn "Skipped resize." - return 0 - fi - - log_info "Extending partition ${LUKS_PART} 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 "$LUKS_PART" "$part_size_before"; then - log_warn "Kernel has not reported the new partition size yet; continuing anyway." - fi - - log_info "Resizing LUKS device..." - if ! resize_open_luks_mapping; then - log_warn "LUKS resize failed." - return 0 - fi - - log_info "Resizing LVM physical volume..." - if ! pvresize "$LUKS_DEV"; 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." -} - -change_luks_passphrase() { - if [ "$STORAGE_DETECTED" -ne 1 ]; then - log_warn "Storage layout not detected; skipping LUKS passphrase change." - return 0 - fi - if [ -z "$LUKS_PART" ] || [ ! -e "$LUKS_PART" ]; then - log_warn "LUKS partition not found; skipping." - return 0 - fi - require_cmd cryptsetup || return 0 - - if ! confirm "Change LUKS passphrase in slot 0 now?" "yes"; then - log_warn "Skipped LUKS passphrase change." - return 0 - fi - - local old_pass - local new_pass - local tmp_old - local tmp_new - - old_pass="$(prompt_secret "Enter current LUKS passphrase")" - new_pass="$(prompt_secret_confirm "Enter new LUKS passphrase" "Confirm new LUKS passphrase")" - - tmp_old="$(mktemp)" - tmp_new="$(mktemp)" - add_temp_file "$tmp_old" - add_temp_file "$tmp_new" - - printf '%s' "$old_pass" >"$tmp_old" - printf '%s' "$new_pass" >"$tmp_new" - - log_info "Updating LUKS passphrase in slot 0..." - if cryptsetup luksChangeKey --batch-mode --key-slot 0 --key-file "$tmp_old" "$LUKS_PART" "$tmp_new"; then - log_ok "LUKS passphrase updated." - else - log_warn "Failed to update LUKS passphrase." - fi -} - -setup_clevis() { - log_info "Clevis/Tang setup is not implemented in this template yet." - if confirm "Would you like to configure Clevis with a Tang server now? (will be skipped)" "no"; then - local tang - tang="$(prompt_input "Tang server URL" "http://tang.int.r3w.de")" - log_warn "Clevis setup for ${tang} is not implemented yet. Skipping." - else - log_info "Skipping Clevis setup." - fi -} - -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 on LUKS storage layout." - else - log_warn "Storage layout detection incomplete; some steps may be skipped." - fi - - TASK_TITLES=() - TASK_FUNCS=() - - add_task "Resize LVM on LUKS (if free space exists)" resize_lvm_on_luks - add_task "Configure swap file" setup_swap - add_task "Change LUKS passphrase (slot 0)" change_luks_passphrase - add_task "Clevis/Tang setup (placeholder)" setup_clevis - 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-luks/http/meta-data b/debian/13-trixie-luks/http/meta-data new file mode 100644 index 0000000..e69de29 diff --git a/debian/13-trixie-luks/http/preseed.cfg b/debian/13-trixie-luks/http/preseed.cfg index b93751e..d139d75 100644 --- a/debian/13-trixie-luks/http/preseed.cfg +++ b/debian/13-trixie-luks/http/preseed.cfg @@ -116,7 +116,7 @@ d-i partman-auto/expert_recipe string \ filesystem{ ext4 } \ mountpoint{ /boot } \ . \ - 25770 25770 -1 ext4 \ + 25770 25770 25770 ext4 \ $lvmok{ } \ lv_name{ root } \ method{ format } \ @@ -124,6 +124,11 @@ d-i partman-auto/expert_recipe string \ use_filesystem{ } \ filesystem{ ext4 } \ mountpoint{ / } \ + . \ + 1 10000 -1 ext4 \ + $lvmok{ } \ + lv_name{ reserved } \ + method{ keep } \ . d-i partman-partitioning/confirm_write_new_label boolean true @@ -135,7 +140,7 @@ d-i partman/confirm_nooverwrite boolean true 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/include string qemu-guest-agent cloud-init curl vim d-i pkgsel/upgrade select full-upgrade d-i pkgsel/update-policy select none d-i pkgsel/updatedb boolean true diff --git a/debian/13-trixie-luks/http/user-data b/debian/13-trixie-luks/http/user-data new file mode 100644 index 0000000..44d048d --- /dev/null +++ b/debian/13-trixie-luks/http/user-data @@ -0,0 +1,32 @@ +#cloud-config +autoinstall: + version: 1 + locale: en_US + keyboard: + layout: us + ssh: + install-server: true + allow-pw: false + disable_root: true + ssh_quiet_keygen: true + allow_public_ssh_keys: true + apt: + preserve_sources_list: false + packages: + - qemu-guest-agent + - sudo + storage: + layout: + name: direct + swap: + size: 0 + user-data: + package_upgrade: false + timezone: UTC + users: + - name: root + groups: [adm, sudo] + lock-passwd: true #Disable password login + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + passwd: "" # Remove password diff --git a/debian/13-trixie-luks/scripts/crowdsec-configuration.sh b/debian/13-trixie-luks/scripts/crowdsec-configuration.sh deleted file mode 100644 index 6ac3f7a..0000000 --- a/debian/13-trixie-luks/scripts/crowdsec-configuration.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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-luks/scripts/crowdsec-repo-setup.sh b/debian/13-trixie-luks/scripts/crowdsec-repo-setup.sh deleted file mode 100644 index 7b2e815..0000000 --- a/debian/13-trixie-luks/scripts/crowdsec-repo-setup.sh +++ /dev/null @@ -1,370 +0,0 @@ -#!/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-luks/scripts/tailscale.sh b/debian/13-trixie-luks/scripts/tailscale.sh deleted file mode 100644 index 8ffd3f5..0000000 --- a/debian/13-trixie-luks/scripts/tailscale.sh +++ /dev/null @@ -1,726 +0,0 @@ -#!/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