Browse Source

chore(deploy): drop the AWS golden-image build stack

Remove the release-driven Packer AMI/qcow2 pipeline and everything that existed only to feed it: the image.yml workflow, deploy/packer, deploy/lightsail, deploy/firstboot, the AWS Marketplace checklist, and the first-boot smoke test/job.

Keep the cloud-agnostic unattended-install path (cloud-init + install.sh non-interactive) and the Hetzner notes, which never depended on the workflow. Hetzner's snapshot path is dropped too since it relied on firstboot to avoid admin/admin on clones; cloud-init regenerates per-instance credentials on its own.

Update deploy/README, the cloud-init and Hetzner docs, the root README plus its six translations, and .gitattributes to match.
MHSanaei 1 ngày trước cách đây
mục cha
commit
30796dc2ce

+ 1 - 4
.gitattributes

@@ -5,8 +5,5 @@ frontend/src/generated/** text eol=lf
 frontend/public/openapi.json text eol=lf
 frontend/src/test/__snapshots__/** text eol=lf
 
-# Cloud-image deploy assets are consumed on Linux — force LF regardless of host.
-*.service text eol=lf
-deploy/**/*.service text eol=lf
-deploy/**/*.hcl text eol=lf
+# Cloud-init deploy assets are consumed on Linux — force LF regardless of host.
 deploy/**/*.yaml text eol=lf

+ 0 - 260
.github/workflows/image.yml

@@ -1,260 +0,0 @@
-name: Build Cloud Images
-
-# Build golden cloud images from a published release, for amd64 and arm64:
-#   * qemu       -> qcow2 attached to the GitHub release (always)
-#   * amazon-ebs -> AWS AMI (only when AWS credentials are configured)
-#
-# Images contain NO database and NO baked credentials; first boot generates
-# unique per-instance credentials (see deploy/firstboot + deploy/packer).
-
-on:
-  release:
-    types: [published]
-  workflow_dispatch:
-    inputs:
-      tag:
-        description: "Release tag to build images for (e.g. v3.3.1)"
-        required: true
-        type: string
-
-permissions:
-  contents: write
-
-concurrency:
-  group: image-${{ github.event.release.tag_name || inputs.tag }}
-  cancel-in-progress: false
-
-jobs:
-  # Resolve the tag and wait until BOTH arch tarballs are actually published
-  # (the release matrix uploads assets one by one, so 'published' can fire
-  # before the tarballs exist).
-  setup:
-    runs-on: ubuntu-latest
-    outputs:
-      tag: ${{ steps.resolve.outputs.tag }}
-    steps:
-      - name: Resolve tag
-        id: resolve
-        run: |
-          if [ "${{ github.event_name }}" = "release" ]; then
-            TAG="${{ github.event.release.tag_name }}"
-          else
-            TAG="${{ inputs.tag }}"
-          fi
-          [ -n "$TAG" ] || { echo "::error::no tag resolved"; exit 1; }
-          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
-
-      - name: Wait for released binary assets (amd64 + arm64)
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          TAG: ${{ steps.resolve.outputs.tag }}
-        run: |
-          want="x-ui-linux-amd64.tar.gz x-ui-linux-arm64.tar.gz"
-          for i in $(seq 1 30); do
-            names=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets -q '.assets[].name')
-            missing=""
-            for w in $want; do
-              echo "$names" | grep -qx "$w" || missing="$missing $w"
-            done
-            if [ -z "$missing" ]; then
-              echo "All assets present on $TAG"
-              exit 0
-            fi
-            echo "Waiting for$missing on $TAG ($i/30)..."
-            sleep 20
-          done
-          echo "::error::missing release assets on $TAG after 10 minutes:$missing"
-          exit 1
-
-  # Gate the AWS AMI build so forks without secrets skip it cleanly
-  # (secrets cannot be referenced directly in job-level `if`).
-  check-aws:
-    runs-on: ubuntu-latest
-    outputs:
-      enabled: ${{ steps.c.outputs.enabled }}
-      use_oidc: ${{ steps.c.outputs.use_oidc }}
-    steps:
-      - id: c
-        env:
-          ROLE: ${{ secrets.AWS_ROLE_ARN }}
-          KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
-        run: |
-          if [ -n "$ROLE" ]; then
-            echo "enabled=true" >> "$GITHUB_OUTPUT"
-            echo "use_oidc=true" >> "$GITHUB_OUTPUT"
-          elif [ -n "$KEY" ]; then
-            echo "enabled=true" >> "$GITHUB_OUTPUT"
-            echo "use_oidc=false" >> "$GITHUB_OUTPUT"
-          else
-            echo "enabled=false" >> "$GITHUB_OUTPUT"
-            echo "use_oidc=false" >> "$GITHUB_OUTPUT"
-            echo "::notice::No AWS credentials configured; skipping the AMI build."
-          fi
-
-  qemu-image:
-    needs: setup
-    timeout-minutes: 90
-    strategy:
-      fail-fast: false
-      matrix:
-        include:
-          - arch: amd64
-            runner: ubuntu-latest
-            qemu_pkgs: qemu-system-x86 qemu-utils
-          - arch: arm64
-            runner: ubuntu-24.04-arm
-            qemu_pkgs: qemu-system-arm qemu-efi-aarch64 qemu-utils
-    runs-on: ${{ matrix.runner }}
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v7
-
-      - name: Install QEMU
-        run: |
-          sudo apt-get update
-          sudo apt-get install -y --no-install-recommends ${{ matrix.qemu_pkgs }}
-
-      - name: Setup Packer
-        uses: hashicorp/setup-packer@v3
-        with:
-          version: latest
-
-      - name: Verify released binary asset
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          TAG: ${{ needs.setup.outputs.tag }}
-        run: |
-          mkdir -p _asset
-          gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
-            --pattern "x-ui-linux-${{ matrix.arch }}.tar.gz" --dir _asset
-          ls -la _asset
-
-      - name: Select accelerator
-        id: accel
-        run: |
-          if [ -e /dev/kvm ]; then echo "value=kvm" >> "$GITHUB_OUTPUT"; else echo "value=tcg" >> "$GITHUB_OUTPUT"; fi
-
-      - name: Packer init
-        run: packer init deploy/packer/
-
-      - name: Build qcow2 image
-        env:
-          TAG: ${{ needs.setup.outputs.tag }}
-          ACCEL: ${{ steps.accel.outputs.value }}
-        run: |
-          packer build -only='qemu.x-ui' \
-            -var "xui_version=${TAG}" \
-            -var "xui_arch=${{ matrix.arch }}" \
-            -var "qemu_accelerator=${ACCEL}" \
-            deploy/packer/
-
-      - name: Compress qcow2
-        id: pack
-        env:
-          TAG: ${{ needs.setup.outputs.tag }}
-        run: |
-          cd deploy/packer/output-qemu
-          src="3x-ui-ubuntu-24.04-${{ matrix.arch }}.qcow2"
-          out="3x-ui-ubuntu-24.04-${TAG}-${{ matrix.arch }}.qcow2.xz"
-          xz -T0 -6 -c "$src" > "$out"
-          sha256sum "$out" > "${out}.sha256"
-          echo "file=deploy/packer/output-qemu/${out}" >> "$GITHUB_OUTPUT"
-          echo "sha=deploy/packer/output-qemu/${out}.sha256" >> "$GITHUB_OUTPUT"
-          ls -la
-
-      - name: Attach qcow2 to release
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          TAG: ${{ needs.setup.outputs.tag }}
-        run: |
-          gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
-            "${{ steps.pack.outputs.file }}" "${{ steps.pack.outputs.sha }}"
-
-      - name: Summary
-        env:
-          TAG: ${{ needs.setup.outputs.tag }}
-          ACCEL: ${{ steps.accel.outputs.value }}
-        run: |
-          {
-            echo "## QEMU image (${{ matrix.arch }})"
-            echo "- Tag: \`${TAG}\`"
-            echo "- Accelerator: \`${ACCEL}\`"
-            echo "- Attached: \`$(basename "${{ steps.pack.outputs.file }}")\`"
-          } >> "$GITHUB_STEP_SUMMARY"
-
-  ami-image:
-    needs: [setup, check-aws]
-    if: needs.check-aws.outputs.enabled == 'true'
-    runs-on: ubuntu-latest
-    timeout-minutes: 60
-    permissions:
-      contents: read
-      id-token: write
-    strategy:
-      fail-fast: false
-      matrix:
-        include:
-          - arch: amd64
-            instance_type: t3.small
-          - arch: arm64
-            instance_type: t4g.small
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v7
-
-      - name: Setup Packer
-        uses: hashicorp/setup-packer@v3
-        with:
-          version: latest
-
-      - name: Configure AWS credentials (OIDC)
-        if: needs.check-aws.outputs.use_oidc == 'true'
-        uses: aws-actions/configure-aws-credentials@v6
-        with:
-          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
-          aws-region: ${{ vars.AWS_REGION || 'eu-central-1' }}
-
-      - name: Configure AWS credentials (access keys)
-        if: needs.check-aws.outputs.use_oidc != 'true'
-        uses: aws-actions/configure-aws-credentials@v6
-        with:
-          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
-          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
-          aws-region: ${{ vars.AWS_REGION || 'eu-central-1' }}
-
-      - name: Verify released binary asset
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          TAG: ${{ needs.setup.outputs.tag }}
-        run: |
-          mkdir -p _asset
-          gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
-            --pattern "x-ui-linux-${{ matrix.arch }}.tar.gz" --dir _asset
-          ls -la _asset
-
-      - name: Packer init
-        run: packer init deploy/packer/
-
-      - name: Build AMI
-        env:
-          TAG: ${{ needs.setup.outputs.tag }}
-          REGION: ${{ vars.AWS_REGION || 'eu-central-1' }}
-        run: |
-          packer build -only='amazon-ebs.x-ui' \
-            -var "xui_version=${TAG}" \
-            -var "xui_arch=${{ matrix.arch }}" \
-            -var "instance_type=${{ matrix.instance_type }}" \
-            -var "region=${REGION}" \
-            deploy/packer/
-
-      - name: Publish AMI id to summary
-        env:
-          REGION: ${{ vars.AWS_REGION || 'eu-central-1' }}
-        run: |
-          AMI_ID=$(jq -r '.builds[] | select(.builder_type=="amazon-ebs") | .artifact_id' packer-manifest.json | tail -1 | cut -d: -f2)
-          {
-            echo "## AWS AMI (${{ matrix.arch }})"
-            echo "- Region: \`${REGION}\`"
-            echo "- Instance type: \`${{ matrix.instance_type }}\`"
-            echo "- AMI ID: \`${AMI_ID}\`"
-          } >> "$GITHUB_STEP_SUMMARY"

+ 2 - 14
.github/workflows/smoke.yml

@@ -1,7 +1,7 @@
 name: Deploy Smoke Tests
 
-# Container smoke tests for the unattended install path and first-boot
-# credential generation. Runs only when the install/deploy assets change.
+# Container smoke test for the unattended (cloud-init) install path.
+# Runs only when the install/deploy assets change.
 
 on:
   push:
@@ -30,15 +30,3 @@ jobs:
       - uses: actions/checkout@v7
       - name: Non-interactive install smoke test
         run: bash deploy/test/smoke-noninteractive.sh
-
-  first-boot:
-    strategy:
-      fail-fast: false
-      matrix:
-        runner: [ubuntu-latest, ubuntu-24.04-arm]
-    runs-on: ${{ matrix.runner }}
-    timeout-minutes: 15
-    steps:
-      - uses: actions/checkout@v7
-      - name: First-boot credential smoke test
-        run: bash deploy/test/smoke-firstboot.sh

+ 3 - 5
README.ar_EG.md

@@ -89,17 +89,15 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
 
 للحصول على الوثائق الكاملة، يرجى زيارة [ويكي المشروع](https://github.com/MHSanaei/3x-ui/wiki).
 
-### التثبيت غير التفاعلي وصور السحابة
+### التثبيت غير التفاعلي
 
-يعمل المثبِّت أيضًا **بشكل غير تفاعلي** لـ cloud-init والصور الجاهزة (golden images).
+يعمل المثبِّت أيضًا **بشكل غير تفاعلي** لـ cloud-init.
 عيّن `XUI_NONINTERACTIVE=1` (أو مرّره عبر أنبوب دون TTY) وسيتولى التثبيت من البداية إلى النهاية
 دون أي مطالبات، مُنشئًا بيانات اعتماد عشوائية وكاتبًا إياها في
 `/etc/x-ui/install-result.env`. راجع [`deploy/`](deploy/) لـ:
 
 - [بيانات مستخدم cloud-init](deploy/cloud-init/) — تثبيت غير تفاعلي على أي سحابة (Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [صورة Packer الجاهزة](deploy/packer/) — بناء صورة AWS EC2 AMI و qcow2 (amd64/arm64) مع بيانات اعتماد لكل نسخة يتم إنشاؤها عند الإقلاع الأول
-- [Amazon Lightsail](deploy/lightsail/) — سكربت إطلاق وأداة بناء لقطات قابلة لإعادة الاستخدام
-- [قائمة تحقق AWS Marketplace](deploy/marketplace/aws/)
+- [ملاحظات Hetzner Cloud](deploy/marketplace/hetzner/) — نشر يعتمد على cloud-init على Hetzner
 
 ## المنصات المدعومة
 

+ 3 - 5
README.es_ES.md

@@ -89,17 +89,15 @@ Durante la instalación se generan un nombre de usuario, una contraseña y una r
 
 Para la documentación completa, visita la [Wiki del proyecto](https://github.com/MHSanaei/3x-ui/wiki).
 
-### Instalación desatendida e imágenes de nube
+### Instalación desatendida
 
-El instalador también se ejecuta de forma **no interactiva** para cloud-init e imágenes doradas (golden images).
+El instalador también se ejecuta de forma **no interactiva** para cloud-init.
 Define `XUI_NONINTERACTIVE=1` (o canalízalo sin TTY) y realizará la instalación de principio a fin sin
 ninguna pregunta, generando credenciales aleatorias y escribiéndolas en
 `/etc/x-ui/install-result.env`. Consulta [`deploy/`](deploy/) para:
 
 - [User-data de cloud-init](deploy/cloud-init/) — instalación desatendida en cualquier nube (Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [Imagen dorada de Packer](deploy/packer/) — crea una AMI de AWS EC2 + qcow2 (amd64/arm64) con credenciales por instancia generadas en el primer arranque
-- [Amazon Lightsail](deploy/lightsail/) — script de lanzamiento + constructor de snapshots reutilizable
-- [Lista de verificación de AWS Marketplace](deploy/marketplace/aws/)
+- [Notas de Hetzner Cloud](deploy/marketplace/hetzner/) — despliegue basado en cloud-init en Hetzner
 
 ## Plataformas Compatibles
 

+ 3 - 5
README.fa_IR.md

@@ -89,17 +89,15 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
 
 برای مستندات کامل، لطفاً به [ویکی پروژه](https://github.com/MHSanaei/3x-ui/wiki) مراجعه کنید.
 
-### نصب بدون نظارت و ایمیج‌های ابری
+### نصب بدون نظارت
 
-نصب‌کننده به‌صورت **غیرتعاملی** نیز برای cloud-init و ایمیج‌های آماده (golden images) اجرا می‌شود.
+نصب‌کننده به‌صورت **غیرتعاملی** نیز برای cloud-init اجرا می‌شود.
 ‏`XUI_NONINTERACTIVE=1` را تنظیم کنید (یا بدون TTY از طریق pipe اجرا کنید) تا نصب به‌صورت سرتاسری و بدون
 هیچ پرسشی انجام شود، اطلاعات ورود تصادفی تولید کرده و آن‌ها را در
 `/etc/x-ui/install-result.env` می‌نویسد. برای موارد زیر به [`deploy/`](deploy/) مراجعه کنید:
 
 - [user-data مربوط به Cloud-init](deploy/cloud-init/) — نصب بدون نظارت روی هر ابری (Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [ایمیج آماده‌ی Packer](deploy/packer/) — ساخت یک AMI برای AWS EC2 به‌همراه qcow2 (amd64/arm64) با اطلاعات ورودِ مخصوص هر اینستنس که در نخستین بوت تولید می‌شود
-- [Amazon Lightsail](deploy/lightsail/) — اسکریپت راه‌اندازی به‌همراه سازنده‌ی اسنپ‌شات قابل‌استفاده‌ی مجدد
-- [چک‌لیست AWS Marketplace](deploy/marketplace/aws/)
+- [یادداشت‌های Hetzner Cloud](deploy/marketplace/hetzner/) — استقرار مبتنی بر cloud-init روی Hetzner
 
 ## پلتفرم‌های پشتیبانی‌شده
 

+ 3 - 5
README.md

@@ -89,17 +89,15 @@ During installation a random username, password, and access path are generated.
 
 For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki).
 
-### Unattended install & cloud images
+### Unattended install
 
-The installer also runs **non-interactively** for cloud-init and golden images.
+The installer also runs **non-interactively** for cloud-init.
 Set `XUI_NONINTERACTIVE=1` (or pipe with no TTY) and it installs end-to-end with
 zero prompts, generating random credentials and writing them to
 `/etc/x-ui/install-result.env`. See [`deploy/`](deploy/) for:
 
 - [Cloud-init user-data](deploy/cloud-init/) — unattended install on any cloud (Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [Packer golden image](deploy/packer/) — build an AWS EC2 AMI + qcow2 (amd64/arm64) with per-instance credentials generated on first boot
-- [Amazon Lightsail](deploy/lightsail/) — launch script + reusable snapshot builder
-- [AWS Marketplace checklist](deploy/marketplace/aws/)
+- [Hetzner Cloud notes](deploy/marketplace/hetzner/) — cloud-init deployment on Hetzner
 
 ## Supported Platforms
 

+ 3 - 5
README.ru_RU.md

@@ -89,17 +89,15 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
 
 Полную документацию смотрите в [вики проекта](https://github.com/MHSanaei/3x-ui/wiki).
 
-### Автоматическая установка и облачные образы
+### Автоматическая установка
 
-Установщик также работает в **неинтерактивном** режиме для cloud-init и готовых образов.
+Установщик также работает в **неинтерактивном** режиме для cloud-init.
 Задайте `XUI_NONINTERACTIVE=1` (или передайте по конвейеру без TTY), и установка пройдёт от начала до конца
 без единого запроса: будут сгенерированы случайные учётные данные и записаны в
 `/etc/x-ui/install-result.env`. Смотрите [`deploy/`](deploy/) для:
 
 - [Cloud-init user-data](deploy/cloud-init/) — автоматическая установка в любом облаке (Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [Готовый образ Packer](deploy/packer/) — сборка AWS EC2 AMI + qcow2 (amd64/arm64) с учётными данными для каждого экземпляра, генерируемыми при первой загрузке
-- [Amazon Lightsail](deploy/lightsail/) — скрипт запуска + переиспользуемый сборщик снимков
-- [Чек-лист для AWS Marketplace](deploy/marketplace/aws/)
+- [Заметки по Hetzner Cloud](deploy/marketplace/hetzner/) — развёртывание на Hetzner на базе cloud-init
 
 ## Поддерживаемые платформы
 

+ 3 - 5
README.tr_TR.md

@@ -89,17 +89,15 @@ Kurulum sırasında rastgele bir kullanıcı adı, şifre ve erişim yolu oluşt
 
 Tam dokümantasyon için lütfen [proje Wiki sayfasını](https://github.com/MHSanaei/3x-ui/wiki) ziyaret edin.
 
-### Etkileşimsiz kurulum ve hazır bulut imajları
+### Etkileşimsiz kurulum
 
-Yükleyici, cloud-init ve hazır (golden) imajlar için **etkileşimsiz** olarak da çalışır.
+Yükleyici, cloud-init için **etkileşimsiz** olarak da çalışır.
 `XUI_NONINTERACTIVE=1` ayarlayın (veya TTY olmadan boru hattına aktarın); kurulum baştan
 sona hiçbir soru sormadan tamamlanır, rastgele kimlik bilgileri oluşturup bunları
 `/etc/x-ui/install-result.env` dosyasına yazar. Şunlar için [`deploy/`](deploy/) klasörüne bakın:
 
 - [Cloud-init user-data](deploy/cloud-init/) — herhangi bir bulutta etkileşimsiz kurulum (Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [Packer hazır imajı](deploy/packer/) — ilk açılışta her örnek (instance) için kimlik bilgileri oluşturan bir AWS EC2 AMI + qcow2 (amd64/arm64) imajı oluşturun
-- [Amazon Lightsail](deploy/lightsail/) — başlatma betiği + yeniden kullanılabilir anlık görüntü (snapshot) oluşturucu
-- [AWS Marketplace kontrol listesi](deploy/marketplace/aws/)
+- [Hetzner Cloud notları](deploy/marketplace/hetzner/) — Hetzner üzerinde cloud-init tabanlı dağıtım
 
 ## Desteklenen Platformlar
 

+ 3 - 5
README.zh_CN.md

@@ -89,17 +89,15 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
 
 完整文档请参阅 [项目Wiki](https://github.com/MHSanaei/3x-ui/wiki)。
 
-### 无人值守安装与云镜像
+### 无人值守安装
 
-安装程序也可以**非交互式**运行,适用于 cloud-init 和黄金镜像(golden image)
+安装程序也可以**非交互式**运行,适用于 cloud-init。
 设置 `XUI_NONINTERACTIVE=1`(或在无 TTY 的情况下通过管道传入),它就会全程
 零提示地完成端到端安装,生成随机凭据并写入
 `/etc/x-ui/install-result.env`。请参阅 [`deploy/`](deploy/):
 
 - [Cloud-init user-data](deploy/cloud-init/) — 在任意云平台上无人值守安装(Hetzner/AWS/DO/Vultr/GCP/Azure/Oracle)
-- [Packer golden image](deploy/packer/) — 构建 AWS EC2 AMI + qcow2(amd64/arm64),首次启动时生成每个实例独有的凭据
-- [Amazon Lightsail](deploy/lightsail/) — 启动脚本 + 可复用的快照构建器
-- [AWS Marketplace 清单](deploy/marketplace/aws/)
+- [Hetzner Cloud 说明](deploy/marketplace/hetzner/) — 在 Hetzner 上基于 cloud-init 的部署
 
 ## 支持的平台
 

+ 9 - 16
deploy/README.md

@@ -1,27 +1,20 @@
-# Cloud deployment & golden images
+# Cloud deployment (unattended install)
 
-Tooling to ship the 3x-ui panel as a cloud image or via unattended install,
-with **per-instance credentials generated on first boot** (never `admin/admin`,
-never a shared session secret). Everything here supports **amd64 and arm64**.
+Tooling to ship the 3x-ui panel via unattended install, with **per-instance
+credentials generated on first boot** (never `admin/admin`, never a shared
+session secret). Works on amd64 and arm64.
 
 | Path | What it is | Use when |
 | --- | --- | --- |
 | [`cloud-init/`](cloud-init/) | Generic cloud-init user-data (unattended `install.sh`) | Any cloud, no image build |
-| [`packer/`](packer/) | Packer build → AWS AMI + qcow2/raw | Reusable / Marketplace images |
-| [`lightsail/`](lightsail/) | Launch script + snapshot builder | Amazon Lightsail |
-| [`firstboot/`](firstboot/) | First-boot unit + script that mints per-instance creds | Used by the Packer/Lightsail images |
-| [`marketplace/aws/`](marketplace/aws/) | AWS Marketplace submission checklist | Publishing an EC2 AMI |
 | [`marketplace/hetzner/`](marketplace/hetzner/) | Hetzner Cloud notes | Hetzner deployments |
-| [`test/`](test/) | Container smoke tests | Verifying the install/firstboot paths |
+| [`test/`](test/) | Container smoke test | Verifying the install path |
 
-## Two models
+## How it works
 
-- **Non-interactive install (cloud-init):** `install.sh` runs unattended when
-  `XUI_NONINTERACTIVE=1` or stdin is not a TTY. Each instance installs and
-  configures itself with random credentials. See [`cloud-init/README.md`](cloud-init/README.md).
-- **Golden image (Packer):** the image contains the panel but **no DB and no
-  secrets**; `firstboot` generates unique credentials on first boot. See
-  [`packer/README.md`](packer/README.md).
+`install.sh` runs unattended when `XUI_NONINTERACTIVE=1` or stdin is not a TTY.
+Each instance installs and configures itself with random credentials. See
+[`cloud-init/README.md`](cloud-init/README.md).
 
 ## Unattended install knobs
 

+ 4 - 9
deploy/cloud-init/README.md

@@ -1,12 +1,8 @@
-# 3x-ui via cloud-init (generic, no golden image)
+# 3x-ui via cloud-init
 
-This is the **secondary** deployment path: a single [`cloud-init.yaml`](cloud-init.yaml)
-user-data file that installs 3x-ui non-interactively on a fresh Ubuntu/Debian
-VM and generates **unique random credentials per instance**. Use it when you do
-not want to build a golden image — it works on any cloud-init platform.
-
-> For AWS Marketplace / reusable images, use the Packer build in
-> [`../packer/`](../packer/) instead.
+A single [`cloud-init.yaml`](cloud-init.yaml) user-data file that installs 3x-ui
+non-interactively on a fresh Ubuntu/Debian VM and generates **unique random
+credentials per instance**. It works on any cloud-init platform.
 
 ## How it works
 
@@ -53,7 +49,6 @@ Edit the `export XUI_*` lines inside the `write_files` block of
   `hcloud server create --image ubuntu-24.04 --user-data-from-file cloud-init.yaml ...`
 - **AWS EC2** — *Advanced details → User data*: paste the file. Or
   `aws ec2 run-instances --user-data file://cloud-init.yaml ...`
-  (For a reusable Marketplace image use the Packer AMI build instead.)
 - **DigitalOcean** — *Create Droplet → Advanced options → Add Initialization
   scripts (user data)*: paste the file. Or `doctl compute droplet create --user-data-file cloud-init.yaml ...`
 - **Vultr** — *Deploy → Additional Features → Cloud-Init User-Data*: paste the file.

+ 0 - 22
deploy/firstboot/x-ui-firstboot.service

@@ -1,22 +0,0 @@
-[Unit]
-Description=3x-ui first-boot per-instance credential generation
-Documentation=https://github.com/MHSanaei/3x-ui
-# Run after the network and cloud-init are up, but BEFORE the panel starts, so
-# the panel never serves the default admin/admin account.
-After=network-online.target cloud-init.service
-Wants=network-online.target
-Before=x-ui.service
-# Skip entirely once the sentinel exists (cheap guard; the script re-checks too).
-ConditionPathExists=!/etc/x-ui/.firstboot-done
-
-[Service]
-Type=oneshot
-RemainAfterExit=yes
-# Inherit the same DB configuration the panel uses (sqlite default / postgres).
-EnvironmentFile=-/etc/default/x-ui
-EnvironmentFile=-/etc/conf.d/x-ui
-EnvironmentFile=-/etc/sysconfig/x-ui
-ExecStart=/usr/local/x-ui/x-ui-firstboot.sh
-
-[Install]
-WantedBy=multi-user.target

+ 0 - 166
deploy/firstboot/x-ui-firstboot.sh

@@ -1,166 +0,0 @@
-#!/usr/bin/env bash
-#
-# x-ui-firstboot.sh — generate per-instance 3x-ui panel credentials on first boot.
-#
-# A golden image (AMI / qcow2) MUST ship without an initialized x-ui.db: the
-# panel seeds a hardcoded admin/admin user and generates its session secret +
-# panel GUID on first start, so a baked DB would make every clone share the same
-# credentials and secret. This script runs ONCE, before x-ui.service starts, and
-# replaces the default admin with fresh random credentials on a random high port.
-#
-# Idempotent: a sentinel file guards against re-running. If a non-default admin
-# already exists (operator pre-configured the box), regeneration is skipped.
-#
-# Wired up by deploy/packer/scripts/provision.sh; ordered Before=x-ui.service.
-
-set -u
-
-SENTINEL="/etc/x-ui/.firstboot-done"
-CRED_FILE="/etc/x-ui/credentials.txt"
-MOTD_FILE="/etc/motd"
-XUI_DIR="${XUI_MAIN_FOLDER:-/usr/local/x-ui}"
-XUI_BIN="${XUI_DIR}/x-ui"
-
-log() { echo "[x-ui-firstboot] $*"; }
-
-# Already provisioned — nothing to do (idempotent on re-run / re-image).
-if [ -f "$SENTINEL" ]; then
-    log "sentinel $SENTINEL present; skipping."
-    exit 0
-fi
-
-if [ ! -x "$XUI_BIN" ]; then
-    log "ERROR: x-ui binary not found at $XUI_BIN"
-    exit 1
-fi
-
-# Inherit DB configuration (sqlite default; postgres via XUI_DB_TYPE/XUI_DB_DSN)
-# from the same env files the systemd unit loads, so the binary talks to the
-# same database the panel will use.
-for ef in /etc/default/x-ui /etc/conf.d/x-ui /etc/sysconfig/x-ui; do
-    if [ -r "$ef" ]; then
-        set -a
-        # shellcheck disable=SC1090
-        . "$ef"
-        set +a
-    fi
-done
-
-install -d -m 755 /etc/x-ui 2> /dev/null || true
-
-# Defense-in-depth: make sure the panel is not running while we mutate the DB.
-if command -v systemctl > /dev/null 2>&1; then
-    systemctl stop x-ui > /dev/null 2>&1 || true
-fi
-
-gen_random_string() {
-    local length="$1"
-    openssl rand -base64 $((length * 2)) | tr -dc 'a-zA-Z0-9' | head -c "$length"
-}
-
-# Best-effort public IPv4 for the displayed access URL (cosmetic only — the
-# panel binds 0.0.0.0). Falls back to the primary local IP, then a placeholder.
-detect_ip() {
-    local ip=""
-    local url
-    for url in https://api4.ipify.org https://ipv4.icanhazip.com https://4.ident.me; do
-        ip=$(curl -fsS4 --max-time 3 "$url" 2> /dev/null | tr -d '[:space:]')
-        if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
-            echo "$ip"
-            return 0
-        fi
-    done
-    ip=$(hostname -I 2> /dev/null | awk '{print $1}')
-    if [ -n "$ip" ]; then
-        echo "$ip"
-        return 0
-    fi
-    echo "<server-ip>"
-}
-
-# Detect whether the seeded admin/admin default is still in place.
-default_creds=$("$XUI_BIN" setting -show true 2> /dev/null | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
-
-# The parse MUST yield exactly "true" or "false". If the command failed or its
-# output format changed, refuse to proceed: do NOT write the sentinel, so the
-# next boot retries instead of silently leaving admin/admin in place.
-if [ "$default_creds" != "true" ] && [ "$default_creds" != "false" ]; then
-    log "ERROR: could not determine credential state (hasDefaultCredential='${default_creds}'); not writing sentinel, will retry next boot."
-    exit 1
-fi
-
-if [ "$default_creds" = "false" ]; then
-    log "non-default admin already configured; skipping credential regeneration."
-    {
-        echo "3x-ui first-boot: a non-default admin account already exists on this"
-        echo "instance, so credentials were left unchanged."
-    } > "$MOTD_FILE" 2> /dev/null || true
-    : > "$SENTINEL" 2> /dev/null || true
-    chmod 600 "$SENTINEL" 2> /dev/null || true
-    exit 0
-fi
-
-log "generating per-instance credentials..."
-
-NEW_USER="${XUI_USERNAME:-$(gen_random_string 10)}"
-NEW_PASS="${XUI_PASSWORD:-$(gen_random_string 16)}"
-NEW_PATH="${XUI_WEB_BASE_PATH:-$(gen_random_string 18)}"
-NEW_PORT="${XUI_PANEL_PORT:-$(shuf -i 1024-62000 -n 1)}"
-
-# Clean settings slate: drops any baked port/webBasePath and forces the panel
-# to regenerate its session secret + panel GUID on next start (per-instance).
-"$XUI_BIN" setting -reset > /dev/null 2>&1 || true
-
-# Apply fresh random identity. UpdateFirstUser renames the seeded admin row and
-# rehashes the password, so admin/admin no longer exists after this call.
-if ! "$XUI_BIN" setting -username "$NEW_USER" -password "$NEW_PASS" -port "$NEW_PORT" -webBasePath "$NEW_PATH" > /dev/null 2>&1; then
-    log "ERROR: failed to apply new panel settings."
-    exit 1
-fi
-
-API_TOKEN=$("$XUI_BIN" setting -getApiToken true 2> /dev/null | grep -Eo 'apiToken: .+' | awk '{print $2}')
-SERVER_IP=$(detect_ip)
-ACCESS_URL="http://${SERVER_IP}:${NEW_PORT}/${NEW_PATH}"
-
-# Persist credentials for the operator (root-only). Values are shell-escaped
-# with %q so the file stays safe to `source` even if a value contains shell
-# metacharacters (the smoke test and operators source this file).
-umask 077
-{
-    echo "# 3x-ui per-instance credentials (generated on first boot)"
-    printf 'XUI_USERNAME=%q\n' "$NEW_USER"
-    printf 'XUI_PASSWORD=%q\n' "$NEW_PASS"
-    printf 'XUI_PANEL_PORT=%q\n' "$NEW_PORT"
-    printf 'XUI_WEB_BASE_PATH=%q\n' "$NEW_PATH"
-    printf 'XUI_ACCESS_URL=%q\n' "$ACCESS_URL"
-    printf 'XUI_API_TOKEN=%q\n' "$API_TOKEN"
-} > "$CRED_FILE"
-chmod 600 "$CRED_FILE" 2> /dev/null || true
-
-# Friendly login banner shown on SSH / console before the panel is reachable.
-# /etc/motd is world-readable, so it MUST NOT contain the password or API token;
-# those secrets live only in ${CRED_FILE} (mode 600). Show non-secret info only.
-cat > "$MOTD_FILE" 2> /dev/null << EOF
-
-========================================================================
-  3x-ui panel — per-instance credentials (generated on first boot)
-========================================================================
-  Access URL : ${ACCESS_URL}
-  Username   : ${NEW_USER}
-
-  The password and API token are NOT shown here (this banner is
-  world-readable). Read them as root with:
-      sudo cat ${CRED_FILE}
-
-  Change the password after login. If no public IP is shown above,
-  replace <server-ip> with the address you reach this server on.
-========================================================================
-
-EOF
-
-# Mark complete so we never regenerate on subsequent boots.
-: > "$SENTINEL" 2> /dev/null || true
-chmod 600 "$SENTINEL" 2> /dev/null || true
-
-log "done. Panel will start on port ${NEW_PORT} with a unique admin account."
-exit 0

+ 0 - 94
deploy/lightsail/README.md

@@ -1,94 +0,0 @@
-# 3x-ui on Amazon Lightsail
-
-Two self-service ways to run 3x-ui on Lightsail, both producing **unique
-per-instance credentials** (never `admin/admin`, never a shared secret).
-
-> **Reality check.** The Lightsail *blueprint* list (WordPress, LAMP, GitLab…)
-> is curated by AWS — you **cannot** self-publish your panel there, and Lightsail
-> **cannot** launch from an arbitrary EC2 AMI. What you *can* do yourself is the
-> two paths below. (For a public AWS listing you'd use the EC2 **AMI** +
-> Marketplace path in [`../marketplace/aws/`](../marketplace/aws/), which is a
-> different product from Lightsail.)
-
----
-
-## Path A — launch script (simplest, self-service)
-
-Install on a fresh instance at creation time. No image to build.
-
-1. **Create instance** → platform **Linux/Unix** → blueprint **OS Only → Ubuntu 24.04**.
-2. **Add launch script** → paste [`launch-script.sh`](launch-script.sh).
-3. Create the instance.
-4. After it boots, read the credentials:
-   ```bash
-   ssh ubuntu@<public-ip> 'sudo cat /etc/x-ui/install-result.env'
-   ```
-5. **Open the panel port** (see the firewall note below) and log in.
-
-CLI equivalent:
-
-```bash
-aws lightsail create-instances \
-  --instance-names my-3xui \
-  --availability-zone eu-central-1a \
-  --blueprint-id ubuntu_24_04 \
-  --bundle-id small_3_0 \
-  --user-data file://deploy/lightsail/launch-script.sh \
-  --region eu-central-1
-```
-
-By default the panel uses a **random** high port (in `install-result.env`). To
-pin a known port so you can pre-open it, set `export XUI_PANEL_PORT=54321` inside
-`launch-script.sh`.
-
----
-
-## Path B — reusable snapshot (your own "ready image")
-
-Build a Lightsail **snapshot** once; launch as many instances from it as you
-like, each generating its own credentials on first boot (the golden-image model).
-
-```bash
-deploy/lightsail/build-snapshot.sh --region eu-central-1 --panel-port 54321
-```
-
-What it does: launches a temporary Ubuntu instance with
-[`snapshot-userdata.sh`](snapshot-userdata.sh) (installs the panel, **no DB**,
-enables the first-boot unit), strips all state via the shared
-[`cleanup.sh`](../packer/scripts/cleanup.sh), then snapshots and deletes the
-build instance. Requires `awscli`, `jq`, `ssh` and Lightsail permissions.
-
-Launch instances from the snapshot:
-
-```bash
-aws lightsail create-instances-from-snapshot \
-  --instance-snapshot-name 3x-ui-ubuntu-24.04-<stamp> \
-  --instance-names my-3xui-1 --bundle-id small_3_0 \
-  --availability-zone eu-central-1a --region eu-central-1
-```
-
-Each launched instance runs `x-ui-firstboot` and writes its unique credentials to
-`/etc/x-ui/credentials.txt` + `/etc/motd`. With `--panel-port` the port is the
-same across instances (only the credentials differ), so you can pre-open it.
-
-> Lightsail snapshots are **private to your AWS account** (and region). To use one
-> elsewhere you can export it to EC2 (`aws lightsail export-snapshot`) and share
-> the resulting AMI.
-
----
-
-## Lightsail firewall note (important)
-
-Lightsail's per-instance firewall only opens **22 / 80 / 443** by default. The
-panel runs on a different port, so you must open it:
-
-- Console: instance → **Networking → IPv4 Firewall → Add rule** (TCP, the panel port).
-- CLI:
-  ```bash
-  aws lightsail open-instance-public-ports --region eu-central-1 \
-    --instance-name my-3xui \
-    --port-info fromPort=54321,toPort=54321,protocol=TCP
-  ```
-
-The panel port is in `/etc/x-ui/install-result.env` (Path A) or
-`/etc/x-ui/credentials.txt` (Path B), or fixed via `--panel-port` / `XUI_PANEL_PORT`.

+ 0 - 192
deploy/lightsail/build-snapshot.sh

@@ -1,192 +0,0 @@
-#!/usr/bin/env bash
-#
-# build-snapshot.sh — build a reusable Amazon Lightsail snapshot of 3x-ui.
-#
-# Flow (mirrors the Packer golden-image model, via the Lightsail API):
-#   1. create an Ubuntu Lightsail instance with snapshot-userdata.sh
-#      (installs the panel, NO database, enables the first-boot unit)
-#   2. wait for provisioning, then (optionally) pin a known panel port and run
-#      the shared cleanup.sh (wipes any DB/creds/keys/host-keys/cloud-init state)
-#   3. stop the instance and create an instance snapshot
-#   4. delete the build instance (unless --keep-instance)
-#
-# Every instance you later launch from the snapshot generates its OWN unique
-# credentials on first boot (see deploy/firstboot/). The snapshot is private to
-# your AWS account.
-#
-# Requirements: awscli v2, jq, ssh. AWS credentials with Lightsail permissions.
-# Usage:
-#   deploy/lightsail/build-snapshot.sh --region eu-central-1 [options]
-# Options:
-#   --region <r>            AWS region (default: $AWS_REGION or eu-central-1)
-#   --blueprint-id <id>     Lightsail blueprint (default: ubuntu_24_04)
-#   --bundle-id <id>        Lightsail bundle/size (default: small_3_0)
-#   --availability-zone <z> AZ (default: <region>a)
-#   --panel-port <p>        Pin the panel port in the snapshot so you can pre-open
-#                           it in the Lightsail firewall (default: random per instance)
-#   --snapshot-name <n>     Snapshot name (default: 3x-ui-ubuntu-24.04-<timestamp>)
-#   --keep-instance         Do not delete the build instance afterwards
-set -euo pipefail
-
-REGION="${AWS_REGION:-eu-central-1}"
-BLUEPRINT="ubuntu_24_04"
-BUNDLE="small_3_0"
-AZ=""
-PANEL_PORT=""
-SNAPSHOT_NAME=""
-KEEP_INSTANCE=0
-
-SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-STAMP="$(date +%Y%m%d-%H%M%S)"
-INSTANCE_NAME="3xui-build-${STAMP}"
-KEY_FILE=""
-
-log() { echo "[build-snapshot] $*"; }
-die() {
-    echo "[build-snapshot] ERROR: $*" >&2
-    exit 1
-}
-
-while [ $# -gt 0 ]; do
-    case "$1" in
-        --region) REGION="$2"; shift 2 ;;
-        --blueprint-id) BLUEPRINT="$2"; shift 2 ;;
-        --bundle-id) BUNDLE="$2"; shift 2 ;;
-        --availability-zone) AZ="$2"; shift 2 ;;
-        --panel-port) PANEL_PORT="$2"; shift 2 ;;
-        --snapshot-name) SNAPSHOT_NAME="$2"; shift 2 ;;
-        --keep-instance) KEEP_INSTANCE=1; shift ;;
-        -h | --help) sed -n '2,40p' "$0"; exit 0 ;;
-        *) die "unknown option: $1" ;;
-    esac
-done
-
-[ -n "$AZ" ] || AZ="${REGION}a"
-[ -n "$SNAPSHOT_NAME" ] || SNAPSHOT_NAME="3x-ui-ubuntu-24.04-${STAMP}"
-
-for cmd in aws jq ssh; do
-    command -v "$cmd" > /dev/null 2>&1 || die "'$cmd' is required"
-done
-
-SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o LogLevel=ERROR)
-
-cleanup() {
-    [ -n "$KEY_FILE" ] && rm -f "$KEY_FILE"
-    if [ "$KEEP_INSTANCE" -eq 0 ]; then
-        aws lightsail delete-instance --instance-name "$INSTANCE_NAME" --region "$REGION" > /dev/null 2>&1 || true
-    fi
-}
-trap cleanup EXIT
-
-wait_state() {
-    local want="$1" tries="${2:-60}" st
-    for _ in $(seq 1 "$tries"); do
-        st=$(aws lightsail get-instance-state --instance-name "$INSTANCE_NAME" --region "$REGION" \
-            --query 'state.name' --output text 2> /dev/null || echo "")
-        [ "$st" = "$want" ] && return 0
-        sleep 5
-    done
-    return 1
-}
-
-log "creating build instance ${INSTANCE_NAME} (${BLUEPRINT}/${BUNDLE}) in ${REGION}..."
-aws lightsail create-instances \
-    --instance-names "$INSTANCE_NAME" \
-    --availability-zone "$AZ" \
-    --blueprint-id "$BLUEPRINT" \
-    --bundle-id "$BUNDLE" \
-    --user-data "file://${SCRIPT_DIR}/snapshot-userdata.sh" \
-    --region "$REGION" > /dev/null
-
-log "waiting for instance to run..."
-wait_state running 60 || die "instance did not reach 'running'"
-
-IP=$(aws lightsail get-instance --instance-name "$INSTANCE_NAME" --region "$REGION" \
-    --query 'instance.publicIpAddress' --output text)
-if [ -z "$IP" ] || [ "$IP" = "None" ]; then die "no public IP"; fi
-log "instance IP: ${IP}"
-
-KEY_FILE="$(mktemp)"
-# download-default-key-pair returns the key in 'privateKeyBase64'. Despite the
-# name, the CLI historically emits the plaintext PEM (-----BEGIN...); the API
-# docs describe it as base64. Handle both: write PEM as-is, else base64-decode.
-KEY_RAW="$(aws lightsail download-default-key-pair --region "$REGION" \
-    --query 'privateKeyBase64' --output text)"
-[ -n "$KEY_RAW" ] && [ "$KEY_RAW" != "None" ] || die "failed to download default key pair"
-case "$KEY_RAW" in
-    *-----BEGIN*) printf '%s\n' "$KEY_RAW" > "$KEY_FILE" ;;
-    *) printf '%s' "$KEY_RAW" | base64 -d > "$KEY_FILE" 2> /dev/null \
-        || die "private key is neither PEM nor valid base64" ;;
-esac
-grep -q -- "-----BEGIN" "$KEY_FILE" || die "downloaded key is not a valid PEM private key"
-chmod 600 "$KEY_FILE"
-
-log "waiting for provisioning to finish (this installs the panel)..."
-ok=0
-for _ in $(seq 1 72); do # ~12 min
-    if ssh "${SSH_OPTS[@]}" -i "$KEY_FILE" "ubuntu@${IP}" \
-        'test -f /var/lib/3xui-provision-done' 2> /dev/null; then
-        ok=1
-        break
-    fi
-    sleep 10
-done
-[ "$ok" -eq 1 ] || die "provisioning did not complete in time"
-log "provisioning complete."
-
-if [ -n "$PANEL_PORT" ]; then
-    log "pinning panel port ${PANEL_PORT} (username/password stay random)..."
-    ssh "${SSH_OPTS[@]}" -i "$KEY_FILE" "ubuntu@${IP}" \
-        "echo 'XUI_PANEL_PORT=${PANEL_PORT}' | sudo tee -a /etc/default/x-ui >/dev/null"
-fi
-
-log "stripping instance state (shared cleanup.sh)..."
-ssh "${SSH_OPTS[@]}" -i "$KEY_FILE" "ubuntu@${IP}" \
-    'curl -fsSL https://raw.githubusercontent.com/MHSanaei/3x-ui/main/deploy/packer/scripts/cleanup.sh | sudo bash'
-
-log "stopping instance..."
-aws lightsail stop-instance --instance-name "$INSTANCE_NAME" --region "$REGION" > /dev/null
-wait_state stopped 60 || die "instance did not stop"
-
-log "creating snapshot ${SNAPSHOT_NAME}..."
-aws lightsail create-instance-snapshot \
-    --instance-name "$INSTANCE_NAME" \
-    --instance-snapshot-name "$SNAPSHOT_NAME" \
-    --region "$REGION" > /dev/null
-
-log "waiting for snapshot to become available..."
-snap_ok=0
-for _ in $(seq 1 120); do # ~20 min
-    state=$(aws lightsail get-instance-snapshot --instance-snapshot-name "$SNAPSHOT_NAME" \
-        --region "$REGION" --query 'instanceSnapshot.state' --output text 2> /dev/null || echo "")
-    [ "$state" = "available" ] && {
-        snap_ok=1
-        break
-    }
-    sleep 10
-done
-[ "$snap_ok" -eq 1 ] || die "snapshot did not become available"
-
-log "DONE."
-echo
-echo "================================================================"
-echo " Lightsail snapshot ready: ${SNAPSHOT_NAME}  (region ${REGION})"
-echo "================================================================"
-echo " Launch an instance from it:"
-echo "   aws lightsail create-instances-from-snapshot \\"
-echo "     --instance-snapshot-name ${SNAPSHOT_NAME} \\"
-echo "     --instance-names my-3xui-1 --bundle-id ${BUNDLE} \\"
-echo "     --availability-zone ${AZ} --region ${REGION}"
-if [ -n "$PANEL_PORT" ]; then
-    echo
-    echo " Then open the panel port (pinned to ${PANEL_PORT}):"
-    echo "   aws lightsail open-instance-public-ports --region ${REGION} \\"
-    echo "     --instance-name my-3xui-1 \\"
-    echo "     --port-info fromPort=${PANEL_PORT},toPort=${PANEL_PORT},protocol=TCP"
-else
-    echo
-    echo " Each instance picks a RANDOM panel port. After it boots, read it from"
-    echo "   sudo cat /etc/x-ui/credentials.txt"
-    echo " and open that TCP port in the instance's Lightsail IPv4 firewall."
-fi
-echo "================================================================"

+ 0 - 51
deploy/lightsail/launch-script.sh

@@ -1,51 +0,0 @@
-#!/bin/bash
-#
-# Amazon Lightsail launch script for 3x-ui (self-service, per-instance creds).
-#
-# Use it one of two ways when creating an Ubuntu 24.04 Lightsail instance:
-#   * Console: "Add launch script" -> paste this file.
-#   * CLI:     aws lightsail create-instances --user-data file://launch-script.sh ...
-#
-# It installs the latest 3x-ui release non-interactively and generates unique
-# random credentials for THIS instance. The full credentials land in
-# /etc/x-ui/install-result.env (mode 600); /etc/motd shows only the URL + username.
-#
-# IMPORTANT (Lightsail firewall): Lightsail only opens 22/80/443 by default. The
-# panel listens on a random high port, so after boot read the port from
-# /etc/x-ui/install-result.env and open it under the instance's Networking tab
-# (IPv4 Firewall), or pin a known port below and pre-open it.
-set -e
-export DEBIAN_FRONTEND=noninteractive
-
-# --- Non-interactive install knobs ------------------------------------------
-export XUI_NONINTERACTIVE=1
-export XUI_SSL_MODE="${XUI_SSL_MODE:-none}"
-# Pin a known panel port so you can pre-open it in the Lightsail firewall
-# (otherwise a random high port is chosen). Username/password stay random:
-#   export XUI_PANEL_PORT="54321"
-# Other optional pins (unset => secure random):
-#   export XUI_USERNAME="admin2"
-#   export XUI_PASSWORD="change-me"
-#   export XUI_WEB_BASE_PATH="panel"
-# Domain TLS instead of plain HTTP:
-#   export XUI_SSL_MODE="domain" XUI_DOMAIN="panel.example.com" XUI_ACME_EMAIL="[email protected]"
-# ----------------------------------------------------------------------------
-
-curl -fsSL https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh | bash
-
-# /etc/motd is world-readable, so it gets ONLY non-secret info (URL + username);
-# the full credentials stay in the root-only /etc/x-ui/install-result.env
-# (mode 600) — read them with `sudo cat` over SSH.
-if [ -r /etc/x-ui/install-result.env ]; then
-    # shellcheck disable=SC1091
-    . /etc/x-ui/install-result.env
-    {
-        echo
-        echo "=== 3x-ui panel (generated on first boot) ==="
-        echo "URL:      ${XUI_ACCESS_URL:-unknown}"
-        echo "Username: ${XUI_USERNAME:-unknown}"
-        echo "Password + API token: sudo cat /etc/x-ui/install-result.env"
-        echo "Open the panel port in the Lightsail IPv4 firewall, then log in."
-        echo "============================================="
-    } >> /etc/motd 2>/dev/null || true
-fi

+ 0 - 59
deploy/lightsail/snapshot-userdata.sh

@@ -1,59 +0,0 @@
-#!/bin/bash
-#
-# Lightsail snapshot provisioning user-data (used by build-snapshot.sh).
-#
-# Installs the 3x-ui panel into a build instance but creates NO database and
-# NO credentials, and enables the first-boot unit. The instance is then snapshot
-# so that every instance launched from the snapshot generates its own unique
-# credentials on first boot (see deploy/firstboot/).
-#
-# This is the Lightsail equivalent of deploy/packer/scripts/provision.sh. It is
-# NOT for end users — use deploy/lightsail/launch-script.sh for a direct install.
-set -e
-export DEBIAN_FRONTEND=noninteractive
-
-REPO=MHSanaei/3x-ui
-XUI_DIR=/usr/local/x-ui
-RAW="https://raw.githubusercontent.com/${REPO}/main"
-
-apt-get update
-apt-get install -y --no-install-recommends \
-    ca-certificates curl tar tzdata socat openssl cron jq
-
-ARCH=$(dpkg --print-architecture) # amd64 | arm64
-VER=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | jq -r .tag_name)
-if [ -z "$VER" ] || [ "$VER" = "null" ]; then
-    echo "failed to resolve 3x-ui version" >&2
-    exit 1
-fi
-
-tmp=$(mktemp -d)
-curl -fL4 --retry 3 -o "${tmp}/x.tar.gz" \
-    "https://github.com/${REPO}/releases/download/${VER}/x-ui-linux-${ARCH}.tar.gz"
-
-systemctl stop x-ui > /dev/null 2>&1 || true
-rm -rf "$XUI_DIR"
-tar -xzf "${tmp}/x.tar.gz" -C /usr/local/
-chmod +x "${XUI_DIR}/x-ui" "${XUI_DIR}/x-ui.sh"
-chmod +x "${XUI_DIR}"/bin/* 2> /dev/null || true
-cp -f "${XUI_DIR}/x-ui.sh" /usr/bin/x-ui
-chmod +x /usr/bin/x-ui
-mkdir -p /var/log/x-ui
-
-# Panel + first-boot systemd units.
-install -m 644 "${XUI_DIR}/x-ui.service.debian" /etc/systemd/system/x-ui.service
-curl -fL4 -o "${XUI_DIR}/x-ui-firstboot.sh" "${RAW}/deploy/firstboot/x-ui-firstboot.sh"
-curl -fL4 -o /etc/systemd/system/x-ui-firstboot.service "${RAW}/deploy/firstboot/x-ui-firstboot.service"
-chmod 755 "${XUI_DIR}/x-ui-firstboot.sh"
-chmod 644 /etc/systemd/system/x-ui-firstboot.service
-
-systemctl daemon-reload
-systemctl enable x-ui-firstboot.service
-systemctl enable x-ui.service
-
-# No DB, no creds in the image — first boot generates them per-instance.
-rm -f /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db-* /etc/x-ui/.firstboot-done 2> /dev/null || true
-
-# Marker that build-snapshot.sh polls for over SSH.
-touch /var/lib/3xui-provision-done
-echo "[snapshot-userdata] provisioned 3x-ui ${VER} (${ARCH}); no DB created."

+ 0 - 92
deploy/marketplace/aws/README.md

@@ -1,92 +0,0 @@
-# Publishing 3x-ui to the AWS Marketplace (AMI)
-
-This is the checklist for turning the Packer-built AMI into an AWS Marketplace
-listing. It assumes you have already built an AMI with
-[`../../packer/`](../../packer/) (locally or via `.github/workflows/image.yml`).
-
-> Do **not** commit AMI IDs, AWS account numbers, or credentials. The AMI ID is
-> printed to the workflow job summary at build time.
-
-## 1. Seller registration (one-time)
-
-1. Sign in to the [AWS Marketplace Management Portal](https://aws.amazon.com/marketplace/management/)
-   with the AWS account that will own the listing.
-2. Complete **seller registration** (legal entity, bank, tax interview). Required
-   before any product can be submitted.
-
-## 2. Build a compliant AMI
-
-Build in the seller account (or share the AMI into it):
-
-```bash
-cd deploy/packer
-packer init .
-# amd64
-packer build -only='amazon-ebs.x-ui' \
-  -var 'xui_version=vX.Y.Z' -var 'xui_arch=amd64' -var 'instance_type=t3.small' -var 'region=eu-central-1' .
-# arm64 (Graviton)
-packer build -only='amazon-ebs.x-ui' \
-  -var 'xui_version=vX.Y.Z' -var 'xui_arch=arm64' -var 'instance_type=t4g.small' -var 'region=eu-central-1' .
-```
-
-You can list both AMIs (amd64 + arm64) as architectures of a single Marketplace
-product, or as separate products.
-
-The image already satisfies the Marketplace AMI policies enforced by `harden.sh`
-+ `cleanup.sh`:
-
-- ✅ `PasswordAuthentication no`, `PermitRootLogin prohibit-password`
-- ✅ no default OS account passwords (all locked)
-- ✅ no baked `authorized_keys`, no SSH host keys (regenerated on boot)
-- ✅ base OS = current Ubuntu 24.04 LTS, patched at build time
-- ✅ no application default credentials — the panel admin is generated on first
-  boot on a random high port (no `admin/admin`, no shipped `x-ui.db`)
-
-## 3. Run the self-service AMI scan
-
-1. In the Management Portal: **Server products → AMIs → Upload/scan an AMI**.
-2. Share the AMI with the AWS Marketplace scanning account when prompted
-   (the portal gives you the exact account id and the `modify-image-attribute`
-   command, or share it from the EC2 console).
-3. Start the scan. It checks SSH config, default credentials, open ports, and
-   for malware. Fix any finding and re-scan.
-
-Common scan findings and where they're handled:
-
-| Finding | Fix (already in the build) |
-| --- | --- |
-| Password authentication enabled | `harden.sh` sshd drop-in |
-| Root login with password | `harden.sh` `PermitRootLogin prohibit-password` |
-| Default user password set | `harden.sh` `passwd -l` on all accounts |
-| Authorized keys present | `cleanup.sh` removes them |
-| Out-of-date packages | base image is the latest LTS; `provision.sh` runs `apt-get update` |
-
-## 4. Create the product (limited / private first)
-
-1. **Server products → Create new product → AMI** (or AMI + CloudFormation).
-2. Add title, description, categories, pricing (free or paid), regions, the AMI
-   id, recommended instance types, and the **usage instructions** (tell buyers
-   to read `/etc/x-ui/credentials.txt` / MOTD after first boot for the generated
-   admin login, then change the password).
-3. Submit as a **Limited** (private) listing first. AWS publishes it with
-   restricted visibility so only your account / allow-listed accounts see it.
-
-## 5. Preview & launch test
-
-1. From the limited listing, **subscribe and launch** a test instance.
-2. SSH in, `sudo cat /etc/x-ui/credentials.txt`, open the panel URL, log in,
-   confirm the panel works and the credentials are unique to that instance.
-3. Launch a second instance and confirm its credentials differ (no shared
-   secrets).
-
-## 6. Go public
-
-1. Once the scan passes and the preview looks correct, request **public
-   visibility** (move from Limited to Public) in the listing.
-2. AWS does a final review before the listing goes live.
-
-## References
-
-- AWS Marketplace seller guide: <https://docs.aws.amazon.com/marketplace/latest/userguide/>
-- AMI-based product requirements: <https://docs.aws.amazon.com/marketplace/latest/userguide/product-and-ami-policies.html>
-- Self-service AMI scanning: <https://docs.aws.amazon.com/marketplace/latest/userguide/product-submission.html>

+ 3 - 24
deploy/marketplace/hetzner/README.md

@@ -1,9 +1,10 @@
 # 3x-ui on Hetzner Cloud
 
 Hetzner Cloud does **not** have a third-party image marketplace the way AWS does.
-There are two practical ways to ship 3x-ui on Hetzner.
+Ship 3x-ui via **cloud-init**: each instance installs non-interactively and
+generates unique per-instance credentials (no `admin/admin`, no shared secret).
 
-## Option A — cloud-init (recommended, no image build)
+## cloud-init (no image build)
 
 Use the generic user-data from [`../../cloud-init/`](../../cloud-init/). It installs
 3x-ui non-interactively and generates unique per-instance credentials.
@@ -27,28 +28,6 @@ After boot, fetch the generated credentials:
 ssh root@<server-ip> 'cat /etc/x-ui/install-result.env'
 ```
 
-## Option B — snapshot from the qcow2 / a configured server
-
-Hetzner lets you create a **snapshot** of a running server and launch new
-servers from it. Two ways to get there:
-
-1. **From the Packer qcow2:** Hetzner does not allow direct qcow2 upload via the
-   normal API, but you can boot a server, write the image to its disk in rescue
-   mode, then take a snapshot — or simply use Option A, which needs no image.
-2. **From a configured server:** spin up a server, install via cloud-init
-   (Option A), verify, then **delete `/etc/x-ui/x-ui.db` and the first-boot
-   sentinel** before snapshotting so clones regenerate their own credentials:
-
-   ```bash
-   systemctl stop x-ui
-   rm -f /etc/x-ui/x-ui.db /etc/x-ui/.firstboot-done /etc/x-ui/credentials.txt
-   # re-enable first-boot regeneration if you installed via Packer:
-   systemctl enable x-ui-firstboot 2>/dev/null || true
-   ```
-
-   > ⚠️ If you snapshot a server **with** its `x-ui.db`, every clone shares the
-   > same admin credentials and session secret. Always remove the DB first.
-
 ## "App"-style listing
 
 Hetzner's curated apps live in the community repo

+ 0 - 7
deploy/packer/.gitignore

@@ -1,7 +0,0 @@
-# Packer build artifacts (never commit images or manifests)
-output-qemu/
-*.qcow2
-*.raw
-packer-manifest.json
-packer_cache/
-crash.log

+ 0 - 116
deploy/packer/README.md

@@ -1,116 +0,0 @@
-# 3x-ui golden image (Packer)
-
-Builds a cloud image with the 3x-ui panel pre-installed but **not configured**:
-the image ships with **no database and no credentials**, and generates a unique
-admin account on first boot. This is the **primary** path for AWS Marketplace
-and any reusable image.
-
-Two sources, one build:
-
-| Source | Output | For |
-| --- | --- | --- |
-| `amazon-ebs` | AWS AMI | AWS / Marketplace |
-| `qemu` | `qcow2` (+ `raw`) | Hetzner, DigitalOcean, Vultr, GCP, Azure, Oracle, bare metal |
-
-Both sources build for **`amd64` and `arm64`** (select with `-var xui_arch=...`).
-
-## Why no baked DB
-
-3x-ui seeds a hardcoded `admin/admin` user and generates its session secret +
-panel GUID the first time it starts. If an image shipped an initialized
-`x-ui.db`, **every clone would share the same credentials and secret**. So the
-build deliberately:
-
-- installs the panel binary + systemd unit but **never starts it** and **never
-  creates a DB** (`scripts/provision.sh`);
-- wipes any stray DB/credentials/host-keys at the end (`scripts/cleanup.sh`);
-- enables `x-ui-firstboot.service`, which on first boot resets settings, sets a
-  random username/password on a random high port, regenerates the secret/GUID,
-  and writes the credentials to `/etc/x-ui/credentials.txt` + `/etc/motd`
-  (`deploy/firstboot/`).
-
-## Prerequisites
-
-- [Packer](https://developer.hashicorp.com/packer) ≥ 1.9
-- For `qemu` amd64: `qemu-system-x86`, `qemu-utils` (and `/dev/kvm` for acceptable speed)
-- For `qemu` arm64: `qemu-system-arm`, `qemu-efi-aarch64`, `qemu-utils` — best built on an
-  arm64 host (native KVM); cross-building from x86 works but uses slow TCG emulation
-- For `amazon-ebs`: AWS credentials with EC2 build permissions (arm64 builds on a Graviton
-  instance such as `t4g.small`)
-
-```bash
-cd deploy/packer
-packer init .
-packer fmt -check .      # formatting
-packer validate .        # both sources
-```
-
-## Build
-
-Build a specific release (recommended) or `latest`:
-
-```bash
-# amd64 qcow2 (no cloud account needed)
-packer build -only='qemu.x-ui' -var 'xui_version=v3.3.1' -var 'xui_arch=amd64' .
-
-# arm64 qcow2 (run on an arm64 host for native KVM)
-packer build -only='qemu.x-ui' -var 'xui_version=v3.3.1' -var 'xui_arch=arm64' .
-
-# amd64 AWS AMI
-packer build -only='amazon-ebs.x-ui' \
-  -var 'xui_version=v3.3.1' -var 'xui_arch=amd64' -var 'instance_type=t3.small' -var 'region=eu-central-1' .
-
-# arm64 AWS AMI (Graviton)
-packer build -only='amazon-ebs.x-ui' \
-  -var 'xui_version=v3.3.1' -var 'xui_arch=arm64' -var 'instance_type=t4g.small' -var 'region=eu-central-1' .
-```
-
-Outputs (per arch):
-- `output-qemu/3x-ui-ubuntu-24.04-<arch>.qcow2` and `.raw`
-- the AMI id (also recorded in `packer-manifest.json`)
-
-If `/dev/kvm` is unavailable, add `-var 'qemu_accelerator=tcg'` (much slower).
-
-## Key variables
-
-See [`variables.pkr.hcl`](variables.pkr.hcl) for the full list.
-
-| Variable | Default | Notes |
-| --- | --- | --- |
-| `xui_version` | `latest` | Release tag to install, e.g. `v3.3.1` |
-| `xui_arch` | `amd64` | `amd64` or `arm64` (derives the base AMI / cloud image) |
-| `region` | `eu-central-1` | AWS region (amazon-ebs) |
-| `instance_type` | `t3.small` | EC2 build instance — must match the arch (`t4g.small` for arm64) |
-| `qemu_accelerator` | `kvm` | `kvm` or `tcg` |
-| `qemu_cpu` | `host` | arm64 `-cpu` model (`host` with KVM, `max` for TCG) |
-| `ubuntu_version` | `24.04` | Base Ubuntu LTS (naming/tags) |
-
-The CI workflow builds both arches automatically: amd64 qcow2 on a standard runner,
-arm64 qcow2 on a native `ubuntu-24.04-arm` runner, and both AMIs from a single runner
-(the build instance runs in AWS).
-
-## First boot
-
-On the first boot of any instance launched from the image:
-
-1. `x-ui-firstboot.service` runs **before** `x-ui.service`.
-2. It generates a unique admin username/password, a random panel port, a random
-   base path, and an API token.
-3. Credentials are written to `/etc/x-ui/credentials.txt` (root-only) and shown
-   in `/etc/motd`. Retrieve them with `sudo cat /etc/x-ui/credentials.txt`.
-4. The panel then starts on the random port. `admin/admin` never exists.
-
-## CI
-
-`.github/workflows/image.yml` runs this build on `release: published` (and via
-`workflow_dispatch`), attaching the compressed `qcow2` to the release and
-building the AMI when AWS credentials are configured.
-
-## A note on host firewalls
-
-`scripts/harden.sh` intentionally does **not** enable a restrictive host
-firewall. 3x-ui opens Xray inbound ports on admin-chosen ports at runtime, which
-a host firewall would block. Use your cloud provider's security groups/firewall
-instead, and open the panel port + your inbound ports there. If you still want a
-host firewall, add `ufw` rules in `harden.sh` allowing SSH, the panel port and
-your inbound ports.

+ 0 - 59
deploy/packer/scripts/cleanup.sh

@@ -1,59 +0,0 @@
-#!/usr/bin/env bash
-#
-# cleanup.sh — strip all instance-specific state and secrets from the image.
-#
-# Runs LAST. The output image must contain no panel database, no credentials,
-# no SSH host keys, and no baked authorized_keys. Fails the build if any of
-# those survive.
-set -euo pipefail
-
-echo "[cleanup] removing panel database, credentials and first-boot sentinel..."
-rm -f /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db-* 2> /dev/null || true
-rm -f /etc/x-ui/install-result.env /etc/x-ui/credentials.txt 2> /dev/null || true
-rm -f /etc/x-ui/.firstboot-done 2> /dev/null || true
-
-echo "[cleanup] removing SSH host keys (regenerated on first boot)..."
-rm -f /etc/ssh/ssh_host_* 2> /dev/null || true
-
-echo "[cleanup] removing any baked authorized_keys..."
-rm -f /root/.ssh/authorized_keys 2> /dev/null || true
-find /home -maxdepth 3 -name authorized_keys -type f -delete 2> /dev/null || true
-
-echo "[cleanup] resetting machine-id..."
-truncate -s 0 /etc/machine-id 2> /dev/null || true
-rm -f /var/lib/dbus/machine-id 2> /dev/null || true
-ln -sf /etc/machine-id /var/lib/dbus/machine-id 2> /dev/null || true
-
-echo "[cleanup] resetting cloud-init so it re-runs on the real first boot..."
-cloud-init clean --logs --seed > /dev/null 2>&1 || rm -rf /var/lib/cloud/* 2> /dev/null || true
-
-echo "[cleanup] truncating logs, history and package caches..."
-find /var/log -type f -exec truncate -s 0 {} + 2> /dev/null || true
-rm -rf /var/lib/x-ui /var/log/x-ui/* 2> /dev/null || true
-apt-get clean || true
-rm -rf /var/lib/apt/lists/* 2> /dev/null || true
-rm -f /root/.bash_history 2> /dev/null || true
-find /home -maxdepth 3 -name .bash_history -type f -delete 2> /dev/null || true
-rm -rf /tmp/firstboot 2> /dev/null || true
-
-echo "[cleanup] verifying the image is clean..."
-fail=0
-for f in /etc/x-ui/x-ui.db /etc/x-ui/credentials.txt /etc/x-ui/install-result.env /etc/x-ui/.firstboot-done; do
-    if [ -e "$f" ]; then
-        echo "[cleanup] FATAL: $f is present in the image" >&2
-        fail=1
-    fi
-done
-if ls /etc/ssh/ssh_host_* > /dev/null 2>&1; then
-    echo "[cleanup] FATAL: SSH host keys present in the image" >&2
-    fail=1
-fi
-if [ -e /root/.ssh/authorized_keys ]; then
-    echo "[cleanup] FATAL: /root/.ssh/authorized_keys present in the image" >&2
-    fail=1
-fi
-if [ "$fail" -ne 0 ]; then
-    exit 1
-fi
-
-echo "[cleanup] OK — no DB, no credentials, no host keys, no authorized_keys."

+ 0 - 39
deploy/packer/scripts/harden.sh

@@ -1,39 +0,0 @@
-#!/usr/bin/env bash
-#
-# harden.sh — baseline OS hardening for AWS Marketplace AMI scanner compliance.
-#
-# Focus: the controls the scanner actually checks — key-only SSH, no root
-# password login, and no default OS account passwords. A restrictive host
-# firewall is intentionally NOT enforced by default because 3x-ui opens Xray
-# inbound ports on admin-chosen ports at runtime (see README for the rationale
-# and how to add ufw rules if you want them).
-set -euo pipefail
-export DEBIAN_FRONTEND=noninteractive
-
-echo "[harden] applying SSH hardening..."
-install -d -m 755 /etc/ssh/sshd_config.d
-cat > /etc/ssh/sshd_config.d/99-3xui-hardening.conf << 'EOF'
-# 3x-ui golden image hardening (AWS Marketplace scanner compliance)
-PasswordAuthentication no
-PermitRootLogin prohibit-password
-KbdInteractiveAuthentication no
-ChallengeResponseAuthentication no
-EOF
-chmod 644 /etc/ssh/sshd_config.d/99-3xui-hardening.conf
-
-echo "[harden] locking passwords on default OS accounts..."
-# No account may ship with a usable password. Keys are provisioned per-instance
-# by the cloud platform (EC2 metadata / cloud-init) on first boot.
-# passwd -l locks the PASSWORD only; key-based login keeps working.
-for u in root ubuntu admin; do
-    if id "$u" > /dev/null 2>&1; then
-        passwd -l "$u" > /dev/null 2>&1 || true
-    fi
-done
-
-echo "[harden] enabling automatic security updates..."
-apt-get update
-apt-get install -y --no-install-recommends unattended-upgrades
-systemctl enable unattended-upgrades > /dev/null 2>&1 || true
-
-echo "[harden] done."

+ 0 - 76
deploy/packer/scripts/provision.sh

@@ -1,76 +0,0 @@
-#!/usr/bin/env bash
-#
-# provision.sh — install the 3x-ui panel into a golden image (Packer).
-#
-# Self-contained: mirrors install.sh's download/extract logic but DELIBERATELY
-# does NOT run config_after_install and does NOT create a database. The image
-# must ship without /etc/x-ui/x-ui.db so that deploy/firstboot generates unique
-# per-instance credentials on first boot. Both x-ui.service and
-# x-ui-firstboot.service are enabled but NOT started here.
-#
-# Inputs (from Packer environment_vars):
-#   XUI_VERSION  release tag (e.g. v3.3.1) or 'latest'
-#   XUI_ARCH     amd64 (default) or arm64
-set -euo pipefail
-
-XUI_VERSION="${XUI_VERSION:-latest}"
-XUI_ARCH="${XUI_ARCH:-amd64}"
-XUI_DIR="/usr/local/x-ui"
-REPO="MHSanaei/3x-ui"
-export DEBIAN_FRONTEND=noninteractive
-
-echo "[provision] installing base packages..."
-apt-get update
-apt-get install -y --no-install-recommends \
-    ca-certificates curl tar tzdata socat openssl cron jq
-
-echo "[provision] resolving 3x-ui version..."
-if [ "$XUI_VERSION" = "latest" ]; then
-    XUI_VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | jq -r '.tag_name')
-fi
-if [ -z "$XUI_VERSION" ] || [ "$XUI_VERSION" = "null" ]; then
-    echo "[provision] ERROR: could not resolve 3x-ui release tag" >&2
-    exit 1
-fi
-echo "[provision] installing 3x-ui ${XUI_VERSION} (${XUI_ARCH})"
-
-tarball="x-ui-linux-${XUI_ARCH}.tar.gz"
-url="https://github.com/${REPO}/releases/download/${XUI_VERSION}/${tarball}"
-tmp="$(mktemp -d)"
-trap 'rm -rf "$tmp"' EXIT
-
-# Download the RELEASED binary tarball (no Go build inside the image).
-curl -fL4 --retry 3 -o "${tmp}/${tarball}" "$url"
-
-# Extract into /usr/local/ (the tarball contains an x-ui/ directory).
-systemctl stop x-ui > /dev/null 2>&1 || true
-rm -rf "$XUI_DIR"
-tar -xzf "${tmp}/${tarball}" -C /usr/local/
-chmod +x "${XUI_DIR}/x-ui" "${XUI_DIR}/x-ui.sh"
-chmod +x "${XUI_DIR}"/bin/* 2> /dev/null || true
-
-# Install the x-ui management CLI.
-if [ -f "${XUI_DIR}/x-ui.sh" ]; then
-    cp -f "${XUI_DIR}/x-ui.sh" /usr/bin/x-ui
-else
-    curl -fL4 -o /usr/bin/x-ui "https://raw.githubusercontent.com/${REPO}/main/x-ui.sh"
-fi
-chmod +x /usr/bin/x-ui
-mkdir -p /var/log/x-ui
-
-# Panel systemd unit (Ubuntu base => debian variant).
-install -m 644 "${XUI_DIR}/x-ui.service.debian" /etc/systemd/system/x-ui.service
-
-# First-boot per-instance credential unit + script (uploaded to /tmp/firstboot).
-install -m 755 /tmp/firstboot/x-ui-firstboot.sh "${XUI_DIR}/x-ui-firstboot.sh"
-install -m 644 /tmp/firstboot/x-ui-firstboot.service /etc/systemd/system/x-ui-firstboot.service
-
-systemctl daemon-reload
-# Enable (start on next boot) but do NOT start now — there is no DB yet.
-systemctl enable x-ui-firstboot.service
-systemctl enable x-ui.service
-
-# Belt-and-braces: ensure no DB / sentinel was created during provisioning.
-rm -f /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db-* /etc/x-ui/.firstboot-done 2> /dev/null || true
-
-echo "[provision] done — panel installed, services enabled, NO database initialized."

+ 0 - 109
deploy/packer/variables.pkr.hcl

@@ -1,109 +0,0 @@
-// Input variables for the 3x-ui golden-image build.
-// See README.md for usage. Override with -var / -var-file or env (PKR_VAR_*).
-
-variable "xui_version" {
-  type        = string
-  description = "3x-ui release tag to install, e.g. v3.3.1. 'latest' resolves the newest GitHub release at build time."
-  default     = "latest"
-}
-
-variable "xui_arch" {
-  type        = string
-  description = "CPU architecture to build for: amd64 or arm64."
-  default     = "amd64"
-  validation {
-    condition     = contains(["amd64", "arm64"], var.xui_arch)
-    error_message = "The xui_arch value must be 'amd64' or 'arm64'."
-  }
-}
-
-variable "ubuntu_version" {
-  type        = string
-  description = "Ubuntu LTS version label, used only for image naming/tags."
-  default     = "24.04"
-}
-
-// --- amazon-ebs (AMI) ---------------------------------------------------------
-
-variable "region" {
-  type        = string
-  description = "AWS region the AMI is built in."
-  default     = "eu-central-1"
-}
-
-variable "instance_type" {
-  type        = string
-  description = "EC2 instance type used to build the AMI. Must match xui_arch (e.g. t3.small for amd64, t4g.small for arm64/Graviton)."
-  default     = "t3.small"
-}
-
-variable "ami_name_prefix" {
-  type        = string
-  description = "Prefix for the produced AMI name."
-  default     = "3x-ui"
-}
-
-variable "source_ami_filter_name" {
-  type        = string
-  description = "Override for the Canonical Ubuntu base AMI name filter. Empty ⇒ derived from xui_arch (latest patched 24.04 LTS for that arch)."
-  default     = ""
-}
-
-variable "ssh_username" {
-  type        = string
-  description = "Default SSH user on the base Ubuntu cloud image."
-  default     = "ubuntu"
-}
-
-// --- qemu (qcow2 / raw) -------------------------------------------------------
-
-variable "qemu_iso_url" {
-  type        = string
-  description = "Override for the Ubuntu cloud image used as the qemu base disk. Empty ⇒ derived from xui_arch (amd64/arm64 cloud image)."
-  default     = ""
-}
-
-variable "qemu_iso_checksum" {
-  type        = string
-  description = "Checksum for the qemu base disk. 'file:<SHA256SUMS url>' auto-fetches; 'none' skips verification."
-  default     = "file:https://cloud-images.ubuntu.com/releases/24.04/release/SHA256SUMS"
-}
-
-variable "qemu_accelerator" {
-  type        = string
-  description = "QEMU accelerator: 'kvm' when /dev/kvm is available, else 'tcg' (slow software emulation)."
-  default     = "kvm"
-}
-
-variable "qemu_headless" {
-  type        = bool
-  description = "Run QEMU without a display (required on CI runners)."
-  default     = true
-}
-
-variable "qemu_build_password" {
-  type        = string
-  description = "Temporary password injected via cloud-init for Packer's build-time SSH. Locked/removed before the image is finalized."
-  default     = "packer-build-temp-pw"
-  sensitive   = true
-}
-
-# --- qemu arm64-only knobs (ignored for amd64) -------------------------------
-
-variable "qemu_cpu" {
-  type        = string
-  description = "QEMU -cpu model for arm64 builds: 'host' with KVM on an arm64 host, 'max' for TCG emulation."
-  default     = "host"
-}
-
-variable "qemu_efi_code" {
-  type        = string
-  description = "Path to the arm64 UEFI code firmware (AAVMF). Only used when xui_arch=arm64."
-  default     = "/usr/share/AAVMF/AAVMF_CODE.fd"
-}
-
-variable "qemu_efi_vars" {
-  type        = string
-  description = "Path to the arm64 UEFI vars firmware template (AAVMF). Only used when xui_arch=arm64."
-  default     = "/usr/share/AAVMF/AAVMF_VARS.fd"
-}

+ 0 - 160
deploy/packer/x-ui.pkr.hcl

@@ -1,160 +0,0 @@
-// 3x-ui golden image — one build, two sources:
-//   * amazon-ebs : produces an AWS AMI (Marketplace-scannable)
-//   * qemu       : produces a qcow2 (+ raw) for Hetzner/DO/Vultr/GCP/Azure/Oracle
-//
-// The image ships WITHOUT an initialized x-ui.db and WITHOUT any baked
-// credentials. deploy/firstboot/x-ui-firstboot.{sh,service} generates unique
-// per-instance credentials on first boot, before x-ui.service starts.
-//
-// Provisioner order is fixed: provision.sh -> harden.sh -> cleanup.sh.
-
-packer {
-  required_plugins {
-    amazon = {
-      version = ">= 1.3.0"
-      source  = "github.com/hashicorp/amazon"
-    }
-    qemu = {
-      version = ">= 1.1.0"
-      source  = "github.com/hashicorp/qemu"
-    }
-  }
-}
-
-locals {
-  build_stamp = formatdate("YYYYMMDD-hhmmss", timestamp())
-  image_name  = "${var.ami_name_prefix}-ubuntu-${var.ubuntu_version}-${var.xui_arch}"
-  is_arm      = var.xui_arch == "arm64"
-
-  # Base images are derived from xui_arch unless explicitly overridden.
-  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-*"
-  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"
-}
-
-source "amazon-ebs" "x-ui" {
-  region        = var.region
-  instance_type = var.instance_type
-  ssh_username  = var.ssh_username
-
-  ami_name        = "${local.image_name}-${var.xui_version}-${local.build_stamp}"
-  ami_description = "3x-ui panel on Ubuntu ${var.ubuntu_version}. Per-instance credentials are generated on first boot."
-
-  source_ami_filter {
-    filters = {
-      name                = local.source_ami_name
-      root-device-type    = "ebs"
-      virtualization-type = "hvm"
-    }
-    owners      = ["099720109477"] // Canonical
-    most_recent = true
-  }
-
-  launch_block_device_mappings {
-    device_name           = "/dev/sda1"
-    volume_size           = 8
-    volume_type           = "gp3"
-    delete_on_termination = true
-  }
-
-  tags = {
-    Name       = local.image_name
-    Project    = "3x-ui"
-    XuiVersion = var.xui_version
-    BuildTool  = "packer"
-    BaseOS     = "ubuntu-${var.ubuntu_version}"
-  }
-}
-
-source "qemu" "x-ui" {
-  iso_url      = local.qemu_iso_url
-  iso_checksum = var.qemu_iso_checksum
-  disk_image   = true
-  disk_size    = "10G"
-  format       = "qcow2"
-
-  accelerator    = var.qemu_accelerator
-  headless       = var.qemu_headless
-  cpus           = 2
-  memory         = 2048
-  net_device     = "virtio-net"
-  disk_interface = "virtio"
-
-  // Arch-specific QEMU machine. amd64 uses Packer defaults (BIOS boot, x86_64);
-  // arm64 needs the aarch64 binary, the 'virt' machine and UEFI (AAVMF) firmware.
-  qemu_binary       = local.is_arm ? "qemu-system-aarch64" : null
-  machine_type      = local.is_arm ? "virt" : null
-  efi_boot          = local.is_arm
-  efi_firmware_code = local.is_arm ? var.qemu_efi_code : null
-  efi_firmware_vars = local.is_arm ? var.qemu_efi_vars : null
-  qemuargs          = local.is_arm ? [["-cpu", var.qemu_cpu]] : []
-
-  output_directory = "output-qemu"
-  vm_name          = "${local.image_name}.qcow2"
-
-  // Build-time access: a NoCloud seed sets a temporary password for the default
-  // user so Packer can SSH in. The seed is a separate CD-ROM (not part of the
-  // output disk); the password is locked by harden.sh and state wiped by cleanup.sh.
-  cd_label = "cidata"
-  cd_content = {
-    "meta-data" = ""
-    "user-data" = <<-EOT
-      #cloud-config
-      password: ${var.qemu_build_password}
-      chpasswd: { expire: false }
-      ssh_pwauth: true
-    EOT
-  }
-
-  ssh_username = var.ssh_username
-  ssh_password = var.qemu_build_password
-  ssh_timeout  = "20m"
-  boot_wait    = "45s"
-
-  shutdown_command = "sudo shutdown -P now"
-}
-
-build {
-  name    = "3x-ui"
-  sources = ["source.amazon-ebs.x-ui", "source.qemu.x-ui"]
-
-  // Upload the first-boot unit + script so provision.sh can install them.
-  provisioner "shell" {
-    inline = ["mkdir -p /tmp/firstboot"]
-  }
-  provisioner "file" {
-    source      = "${path.root}/../firstboot/x-ui-firstboot.sh"
-    destination = "/tmp/firstboot/x-ui-firstboot.sh"
-  }
-  provisioner "file" {
-    source      = "${path.root}/../firstboot/x-ui-firstboot.service"
-    destination = "/tmp/firstboot/x-ui-firstboot.service"
-  }
-
-  provisioner "shell" {
-    environment_vars = [
-      "XUI_VERSION=${var.xui_version}",
-      "XUI_ARCH=${var.xui_arch}",
-      "DEBIAN_FRONTEND=noninteractive",
-    ]
-    execute_command = "chmod +x {{ .Path }}; sudo -E bash {{ .Path }}"
-    scripts = [
-      "${path.root}/scripts/provision.sh",
-      "${path.root}/scripts/harden.sh",
-      "${path.root}/scripts/cleanup.sh",
-    ]
-    // give cloud-init time to release apt locks on the very first boot
-    pause_before = "10s"
-  }
-
-  // Convert the qcow2 to raw for clouds that need it (qemu source only).
-  post-processor "shell-local" {
-    only   = ["qemu.x-ui"]
-    inline = ["qemu-img convert -p -O raw output-qemu/${local.image_name}.qcow2 output-qemu/${local.image_name}.raw"]
-  }
-
-  // Record the AMI id / artifacts for CI to surface.
-  post-processor "manifest" {
-    output     = "packer-manifest.json"
-    strip_path = true
-  }
-}

+ 0 - 86
deploy/test/smoke-firstboot.sh

@@ -1,86 +0,0 @@
-#!/usr/bin/env bash
-#
-# smoke-firstboot.sh — verify the first-boot per-instance credential script.
-#
-# Installs the released x-ui binary into a container WITHOUT a database, runs
-# x-ui-firstboot.sh, and asserts:
-#   * fresh random credentials are generated (no admin/admin)
-#   * /etc/x-ui/credentials.txt (600) and /etc/motd are written
-#   * the sentinel is created and a second run is a no-op (creds unchanged)
-#
-# Requires Docker and network access. Usage: bash deploy/test/smoke-firstboot.sh
-set -euo pipefail
-
-REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
-IMAGE="${SMOKE_IMAGE:-ubuntu:24.04}"
-
-if ! command -v docker > /dev/null 2>&1; then
-    echo "ERROR: docker is required for this smoke test." >&2
-    exit 1
-fi
-
-echo "== first-boot credential smoke test (image: $IMAGE) =="
-
-docker run --rm \
-    -v "${REPO_ROOT}/deploy/firstboot/x-ui-firstboot.sh:/root/x-ui-firstboot.sh:ro" \
-    -e DEBIAN_FRONTEND=noninteractive \
-    "$IMAGE" bash -euo pipefail -c '
-        apt-get update -qq
-        apt-get install -y -qq curl tar openssl ca-certificates jq > /dev/null
-
-        echo "--- installing released x-ui binary (no DB, no systemd) ---"
-        REPO=MHSanaei/3x-ui
-        ARCH=$(dpkg --print-architecture)   # amd64 | arm64
-        echo "container arch: $ARCH"
-        VER=$(curl --fail --location --silent --show-error \
-            --retry 5 --retry-all-errors --retry-delay 3 \
-            --connect-timeout 15 --max-time 60 \
-            "https://api.github.com/repos/${REPO}/releases/latest" | jq -r .tag_name)
-        [ -n "$VER" ] && [ "$VER" != "null" ] || { echo "FAIL: cannot resolve version"; exit 1; }
-        tmp=$(mktemp -d)
-        # 504s and other transient GitHub/CDN hiccups are retried; a real HTTP
-        # failure (e.g. missing arch asset) still aborts after the retries.
-        if ! curl -4 --fail --location --silent --show-error \
-            --retry 5 --retry-all-errors --retry-delay 3 \
-            --connect-timeout 15 --max-time 300 \
-            -o "${tmp}/x.tar.gz" \
-            "https://github.com/${REPO}/releases/download/${VER}/x-ui-linux-${ARCH}.tar.gz"; then
-            echo "FAIL: cannot download x-ui-linux-${ARCH}.tar.gz (${VER})" >&2; exit 1
-        fi
-        test -s "${tmp}/x.tar.gz" || { echo "FAIL: downloaded tarball is empty"; exit 1; }
-        tar -xzf "${tmp}/x.tar.gz" -C /usr/local/
-        chmod +x /usr/local/x-ui/x-ui
-        install -m 755 /root/x-ui-firstboot.sh /usr/local/x-ui/x-ui-firstboot.sh
-
-        # Guarantee a clean slate (the image must never ship a DB).
-        rm -f /etc/x-ui/x-ui.db /etc/x-ui/.firstboot-done
-
-        echo "--- run 1: generate per-instance credentials ---"
-        /usr/local/x-ui/x-ui-firstboot.sh
-
-        test -f /etc/x-ui/.firstboot-done || { echo "FAIL: sentinel not created"; exit 1; }
-        test -f /etc/x-ui/credentials.txt || { echo "FAIL: credentials.txt missing"; exit 1; }
-        perms=$(stat -c %a /etc/x-ui/credentials.txt)
-        [ "$perms" = "600" ] || { echo "FAIL: credentials.txt perms=$perms (want 600)"; exit 1; }
-        grep -q "3x-ui" /etc/motd || { echo "FAIL: motd not written"; exit 1; }
-
-        # shellcheck disable=SC1090
-        . /etc/x-ui/credentials.txt
-        [ -n "${XUI_USERNAME:-}" ] && [ "$XUI_USERNAME" != "admin" ] \
-            || { echo "FAIL: username missing or still admin"; exit 1; }
-        first_user="$XUI_USERNAME"
-
-        /usr/local/x-ui/x-ui setting -show | grep -q "hasDefaultCredential: false" \
-            || { echo "FAIL: hasDefaultCredential is not false"; exit 1; }
-
-        echo "--- run 2: must be a no-op (sentinel honored) ---"
-        /usr/local/x-ui/x-ui-firstboot.sh
-        # shellcheck disable=SC1090
-        . /etc/x-ui/credentials.txt
-        [ "$XUI_USERNAME" = "$first_user" ] \
-            || { echo "FAIL: credentials changed on re-run"; exit 1; }
-
-        echo "SMOKE_PASS: firstboot user=$first_user (stable across re-run)"
-    '
-
-echo "== first-boot smoke test PASSED =="