x-ui.pkr.hcl 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // 3x-ui golden image — one build, two sources:
  2. // * amazon-ebs : produces an AWS AMI (Marketplace-scannable)
  3. // * qemu : produces a qcow2 (+ raw) for Hetzner/DO/Vultr/GCP/Azure/Oracle
  4. //
  5. // The image ships WITHOUT an initialized x-ui.db and WITHOUT any baked
  6. // credentials. deploy/firstboot/x-ui-firstboot.{sh,service} generates unique
  7. // per-instance credentials on first boot, before x-ui.service starts.
  8. //
  9. // Provisioner order is fixed: provision.sh -> harden.sh -> cleanup.sh.
  10. packer {
  11. required_plugins {
  12. amazon = {
  13. version = ">= 1.3.0"
  14. source = "github.com/hashicorp/amazon"
  15. }
  16. qemu = {
  17. version = ">= 1.1.0"
  18. source = "github.com/hashicorp/qemu"
  19. }
  20. }
  21. }
  22. locals {
  23. build_stamp = formatdate("YYYYMMDD-hhmmss", timestamp())
  24. image_name = "${var.ami_name_prefix}-ubuntu-${var.ubuntu_version}-${var.xui_arch}"
  25. is_arm = var.xui_arch == "arm64"
  26. # Base images are derived from xui_arch unless explicitly overridden.
  27. source_ami_name = var.source_ami_filter_name != "" ? var.source_ami_filter_name : "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-${var.xui_arch}-server-*"
  28. qemu_iso_url = var.qemu_iso_url != "" ? var.qemu_iso_url : "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-${var.xui_arch}.img"
  29. }
  30. source "amazon-ebs" "x-ui" {
  31. region = var.region
  32. instance_type = var.instance_type
  33. ssh_username = var.ssh_username
  34. ami_name = "${local.image_name}-${var.xui_version}-${local.build_stamp}"
  35. ami_description = "3x-ui panel on Ubuntu ${var.ubuntu_version}. Per-instance credentials are generated on first boot."
  36. source_ami_filter {
  37. filters = {
  38. name = local.source_ami_name
  39. root-device-type = "ebs"
  40. virtualization-type = "hvm"
  41. }
  42. owners = ["099720109477"] // Canonical
  43. most_recent = true
  44. }
  45. launch_block_device_mappings {
  46. device_name = "/dev/sda1"
  47. volume_size = 8
  48. volume_type = "gp3"
  49. delete_on_termination = true
  50. }
  51. tags = {
  52. Name = local.image_name
  53. Project = "3x-ui"
  54. XuiVersion = var.xui_version
  55. BuildTool = "packer"
  56. BaseOS = "ubuntu-${var.ubuntu_version}"
  57. }
  58. }
  59. source "qemu" "x-ui" {
  60. iso_url = local.qemu_iso_url
  61. iso_checksum = var.qemu_iso_checksum
  62. disk_image = true
  63. disk_size = "10G"
  64. format = "qcow2"
  65. accelerator = var.qemu_accelerator
  66. headless = var.qemu_headless
  67. cpus = 2
  68. memory = 2048
  69. net_device = "virtio-net"
  70. disk_interface = "virtio"
  71. // Arch-specific QEMU machine. amd64 uses Packer defaults (BIOS boot, x86_64);
  72. // arm64 needs the aarch64 binary, the 'virt' machine and UEFI (AAVMF) firmware.
  73. qemu_binary = local.is_arm ? "qemu-system-aarch64" : null
  74. machine_type = local.is_arm ? "virt" : null
  75. efi_boot = local.is_arm
  76. efi_firmware_code = local.is_arm ? var.qemu_efi_code : null
  77. efi_firmware_vars = local.is_arm ? var.qemu_efi_vars : null
  78. qemuargs = local.is_arm ? [["-cpu", var.qemu_cpu]] : []
  79. output_directory = "output-qemu"
  80. vm_name = "${local.image_name}.qcow2"
  81. // Build-time access: a NoCloud seed sets a temporary password for the default
  82. // user so Packer can SSH in. The seed is a separate CD-ROM (not part of the
  83. // output disk); the password is locked by harden.sh and state wiped by cleanup.sh.
  84. cd_label = "cidata"
  85. cd_content = {
  86. "meta-data" = ""
  87. "user-data" = <<-EOT
  88. #cloud-config
  89. password: ${var.qemu_build_password}
  90. chpasswd: { expire: false }
  91. ssh_pwauth: true
  92. EOT
  93. }
  94. ssh_username = var.ssh_username
  95. ssh_password = var.qemu_build_password
  96. ssh_timeout = "20m"
  97. boot_wait = "45s"
  98. shutdown_command = "sudo shutdown -P now"
  99. }
  100. build {
  101. name = "3x-ui"
  102. sources = ["source.amazon-ebs.x-ui", "source.qemu.x-ui"]
  103. // Upload the first-boot unit + script so provision.sh can install them.
  104. provisioner "shell" {
  105. inline = ["mkdir -p /tmp/firstboot"]
  106. }
  107. provisioner "file" {
  108. source = "${path.root}/../firstboot/x-ui-firstboot.sh"
  109. destination = "/tmp/firstboot/x-ui-firstboot.sh"
  110. }
  111. provisioner "file" {
  112. source = "${path.root}/../firstboot/x-ui-firstboot.service"
  113. destination = "/tmp/firstboot/x-ui-firstboot.service"
  114. }
  115. provisioner "shell" {
  116. environment_vars = [
  117. "XUI_VERSION=${var.xui_version}",
  118. "XUI_ARCH=${var.xui_arch}",
  119. "DEBIAN_FRONTEND=noninteractive",
  120. ]
  121. execute_command = "chmod +x {{ .Path }}; sudo -E bash {{ .Path }}"
  122. scripts = [
  123. "${path.root}/scripts/provision.sh",
  124. "${path.root}/scripts/harden.sh",
  125. "${path.root}/scripts/cleanup.sh",
  126. ]
  127. // give cloud-init time to release apt locks on the very first boot
  128. pause_before = "10s"
  129. }
  130. // Convert the qcow2 to raw for clouds that need it (qemu source only).
  131. post-processor "shell-local" {
  132. only = ["qemu.x-ui"]
  133. inline = ["qemu-img convert -p -O raw output-qemu/${local.image_name}.qcow2 output-qemu/${local.image_name}.raw"]
  134. }
  135. // Record the AMI id / artifacts for CI to surface.
  136. post-processor "manifest" {
  137. output = "packer-manifest.json"
  138. strip_path = true
  139. }
  140. }