[
  {
    "path": ".editorconfig",
    "content": "; editorconfig.org\nroot = true\n\n[*]\ncharset                     = utf-8\ncontinuation_indent_size    = 2\nend_of_line                 = lf\nindent_size                 = 2\nindent_style                = space\ninsert_final_newline        = true\nmax_line_length             = 120\ntab_width                   = 2\n; trim_trailing_whitespace    = true ; disabled until files are cleaned up\n\n[*.md]\ntrim_trailing_whitespace    = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "# ---------------------------------------\r\n# Treat Shell files as first-class code\r\n# ---------------------------------------\r\n*.sh linguist-detectable=true\r\n*.bash linguist-language=Shell\r\n*.func linguist-language=Shell\r\n*.func linguist-detectable=true\r\n*.install linguist-language=Shell\r\n\r\n# ---------------------------------------\r\n# Exclude header art from stats\r\n# ---------------------------------------\r\nct/headers/* linguist-documentation\r\n\r\n# ---------------------------------------\r\n# Exclude documentation from stats\r\n# ---------------------------------------\r\n*.md linguist-documentation\r\nREADME.md linguist-documentation\r\nCONTRIBUTING.md linguist-documentation\r\nSECURITY.md linguist-documentation\r\n\r\n# ---------------------------------------\r\n# Exclude generated/config files\r\n# ---------------------------------------\r\n.github/** linguist-generated\r\n.vscode/** linguist-generated\r\n\r\n# ---------------------------------------\r\n# Standard text handling\r\n# ---------------------------------------\r\n* text=auto eol=lf\r\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "#\n# CODEOWNERS for ProxmoxVE\n#\n\n# Order is important; the last matching pattern takes the most\n# precedence.\n\n\n# Codeowners for specific folders and files\n# Remember ending folders with /\n\n\n# Set default reviewers\n*                                                                           @asylumexp\n\n"
  },
  {
    "path": ".github/DISCUSSION_TEMPLATE/request-script.yml",
    "content": "title: \"[Script request]: \"\nlabels: [\"enhancement\"]\nbody:\n- type: input\n  attributes:\n    label: Application Name\n    description: Enter the application name.\n    placeholder: \"e.g., Home Assistant\"\n  validations:\n    required: true\n- type: input\n  attributes:\n    label: Website\n    description: Official website or github page.\n    placeholder: \"e.g., https://www.home-assistant.io/\"\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Description\n    description: Explain what the application does and why it should be added to Proxmox VE Helper-Scripts.\n    placeholder: \"e.g., Home Assistant is a popular open-source platform that brings all your smart home devices together in one place. Adding it to Proxmox VE Helper-Scripts would make setup and management on Proxmox easy, letting users quickly get a powerful, self-hosted smart home system up and running.\"\n  validations:\n    required: true\n- type: checkboxes\n  attributes:\n    label: Due Diligence\n    options:\n      - label: \"I have searched existing [scripts](https://community-scripts.github.io/Proxmox/scripts) and found no duplicates.\"\n        required: true\n      - label: \"I have searched existing [discussions](https://github.com/community-scripts/ProxmoxVE/discussions?discussions_q=) and found no duplicate requests.\"\n        required: true\n      - label: \"The application requested has 600+ stars on Github (if applicable), is older than 6 months, actively maintained and has release tarballs published.\"\n        required: true\n      - label: \"I understand that not all applications will be accepted due to various reasons and criteria by the community-scripts ORG.\"\n        required: true\n- type: markdown\n  attributes:\n    value: \"Thanks for submitting your request! The team will review it and reach out if we need more information.\"\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: community_scripts\ngithub: community_scripts\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"🐞 Script Issue Report\"\ndescription: Report a specific issue with a script. For other inquiries, please use the Discussions section.\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # 🐞 **Script Issue Report**\n        Thank you for taking the time to report an issue! Please provide as much detail as possible to help us address the problem efficiently.  \n\n        ## ⚠️ **IMPORTANT - READ FIRST**  \n        - 🔍 **Search first:** Before submitting, check if the issue has already been reported or resolved in [closed issues](https://github.com/community-scripts/ProxmoxVE/issues?q=is%3Aissue+is%3Aclosed). If found, comment on that issue instead of creating a new one.  \n        Alternatively, check the **[Discussions](https://github.com/community-scripts/ProxmoxVE/discussions)** under the *\"Announcement\"* or *\"Guide\"* categories for relevant information.  \n        - 🛠️ **Supported environments only:** Ensure you are using a default Linux distribution. Custom setups may not be supported.  \n        - 🔎 If you encounter `[ERROR] in line 23: exit code *: while executing command \"$@\" > /dev/null 2>&1`, rerun the script with verbose mode before submitting the issue.  \n        - 💡 For general questions, feature requests, or suggestions, use the [Discussions section](https://github.com/community-scripts/ProxmoxVE/discussions).  \n        \n  - type: input\n    id: guidelines\n    attributes:\n      label: ✅ Have you read and understood the above guidelines?\n      placeholder: \"yes\"\n    validations:\n      required: true\n\n  - type: dropdown\n    id: verbose_run\n    attributes:\n      label: 🔎 Did you run the script with verbose mode enabled?\n      description: \"Required for debugging any script issue. A verbose log is mandatory.\"\n      options:\n        - \"\"\n        - \"Yes, verbose mode was enabled and the output is included below\"\n        - \"No (this issue will likely be closed automatically)\"\n    validations:\n      required: true\n\n  - type: input\n    id: script_name\n    attributes:\n      label: 📜 What is the name of the script you are using?\n      placeholder: \"e.g., NextcloudPi, Zigbee2MQTT\"\n    validations:\n      required: true\n\n  - type: input\n    id: script_command\n    attributes:\n      label: 📂 What was the exact command used to execute the script?\n      placeholder: \"e.g., bash -c \\\"$(wget -qLO - https://github.com/asylumexp/Proxmox/raw/main/ct/zigbee2mqtt.sh)\\\" or \\\"update\\\"\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: issue_description\n    attributes:\n      label: 📝 Provide a clear and concise description of the issue.\n    validations:\n      required: true\n\n  - type: checkboxes\n    validations:\n      required: true\n    attributes:\n      label: ⚙️ What settings are you using?\n      options:\n        - label: Default Settings\n        - label: Advanced Settings\n\n  - type: markdown\n    attributes:\n      value: \"💡 **Tip:** If you are using Advanced Settings, please test with Default Settings before submitting an issue.\"\n\n  - type: dropdown\n    id: linux_distribution\n    attributes:\n      label: 🖥️ Which Linux distribution are you using?\n      options:\n        - \n        - Alpine\n        - Debian 11\n        - Debian 12\n        - Debian 13\n        - Ubuntu 22.04\n        - Ubuntu 24.04\n        - Ubuntu 24.10\n    validations:\n      required: true\n\n  - type: input\n    id: pve_version\n    attributes:\n      label: 📈 Which Proxmox version are you on?\n      placeholder: \"run pveversion in your PVE node console\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: steps_to_reproduce\n    attributes:\n      label: 🔄 Steps to reproduce the issue.\n      placeholder: \"e.g., Step 1: ..., Step 2: ...\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: error_output\n    attributes:\n      label: ❌ Paste the full error output (if available).\n      placeholder: \"Include any relevant logs or error messages.\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: additional_context\n    attributes:\n      label: 🖼️ Additional context (optional).\n      placeholder: \"Include screenshots, code blocks (use triple backticks ```), or any other relevant information.\"\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n    - name: 🌟 new Script request\n      url: https://github.com/community-scripts/ProxmoxVE/discussions/new?category=request-script\n      about: For feature/script requests, please use the Discussions section.\n    - name: 🤔 Questions and Help\n      url: https://github.com/community-scripts/ProxmoxVE/discussions\n      about: For suggestions or questions, please use the Discussions section.\n    - name: 💻 Discord\n      url: https://discord.gg/UHrpNWGwkH\n      about: Join our Discord server to chat with other users in the Proxmox Helper Scripts community.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: \"✨ Feature Request\"\ndescription: \"Suggest a new feature or enhancement. (not for script requests)\"\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # ✨ **Feature Request**\n        Have an idea for a new feature? Share your thoughts below!\n\n  - type: input\n    id: feature_summary\n    attributes:\n      label: \"🌟 Briefly describe the feature\"\n      placeholder: \"e.g., Add support for XYZ\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: feature_description\n    attributes:\n      label: \"📝 Detailed description\"\n      placeholder: \"Explain the feature in detail\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: use_case\n    attributes:\n      label: \"💡 Why is this useful?\"\n      placeholder: \"Describe the benefit of this feature\"\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/frontend_report.yml",
    "content": "name: \"🌐 Website Issue Report\"\ndescription: Report an issue, an optimization request or an documentation issue specifically related to the website.\nlabels: \"website\"\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **IMPORTANT:** Failure to comply with the following guidelines may result in immediate closure.\n        - Prior to submitting, kindly search the closed issues to check if the problem you are reporting has already been addressed and resolved. If you come across a closed issue that pertains to your problem, please leave a comment on that issue instead of creating a new one.\n        - If the problem is related to a bug in the website, kindly check for browser compatibility and ensure the issue occurs in multiple browsers before submitting.\n        - For suggestions, questions, or feature requests, please use the [Discussions section.](https://github.com/community-scripts/ProxmoxVE/discussions)\n  \n  - type: input\n    id: guidelines\n    attributes:\n      label: Please verify that you have read and understood the guidelines.\n      placeholder: 'yes'\n    validations:\n      required: true\n\n  - type: dropdown\n    id: issue_type\n    validations:\n      required: true\n    attributes:\n      label: What type of issue is this?\n      options:\n        - \n        - Bug\n        - Optimization\n        - Documentation\n        - Other\n  \n  - type: textarea\n    id: bug_description\n    attributes:\n      label: A clear and concise description of the issue.\n    validations:\n      required: true\n\n  - type: dropdown\n    id: browser\n    validations:\n      required: true\n    attributes:\n      label: Which browser are you using?\n      options:\n        - \n        - Chrome\n        - Firefox\n        - Safari\n        - Edge\n        - Other\n  \n  - type: markdown\n    attributes:\n      value: |\n        **If the issue is browser-related**, please provide information on the version and platform (Windows, MacOS, Linux, etc.).\n\n  - type: textarea\n    id: screenshot\n    attributes:\n      label: If relevant, including screenshots or a code block can be helpful in clarifying the issue.\n      placeholder: \"Code blocks begin and conclude by enclosing the code with three backticks (```) above and below it.\"\n    validations:\n      required: false\n\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: Please provide detailed steps to reproduce the issue.\n      placeholder: \"First do this, then this ...\"\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/task.yml",
    "content": "name: \"🛠️ Task / General Request\"\ndescription: \"Request a general task, improvement, or refactor.\"\nlabels: [\"task\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # 🛠️ **Task / General Request**\n        Request a task that isn't a bug or feature request.\n\n  - type: input\n    id: task_summary\n    attributes:\n      label: \"📌 Task summary\"\n      placeholder: \"e.g., Refactor XYZ\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: task_details\n    attributes:\n      label: \"📋 Task details\"\n      placeholder: \"Explain what needs to be done\"\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/autolabeler-config.json",
    "content": "{\n  \"new script\": [\n    {\n      \"fileStatus\": \"added\",\n      \"includeGlobs\": [\n        \"ct/**\",\n        \"install/**\",\n        \"turnkey/**\",\n        \"vm/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"update script\": [\n    {\n      \"fileStatus\": \"modified\",\n      \"includeGlobs\": [\n        \"ct/**\",\n        \"install/**\",\n        \"turnkey/**\",\n        \"vm/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"delete script\": [\n    {\n      \"fileStatus\": \"removed\",\n      \"includeGlobs\": [\n        \"ct/**\",\n        \"install/**\",\n        \"turnkey/**\",\n        \"vm/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"vm\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"vm/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"tools\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"tools/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"addon\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"tools/addon/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"pve-tool\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"tools/pve/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"core\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"misc/*.func\"\n      ],\n      \"excludeGlobs\": [\n        \"misc/api.func\"\n      ]\n    }\n  ],\n  \"documentation\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"docs/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"github\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \".github/**\",\n        \"README.md\",\n        \"SECURITY.md\",\n        \"LICENSE\",\n        \"CHANGELOG.md\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"api\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"api/**\",\n        \"misc/api.func\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ],\n  \"website\": [\n    {\n      \"fileStatus\": null,\n      \"includeGlobs\": [\n        \"frontend/**\"\n      ],\n      \"excludeGlobs\": [\n        \"frontend/public/json/**\"\n      ]\n    }\n  ],\n  \"json\": [\n    {\n      \"fileStatus\": \"modified\",\n      \"includeGlobs\": [\n        \"frontend/public/json/**\"\n      ],\n      \"excludeGlobs\": []\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/changelog-pr-config.json",
    "content": "[\n  {\n    \"title\": \"🆕 New Scripts\",\n    \"labels\": [\n      \"new script\"\n    ]\n  },\n  {\n    \"title\": \"🚀 Updated Scripts\",\n    \"labels\": [\n      \"update script\"\n    ],\n    \"subCategories\": [\n      {\n        \"title\": \"🐞 Bug Fixes\",\n        \"labels\": [\n          \"bugfix\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"✨ New Features\",\n        \"labels\": [\n          \"feature\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"💥 Breaking Changes\",\n        \"labels\": [\n          \"breaking change\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"🔧 Refactor\",\n        \"labels\": [\n          \"refactor\"\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  {\n    \"title\": \"🗑️ Deleted Scripts\",\n    \"labels\": [\n      \"delete script\"\n    ]\n  },\n  {\n    \"title\": \"💾 Core\",\n    \"labels\": [\n      \"core\"\n    ],\n    \"subCategories\": [\n      {\n        \"title\": \"🐞 Bug Fixes\",\n        \"labels\": [\n          \"bugfix\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"✨ New Features\",\n        \"labels\": [\n          \"feature\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"💥 Breaking Changes\",\n        \"labels\": [\n          \"breaking change\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"🔧 Refactor\",\n        \"labels\": [\n          \"refactor\"\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  {\n    \"title\": \"🧰 Tools\",\n    \"labels\": [\n      \"tools\"\n    ],\n    \"subCategories\": [\n      {\n        \"title\": \"🐞 Bug Fixes\",\n        \"labels\": [\n          \"bugfix\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"✨ New Features\",\n        \"labels\": [\n          \"feature\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"💥 Breaking Changes\",\n        \"labels\": [\n          \"breaking change\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"🔧 Refactor\",\n        \"labels\": [\n          \"refactor\"\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  {\n    \"title\": \"📚 Documentation\",\n    \"labels\": [\n      \"documentation\"\n    ]\n  },\n  {\n    \"title\": \"📂 Github\",\n    \"labels\": [\n      \"github\"\n    ]\n  },\n  {\n    \"title\": \"📡 API\",\n    \"labels\": [\n      \"api\"\n    ],\n    \"subCategories\": [\n      {\n        \"title\": \"🐞 Bug Fixes\",\n        \"labels\": [\n          \"bugfix\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"✨ New Features\",\n        \"labels\": [\n          \"feature\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"💥 Breaking Changes\",\n        \"labels\": [\n          \"breaking change\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"🔧 Refactor\",\n        \"labels\": [\n          \"refactor\"\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  {\n    \"title\": \"🌐 Website\",\n    \"labels\": [\n      \"website\"\n    ],\n    \"subCategories\": [\n      {\n        \"title\": \"🐞 Bug Fixes\",\n        \"labels\": [\n          \"bugfix\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"✨ New Features\",\n        \"labels\": [\n          \"feature\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"💥 Breaking Changes\",\n        \"labels\": [\n          \"breaking change\"\n        ],\n        \"notes\": []\n      },\n      {\n        \"title\": \"📝 Script Information\",\n        \"labels\": [\n          \"json\"\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  {\n    \"title\": \"❔ Uncategorized\",\n    \"labels\": [\n      \"needs triage\"\n    ]\n  }\n]\n"
  },
  {
    "path": ".github/changelogs/2022/01.md",
    "content": "﻿## 2022-01-30\n\n### Changed\n\n- **Zigbee2MQTT LXC**\n  - Clean up / Improve script\n  - Improve documentation\n\n## 2022-01-29\n\n### Changed\n\n- **Node-Red LXC**\n  - Clean up / Improve script\n  - Improve documentation\n\n## 2022-01-25\n\n### Changed\n\n- **Jellyfin Media Server LXC**\n  - new script\n\n## 2022-01-24\n\n### Changed\n\n- **Plex Media Server LXC**\n  - better Hardware Acceleration Support\n  - `va-driver-all` is preinstalled\n  - now using Ubuntu 21.10\n- **misc**\n  - new GUI script to copy data from one Plex Media Server LXC to another Plex Media Server LXC\n\n\n## Initial Catch up - 2022-01-23\n \n### Changed\n\n- **Plex Media Server LXC**\n  - add Hardware Acceleration Support\n  - add script to install Intel Drivers\n- **Zwavejs2MQTT LXC**\n  - new script to solve no auto start at boot\n- **Nginx Proxy Manager LXC** \n  - new script to use Debian 11\n- **Ubuntu 21.10 LXC** \n  - new script\n- **Mariadb LXC** \n  - add MariaDB Package Repository\n- **MQTT LXC** \n  - add Eclipse Mosquitto Package Repository\n- **Home Assistant Container LXC** \n  - change if ZFS filesystem is detected, execute automatic installation of static fuse-overlayfs\n  - add script for easy Home Assistant update\n- **Home Assistant Container LXC (Podman)** \n  - change if ZFS filesystem is detected, execute automatic installation of static fuse-overlayfs\n- **Home Assistant OS VM** \n  - change disk type from SATA to SCSI to follow Proxmox official recommendations of choosing VirtIO-SCSI with SCSI disk\n  - clean up\n- **Proxmox VE 7 Post Install** \n  - new *No-Nag* method\n- **misc**\n  - new GUI script to copy data from one Home Assistant LXC to another Home Assistant LXC\n  - new GUI script to copy data from one Zigbee2MQTT LXC to another Zigbee2MQTT LXC\n"
  },
  {
    "path": ".github/changelogs/2022/02.md",
    "content": "﻿## 2022-02-28\n\n### Changed\n\n- **Vaultwarden LXC**\n  - Add Update Script\n\n## 2022-02-24\n\n### Changed\n\n- **Nginx Proxy Manager LXC**\n  - New V2 Install Script\n\n## 2022-02-23\n\n### Changed\n\n- **Adguard Home LXC**\n  - New V2 Install Script\n- **Zigbee2MQTT LXC**\n  - New V2 Install Script\n- **Home Assistant Container LXC**\n  - Update Menu usability improvements\n\n## 2022-02-22\n\n### Changed\n\n- **Home Assistant Container LXC**\n  - New V2 Install Script\n- **Node-Red LXC**\n  - New V2 Install Script\n- **Mariadb LXC**\n  - New V2 Install Script\n- **MQTT LXC**\n  - New V2 Install Script\n- **Debian 11 LXC**\n  - New V2 Install Script\n- **Ubuntu 21.10 LXC**\n  - New V2 Install Script\n\n## 2022-02-20\n\n### Changed\n\n- **Home Assistant Container LXC**\n  - New Script to migrate to the latest Update Menu\n\n## 2022-02-19\n\n### Changed\n\n- **Nginx Proxy Manager LXC**\n  - Add Update Script\n- **Vaultwarden LXC**\n  - Make unattended install & Cleanup Script\n\n## 2022-02-18\n\n### Changed\n\n- **Node-Red LXC**\n  - Add Install Themes Script\n\n## 2022-02-16\n\n### Changed\n\n- **Home Assistant Container LXC**\n  - Add Options to Update Menu\n\n## 2022-02-14\n\n### Changed\n\n- **Home Assistant Container LXC**\n  - Add Update Menu\n\n## 2022-02-13\n\n### Changed\n\n- **Mariadb LXC**\n  - Add Adminer (formerly phpMinAdmin), a full-featured database management tool\n\n## 2022-02-12\n\n### Changed\n\n- **Home Assistant Container LXC (Podman)**\n  - Add Yacht web interface for managing Podman containers\n  - new GUI script to copy data from a **Home Assistant LXC** to a **Podman Home Assistant LXC**\n  - Improve documentation for several LXC's\n\n## 2022-02-10\n\n### Changed\n\n- **GamUntu LXC**\n  - New Script\n- **Jellyfin Media Server LXC**\n  - new script to fix [start issue](https://github.com/tteck/Proxmox/issues/29#issue-1127457380)\n- **MotionEye NVR LXC**\n  - New script\n\n## 2022-02-09\n\n### Changed\n\n- **Zigbee2MQTT LXC**\n  - added USB passthrough during installation (no extra script)\n  - Improve documentation\n- **Zwavejs2MQTT LXC**\n  - added USB passthrough during installation (no extra script)\n- **Jellyfin Media Server LXC**\n  - Moved to testing due to issues. \n  - Changed install method.\n- **Home Assistant Container LXC (Podman)** \n  - add script for easy Home Assistant update\n\n## 2022-02-06\n\n### Changed\n\n- **Debian 11 LXC**\n  - Add Docker Support\n- **Ubuntu 21.10 LXC**\n  - Add Docker Support\n\n## 2022-02-05\n\n### Changed\n\n- **Vaultwarden LXC**\n  - New script\n\n## 2022-02-01\n\n### Changed\n\n- **All Scripts**\n  - Fix issue where some networks were slow to assign a IP address to the container causing scripts to fail.\n"
  },
  {
    "path": ".github/changelogs/2022/03.md",
    "content": "﻿## 2022-03-28\n\n### Changed\n\n- **Docker LXC**\n  - Add Docker Compose Option (@wovalle)\n\n## 2022-03-27\n\n### Changed\n\n- **Heimdall Dashboard LXC**\n  - New Update Script\n\n## 2022-03-26\n\n### Changed\n\n- **UniFi Network Application LXC**\n  - New Script V2\n- **Omada Controller LXC**\n  - New Script V2\n\n## 2022-03-25\n\n### Changed\n\n- **Proxmox CPU Scaling Governor**\n  - New Script\n\n## 2022-03-24\n\n### Changed\n\n- **Plex Media Server LXC**\n  - Switch to Ubuntu 20.04 to support HDR tone mapping\n- **Docker LXC**\n  - Add Portainer Option\n\n## 2022-03-23\n\n### Changed\n\n- **Heimdall Dashboard LXC**\n  - New Script V2\n\n## 2022-03-20\n\n### Changed\n\n- **Scripts** (V2)\n  - ADD choose between Automatic or Manual DHCP\n\n## 2022-03-18\n\n### Changed\n\n- **Technitium DNS LXC**\n  - New Script V2\n- **WireGuard LXC**\n  - Add WGDashboard\n\n## 2022-03-17\n\n### Changed\n\n- **Docker LXC**\n  - New Script V2\n\n## 2022-03-16\n\n### Changed\n\n- **PhotoPrism LXC**\n  - New Update/Branch Script\n\n## 2022-03-15\n\n### Changed\n\n- **Dashy LXC**\n  - New Update Script\n\n## 2022-03-14\n\n### Changed\n\n- **Zwavejs2MQTT LXC**\n  - New Update Script\n\n## 2022-03-12\n\n### Changed\n\n- **PhotoPrism LXC**\n  - New Script V2\n\n## 2022-03-11\n\n### Changed\n\n- **Vaultwarden LXC**\n  - New V2 Install Script\n\n## 2022-03-08\n\n### Changed\n\n- **Scripts** (V2)\n  - Choose between Privileged or Unprivileged CT and Automatic or Password Login \n- **ESPHome LXC**\n  - New V2 Install Script\n- **Zwavejs2MQTT LXC**\n  - New V2 Install Script\n- **Motioneye LXC**\n  - New V2 Install Script\n- **Pihole LXC**\n  - New V2 Install Script\n- **GamUntu LXC**\n  - New V2 Install Script\n\n## 2022-03-06\n\n### Changed\n\n- **Zwavejs2MQTT LXC**\n  - New GUI script to copy data from one Zwavejs2MQTT LXC to another Zwavejs2MQTT LXC\n\n## 2022-03-05\n\n### Changed\n\n- **Homebridge LXC**\n  - New Script V2\n\n## 2022-03-04\n\n### Changed\n\n- **Proxmox Kernel Clean**\n  - New Script\n\n## 2022-03-03\n\n### Changed\n\n- **WireGuard LXC**\n  - New Script V2\n\n## 2022-03-02\n\n### Changed\n\n- **Proxmox LXC Updater**\n  - New Script\n- **Dashy LXC**\n  - New Script V2\n- **Grafana LXC**\n  - New Script V2\n- **InfluxDB/Telegraf LXC**\n  - New Script V2\n\n## 2022-03-01\n\n### Changed\n\n- **Daemon Sync Server LXC**\n  - New Script V2\n"
  },
  {
    "path": ".github/changelogs/2022/04.md",
    "content": "﻿## 2022-04-28\n\n### Changed\n\n- **v3 Script**\n  - Remove Internet Check\n\n## 2022-04-27\n\n### Changed\n\n- **Home Assistant OS VM**\n  - ADD Option to set Bridge, VLAN and MAC Address\n- **v3 Script**\n  - Improve Internet Check (prevent ‼ ERROR 4@57)\n\n## 2022-04-26\n\n### Changed\n\n- **Home Assistant OS VM**\n  - Fixed bad path\n  - ADD Option to create VM using Latest or Stable image\n- **UniFi Network Application LXC**\n  - ADD Local Controller Option\n\n## 2022-04-25\n\n### Changed\n\n- **v3 Script**\n  - Improve Error Handling\n\n## 2022-04-23\n\n### Changed\n\n- **v3 Script**\n  - ADD Internet Connection Check\n- **Proxmox VE 7 Post Install**\n  - NEW v3 Script\n- **Proxmox Kernel Clean**\n  - NEW v3 Script\n\n## 2022-04-22\n\n### Changed\n\n- **Omada Controller LXC**\n  - Update script to install version 5.1.7\n- **Uptime Kuma LXC**\n  - ADD Update script\n\n## 2022-04-20\n\n### Changed\n\n- **Ubuntu LXC**\n  - ADD option to install version 20.04 or 21.10\n- **v3 Script**\n  - ADD option to set Bridge\n\n## 2022-04-19\n\n### Changed\n\n- **ALL LXC's**\n  - New [V3 Install Script](https://github.com/tteck/Proxmox/issues/162) \n- **ioBroker LXC**\n  - New Script V3\n\n## 2022-04-13\n\n### Changed\n\n- **Uptime Kuma LXC**\n  - New Script V2\n\n## 2022-04-11\n\n### Changed\n\n- **Proxmox LXC Updater**\n  - ADD option to skip stopped containers\n- **Proxmox VE 7 Post Install**\n  - ADD PVE 7 check\n\n## 2022-04-10\n\n### Changed\n\n- **Debian 11 LXC**\n  - ADD An early look at the v3 install script\n\n## 2022-04-09\n\n### Changed\n\n- **NocoDB LXC**\n  - New Script V2\n\n## 2022-04-05\n\n### Changed\n\n- **MeshCentral LXC**\n  - New Script V2\n\n## 2022-04-01\n\n### Changed\n\n- **Scripts** (V2)\n  - FIX Pressing enter without making a selection first would cause an Error\n"
  },
  {
    "path": ".github/changelogs/2022/05.md",
    "content": "﻿## 2022-05-29\n\n### Changed\n\n- **Vaultwarden LXC**\n  - Code refactoring\n- **CrowdSec**\n  - NEW Script\n\n## 2022-05-21\n\n### Changed\n\n- **Home Assistant OS VM**\n  - Code refactoring\n\n## 2022-05-19\n\n### Changed\n\n- **Keycloak LXC**\n  - NEW Script\n\n## 2022-05-18\n\n### Changed\n\n- **File Browser**\n  - NEW Script\n\n## 2022-05-13\n\n### Changed\n\n- **PostgreSQL LXC**\n  - NEW Script\n\n## 2022-05-10\n\n### Changed\n\n- **deCONZ LXC**\n  - NEW Script\n\n## 2022-05-07\n\n### Changed\n\n- **NocoDB LXC**\n  - ADD update script\n\n## 2022-05-06\n\n### Changed\n\n- **PhotoPrism LXC**\n  - ADD GO Dependencies for full functionality\n\n## 2022-05-05\n\n### Changed\n\n- **Ubuntu LXC**\n  - ADD option to define version (18.04 20.04 21.10 22.04)\n"
  },
  {
    "path": ".github/changelogs/2022/06.md",
    "content": "﻿## 2022-06-30\n\n### Changed\n\n- **Prometheus LXC**\n  - NEW Script\n\n## 2022-06-06\n\n### Changed\n\n- **Whoogle LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2022/07.md",
    "content": "﻿## 2022-07-26\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Set the real time clock (RTC) to local time.\n  - Disable the USB tablet device (save resources / not needed).\n\n## 2022-07-24\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Present the drive to the guest as a solid-state drive rather than a rotational hard disk. There is no requirement that the underlying storage actually be backed by SSD's. \n  - When the VM’s filesystem marks blocks as unused after deleting files, the SCSI controller will relay this information to the storage, which will then shrink the disk image accordingly.\n  - 👉 [more info](https://github.com/tteck/Proxmox/discussions/378)\n\n## 2022-07-22\n\n### Changed\n\n- **n8n LXC** (thanks to @cyakimov)\n  - NEW Script\n\n## 2022-07-21\n\n### Changed\n\n- **grocy LXC**\n  - NEW Script\n\n## 2022-07-17\n\n### Changed\n\n- **Vaultwarden LXC**\n  - NEW Vaultwarden Update (post 2022-05-29 installs only) Script\n  - NEW Web-vault Update (any) Script\n\n## 2022-07-14\n\n### Changed\n\n- **MagicMirror Server LXC**\n  - NEW Script\n\n## 2022-07-13\n\n### Changed\n\n- **Proxmox Edge Kernel Tool**\n  - NEW Script\n\n## 2022-07-11\n\n### Changed\n\n- **Home Assistant OS VM**\n  - Supports lvmthin, zfspool, nfs, dir and btrfs storage types.\n\n## 2022-07-08\n\n### Changed\n\n- **openHAB LXC**\n  - NEW Script\n\n## 2022-07-03\n\n### Changed\n\n- **Tailscale**\n  - NEW Script\n\n## 2022-07-01\n\n### Changed\n\n- **Home Assistant OS VM**\n  - Allow different storage types (lvmthin, nfs, dir).\n"
  },
  {
    "path": ".github/changelogs/2022/08.md",
    "content": "﻿## 2022-08-31\n\n### Changed\n\n- **All LXC's** \n  - Add Internet & DNS Check\n\n## 2022-08-22\n\n### Changed\n\n- **Wiki.js LXC** \n  - NEW Script\n- **Emby Media Server LXC**\n  - NEW Script\n\n## 2022-08-20\n\n### Changed\n\n- **Mikrotik RouterOS VM** \n  - NEW Script\n\n## 2022-08-19\n\n### Changed\n\n- **PhotoPrism LXC** \n  - Fixed .env bug (Thanks @cklam2)\n\n## 2022-08-13\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Option to create VM using Stable, Beta or Dev Image\n\n## 2022-08-11\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Validate Storage\n\n## 2022-08-04\n\n### Changed\n\n- **VS Code Server** \n  - NEW Script\n\n## 2022-08-02\n\n### Changed\n\n- **All LXC/VM** \n  - v4 Script - Whiptail menu's\n"
  },
  {
    "path": ".github/changelogs/2022/09.md",
    "content": "﻿## 2022-09-29\n\n### Changed\n\n- **Home Assistant Container LXC** \n  - If the LXC is created Privileged, the script will automatically set up USB passthrough.\n- **Home Assistant Core LXC** \n  - NEW Script\n- **PiMox HAOS VM** \n  - NEW Script\n\n## 2022-09-23\n\n### Changed\n\n- **EMQX LXC** \n  - NEW Script\n\n## 2022-09-22\n\n### Changed\n\n- **NextCloudPi LXC** \n  - NEW Script\n\n## 2022-09-21\n\n### Changed\n\n- **Proxmox Backup Server Post Install** \n  - NEW Script\n- **Z-wave JS UI LXC** \n  - NEW Script (and all sub scripts 🤞)\n- **Zwave2MQTT LXC** \n  - Bye Bye Script\n\n## 2022-09-20\n\n### Changed\n\n- **OpenMediaVault LXC** \n  - NEW Script\n\n## 2022-09-16\n\n### Changed\n\n- **Paperless-ngx LXC** \n  - NEW Script (Thanks @Donkeykong307)\n\n## 2022-09-11\n\n### Changed\n\n- **Trilium LXC** \n  - NEW Script\n\n## 2022-09-10\n\n### Changed\n\n- **Syncthing LXC** \n  - NEW Script\n\n## 2022-09-09\n\n### Changed\n\n- **CasaOS LXC** \n  - NEW Script\n- **Proxmox Kernel Clean** \n  - Now works with Proxmox Backup Server\n\n## 2022-09-08\n\n### Changed\n\n- **Navidrome LXC** \n  - NEW Script\n- **Homepage LXC** \n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2022/10.md",
    "content": "﻿## 2022-10-27\n\n### Changed\n\n- **Container & Core Restore from Backup** \n  - [NEW Scripts](https://github.com/tteck/Proxmox/discussions/674)\n\n## 2022-10-07\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Add \"Latest\" Image\n\n## 2022-10-05\n\n### Changed\n\n- **Umbrel LXC** \n  - NEW Script (Docker)\n- **Blocky LXC** \n  - NEW Script (Adblocker - DNS)\n"
  },
  {
    "path": ".github/changelogs/2022/11.md",
    "content": "﻿## 2022-11-27\n\n### Changed\n\n- **Shinobi LXC** \n  - NEW Script\n\n## 2022-11-24\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Add option to set machine type during VM creation (Advanced)\n\n## 2022-11-23\n\n### Changed\n\n- **All LXC's** \n  - Add option to enable root ssh access during LXC creation (Advanced)\n\n## 2022-11-21\n\n### Changed\n\n- **Proxmox LXC Updater** \n  - Now updates Ubuntu, Debian, Devuan, Alpine Linux, CentOS-Rocky-Alma, Fedora, ArchLinux [(@Uruknara)](https://github.com/community-scripts/ProxmoxVE/commits?author=Uruknara)\n\n## 2022-11-13\n\n### Changed\n\n- **All LXC's** \n  - Add option to continue upon Internet NOT Connected\n\n## 2022-11-11\n\n### Changed\n\n- **HA Bluetooth Integration Preparation** \n  - [NEW Script](https://github.com/tteck/Proxmox/discussions/719)\n\n## 2022-11-04\n\n### Changed\n\n- **Scrypted LXC** \n  - NEW Script\n\n## 2022-11-01\n\n### Changed\n\n- **Alpine LXC** \n  - NEW Script\n- **Arch LXC** \n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2022/12.md",
    "content": "﻿## 2022-12-31\n\n### Changed\n\n- **v5 Scripts** (Testing before moving forward https://github.com/tteck/Proxmox/discussions/881)\n  - Adguard Home LXC\n  - Docker LXC\n  - Home Assistant Core LXC\n  - PhotoPrism LXC\n  - Shinobi NVR LXC\n  - Vaultwarden LXC\n\n## 2022-12-27\n\n### Changed\n\n- **Home Assistant Container LXC** \n  - Add an option to use Fuse Overlayfs (ZFS) (Advanced)\n\n- **Docker LXC** \n  - Add an option to use Fuse Overlayfs (ZFS) (Advanced)\n  - If the LXC is created Privileged, the script will automatically set up USB passthrough.\n\n## 2022-12-22\n\n### Changed\n\n- **All LXC's** \n  - Add an option to run the script in Verbose Mode (Advanced)\n\n## 2022-12-20\n\n### Changed\n\n- **Hyperion LXC** \n  - NEW Script\n\n## 2022-12-17\n\n### Changed\n\n- **Home Assistant Core LXC** \n  - Linux D-Bus Message Broker\n  - Mariadb & PostgreSQL Ready\n  - Bluetooth Ready\n  - Fix for Inconsistent Dependency Versions (dbus-fast & bleak)\n\n## 2022-12-16\n\n### Changed\n\n- **Home Assistant Core LXC** \n  - Python 3.10.8\n\n## 2022-12-09\n\n### Changed\n\n- **Change Detection LXC** \n  - NEW Script\n\n## 2022-12-03\n\n### Changed\n\n- **All LXC's** \n  - Add options to set DNS Server IP Address and DNS Search Domain (Advanced)\n"
  },
  {
    "path": ".github/changelogs/2023/01.md",
    "content": "﻿## 2023-01-28\n\n### Changed\n\n- **LXC Cleaner** \n  - Code refactoring to give the user the option to choose whether cache or logs will be deleted for each app/service.\n  - Leaves directory structure intact\n\n## 2023-01-27\n\n### Changed\n\n- **LXC Cleaner** \n  - NEW Script\n\n## 2023-01-26\n\n### Changed\n\n- **ALL LXC's** \n  - Add an option to disable IPv6 (Advanced)\n\n## 2023-01-25\n\n### Changed\n\n- **Home Assistant OS VM** \n  - switch to v5\n  - add an option to set MTU size (Advanced)\n  - add arch check (no ARM64) (issue from community.home-assistant.io)\n  - add check to insure VMID isn't already used before VM creation (Advanced) (issue from forum.proxmox.com)\n  - code refactoring\n- **PiMox Home Assistant OS VM** \n  - switch to v5\n  - add an option to set MTU size (Advanced)\n  - add arch check (no AMD64)\n  - add pve check (=>7.2)\n  - add check to insure VMID isn't already used before VM creation (Advanced)\n  - code refactoring\n- **All LXC's** \n  - add arch check (no ARM64) (issue from forum.proxmox.com)\n\n## 2023-01-24\n\n### Changed\n\n- **Transmission LXC** \n  - NEW Script\n\n## 2023-01-23\n\n### Changed\n\n- **ALL LXC's** \n  - Add [Midnight Commander (mc)](https://www.linuxcommand.org/lc3_adv_mc.php)\n\n## 2023-01-22\n\n### Changed\n\n- **Autobrr LXC** \n  - NEW Script\n\n## 2023-01-21\n\n### Changed\n\n- **Kavita LXC** \n  - NEW Script\n\n## 2023-01-19\n\n### Changed\n\n- **SABnzbd LXC** \n  - NEW Script\n\n## 2023-01-17\n\n### Changed\n\n- **Homer LXC** \n  - NEW Script\n\n## 2023-01-14\n\n### Changed\n\n- **Tdarr LXC** \n  - NEW Script\n- **Deluge LXC** \n  - NEW Script\n\n## 2023-01-13\n\n### Changed\n\n- **Lidarr LXC** \n  - NEW Script\n- **Prowlarr LXC** \n  - NEW Script\n- **Radarr LXC** \n  - NEW Script\n- **Readarr LXC** \n  - NEW Script\n- **Sonarr LXC** \n  - NEW Script\n- **Whisparr LXC** \n  - NEW Script\n\n## 2023-01-12\n\n### Changed\n\n- **ALL LXC's** \n  - Add an option to set MTU size (Advanced)\n\n## 2023-01-11\n\n### Changed\n\n- **Home Assistant Core LXC** \n  - Auto Initialize\n- **Cronicle Primary/Worker LXC** \n  - NEW Script\n\n## 2023-01-09\n\n### Changed\n\n- **ALL LXC's** \n  - v5\n- **k0s Kubernetes LXC** \n  - NEW Script\n- **Podman LXC** \n  - NEW Script\n\n## 2023-01-04\n\n### Changed\n\n- **YunoHost LXC** \n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/02.md",
    "content": "﻿## 2023-02-24\n\n### Changed\n\n- **qBittorrent LXC** (Thanks @romka777)\n  - NEW Script\n- **Jackett LXC** (Thanks @romka777)\n  - NEW Script\n\n## 2023-02-23\n\n### Changed\n\n- **Proxmox LXC Updater** \n  - Skip all templates, allowing for the starting, updating, and shutting down of containers to be resumed automatically.\n  - Exclude an additional container by adding the CTID at the end of the shell command ( -s 103).\n\n## 2023-02-16\n\n### Changed\n\n- **RSTPtoWEB LXC** \n  - NEW Script\n- **go2rtc LXC** \n  - NEW Script\n\n## 2023-02-12\n\n### Changed\n\n- **OliveTin** \n  - NEW Script\n\n## 2023-02-10\n\n### Changed\n\n- **Home Assistant OS VM** \n  - Code Refactoring\n\n## 2023-02-05\n\n### Changed\n\n- **Devuan LXC** \n  - NEW Script\n\n## 2023-02-02\n\n### Changed\n\n- **Audiobookshelf LXC** \n  - NEW Script\n- **Rocky Linux LXC** \n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/03.md",
    "content": "﻿## 2023-03-31\n\n### Changed\n\n- **Home Assistant OS VM**\n  - Include a choice within the \"Advanced\" settings to configure the disk cache between none (default) or Write Through.\n\n## 2023-03-27\n\n### Changed\n\n- **Removed Alpine-ESPHome LXC**\n  - Nonoperational\n- **All Scripts**\n  - Incorporate code that examines whether SSH is being used and, if yes, offers a suggestion against it without restricting or blocking its usage.\n\n## 2023-03-25\n\n### Changed\n\n- **Alpine-ESPHome LXC**\n  - NEW Script\n- **Alpine-Whoogle LXC**\n  - NEW Script\n\n## 2023-03-22\n\n### Changed\n\n- **The latest iteration of the scripts**\n  - Going forward, versioning will no longer be utilized in order to avoid breaking web-links in blogs and YouTube videos.\n  - The scripts have been made more legible as the repetitive code has been moved to function files, making it simpler to share among the scripts and hopefully easier to maintain. This also makes it simpler to contribute to the project.\n  - When a container is created with privileged mode enabled, the USB passthrough feature is automatically activated.\n\n## 2023-03-18\n\n### Changed\n\n- **Alpine-AdGuard Home LXC** (Thanks @nicedevil007)\n  - NEW Script\n- **Alpine-Docker LXC**\n  - NEW Script\n- **Alpine-Zigbee2MQTT LXC**\n  - NEW Script\n\n## 2023-03-15\n\n### Changed\n\n- **Alpine-Grafana LXC** (Thanks @nicedevil007)\n  - NEW Script\n\n## 2023-03-10\n\n### Changed\n\n- **Proxmox LXC Updater** \n  - You can use the command line to exclude multiple containers simultaneously.\n\n## 2023-03-08\n\n### Changed\n\n- **Proxmox CPU Scaling Governor**\n  - Menu options dynamically based on the available scaling governors.\n\n## 2023-03-07\n\n### Changed\n\n- **Alpine-Vaultwarden LXC**\n  - NEW Script\n- **All LXC Scripts**\n  - Retrieve the time zone from Proxmox and configure the container to use the same time zone\n"
  },
  {
    "path": ".github/changelogs/2023/04.md",
    "content": "﻿## 2023-04-30\n\n### Changed\n\n- **Proxmox VE Monitor-All**\n  - NEW Script\n  - Replaces Proxmox VE LXC Monitor\n\n## 2023-04-28\n\n### Changed\n\n- **Proxmox VE LXC Monitor**\n  - NEW Script\n\n## 2023-04-26\n\n### Changed\n\n- **The site can now be accessed through a more memorable URL, which is [helper-scripts.com](http://helper-scripts.com).**\n\n## 2023-04-23\n\n### Changed\n\n- **Non-Alpine LXC's**\n  - Advanced settings provide the option for users to switch between Debian and Ubuntu distributions. However, some applications or services, such as Deconz, grocy or Omada, may not be compatible with the selected distribution due to dependencies.\n\n## 2023-04-16\n\n### Changed\n\n- **Home Assistant Core LXC**\n  - Python 3.11.2\n\n## 2023-04-15\n\n### Changed\n\n- **InfluxDB LXC**\n  - Choosing InfluxDB v1 will result in Chronograf being installed automatically.\n- **[User Submitted Guides](https://github.com/community-scripts/ProxmoxVE/blob/main/USER_SUBMITTED_GUIDES.md)**\n  -  Informative guides that demonstrate how to install various software packages using Proxmox VE Helper Scripts.\n\n## 2023-04-14\n\n### Changed\n\n- **Cloudflared LXC**\n  - NEW Script\n\n## 2023-04-05\n\n### Changed\n\n- **Jellyfin LXC**\n  - Set Ubuntu 22.04 as default\n  - Use the Deb822 format jellyfin.sources configuration (jellyfin.list configuration has been obsoleted)\n\n## 2023-04-02\n\n### Changed\n\n- **Home Assistant OS VM**\n  - Include a choice within the \"Advanced\" settings to configure the CPU model between kvm64 (default) or host.\n"
  },
  {
    "path": ".github/changelogs/2023/05.md",
    "content": "﻿## 2023-05-27\n\n### Changed\n\n- **Proxmox VE 7 Post Install**\n  - If an Intel N-series processor is detected, ~the script provides options to install both the Proxmox 6.2 kernel and the Intel microcode.~ and using PVE7, recommend using PVE8\n\n## 2023-05-23\n\n### Changed\n\n- **OpenWrt VM**\n  - NEW Script\n\n## 2023-05-17\n\n### Changed\n\n- **Alpine-AdGuard Home LXC**\n  - Removed, it wasn't installed through the Alpine package manager.\n- **Alpine-Whoogle LXC**\n  - Removed, it wasn't installed through the Alpine package manager.\n\n## 2023-05-16\n\n### Changed\n\n- **Proxmox VE LXC Updater**\n  - Add information about the boot disk, which provides an easy way to determine if you need to expand the disk.\n- **Proxmox VE Processor Microcode**\n  - [Intel microcode-20230512 Release](https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files/releases/tag/microcode-20230512)\n\n## 2023-05-13\n\n### Changed\n\n- **Tautulli LXC**\n  - NEW Script\n\n## 2023-05-12\n\n### Changed\n\n- **Bazarr LXC**\n  - NEW Script\n\n## 2023-05-08\n\n### Changed\n\n- **Proxmox VE Intel Processor Microcode**\n  - Renamed to **Proxmox VE Processor Microcode**\n  - Automatically identifies the processor vendor (Intel/AMD) and installs the appropriate microcode.\n\n## 2023-05-07\n\n### Changed\n\n- **FHEM LXC**\n  - NEW Script\n\n## 2023-05-01\n\n### Changed\n\n- **OctoPrint LXC**\n  - NEW Script\n- **Proxmox VE Intel Processor Microcode**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/06.md",
    "content": "﻿## 2023-06-18\n\n### Changed\n\n- **OpenObserve LXC**\n  - NEW Script\n\n## 2023-06-17\n\n### Changed\n\n- **UniFi Network Application LXC**\n  - Now distribution agnostic.\n- **Omada Controller LXC**\n  - Now distribution agnostic.\n\n## 2023-06-16\n\n### Changed\n\n- **Proxmox VE Monitor-All**\n  - Skip instances based on onboot and templates. [8c2a3cc](https://github.com/community-scripts/ProxmoxVE/commit/8c2a3cc4d774fa13d17f695d6bdf9a4deedb1372).\n\n## 2023-06-12\n\n### Changed\n\n- **Proxmox VE Edge Kernel**\n  - Removed, with the Proxmox opt-in kernels and the upcoming Proxmox Virtual Environment 8, edge kernels are no longer needed.\n- **Proxmox VE Kernel Clean**\n  - Now compatible with PVE8.\n\n## 2023-06-11\n\n### Changed\n\n- **Proxmox VE Post Install**\n  - Now compatible with both Proxmox Virtual Environment 7 (PVE7) and Proxmox Virtual Environment 8 (PVE8).\n\n## 2023-06-02\n\n### Changed\n\n- **Proxmox VE 7 Post Install**\n  - In a non-clustered environment, you can choose to disable high availability, which helps save system resources.\n"
  },
  {
    "path": ".github/changelogs/2023/07.md",
    "content": "﻿## 2023-07-24\n\n### Changed\n\n- **Ombi LXC**\n  - NEW Script\n\n## 2023-07-23\n\n### Changed\n\n- **Zoraxy LXC**\n  - NEW Script\n\n## 2023-07-18\n\n### Changed\n\n- **Proxmox VE Cron LXC Updater**\n  - NEW Script\n\n## 2023-07-11\n\n### Changed\n\n- **Scrypted LXC**\n  - Add VAAPI hardware transcoding\n\n## 2023-07-07\n\n### Changed\n\n- **Real-Debrid Torrent Client LXC**\n  - NEW Script\n\n## 2023-07-05\n\n### Changed\n\n- There have been more than 110 commits since June 18th, although not all of them are significant, with a majority focused on ensuring compatibility with Proxmox VE 8 and Debian 12.\n"
  },
  {
    "path": ".github/changelogs/2023/08.md",
    "content": "﻿## 2023-08-31\n\n### Changed\n\n- **TurnKey ZoneMinder LXC**\n  - NEW Script\n- **TurnKey OpenVPN LXC**\n  - NEW Script\n\n## 2023-08-30\n\n### Changed\n\n- **TurnKey**\n  - Introducing a **NEW** Category on the Site.\n  - My intention is to maintain the TurnKey scripts in their simplest form, contained within a single file, and with minimal options, if any.\n- **TurnKey Core LXC**\n  - NEW Script\n- **TurnKey File Server LXC**\n  - NEW Script\n- **TurnKey Gitea LXC**\n  - NEW Script\n- **TurnKey GitLab LXC**\n  - NEW Script\n- **TurnKey Nextcloud LXC**\n  - NEW Script\n- **TurnKey Observium LXC**\n  - NEW Script\n- **TurnKey ownCloud LXC**\n  - NEW Script\n- **TurnKey Torrent Server LXC**\n  - NEW Script\n- **TurnKey Wordpress LXC**\n  - NEW Script\n\n## 2023-08-24\n\n### Changed\n\n- **qBittorrent LXC**\n  - Added back to repository with UPnP disabled and password changed.\n\n## 2023-08-24\n\n### Changed\n\n- **qBittorrent LXC**\n  - Removed from this repository for potential malicious hidden code https://github.com/tteck/Proxmox/discussions/1725\n\n## 2023-08-16\n\n### Changed\n\n- **Homarr LXC**\n  - NEW Script\n\n## 2023-08-10\n\n### Changed\n\n- **Proxmox VE Processor Microcode**\n  - AMD microcode-20230808 Release\n\n## 2023-08-09\n\n### Changed\n\n- **Omada Controller LXC**\n  - Update via script\n- **Proxmox VE Processor Microcode**\n  - [Intel microcode-20230808 Release](https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files/releases/tag/microcode-20230808)\n\n## 2023-08-01\n\n### Changed\n\n- **Overseerr LXC**\n  - NEW Script\n- **Jellyseerr LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/09.md",
    "content": "﻿## 2023-09-30\n\n### Changed\n\n- **All Templates**\n  - NEW Script\n\n## 2023-09-28\n\n### Changed\n\n- **Alpine Nextcloud Hub LXC**\n  - NEW Script (Thanks to @nicedevil007)\n\n## 2023-09-14\n\n### Changed\n\n- **Proxmox VE Processor Microcode**\n  - Allow users to select available microcode packages.\n\n## 2023-09-13\n\n### Changed\n\n- **Pi.Alert LXC**\n  - NEW Script\n- **Proxmox VE Kernel Clean**\n  - Code overhaul with a fresh start. This script offers the flexibility to select specific kernels for removal, unlike the previous version, which was an all-or-nothing approach.\n\n## 2023-09-11\n\n### Changed\n\n- **Paperless-ngx LXC**\n  - Modify the script to incorporate Redis and PostgreSQL, while also introducing an option to include Adminer during installation.\n\n## 2023-09-10\n\n### Changed\n\n- **TurnKey Game Server LXC**\n  - NEW Script\n\n## 2023-09-09\n\n### Changed\n\n- **Proxmox VE Host Backup**\n  - Users are now able to specify both the backup path and the directory in which they wish to work.\n\n## 2023-09-07\n\n### Changed\n\n- **Proxmox VE Host Backup**\n  - NEW Script\n\n## 2023-09-06\n\n### Changed\n\n- **Proxmox VE LXC Cleaner**\n  - Added a new menu that allows you to choose which containers you want to exclude from the clean process.\n- **Tailscale**\n  - Added a menu that enables you to choose the specific container where you want to install Tailscale.\n\n## 2023-09-05\n\n### Changed\n\n- **Proxmox VE LXC Updater**\n  - Added a new menu that allows you to choose which containers you want to exclude from the update process.\n\n## 2023-09-01\n\n### Changed\n\n- **TurnKey Media Server LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/10.md",
    "content": "﻿## 2023-10-31\n\n### Changed\n\n- **Debian 12 VM**\n  - NEW Script\n\n## 2023-10-29\n\n### Changed\n\n- **Unmanic LXC**\n  - NEW Script\n\n## 2023-10-27\n\n### Changed\n\n- **Webmin**\n  - A full code overhaul.\n\n## 2023-10-15\n\n### Changed\n\n- **TasmoAdmin LXC**\n  - NEW Script\n\n## 2023-10-14\n\n### Changed\n\n- **Sonarr LXC**\n  - Include an option to install v4 (experimental)\n\n## 2023-10-11\n\n### Changed\n\n- **Proxmox VE CPU Scaling Governor**\n  - A full code overhaul.\n  - Include an option to configure a crontab for ensuring that the CPU Scaling Governor configuration persists across reboots.\n\n## 2023-10-08\n\n### Changed\n\n- **Proxmox VE LXC Updater**\n  - Now displays which containers require a reboot.\n- **File Browser**\n  - Uninstall by re-executing the script\n  - Option to use No Authentication\n\n## 2023-10-05\n\n### Changed\n\n- **Pingvin Share LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/11.md",
    "content": "﻿## 2023-11-19\n\n### Changed\n\n- **Dockge LXC**\n  - NEW Script\n\n## 2023-11-18\n\n### Changed\n\n- **Ubuntu 22.04 VM**\n  - NEW Script\n\n## 2023-11-14\n\n### Changed\n\n- **TurnKey Nextcloud VM**\n  - NEW Script\n- **TurnKey ownCloud VM**\n  - NEW Script\n\n## 2023-11-11\n\n### Changed\n\n- **Homarr LXC**\n  - Returns with v0.14.0 (The authentication update).\n\n## 2023-11-9\n\n### Changed\n\n- **AgentDVR LXC**\n  - NEW Script\n\n## 2023-11-8\n\n### Changed\n\n- **Linkwarden LXC**\n  - NEW Script\n\n## 2023-11-2\n\n### Changed\n\n- **PhotoPrism LXC**\n  - Transitioned to PhotoPrism's latest installation package, featuring Linux binaries.\n\n## 2023-11-1\n\n### Changed\n\n- **Owncast LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2023/12.md",
    "content": "﻿## 2023-12-19\n\n### Changed\n\n- **Proxmox VE Netdata**\n  - NEW Script\n\n## 2023-12-10\n\n### Changed\n\n- **Homarr LXC**\n  - Removed, again.\n\n## 2023-12-02\n\n### Changed\n\n- **Runtipi LXC**\n  - NEW Script\n\n## 2023-12-01\n\n### Changed\n\n- **Mikrotik RouterOS VM**\n  - Now Mikrotik RouterOS CHR VM\n  - code refactoring\n  - update to CHR\n  - thanks to @NiccyB\n- **Channels DVR Server LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/01.md",
    "content": "﻿## 2024-01-25\n\n### Changed\n\n- **PairDrop LXC**\n  - NEW Script\n\n## 2024-01-20\n\n### Changed\n\n- **Apache-Cassandra LXC**\n  - NEW Script\n- **Redis LXC**\n  - NEW Script\n\n## 2024-01-17\n\n### Changed\n\n- **ntfy LXC**\n  - NEW Script\n- **HyperHDR LXC**\n  - NEW Script\n\n## 2024-01-16\n\n### Changed\n\n- **Website Improvements**\n  - Refine and correct pointers.\n  - Change hover colors to intuitively indicate categories/items.\n  - Implement opening links in new tabs for better navigation.\n  - Enhance the Copy button to better indicate that the command has been successfully copied.\n  - Introduce a Clear Search button.\n  - While not directly related to the website, it's worth mentioning that the logo in newly created LXC notes now serves as a link to the website, conveniently opening in a new tab.\n\n## 2024-01-12\n\n### Changed\n\n- **Apt-Cacher-NG LXC**\n  - NEW Script\n- **New Feature**\n  - The option to utilize Apt-Cacher-NG (Advanced settings) when creating LXCs. The added functionality is expected to decrease bandwidth usage and expedite package installation and updates. https://github.com/tteck/Proxmox/discussions/2332\n\n## 2024-01-09\n\n### Changed\n\n- **Verbose mode**\n  - Only entries with `$STD` will be shown\n\n## 2024-01-07\n\n### Changed\n\n- **Stirling-PDF LXC**\n  - NEW Script\n- **SFTPGo LXC**\n  - NEW Script\n\n## 2024-01-04\n\n### Changed\n\n- **CommaFeed LXC**\n  - NEW Script\n\n## 2024-01-03\n\n### Changed\n\n- **Sonarr LXC**\n  - Breaking Change\n  - Complete recode\n  - https://github.com/tteck/Proxmox/discussions/1738#discussioncomment-8005107\n\n## 2024-01-01\n\n### Changed\n\n- **Gotify LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/02.md",
    "content": "﻿## 2024-02-26\n\n### Changed\n\n- **Mafl LXC**\n  - NEW Script\n\n## 2024-02-23\n\n### Changed\n\n- **Tandoor Recipes LXC**\n  - NEW Script (Thanks @MickLesk)\n\n## 2024-02-21\n\n### Changed\n\n- **All scripts**\n  - As of today, the scripts require the Bash shell specifically. ([more info](https://github.com/tteck/Proxmox/discussions/2536))\n\n## 2024-02-19\n\n### Changed\n\n- **PairDrop LXC**\n  - Removed from the website ([more info](https://github.com/tteck/Proxmox/discussions/2516))\n\n## 2024-02-16\n\n### Changed\n\n- **Proxmox VE LXC Filesystem Trim**\n  - NEW Script ([more info](https://github.com/tteck/Proxmox/discussions/2505#discussion-6226037))\n\n## 2024-02-11\n\n### Changed\n\n- **HiveMQ CE LXC**\n  - NEW Script\n- **Apache-CouchDB LXC**\n  - NEW Script\n\n## 2024-02-06\n\n### Changed\n\n- **All Scripts**\n  - The scripts will only work with PVE7 Version 7.4-13 or later, or PVE8 Version 8.1.1 or later.\n\n## 2024-02-05\n\n### Changed\n\n- **Gokapi LXC**\n  - NEW Script\n- **Nginx Proxy Manager LXC**\n  - Option to install v2.10.4\n\n## 2024-02-04\n\n### Changed\n\n- **Pi-hole LXC**\n  - Option to add Unbound\n\n## 2024-02-02\n\n### Changed\n\n- **Readeck LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/03.md",
    "content": "﻿## 2024-03-26\n\n### Changed\n\n- **MediaMTX LXC**\n  - NEW Script\n\n## 2024-03-25\n\n### Changed\n\n- **Proxmox VE Post Install**\n  - ~Requires Proxmox Virtual Environment Version 8.1.1 or later.~\n  - Requires Proxmox Virtual Environment Version 8.0 or later.\n- **Proxmox Backup Server LXC**\n  - NEW Script\n\n## 2024-03-24\n\n### Changed\n\n- **SmokePing LXC**\n  - NEW Script\n\n## 2024-03-13\n\n### Changed\n\n- **FlowiseAI LXC**\n  - NEW Script\n\n## 2024-03-11\n\n### Changed\n\n- **Wastebin LXC**\n  - NEW Script\n\n## 2024-03-08\n\n### Changed\n\n- **Proxmox VE Post Install**\n  - Requires Proxmox Virtual Environment Version 8.1.1 or later.\n"
  },
  {
    "path": ".github/changelogs/2024/04.md",
    "content": "﻿## 2024-04-30\n\n### Changed\n\n- **Tdarr LXC**\n  - Default settings are now **Unprivileged**\n  - Unprivileged Hardware Acceleration\n\n## 2024-04-29\n\n### Changed\n\n- **ErsatzTV LXC**\n  - NEW Script\n\n## 2024-04-28\n\n### Changed\n\n- **Scrypted LXC**\n  - Unprivileged Hardware Acceleration\n- **Emby LXC**\n  - Unprivileged Hardware Acceleration\n\n## 2024-04-27\n\n### Changed\n\n- **Frigate LXC**\n  - Unprivileged Hardware Acceleration https://github.com/tteck/Proxmox/discussions/2711#discussioncomment-9244629\n- **Ubuntu 24.04 VM**\n  - NEW Script\n\n## 2024-04-26\n\n### Changed\n\n- **Glances**\n  - NEW Script\n\n## 2024-04-25\n\n### Changed\n\n- **Jellyfin LXC**\n  - Default settings are now **Unprivileged**\n  - Unprivileged Hardware Acceleration\n  - Groups are set automatically\n  - Checks for the existence of `/dev/dri/card0` if not found, use `/dev/dri/card1`. Set the GID to `44`\n  - Set the GID for `/dev/dri/renderD128` to `104`\n  - Not tested <8.1.11\n- **Plex LXC**\n  - Default settings are now **Unprivileged**\n  - Unprivileged Hardware Acceleration\n  - Groups are set automatically\n  - Checks for the existence of `/dev/dri/card0` if not found, use `/dev/dri/card1`. Set the GID to `44`\n  - Set the GID for `/dev/dri/renderD128` to `104`\n  - Not tested <8.1.11\n\n## 2024-04-24\n\n### Changed\n\n- **Traccar LXC**\n  - NEW Script\n- **Calibre-Web LXC**\n  - NEW Script\n\n## 2024-04-21\n\n### Changed\n\n- **Aria2 LXC**\n  - NEW Script\n\n## 2024-04-15\n\n### Changed\n\n- **Homarr LXC**\n  - Add back to website\n- **Umbrel LXC**\n  - Add back to website\n- **OpenMediaVault LXC**\n  - Add back to website\n\n## 2024-04-12\n\n### Changed\n\n- **OpenMediaVault LXC**\n  - Removed from website\n\n## 2024-04-09\n\n### Changed\n\n- **PairDrop LXC**\n  - Add back to website\n\n## 2024-04-05\n\n### Changed\n\n- **Medusa LXC**\n  - NEW Script\n- **WatchYourLAN LXC**\n  - NEW Script\n\n## 2024-04-04\n\n### Changed\n\n- **Actual Budget LXC**\n  - NEW Script\n\n## 2024-04-03\n\n### Changed\n\n- **LazyLibrarian LXC**\n  - NEW Script\n\n## 2024-04-01\n\n### Changed\n\n- **Frigate LXC**\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/05.md",
    "content": "﻿## 2024-05-31\n\n### Changed\n\n- **Advanced Settings** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/fc9dff220b4ea426d3a75178ad8accacae4683ca)\n  - Passwords are now masked\n\n## 2024-05-30\n\n### Changed\n\n- **Forgejo LXC**\n  - NEW Script\n\n## 2024-05-28\n\n### Changed\n\n- **Notifiarr LXC**\n  - NEW Script\n\n## 2024-05-25\n\n### Changed\n\n- **Threadfin LXC**\n  - NEW Script\n\n## 2024-05-23\n\n### Changed\n\n- **BunkerWeb LXC**\n  - NEW Script\n\n## 2024-05-20\n\n### Changed\n\n- **Traefik LXC**\n  - NEW Script\n\n## 2024-05-19\n\n### Changed\n\n- **NetBird**\n  - NEW Script\n- **Tailscale**\n  - Refactor Code\n\n## 2024-05-18\n\n### Changed\n\n- **MongoDB LXC**\n  - NEW Script\n\n## 2024-05-17\n\n### Changed\n\n- **New Website**\n  - We have officially moved to [Helper-Scripts.com](https://helper-scripts.com)\n\n## 2024-05-16\n\n### Changed\n\n- **iVentoy LXC**\n  - NEW Script\n\n## 2024-05-13\n\n### Changed\n\n- **Headscale LXC**\n  - NEW Script\n\n## 2024-05-11\n\n### Changed\n\n- **Caddy LXC**\n  - NEW Script\n\n## 2024-05-09\n\n### Changed\n\n- **Umami LXC**\n  - NEW Script\n\n## 2024-05-08\n\n### Changed\n\n- **Kernel Pin**\n  - NEW Script\n- **Home Assistant Core LXC**\n  - Ubuntu 24.04 ONLY\n\n## 2024-05-07\n\n### Changed\n\n- **Pocketbase LXC**\n  - NEW Script\n\n## 2024-05-05\n\n### Changed\n\n- **Fenrus LXC**\n  - NEW Script\n\n## 2024-05-02\n\n### Changed\n\n- **OpenMediaVault LXC**\n  - Set Debian 12 as default\n  - OpenMediaVault 7 (sandworm)\n"
  },
  {
    "path": ".github/changelogs/2024/06.md",
    "content": "﻿## 2024-06-30\n\n### Changed\n\n- **All Scripts** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/39ea1d4a20b83c07d084ebafdc811eec3548f289)\n  - Requires Proxmox Virtual Environment version 8.1 or later.\n\n## 2024-06-27\n\n### Changed\n\n- **Kubo LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/kubo-install.sh)\n  - NEW Script\n- **RabbitMQ LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/rabbitmq-install.sh)\n  - NEW Script\n- **Scrutiny LXC**\n  - Removed from website, broken.\n\n## 2024-06-26\n\n### Changed\n\n- **Scrutiny LXC**\n  - NEW Script\n\n## 2024-06-14\n\n### Changed\n\n- **MySpeed LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/myspeed-install.sh)\n  - NEW Script\n\n## 2024-06-13\n\n### Changed\n\n- **PeaNUT LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/peanut-install.sh)\n  - NEW Script\n- **Website**\n  - If the Changelog has changed recently, the link on the website will pulse.\n- **Spoolman LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/spoolman-install.sh)\n  - NEW Script\n\n## 2024-06-12\n\n### Changed\n\n- **MeTube LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/metube-install.sh)\n  - NEW Script\n- **Matterbridge LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/matterbridge-install.sh)\n  - NEW Script\n- **Website**\n  - Reopen the gh-pages site (https://tteck.github.io/Proxmox/)\n\n## 2024-06-11\n\n### Changed\n\n- **Zabbix LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/zabbix-install.sh)\n  - NEW Script\n\n## 2024-06-06\n\n### Changed\n\n- **Petio LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/petio-install.sh)\n  - NEW Script\n- **Website**\n  - Important notices will now be displayed on the landing page.\n\n## 2024-06-04\n\n### Changed\n\n- **FlareSolverr LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/flaresolverr-install.sh)\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/07.md",
    "content": "﻿## 2024-07-26\n\n### Changed\n\n- **Gitea LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/gitea-install.sh)\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/08.md",
    "content": "﻿## 2024-08-21\n\n### Changed\n\n- **WireGuard LXC** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/723365a79df7cc0fd29b1af8f7ef200a7e0921b1)\n  - Refactor Code\n  - Breaking Change\n\n## 2024-08-19\n\n### Changed\n\n- **CommaFeed LXC** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/0a33d1739ec3a49011411929bd46a260e92e99f9)\n  - Refactor Code\n  - Breaking Change\n\n## 2024-08-06\n\n### Changed\n\n- **lldap LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/lldap-install.sh)\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/09.md",
    "content": "﻿## 2024-09-16\n\n### Changed\n\n- **HomeBox LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/homebox-install.sh)\n  - NEW Script\n- **Zipline LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/zipline-install.sh)\n  - NEW Script\n\n## 2024-09-13\n\n### Changed\n\n- **Tianji LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/tianji-install.sh)\n  - NEW Script\n"
  },
  {
    "path": ".github/changelogs/2024/10.md",
    "content": "﻿## 2024-10-31\n\n### Changed\n\n- **NZBGet LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/nzbget-install.sh)\n  - NEW Script\n- **Memos LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/memos-install.sh)\n  - NEW Script\n\n## 2024-10-27\n\n### Changed\n\n- **Open WebUI LXC** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/8a21f6e7f025a911865395d4c0fa9a001bd0d512)\n  - Refactor Script to add an option to install Ollama.\n\n## 2024-10-26\n\n### Changed\n\n- **AdventureLog LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/adventurelog-install.sh)\n  - NEW Script\n\n## 2024-10-25\n\n### Changed\n\n- **Zoraxy LXC** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/468a5d367ded4cf453a1507452e112ac3e234e2a)\n  - Switch built from source to a pre-compiled binary version.\n  - Breaking Change\n\n## 2024-10-23\n\n### Changed\n\n- **Wallos LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/wallos-install.sh)\n  - NEW Script\n- **Open WebUI LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/openwebui-install.sh)\n  - NEW Script\n\n## 2024-10-19\n\n### Changed\n\n- **Cockpit LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/cockpit-install.sh)\n  - NEW Script\n- **Neo4j LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/neo4j-install.sh)\n  - NEW Script\n\n## 2024-10-18\n\n### Changed\n\n- **ArchiveBox LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/archivebox-install.sh)\n  - NEW Script\n\n## 2024-10-15\n\n### Changed\n\n- **evcc LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/evcc-install.sh)\n  - NEW Script\n\n## 2024-10-10\n\n### Changed\n\n- **MySQL LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/mysql-install.sh)\n  - NEW Script\n- **Tianji LXC** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/4c83a790ac9b040da1f11ad2cbe13d3fc5f480e9)\n  - Breaking Change\n  - Switch from `pm2` process management to `systemd`\n\n## 2024-10-03\n\n### Changed\n\n- **Home Assistant Core LXC** [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/f2937febe69b2bad8b3a14eb84aa562a8f14cc6a) [(Commit)](https://github.com/community-scripts/ProxmoxVE/commit/f2966ced7f457fd506f865f7f5b70ea12c4b0049)\n  - Refactor Code\n  - Breaking Change\n  - Home Assistant has transitioned to using `uv` for managing the virtual environment and installing additional modules.\n"
  },
  {
    "path": ".github/changelogs/2024/11.md",
    "content": "﻿## 2024-11-30\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Convert line endings in the-lounge.sh [@jamezpolley](https://github.com/jamezpolley) ([#599](https://github.com/community-scripts/ProxmoxVE/pull/599))\n\n### 🌐 Website\n\n- add some Information for Monitor-All Script [@MickLesk](https://github.com/MickLesk) ([#605](https://github.com/community-scripts/ProxmoxVE/pull/605))\n\n## 2024-11-29\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: The Lounge IRC [@quantumryuu](https://github.com/quantumryuu) ([#571](https://github.com/community-scripts/ProxmoxVE/pull/571))\n- New Script: LubeLogger [@quantumryuu](https://github.com/quantumryuu) ([#574](https://github.com/community-scripts/ProxmoxVE/pull/574))\n- New Script: Inspircd [@quantumryuu](https://github.com/quantumryuu) ([#576](https://github.com/community-scripts/ProxmoxVE/pull/576))\n\n### 🚀 Updated Scripts\n\n- Fix msg_error on zwave-js-ui [@MickLesk](https://github.com/MickLesk) ([#585](https://github.com/community-scripts/ProxmoxVE/pull/585))\n- Fix Kimai Apache2 Rights [@MickLesk](https://github.com/MickLesk) ([#577](https://github.com/community-scripts/ProxmoxVE/pull/577))\n\n## 2024-11-28\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Fix Z-Wave JS UI script [@MickLesk](https://github.com/MickLesk) ([#546](https://github.com/community-scripts/ProxmoxVE/pull/546))\n  - [Migration guide](https://github.com/community-scripts/ProxmoxVE/discussions/635)\n\n### 🚀 Updated Scripts\n\n- Add vitest, add json validation tests, fix broken json files [@havardthom](https://github.com/havardthom) ([#566](https://github.com/community-scripts/ProxmoxVE/pull/566))\n- Add update script to Pocketbase [@dsiebel](https://github.com/dsiebel) ([#535](https://github.com/community-scripts/ProxmoxVE/pull/535))\n- Fix MongoDB install in Unifi script [@havardthom](https://github.com/havardthom) ([#564](https://github.com/community-scripts/ProxmoxVE/pull/564))\n- Remove changing DISK_REF for zfspool mikrotik-routeros.sh [@tjcomserv](https://github.com/tjcomserv) ([#529](https://github.com/community-scripts/ProxmoxVE/pull/529))\n\n### 🌐 Website\n\n- Show Changelog on Mobile Devices [@MickLesk](https://github.com/MickLesk) ([#558](https://github.com/community-scripts/ProxmoxVE/pull/558))\n\n## 2024-11-27\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Zabbix: Use Agent2 as Default | Update Script added | some other Improvements [@MickLesk](https://github.com/MickLesk) ([#527](https://github.com/community-scripts/ProxmoxVE/pull/527))\n\n### 🚀 Updated Scripts\n\n- Fix: install mosquitto from mosquitto repo [@dsiebel](https://github.com/dsiebel) ([#534](https://github.com/community-scripts/ProxmoxVE/pull/534))\n- Patch Netbird Script | Container Boot-Check | Debian/Ubuntu Only [@MickLesk](https://github.com/MickLesk) ([#528](https://github.com/community-scripts/ProxmoxVE/pull/528))\n- Install MongoDB 4.2 for non-AVX CPUs in Unifi LXC [@ColinOppenheim](https://github.com/ColinOppenheim) ([#319](https://github.com/community-scripts/ProxmoxVE/pull/319))\n\n### 🌐 Website\n\n- Fix json error in zabbix.json [@havardthom](https://github.com/havardthom) ([#543](https://github.com/community-scripts/ProxmoxVE/pull/543))\n- Fix another json error in add-netbird-lxc.json [@havardthom](https://github.com/havardthom) ([#545](https://github.com/community-scripts/ProxmoxVE/pull/545))\n\n## 2024-11-26\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix Vikunja install script to prevent database deletion upon updating [@vhsdream](https://github.com/vhsdream) ([#524](https://github.com/community-scripts/ProxmoxVE/pull/524))\n\n## 2024-11-25\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Remove Scrypted script [@MickLesk](https://github.com/MickLesk) ([#511](https://github.com/community-scripts/ProxmoxVE/pull/511))\n  - Because of request from Scrypted maintainer: [#494](https://github.com/community-scripts/ProxmoxVE/issues/494)\n  - Official Scrypted script can be used instead: https://docs.scrypted.app/installation.html#proxmox-ve\n\n### 🚀 Updated Scripts\n\n- Fix bugs in Calibre-Web update [@havardthom](https://github.com/havardthom) ([#517](https://github.com/community-scripts/ProxmoxVE/pull/517))\n- Fix upload folder in listmonk LXC [@bvdberg01](https://github.com/bvdberg01) ([#515](https://github.com/community-scripts/ProxmoxVE/pull/515))\n\n### 🌐 Website\n\n- Fix website url in Zoraxy documentation [@miggi92](https://github.com/miggi92) ([#506](https://github.com/community-scripts/ProxmoxVE/pull/506))\n\n## 2024-11-24\n\n### Changed\n\n### ✨ New Scripts\n\n- New script: listmonk LXC [@bvdberg01](https://github.com/bvdberg01) ([#442](https://github.com/community-scripts/ProxmoxVE/pull/442))\n\n### 🧰 Maintenance\n\n- Add release title to github-release.yml [@havardthom](https://github.com/havardthom) ([#481](https://github.com/community-scripts/ProxmoxVE/pull/481))\n\n## 2024-11-23\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix Actual Budget install missing build tools [@cour64](https://github.com/cour64) ([#455](https://github.com/community-scripts/ProxmoxVE/pull/455))\n- Fix Vikunja Update [@MickLesk](https://github.com/MickLesk) ([#440](https://github.com/community-scripts/ProxmoxVE/pull/440))\n- Patch PostInstall-Script to PVE 8.3 | Add PVE 8.3 in Security [@MickLesk](https://github.com/MickLesk) ([#431](https://github.com/community-scripts/ProxmoxVE/pull/431))\n\n### 🌐 Website\n\n- Frontend: fix reported issue with json-editor page and add OS select in installmethod [@BramSuurdje](https://github.com/BramSuurdje) ([#426](https://github.com/community-scripts/ProxmoxVE/pull/426))\n- Fixed Typo [@BenBakDev](https://github.com/BenBakDev) ([#441](https://github.com/community-scripts/ProxmoxVE/pull/441))\n\n### 🧰 Maintenance\n\n- Fix newline issue in changelog pr [@havardthom](https://github.com/havardthom) ([#474](https://github.com/community-scripts/ProxmoxVE/pull/474))\n- Remove newline in changelog-pr action [@havardthom](https://github.com/havardthom) ([#461](https://github.com/community-scripts/ProxmoxVE/pull/461))\n- Add action that creates github release based on CHANGELOG.md [@havardthom](https://github.com/havardthom) ([#462](https://github.com/community-scripts/ProxmoxVE/pull/462))\n\n## 2024-11-21\n\n### Changed\n\n### ✨ New Scripts\n\n- Add new LXC: NextPVR [@MickLesk](https://github.com/MickLesk) ([#391](https://github.com/community-scripts/ProxmoxVE/pull/391))\n- Add new LXC: Kimai [@MickLesk](https://github.com/MickLesk) ([#397](https://github.com/community-scripts/ProxmoxVE/pull/397))\n\n### 🚀 Updated Scripts\n\n- Add .env file support for HomeBox [@404invalid-user](https://github.com/404invalid-user) ([#383](https://github.com/community-scripts/ProxmoxVE/pull/383))\n- RDTClient Remove .NET 8.0 | Add .NET 9.0 [@MickLesk](https://github.com/MickLesk) ([#413](https://github.com/community-scripts/ProxmoxVE/pull/413))\n- Remove old resource message from vaultwarden [@havardthom](https://github.com/havardthom) ([#402](https://github.com/community-scripts/ProxmoxVE/pull/402))\n\n### 🌐 Website\n\n- Add PostInstall Documentation to zigbee2mqtt.json [@MickLesk](https://github.com/MickLesk) ([#411](https://github.com/community-scripts/ProxmoxVE/pull/411))\n- Fix incorrect hdd values in json files [@havardthom](https://github.com/havardthom) ([#403](https://github.com/community-scripts/ProxmoxVE/pull/403))\n- Website: Add discord link to navbar [@BramSuurdje](https://github.com/BramSuurdje) ([#405](https://github.com/community-scripts/ProxmoxVE/pull/405))\n\n### 🧰 Maintenance\n\n- Use github app in changelog-pr.yml and add auto approval [@havardthom](https://github.com/havardthom) ([#416](https://github.com/community-scripts/ProxmoxVE/pull/416))\n\n## 2024-11-20\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- LinkWarden: Moved PATH into service [@newzealandpaul](https://github.com/newzealandpaul) ([#376](https://github.com/community-scripts/ProxmoxVE/pull/376))\n\n### 🌐 Website\n\n- Replace dash \"-\" with \"/\" in metadata [@newzealandpaul](https://github.com/newzealandpaul) ([#374](https://github.com/community-scripts/ProxmoxVE/pull/374))\n- Proxmox VE Cron LXC Updater: Add tteck's notes. [@newzealandpaul](https://github.com/newzealandpaul) ([#378](https://github.com/community-scripts/ProxmoxVE/pull/378))\n\n## 2024-11-19\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix Wallos Update [@MickLesk](https://github.com/MickLesk) ([#339](https://github.com/community-scripts/ProxmoxVE/pull/339))\n- Linkwarden: Move Secret Key above in install.sh [@MickLesk](https://github.com/MickLesk) ([#356](https://github.com/community-scripts/ProxmoxVE/pull/356))\n- Linkwarden: add gnupg to installed dependencies [@erfansamandarian](https://github.com/erfansamandarian) ([#349](https://github.com/community-scripts/ProxmoxVE/pull/349))\n\n### 🌐 Website\n\n- Add *Arr Suite category for Website [@MickLesk](https://github.com/MickLesk) ([#370](https://github.com/community-scripts/ProxmoxVE/pull/370))\n- Refactor Buttons component to use a ButtonLink for cleaner code, simplifying the source URL generation and layout [@BramSuurdje](https://github.com/BramSuurdje) ([#371](https://github.com/community-scripts/ProxmoxVE/pull/371))\n\n### 🧰 Maintenance\n\n- [github]: add new Frontend_Report / Issue_Report & optimize config.yml [@MickLesk](https://github.com/MickLesk) ([#226](https://github.com/community-scripts/ProxmoxVE/pull/226))\n- [chore] Update FUNDING.yml [@MickLesk](https://github.com/MickLesk) ([#352](https://github.com/community-scripts/ProxmoxVE/pull/352))\n\n## 2024-11-18\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Massive Update - Remove old storage check, add new storage and resource check to all scripts - Remove downscaling with pct set [@MickLesk](https://github.com/MickLesk) ([#333](https://github.com/community-scripts/ProxmoxVE/pull/333))\n\n### ✨ New Scripts\n\n- new scripts for NetBox [@bvdberg01](https://github.com/bvdberg01) ([#308](https://github.com/community-scripts/ProxmoxVE/pull/308))\n\n### 🚀 Updated Scripts\n\n- Support SSE 4.2 in Frigate script [@anishp55](https://github.com/anishp55) ([#328](https://github.com/community-scripts/ProxmoxVE/pull/328))\n- Bugfix: Wallos Patch (Cron Log & Media Backup)  [@MickLesk](https://github.com/MickLesk) ([#331](https://github.com/community-scripts/ProxmoxVE/pull/331))\n- Linkwarden - Harmonize Script, Add Monolith & Bugfixing [@MickLesk](https://github.com/MickLesk) ([#306](https://github.com/community-scripts/ProxmoxVE/pull/306))\n- Fix optional installs in Cockpit LXC [@havardthom](https://github.com/havardthom) ([#317](https://github.com/community-scripts/ProxmoxVE/pull/317))\n\n### 🌐 Website\n\n- Added additional instructions to nginxproxymanager [@newzealandpaul](https://github.com/newzealandpaul) ([#329](https://github.com/community-scripts/ProxmoxVE/pull/329))\n\n### 🧰 Maintenance\n\n- Verify changes before commit in changelog-pr.yml [@havardthom](https://github.com/havardthom) ([#310](https://github.com/community-scripts/ProxmoxVE/pull/310))\n\n## 2024-11-17\n\n### Changed\n\n### ✨ New Scripts\n\n- Add Komga LXC [@DysfunctionalProgramming](https://github.com/DysfunctionalProgramming) ([#275](https://github.com/community-scripts/ProxmoxVE/pull/275))\n\n### 🚀 Updated Scripts\n\n- Tweak: Patch Prometheus for v.3.0.0 [@MickLesk](https://github.com/MickLesk) ([#300](https://github.com/community-scripts/ProxmoxVE/pull/300))\n- Wavelog - Small Adjustment [@HB9HIL](https://github.com/HB9HIL) ([#292](https://github.com/community-scripts/ProxmoxVE/pull/292))\n\n### 🌐 Website\n\n- Add Note for Komga Installation (Website)  [@MickLesk](https://github.com/MickLesk) ([#303](https://github.com/community-scripts/ProxmoxVE/pull/303))\n- Fix Komga logo [@havardthom](https://github.com/havardthom) ([#298](https://github.com/community-scripts/ProxmoxVE/pull/298))\n\n### 🧰 Maintenance\n\n- Add github workflow for automatic changelog PR  [@havardthom](https://github.com/havardthom) ([#299](https://github.com/community-scripts/ProxmoxVE/pull/299))\n- Use website label in autolabeler [@havardthom](https://github.com/havardthom) ([#297](https://github.com/community-scripts/ProxmoxVE/pull/297))\n\n## 2024-11-16\n\n### Changed\n\n- **Recyclarr LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/recyclarr-install.sh)\n  - NEW Script\n- **Wavelog LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/wavelog-install.sh)\n  - NEW Script\n- **Vaultwarden LXC:** RAM has now been increased to 6144 MB [(PR)](https://github.com/community-scripts/ProxmoxVE/pull/285)\n  - Breaking Change\n\n## 2024-11-05\n\n### Changed\n\n- **Bookstack LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/bookstack-install.sh)\n  - NEW Script\n- **Vikunja LXC** [(View Source)](https://github.com/community-scripts/ProxmoxVE/blob/main/install/vikunja-install.sh)\n  - NEW Script\n\n## 2024-11-04\n\n### Breaking Change\n- **Automatic Update of Repository:** The update function now uses the new repository `community-scripts/ProxmoxVE` for Debian/Ubuntu LXC containers.\n  \n  ```bash\n  bash -c \"$(curl -fsSL https://github.com/community-scripts/ProxmoxVE/raw/main/misc/update-repo.sh)\"\n"
  },
  {
    "path": ".github/changelogs/2024/12.md",
    "content": "﻿## 2024-12-30\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- [Archivebox] Fix wrong port being printed post install. [@Strana-Mechty](https://github.com/Strana-Mechty) ([#1105](https://github.com/community-scripts/ProxmoxVE/pull/1105))\n- fix: add homepage version during build step [@se-bastiaan](https://github.com/se-bastiaan) ([#1107](https://github.com/community-scripts/ProxmoxVE/pull/1107))\n\n### 🌐 Website\n\n- Fix Trilium Website to TriliumNext [@tmkis2](https://github.com/tmkis2) ([#1103](https://github.com/community-scripts/ProxmoxVE/pull/1103))\n\n## 2024-12-29\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Grist [@cfurrow](https://github.com/cfurrow) ([#1076](https://github.com/community-scripts/ProxmoxVE/pull/1076))\n- New Script: TeddyCloud Server [@dsiebel](https://github.com/dsiebel) ([#1064](https://github.com/community-scripts/ProxmoxVE/pull/1064))\n\n### 🚀 Updated Scripts\n\n- Fix Install / Update on Grist Script [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1091](https://github.com/community-scripts/ProxmoxVE/pull/1091))\n\n### 🌐 Website\n\n- fix: Update add-lxc-iptag.json warn to warning [@BramSuurdje](https://github.com/BramSuurdje) ([#1094](https://github.com/community-scripts/ProxmoxVE/pull/1094))\n\n### 🧰 Maintenance\n\n- Introduce editorconfig for more consistent formatting [@dsiebel](https://github.com/dsiebel) ([#1073](https://github.com/community-scripts/ProxmoxVE/pull/1073))\n\n## 2024-12-28\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Add Figlet into Repo | Creation of local ASCII Header [@MickLesk](https://github.com/MickLesk) ([#1072](https://github.com/community-scripts/ProxmoxVE/pull/1072))\n- Add an IP-Update for MOTD if IP Changed [@MickLesk](https://github.com/MickLesk) ([#1067](https://github.com/community-scripts/ProxmoxVE/pull/1067))\n\n### 🚀 Updated Scripts\n\n- Zabbix: Fix SQL Path for 7.2 [@MickLesk](https://github.com/MickLesk) ([#1069](https://github.com/community-scripts/ProxmoxVE/pull/1069))\n- Authentik: added missing port to access url [@TheRealVira](https://github.com/TheRealVira) ([#1065](https://github.com/community-scripts/ProxmoxVE/pull/1065))\n\n## 2024-12-27\n\n### Changed\n\n### ✨ New Scripts\n\n- new scripts for Authentik [@remz1337](https://github.com/remz1337) ([#291](https://github.com/community-scripts/ProxmoxVE/pull/291))\n\n### 🚀 Updated Scripts\n\n- Add 8.0 for MongoDB Installation [@MickLesk](https://github.com/MickLesk) ([#1046](https://github.com/community-scripts/ProxmoxVE/pull/1046))\n- Update Zabbix to 7.2. Release [@MickLesk](https://github.com/MickLesk) ([#1048](https://github.com/community-scripts/ProxmoxVE/pull/1048))\n- Apache-Guacamole script bug fix [@sannier3](https://github.com/sannier3) ([#1039](https://github.com/community-scripts/ProxmoxVE/pull/1039))\n\n### 🌐 Website\n\n- Updated SAB documentation based on RAM increase [@TheRealVira](https://github.com/TheRealVira) ([#1035](https://github.com/community-scripts/ProxmoxVE/pull/1035))\n\n### ❔ Unlabelled\n\n- Patch Figlet Repo if missing [@MickLesk](https://github.com/MickLesk) ([#1044](https://github.com/community-scripts/ProxmoxVE/pull/1044))\n- fix Tags for Advanced Settings [@MickLesk](https://github.com/MickLesk) ([#1042](https://github.com/community-scripts/ProxmoxVE/pull/1042))\n\n## 2024-12-26\n\n### Changed\n\n### ✨ New Scripts\n\n-  New Script: Jenkins [@quantumryuu](https://github.com/quantumryuu) ([#1019](https://github.com/community-scripts/ProxmoxVE/pull/1019))\n- New Script: 2FAuth [@jkrgr0](https://github.com/jkrgr0) ([#943](https://github.com/community-scripts/ProxmoxVE/pull/943))\n\n### 🚀 Updated Scripts\n\n- ChangeDetection Update: Update also Browsers [@Niklas04](https://github.com/Niklas04) ([#1027](https://github.com/community-scripts/ProxmoxVE/pull/1027))\n- ensure all RFC1918 local Ipv4 addresses are in iptag script [@AskAlice](https://github.com/AskAlice) ([#992](https://github.com/community-scripts/ProxmoxVE/pull/992))\n- Fix Proxmox DataCenter: incorrect build.func url [@rbradley0](https://github.com/rbradley0) ([#1013](https://github.com/community-scripts/ProxmoxVE/pull/1013))\n\n### 🧰 Maintenance\n\n- [GitHub Actions] Introduce Shellcheck to check bash code [@andygrunwald](https://github.com/andygrunwald) ([#1018](https://github.com/community-scripts/ProxmoxVE/pull/1018))\n\n## 2024-12-25\n\n### Changed\n\n### ✨ New Scripts\n\n- add: pve-datacenter-manager [@CrazyWolf13](https://github.com/CrazyWolf13) ([#947](https://github.com/community-scripts/ProxmoxVE/pull/947))\n\n### 🚀 Updated Scripts\n\n- Fix Script: Alpine Nextcloud Upload File Size Limit [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#933](https://github.com/community-scripts/ProxmoxVE/pull/933))\n- Doubled RAM for SAB [@TheRealVira](https://github.com/TheRealVira) ([#1007](https://github.com/community-scripts/ProxmoxVE/pull/1007))\n\n## 2024-12-23\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix Navidrome Update & Install [@MickLesk](https://github.com/MickLesk) ([#991](https://github.com/community-scripts/ProxmoxVE/pull/991))\n- Update emby.sh to correct port [@Rageplant](https://github.com/Rageplant) ([#989](https://github.com/community-scripts/ProxmoxVE/pull/989))\n\n## 2024-12-21\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- update Port in homeassistant-core CT [@fraefel](https://github.com/fraefel) ([#961](https://github.com/community-scripts/ProxmoxVE/pull/961))\n\n## 2024-12-20\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Apache Guacamole [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#657](https://github.com/community-scripts/ProxmoxVE/pull/657))\n- New Script: silverbullet [@dsiebel](https://github.com/dsiebel) ([#659](https://github.com/community-scripts/ProxmoxVE/pull/659))\n- New Script: Zammad [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#640](https://github.com/community-scripts/ProxmoxVE/pull/640))\n- New Script: CheckMk [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#926](https://github.com/community-scripts/ProxmoxVE/pull/926))\n\n### 🚀 Updated Scripts\n\n- Fix: Remove PHP Key generation in Bookstack Update [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#948](https://github.com/community-scripts/ProxmoxVE/pull/948))\n\n### 🌐 Website\n\n- Update checkmk description [@BramSuurdje](https://github.com/BramSuurdje) ([#954](https://github.com/community-scripts/ProxmoxVE/pull/954))\n- Add Login Note for Checkmk [@MickLesk](https://github.com/MickLesk) ([#940](https://github.com/community-scripts/ProxmoxVE/pull/940))\n\n### ❔ Unlabelled\n\n- Update build.func to display the Proxmox Hostname [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#894](https://github.com/community-scripts/ProxmoxVE/pull/894))\n\n## 2024-12-19\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix: Bookstack Update Function [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#844](https://github.com/community-scripts/ProxmoxVE/pull/844))\n- mysql not showing ip after install [@snow2k9](https://github.com/snow2k9) ([#924](https://github.com/community-scripts/ProxmoxVE/pull/924))\n- Fix Omada - Crawling latest version [@MickLesk](https://github.com/MickLesk) ([#918](https://github.com/community-scripts/ProxmoxVE/pull/918))\n\n### 🌐 Website\n\n- Fix script path formatting in InstallMethod component [@BramSuurdje](https://github.com/BramSuurdje) ([#909](https://github.com/community-scripts/ProxmoxVE/pull/909))\n- Fix Part-DB Docu (cred command) [@EvilBlood](https://github.com/EvilBlood) ([#898](https://github.com/community-scripts/ProxmoxVE/pull/898))\n- Enhance Tooltip component by adding CircleHelp icon and fix instructions in script component [@BramSuurdje](https://github.com/BramSuurdje) ([#910](https://github.com/community-scripts/ProxmoxVE/pull/910))\n\n## 2024-12-18\n\n### Changed\n\n### ✨ New Scripts\n\n- New script: Part-DB LXC [@bvdberg01](https://github.com/bvdberg01) ([#591](https://github.com/community-scripts/ProxmoxVE/pull/591))\n\n### 🚀 Updated Scripts\n\n- Fix Kernel-Clean for Proxmox 8.x [@MickLesk](https://github.com/MickLesk) ([#904](https://github.com/community-scripts/ProxmoxVE/pull/904))\n- [Frigate] Remove SSE 4.2 from instruction set supporting OpenVino [@remz1337](https://github.com/remz1337) ([#902](https://github.com/community-scripts/ProxmoxVE/pull/902))\n\n### 🌐 Website\n\n- New Metadata Category: \"Coding & AI\" [@newzealandpaul](https://github.com/newzealandpaul) ([#890](https://github.com/community-scripts/ProxmoxVE/pull/890))\n- Moved Webmin to \"Server & Networking\" [@newzealandpaul](https://github.com/newzealandpaul) ([#891](https://github.com/community-scripts/ProxmoxVE/pull/891))\n\n## 2024-12-17\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix Alpine-Nextcloud: Bump PHP Version to 8.3 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#865](https://github.com/community-scripts/ProxmoxVE/pull/865))\n- Correction of Jellyfin CT Port [@mneten](https://github.com/mneten) ([#884](https://github.com/community-scripts/ProxmoxVE/pull/884))\n- fix spinner on lxc-ip-tag [@MickLesk](https://github.com/MickLesk) ([#876](https://github.com/community-scripts/ProxmoxVE/pull/876))\n- Fix Keycloak Installation [@MickLesk](https://github.com/MickLesk) ([#874](https://github.com/community-scripts/ProxmoxVE/pull/874))\n- Fix ports ressources [@MickLesk](https://github.com/MickLesk) ([#867](https://github.com/community-scripts/ProxmoxVE/pull/867))\n\n### 🧰 Maintenance\n\n- Small Changes to the PR Template [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#862](https://github.com/community-scripts/ProxmoxVE/pull/862))\n\n### ❔ Unlabelled\n\n- calculate terminal size for header_info [@MickLesk](https://github.com/MickLesk) ([#879](https://github.com/community-scripts/ProxmoxVE/pull/879))\n- Fix header creation with figlet for alpine [@MickLesk](https://github.com/MickLesk) ([#869](https://github.com/community-scripts/ProxmoxVE/pull/869))\n\n## 2024-12-16\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Massive Update: build.func | install.func | create_lxc.sh (Part 1) [@MickLesk](https://github.com/MickLesk) ([#643](https://github.com/community-scripts/ProxmoxVE/pull/643))\n- Update ALL CT's to new default (Part 2) [@MickLesk](https://github.com/MickLesk) ([#710](https://github.com/community-scripts/ProxmoxVE/pull/710))\n\n### ✨ New Scripts\n\n- New Script: LXC IP-Tag [@MickLesk](https://github.com/MickLesk) ([#536](https://github.com/community-scripts/ProxmoxVE/pull/536))\n\n### 🚀 Updated Scripts\n\n- Increase Size | Description & Download-URL of Debian VM [@MickLesk](https://github.com/MickLesk) ([#837](https://github.com/community-scripts/ProxmoxVE/pull/837))\n- Update Script: Remove Docker Compose Question [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#847](https://github.com/community-scripts/ProxmoxVE/pull/847))\n\n### 🌐 Website\n\n- Bump nanoid from 3.3.7 to 3.3.8 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#845](https://github.com/community-scripts/ProxmoxVE/pull/845))\n\n### ❔ Unlabelled\n\n- Fix SSH root access in install.func [@havardthom](https://github.com/havardthom) ([#858](https://github.com/community-scripts/ProxmoxVE/pull/858))\n- Fix variable name for CT_TYPE override [@remz1337](https://github.com/remz1337) ([#855](https://github.com/community-scripts/ProxmoxVE/pull/855))\n- Keeps the same style after writing the SEARCH icon [@remz1337](https://github.com/remz1337) ([#851](https://github.com/community-scripts/ProxmoxVE/pull/851))\n\n## 2024-12-13\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix Keycloak Update Function [@MickLesk](https://github.com/MickLesk) ([#762](https://github.com/community-scripts/ProxmoxVE/pull/762))\n- Fix config bug in Alpine Vaultwarden [@havardthom](https://github.com/havardthom) ([#775](https://github.com/community-scripts/ProxmoxVE/pull/775))\n\n### 🌐 Website\n\n- Change MISC from red to green [@MickLesk](https://github.com/MickLesk) ([#815](https://github.com/community-scripts/ProxmoxVE/pull/815))\n- Update some JSON Files for Website [@MickLesk](https://github.com/MickLesk) ([#812](https://github.com/community-scripts/ProxmoxVE/pull/812))\n- Update Notes & Documentation for Proxmox Backup Server [@MickLesk](https://github.com/MickLesk) ([#804](https://github.com/community-scripts/ProxmoxVE/pull/804))\n\n### 🧰 Maintenance\n\n- Github: Optimize Issue Template & PR Template [@MickLesk](https://github.com/MickLesk) ([#802](https://github.com/community-scripts/ProxmoxVE/pull/802))\n\n## 2024-12-12\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Update jellyfin.sh / Fix infinite loop [@gerpo](https://github.com/gerpo) ([#792](https://github.com/community-scripts/ProxmoxVE/pull/792))\n\n### 🌐 Website\n\n- Fix port and website in nextcloudpi.json [@PhoenixEmik](https://github.com/PhoenixEmik) ([#790](https://github.com/community-scripts/ProxmoxVE/pull/790))\n- Add post-install note to mqtt.json [@havardthom](https://github.com/havardthom) ([#783](https://github.com/community-scripts/ProxmoxVE/pull/783))\n\n### 🧰 Maintenance\n\n- Filter pull requests on main branch in changelog-pr.yml [@havardthom](https://github.com/havardthom) ([#793](https://github.com/community-scripts/ProxmoxVE/pull/793))\n- Fix Z-Wave JS UI Breaking Change in CHANGELOG.md [@havardthom](https://github.com/havardthom) ([#781](https://github.com/community-scripts/ProxmoxVE/pull/781))\n\n## 2024-12-09\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix PostgreSQL password bug in Umami install [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#750](https://github.com/community-scripts/ProxmoxVE/pull/750))\n\n## 2024-12-08\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Use MongoDB 4.4 in Unifi for non-AVX users [@havardthom](https://github.com/havardthom) ([#691](https://github.com/community-scripts/ProxmoxVE/pull/691))\n\n### 🌐 Website\n\n- Move homarr to Dashboards section [@CrazyWolf13](https://github.com/CrazyWolf13) ([#740](https://github.com/community-scripts/ProxmoxVE/pull/740))\n\n## 2024-12-07\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Zigbee2MQTT: Remove dev branch choice until v2.0.0 release [@havardthom](https://github.com/havardthom) ([#702](https://github.com/community-scripts/ProxmoxVE/pull/702))\n- Fix Hoarder build failure by installing Chromium stable [@vhsdream](https://github.com/vhsdream) ([#723](https://github.com/community-scripts/ProxmoxVE/pull/723))\n\n### 🌐 Website\n\n- Bugfix: Include script name in website search [@havardthom](https://github.com/havardthom) ([#731](https://github.com/community-scripts/ProxmoxVE/pull/731))\n\n### ❔ Unlabelled\n\n- Fix broken build.func [@havardthom](https://github.com/havardthom) ([#736](https://github.com/community-scripts/ProxmoxVE/pull/736))\n\n## 2024-12-06\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix bugs in Komga update [@DysfunctionalProgramming](https://github.com/DysfunctionalProgramming) ([#717](https://github.com/community-scripts/ProxmoxVE/pull/717))\n- Bookstack: Fix Update function composer [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#700](https://github.com/community-scripts/ProxmoxVE/pull/700))\n\n### 🌐 Website\n\n- fix: note component in json-editor getting out of focus when typing and revert theme switch animation [@BramSuurdje](https://github.com/BramSuurdje) ([#706](https://github.com/community-scripts/ProxmoxVE/pull/706))\n\n### 🧰 Maintenance\n\n- Update frontend CI/CD workflow [@havardthom](https://github.com/havardthom) ([#703](https://github.com/community-scripts/ProxmoxVE/pull/703))\n\n## 2024-12-05\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- PostgreSQL: Change authentication method from peer to md5 for UNIX sockets [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#650](https://github.com/community-scripts/ProxmoxVE/pull/650))\n- Fix stdout in unifi.sh [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#688](https://github.com/community-scripts/ProxmoxVE/pull/688))\n- Fix `rm` bug in Vikunja update [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#692](https://github.com/community-scripts/ProxmoxVE/pull/692))\n\n## 2024-12-04\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Update Spelling 'Environment' in nginxproxymanager [@MathijsG](https://github.com/MathijsG) ([#676](https://github.com/community-scripts/ProxmoxVE/pull/676))\n\n### 🌐 Website\n\n- Update homepage.json documentation and website links [@patchmonkey](https://github.com/patchmonkey) ([#668](https://github.com/community-scripts/ProxmoxVE/pull/668))\n\n## 2024-12-03\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Onedev [@quantumryuu](https://github.com/quantumryuu) ([#612](https://github.com/community-scripts/ProxmoxVE/pull/612))\n\n### 🚀 Updated Scripts\n\n- Script Update: SnipeIT [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#641](https://github.com/community-scripts/ProxmoxVE/pull/641))\n\n## 2024-12-02\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Hoarder LXC [@vhsdream](https://github.com/vhsdream) ([#567](https://github.com/community-scripts/ProxmoxVE/pull/567))\n- New script: SnipeIT LXC [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#538](https://github.com/community-scripts/ProxmoxVE/pull/538))\n- New Script: Glance [@quantumryuu](https://github.com/quantumryuu) ([#595](https://github.com/community-scripts/ProxmoxVE/pull/595))\n- New script: Unbound LXC [@wimb0](https://github.com/wimb0) ([#547](https://github.com/community-scripts/ProxmoxVE/pull/547))\n- New script: Mylar3 LXC [@davalanche](https://github.com/davalanche) ([#554](https://github.com/community-scripts/ProxmoxVE/pull/554))\n\n### 🚀 Updated Scripts\n\n- Stirling-PDF: replace dependency for v0.35.0 and add check and fix in stirling-pdf.sh [@vhsdream](https://github.com/vhsdream) ([#614](https://github.com/community-scripts/ProxmoxVE/pull/614))\n- qbittorrent: do not override the configuration port in systemd [@zdraganov](https://github.com/zdraganov) ([#618](https://github.com/community-scripts/ProxmoxVE/pull/618))\n\n### 🌐 Website\n\n- chore: Update unbound logo to have only the actual logo [@BramSuurdje](https://github.com/BramSuurdje) ([#648](https://github.com/community-scripts/ProxmoxVE/pull/648))\n- fix: vaultwarden info mismatch [@BramSuurdje](https://github.com/BramSuurdje) ([#645](https://github.com/community-scripts/ProxmoxVE/pull/645))\n- Wallos json fix [@quantumryuu](https://github.com/quantumryuu) ([#630](https://github.com/community-scripts/ProxmoxVE/pull/630))\n"
  },
  {
    "path": ".github/changelogs/2025/01.md",
    "content": "﻿## 2025-01-31\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Paymenter [@opastorello](https://github.com/opastorello) ([#1827](https://github.com/community-scripts/ProxmoxVE/pull/1827))\n\n### 🚀 Updated Scripts\n\n- [Fix] Alpine-IT-Tools, add missing ssh package for root ssh access [@CrazyWolf13](https://github.com/CrazyWolf13) ([#1891](https://github.com/community-scripts/ProxmoxVE/pull/1891))\n- [Fix] Change Download of Trilium after there change the tag/release logic [@MickLesk](https://github.com/MickLesk) ([#1892](https://github.com/community-scripts/ProxmoxVE/pull/1892))\n\n### 🌐 Website\n\n- [Website] Enhance DataFetcher with better UI components and add reactive data fetching intervals [@BramSuurdje](https://github.com/BramSuurdje) ([#1902](https://github.com/community-scripts/ProxmoxVE/pull/1902))\n- [Website] Update /data/page.tsx [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1900](https://github.com/community-scripts/ProxmoxVE/pull/1900))\n\n## 2025-01-30\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: IT-Tools [@nicedevil007](https://github.com/nicedevil007) ([#1862](https://github.com/community-scripts/ProxmoxVE/pull/1862))\n- New Script: Mattermost [@Dracentis](https://github.com/Dracentis) ([#1856](https://github.com/community-scripts/ProxmoxVE/pull/1856))\n\n### 🚀 Updated Scripts\n\n- Optimize PVE Manager Version-Check [@MickLesk](https://github.com/MickLesk) ([#1866](https://github.com/community-scripts/ProxmoxVE/pull/1866))\n\n### 🌐 Website\n\n- [API] Update build.func to set the status message correct [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1878](https://github.com/community-scripts/ProxmoxVE/pull/1878))\n- [Website] Update /data/page.tsx [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1876](https://github.com/community-scripts/ProxmoxVE/pull/1876))\n- Fix IT-Tools Website Entry (Default | Alpine)  [@MickLesk](https://github.com/MickLesk) ([#1869](https://github.com/community-scripts/ProxmoxVE/pull/1869))\n- fix: remove rounded styles from command primitive [@steveiliop56](https://github.com/steveiliop56) ([#1840](https://github.com/community-scripts/ProxmoxVE/pull/1840))\n\n### 🧰 Maintenance\n\n- [API] Update build.func: add function to see if a script failed or not [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1874](https://github.com/community-scripts/ProxmoxVE/pull/1874))\n\n## 2025-01-29\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Prometheus Proxmox VE Exporter [@andygrunwald](https://github.com/andygrunwald) ([#1805](https://github.com/community-scripts/ProxmoxVE/pull/1805))\n- New Script: Clean Orphaned LVM [@MickLesk](https://github.com/MickLesk) ([#1838](https://github.com/community-scripts/ProxmoxVE/pull/1838))\n\n### 🌐 Website\n\n- Patch http Url to https in build.func and /data/page.tsx [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1849](https://github.com/community-scripts/ProxmoxVE/pull/1849))\n- [Frontend] Add /data to show API results [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1841](https://github.com/community-scripts/ProxmoxVE/pull/1841))\n- Update clean-orphaned-lvm.json [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1843](https://github.com/community-scripts/ProxmoxVE/pull/1843))\n\n### 🧰 Maintenance\n\n- Update build.func [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1851](https://github.com/community-scripts/ProxmoxVE/pull/1851))\n- [Diagnostic] Introduced optional lxc install diagnostics via API call [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1801](https://github.com/community-scripts/ProxmoxVE/pull/1801))\n\n## 2025-01-28\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Breaking Change: Homarr v1 (Read Guide) [@MickLesk](https://github.com/MickLesk) ([#1825](https://github.com/community-scripts/ProxmoxVE/pull/1825))\n- Update PingVin: Fix problem with update und switch to new method of getting files. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1819](https://github.com/community-scripts/ProxmoxVE/pull/1819))\n\n### ✨ New Scripts\n\n- New script: Monica LXC [@bvdberg01](https://github.com/bvdberg01) ([#1813](https://github.com/community-scripts/ProxmoxVE/pull/1813))\n- New Script: NodeBB [@MickLesk](https://github.com/MickLesk) ([#1811](https://github.com/community-scripts/ProxmoxVE/pull/1811))\n- New Script: Pocket ID [@Snarkenfaugister](https://github.com/Snarkenfaugister) ([#1779](https://github.com/community-scripts/ProxmoxVE/pull/1779))\n\n### 🚀 Updated Scripts\n\n- Update all Alpine LXC's to 3.21 (Docker, Grafana, Nextcloud, Vaultwarden, Zigbee2Mqtt, Alpine) [@MickLesk](https://github.com/MickLesk) ([#1803](https://github.com/community-scripts/ProxmoxVE/pull/1803))\n- [Standardization] Fix Spelling for \"Setup Python3\" [@MickLesk](https://github.com/MickLesk) ([#1810](https://github.com/community-scripts/ProxmoxVE/pull/1810))\n\n### 🌐 Website\n\n- Filter out duplicate scripts in LatestScripts component and sort by creation date [@BramSuurdje](https://github.com/BramSuurdje) ([#1828](https://github.com/community-scripts/ProxmoxVE/pull/1828))\n\n### 🧰 Maintenance\n\n- [core]: Remove Figlet | Get Headers by Repo & Store Local [@MickLesk](https://github.com/MickLesk) ([#1802](https://github.com/community-scripts/ProxmoxVE/pull/1802))\n- [docs] Update AppName.md: Make it clear where to change the URLs [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1809](https://github.com/community-scripts/ProxmoxVE/pull/1809))\n\n## 2025-01-27\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Arch Linux VM [@MickLesk](https://github.com/MickLesk) ([#1780](https://github.com/community-scripts/ProxmoxVE/pull/1780))\n\n### 🚀 Updated Scripts\n\n- Increase alpine-vaultwarden default var_disk size [@nayzm](https://github.com/nayzm) ([#1788](https://github.com/community-scripts/ProxmoxVE/pull/1788))\n- Added change of the mobile GUI to disable nag request [@GarryG](https://github.com/GarryG) ([#1785](https://github.com/community-scripts/ProxmoxVE/pull/1785))\n\n### 🌐 Website\n\n- Update frontend alpine-vaultwarden hdd size and OS version [@nayzm](https://github.com/nayzm) ([#1789](https://github.com/community-scripts/ProxmoxVE/pull/1789))\n- Website: Add Description for Metadata Categories [@MickLesk](https://github.com/MickLesk) ([#1783](https://github.com/community-scripts/ProxmoxVE/pull/1783))\n- [Fix] Double \"VM\" on website (Arch Linux) [@lasharor](https://github.com/lasharor) ([#1782](https://github.com/community-scripts/ProxmoxVE/pull/1782))\n\n## 2025-01-26\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix jellyfin update command [@jcisio](https://github.com/jcisio) ([#1771](https://github.com/community-scripts/ProxmoxVE/pull/1771))\n- openHAB - Use https and include doc url [@moodyblue](https://github.com/moodyblue) ([#1766](https://github.com/community-scripts/ProxmoxVE/pull/1766))\n- Jellyfin: Fix default logging level [@tremor021](https://github.com/tremor021) ([#1768](https://github.com/community-scripts/ProxmoxVE/pull/1768))\n- Calibre-Web: added installation of calibre binaries [@tremor021](https://github.com/tremor021) ([#1763](https://github.com/community-scripts/ProxmoxVE/pull/1763))\n- Added environment variable to accept EULA for SQLServer2022 [@tremor021](https://github.com/tremor021) ([#1755](https://github.com/community-scripts/ProxmoxVE/pull/1755))\n\n### 🌐 Website\n\n- The Lounge: Fix the command to create new users [@tremor021](https://github.com/tremor021) ([#1762](https://github.com/community-scripts/ProxmoxVE/pull/1762))\n\n## 2025-01-24\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Ubuntu 24.10 VM [@MickLesk](https://github.com/MickLesk) ([#1711](https://github.com/community-scripts/ProxmoxVE/pull/1711))\n\n### 🚀 Updated Scripts\n\n- openHAB - Update to Zulu21 [@moodyblue](https://github.com/moodyblue) ([#1734](https://github.com/community-scripts/ProxmoxVE/pull/1734))\n- Feature: Filebrowser Script > Redesign | Update Logic | Remove Logic [@MickLesk](https://github.com/MickLesk) ([#1716](https://github.com/community-scripts/ProxmoxVE/pull/1716))\n- Feature: Ubuntu 22.04 VM > Redesign | Optional HDD-Size Prompt [@MickLesk](https://github.com/MickLesk) ([#1712](https://github.com/community-scripts/ProxmoxVE/pull/1712))\n- Feature: Ubuntu 24.04 VM > Redesign | Optional HDD-Size Prompt | cifs support [@MickLesk](https://github.com/MickLesk) ([#1714](https://github.com/community-scripts/ProxmoxVE/pull/1714))\n\n### 🧰 Maintenance\n\n- [Core] Better Creation of App Headers for next feature [@MickLesk](https://github.com/MickLesk) ([#1719](https://github.com/community-scripts/ProxmoxVE/pull/1719))\n\n## 2025-01-23\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Feature: Add Debian Disk Size / Redesign / Increase Disk [@MickLesk](https://github.com/MickLesk) ([#1695](https://github.com/community-scripts/ProxmoxVE/pull/1695))\n- Fix: Paperless Service Timings & Optimization: Ghostscript Installation [@MickLesk](https://github.com/MickLesk) ([#1688](https://github.com/community-scripts/ProxmoxVE/pull/1688))\n\n### 🌐 Website\n\n- Refactor ScriptInfoBlocks and siteConfig to properly show the most populair scripts [@BramSuurdje](https://github.com/BramSuurdje) ([#1697](https://github.com/community-scripts/ProxmoxVE/pull/1697))\n\n### 🧰 Maintenance\n\n- Update build.func: Ubuntu advanced settings version [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1701](https://github.com/community-scripts/ProxmoxVE/pull/1701))\n\n## 2025-01-22\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Tweak: LubeLogger Script Upcoming Changes 1.4.3 [@JcMinarro](https://github.com/JcMinarro) ([#1656](https://github.com/community-scripts/ProxmoxVE/pull/1656))\n- Fix: SQL Server 2022 Install [@MickLesk](https://github.com/MickLesk) ([#1669](https://github.com/community-scripts/ProxmoxVE/pull/1669))\n\n### 🌐 Website\n\n- Refactor Sidebar component to display unique scripts count [@BramSuurdje](https://github.com/BramSuurdje) ([#1681](https://github.com/community-scripts/ProxmoxVE/pull/1681))\n- Refactor various components and configuration for mobile responsiveness. [@BramSuurdje](https://github.com/BramSuurdje) ([#1679](https://github.com/community-scripts/ProxmoxVE/pull/1679))\n- Add Docker-VM to Containers & Docker Category [@thost96](https://github.com/thost96) ([#1667](https://github.com/community-scripts/ProxmoxVE/pull/1667))\n- Moving SQL Server 2022 to database category [@CamronBorealis](https://github.com/CamronBorealis) ([#1659](https://github.com/community-scripts/ProxmoxVE/pull/1659))\n\n## 2025-01-21\n\n### Changed\n\n### ✨ New Scripts\n\n- Add new Script: LXC Delete (Proxmox) [@MickLesk](https://github.com/MickLesk) ([#1636](https://github.com/community-scripts/ProxmoxVE/pull/1636))\n- New script: ProjectSend [@bvdberg01](https://github.com/bvdberg01) ([#1616](https://github.com/community-scripts/ProxmoxVE/pull/1616))\n- New Script: Beszel [@Sinofage](https://github.com/Sinofage) ([#1619](https://github.com/community-scripts/ProxmoxVE/pull/1619))\n- New Script: Docker VM [@thost96](https://github.com/thost96) ([#1608](https://github.com/community-scripts/ProxmoxVE/pull/1608))\n- New script: SQL Server 2022 [@kris701](https://github.com/kris701) ([#1482](https://github.com/community-scripts/ProxmoxVE/pull/1482))\n\n### 🚀 Updated Scripts\n\n- Fix: Teddycloud Script (install, clean up & update) [@MickLesk](https://github.com/MickLesk) ([#1652](https://github.com/community-scripts/ProxmoxVE/pull/1652))\n- Fix: Docker VM deprecated gpg [@MickLesk](https://github.com/MickLesk) ([#1649](https://github.com/community-scripts/ProxmoxVE/pull/1649))\n- ActualBudget: Fix Update-Function, Fix Wget Crawling, Add Versionscheck [@MickLesk](https://github.com/MickLesk) ([#1643](https://github.com/community-scripts/ProxmoxVE/pull/1643))\n- Fix Photoprism missing folder & environments  [@MickLesk](https://github.com/MickLesk) ([#1639](https://github.com/community-scripts/ProxmoxVE/pull/1639))\n- Update MOTD: Add Dynamic IP with profile.d by @JcMinarro [@MickLesk](https://github.com/MickLesk) ([#1633](https://github.com/community-scripts/ProxmoxVE/pull/1633))\n- PBS.sh: Fix wrong URL after Setup [@thost96](https://github.com/thost96) ([#1629](https://github.com/community-scripts/ProxmoxVE/pull/1629))\n\n### 🌐 Website\n\n- Bump vite from 6.0.1 to 6.0.11 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#1653](https://github.com/community-scripts/ProxmoxVE/pull/1653))\n- Update glpi.json [@opastorello](https://github.com/opastorello) ([#1641](https://github.com/community-scripts/ProxmoxVE/pull/1641))\n- Fix Docker-VM name on website [@Sinofage](https://github.com/Sinofage) ([#1630](https://github.com/community-scripts/ProxmoxVE/pull/1630))\n\n## 2025-01-20\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: UrBackup Server [@kris701](https://github.com/kris701) ([#1569](https://github.com/community-scripts/ProxmoxVE/pull/1569))\n- New Script: Proxmox Mail Gateway Post Installer [@thost96](https://github.com/thost96) ([#1559](https://github.com/community-scripts/ProxmoxVE/pull/1559))\n\n### 🚀 Updated Scripts\n\n- Update Kimai Dependency: Use PHP 8.3 [@MickLesk](https://github.com/MickLesk) ([#1609](https://github.com/community-scripts/ProxmoxVE/pull/1609))\n- Feature: Add xCaddy for external Modules on Caddy-LXC [@MickLesk](https://github.com/MickLesk) ([#1613](https://github.com/community-scripts/ProxmoxVE/pull/1613))\n- Fix Pocketbase URL after install [@MickLesk](https://github.com/MickLesk) ([#1597](https://github.com/community-scripts/ProxmoxVE/pull/1597))\n- Unifi.sh fix wrong URL after Install [@thost96](https://github.com/thost96) ([#1601](https://github.com/community-scripts/ProxmoxVE/pull/1601))\n\n### 🌐 Website\n\n- Update Website | Add new Categories [@MickLesk](https://github.com/MickLesk) ([#1606](https://github.com/community-scripts/ProxmoxVE/pull/1606))\n- Grafana: Mark container as updateable [@andygrunwald](https://github.com/andygrunwald) ([#1603](https://github.com/community-scripts/ProxmoxVE/pull/1603))\n\n### 🧰 Maintenance\n\n- [core] Update build.func: Add defaults to Advanced mode [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1548](https://github.com/community-scripts/ProxmoxVE/pull/1548))\n- Update build.func: Fix Advanced Tags (Remove all if empty / overwrite if default cleared) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1612](https://github.com/community-scripts/ProxmoxVE/pull/1612))\n- Add new Check for LXC MaxKeys by @cricalix [@MickLesk](https://github.com/MickLesk) ([#1602](https://github.com/community-scripts/ProxmoxVE/pull/1602))\n\n## 2025-01-19\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Update Opengist.sh: Fix broken backup function [@bvdberg01](https://github.com/bvdberg01) ([#1572](https://github.com/community-scripts/ProxmoxVE/pull/1572))\n\n## 2025-01-18\n\n### Changed\n\n### 💥 Breaking Changes\n\n- **READ GUIDE FIRST** breaking change: Homeassistant-Core upgrade os and python3 [@MickLesk](https://github.com/MickLesk) ([#1550](https://github.com/community-scripts/ProxmoxVE/pull/1550))\n- Update Openwrt: Delete lines that do WAN input and forward accept [@chackl1990](https://github.com/chackl1990) ([#1540](https://github.com/community-scripts/ProxmoxVE/pull/1540))\n\n### 🚀 Updated Scripts\n\n- added cifs support in ubuntu2404-vm.sh [@plonxyz](https://github.com/plonxyz) ([#1461](https://github.com/community-scripts/ProxmoxVE/pull/1461))\n- Fix linkwarden update [@burgerga](https://github.com/burgerga) ([#1565](https://github.com/community-scripts/ProxmoxVE/pull/1565))\n- [jellyseerr] Update nodejs if not up-to-date [@makstech](https://github.com/makstech) ([#1563](https://github.com/community-scripts/ProxmoxVE/pull/1563))\n- Update VM Tags [@oOStroudyOo](https://github.com/oOStroudyOo) ([#1562](https://github.com/community-scripts/ProxmoxVE/pull/1562))\n- Update apt-cacher-ng.sh: Typo/Missing $ [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1545](https://github.com/community-scripts/ProxmoxVE/pull/1545))\n\n## 2025-01-16\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Update jellyseerr-install.sh to use Node 22 as required by latest Jellyseerr version [@pedrovieira](https://github.com/pedrovieira) ([#1535](https://github.com/community-scripts/ProxmoxVE/pull/1535))\n\n### ✨ New Scripts\n\n- New script: Dotnet ASP.NET Web Server [@kris701](https://github.com/kris701) ([#1501](https://github.com/community-scripts/ProxmoxVE/pull/1501))\n- New script: phpIPAM [@bvdberg01](https://github.com/bvdberg01) ([#1503](https://github.com/community-scripts/ProxmoxVE/pull/1503))\n\n### 🌐 Website\n\n- Add Mobile check for empty icon-url on website [@MickLesk](https://github.com/MickLesk) ([#1532](https://github.com/community-scripts/ProxmoxVE/pull/1532))\n\n### 🧰 Maintenance\n\n- [Workflow]Update autolabeler-config.json [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1525](https://github.com/community-scripts/ProxmoxVE/pull/1525))\n- [core]Update update_json_date.yml [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1526](https://github.com/community-scripts/ProxmoxVE/pull/1526))\n- [core] Recreate Update JSON Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1523](https://github.com/community-scripts/ProxmoxVE/pull/1523))\n\n## 2025-01-15\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix: Add FFMPEG for OpenWebUI [@MickLesk](https://github.com/MickLesk) ([#1497](https://github.com/community-scripts/ProxmoxVE/pull/1497))\n\n### 🧰 Maintenance\n\n- [core] build.func&install.func: Fix ssh keynot added error [@dsiebel](https://github.com/dsiebel) ([#1502](https://github.com/community-scripts/ProxmoxVE/pull/1502))\n\n## 2025-01-14\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Update tianji-install.sh: Add OPENAI_API_KEY to .env [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1480](https://github.com/community-scripts/ProxmoxVE/pull/1480))\n\n### ✨ New Scripts\n\n- New Script: Wordpress [@MickLesk](https://github.com/MickLesk) ([#1485](https://github.com/community-scripts/ProxmoxVE/pull/1485))\n- New Script: Opengist [@jd-apprentice](https://github.com/jd-apprentice) ([#1429](https://github.com/community-scripts/ProxmoxVE/pull/1429))\n\n### 🚀 Updated Scripts\n\n- Update lazylibrarian-install.sh: Add pypdf libary [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1467](https://github.com/community-scripts/ProxmoxVE/pull/1467))\n- Update opengist-install.sh: Add git as dependencie [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1471](https://github.com/community-scripts/ProxmoxVE/pull/1471))\n\n### 🌐 Website\n\n- [website] Update footer text [@rajatdiptabiswas](https://github.com/rajatdiptabiswas) ([#1466](https://github.com/community-scripts/ProxmoxVE/pull/1466))\n\n### 🧰 Maintenance\n\n- Hotfix build.func: Error when tags are empty [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1492](https://github.com/community-scripts/ProxmoxVE/pull/1492))\n- [core] Update build.func: Fix bug with advanced tags [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1473](https://github.com/community-scripts/ProxmoxVE/pull/1473))\n\n## 2025-01-13\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Update Hoarder: Improvement .env location (see PR comment for little migration) [@MahrWe](https://github.com/MahrWe) ([#1325](https://github.com/community-scripts/ProxmoxVE/pull/1325))\n\n### 🚀 Updated Scripts\n\n- Fix: tandoor.sh: Call version.py to write current version [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1454](https://github.com/community-scripts/ProxmoxVE/pull/1454))\n- Fix inexistent folder on actualbudget update script [@dosten](https://github.com/dosten) ([#1444](https://github.com/community-scripts/ProxmoxVE/pull/1444))\n\n### 🌐 Website\n\n- Update kavita.json: Add info on how to enable folder adding. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1447](https://github.com/community-scripts/ProxmoxVE/pull/1447))\n\n### 🧰 Maintenance\n\n- GitHub Actions: Fix Shellsheck workflow to only run on changes `*.sh` files [@andygrunwald](https://github.com/andygrunwald) ([#1423](https://github.com/community-scripts/ProxmoxVE/pull/1423))\n\n### ❔ Unlabelled\n\n- feat: allow adding SSH authorized key for root (advanced settings) by @dsiebel [@MickLesk](https://github.com/MickLesk) ([#1456](https://github.com/community-scripts/ProxmoxVE/pull/1456))\n\n## 2025-01-11\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Prometheus: Fix installation via creating the service file [@andygrunwald](https://github.com/andygrunwald) ([#1416](https://github.com/community-scripts/ProxmoxVE/pull/1416))\n- Update prometheus-install.sh: Service creation fix [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1417](https://github.com/community-scripts/ProxmoxVE/pull/1417))\n- Update prometheus-alertmanager-install.sh: Service Creation Fix [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1418](https://github.com/community-scripts/ProxmoxVE/pull/1418))\n- Fix: LubeLogger CT vehicle tag typo [@kkroboth](https://github.com/kkroboth) ([#1413](https://github.com/community-scripts/ProxmoxVE/pull/1413))\n\n## 2025-01-10\n\n### Changed\n\n### ✨ New Scripts\n\n- New script : Ghost [@fabrice1236](https://github.com/fabrice1236) ([#1361](https://github.com/community-scripts/ProxmoxVE/pull/1361))\n\n### 🚀 Updated Scripts\n\n- Fix user in ghost-cli install command [@fabrice1236](https://github.com/fabrice1236) ([#1408](https://github.com/community-scripts/ProxmoxVE/pull/1408))\n- Update Prometheus + Alertmanager: Unify scripts for easier maintenance [@andygrunwald](https://github.com/andygrunwald) ([#1402](https://github.com/community-scripts/ProxmoxVE/pull/1402))\n- Update komodo.sh: Fix broken paths in update_script(). [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1403](https://github.com/community-scripts/ProxmoxVE/pull/1403))\n- Fix: ActualBudget Update-Function [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1376](https://github.com/community-scripts/ProxmoxVE/pull/1376))\n- Fix: bookstack.sh - Ignore empty folder [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1388](https://github.com/community-scripts/ProxmoxVE/pull/1388))\n- Fix: checkmk-install.sh: Version crawling. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1385](https://github.com/community-scripts/ProxmoxVE/pull/1385))\n\n### 🌐 Website\n\n- Change Website-Category of nzbget [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1379](https://github.com/community-scripts/ProxmoxVE/pull/1379))\n\n### 🧰 Maintenance\n\n- Update check_and_update_json_date.yml: Change path to /json [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1399](https://github.com/community-scripts/ProxmoxVE/pull/1399))\n- Visual Studio Code: Set Workspace recommended extensions [@andygrunwald](https://github.com/andygrunwald) ([#1398](https://github.com/community-scripts/ProxmoxVE/pull/1398))\n- [core]: add support for custom tags [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1384](https://github.com/community-scripts/ProxmoxVE/pull/1384))\n- [core]: check json date of new prs & update it [@MickLesk](https://github.com/MickLesk) ([#1395](https://github.com/community-scripts/ProxmoxVE/pull/1395))\n- Add initial PR for Contributing & Coding Standard [@MickLesk](https://github.com/MickLesk) ([#920](https://github.com/community-scripts/ProxmoxVE/pull/920))\n- [Core] add Github Action for Generate AppHeaders (figlet remove part 1) [@MickLesk](https://github.com/MickLesk) ([#1382](https://github.com/community-scripts/ProxmoxVE/pull/1382))\n\n## 2025-01-09\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Removal calibre-server (no Headless Support) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1362](https://github.com/community-scripts/ProxmoxVE/pull/1362))\n\n### ✨ New Scripts\n\n- New Script: Prometheus Alertmanager [@andygrunwald](https://github.com/andygrunwald) ([#1272](https://github.com/community-scripts/ProxmoxVE/pull/1272))\n- New script: ps5-mqtt [@liecno](https://github.com/liecno) ([#1198](https://github.com/community-scripts/ProxmoxVE/pull/1198))\n\n### 🚀 Updated Scripts\n\n- Fix: AdventureLog: unzip to /opt/ [@JesperDramsch](https://github.com/JesperDramsch) ([#1370](https://github.com/community-scripts/ProxmoxVE/pull/1370))\n- Fix: Stirling-PDF > LibreOffice/unoconv Integration Issues  [@m6urns](https://github.com/m6urns) ([#1322](https://github.com/community-scripts/ProxmoxVE/pull/1322))\n- Fix: AdventureLog - update script bug [@JesperDramsch](https://github.com/JesperDramsch) ([#1334](https://github.com/community-scripts/ProxmoxVE/pull/1334))\n- Install/update ActualBudget based on releases, not latest master [@SpyrosRoum](https://github.com/SpyrosRoum) ([#1254](https://github.com/community-scripts/ProxmoxVE/pull/1254))\n- Fix Checkmk: Version grep broken [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1341](https://github.com/community-scripts/ProxmoxVE/pull/1341))\n\n### 🧰 Maintenance\n\n- fix: only validate scripts in validate-scripts workflow [@se-bastiaan](https://github.com/se-bastiaan) ([#1344](https://github.com/community-scripts/ProxmoxVE/pull/1344))\n\n## 2025-01-08\n\n### Changed\n\n### 🌐 Website\n\n- update postgresql json to add post install password setup [@rdiazlugo](https://github.com/rdiazlugo) ([#1318](https://github.com/community-scripts/ProxmoxVE/pull/1318))\n\n### 🧰 Maintenance\n\n- fix(ci): formatting event & chmod +x [@se-bastiaan](https://github.com/se-bastiaan) ([#1335](https://github.com/community-scripts/ProxmoxVE/pull/1335))\n- fix: correctly handle pull_request_target event [@se-bastiaan](https://github.com/se-bastiaan) ([#1327](https://github.com/community-scripts/ProxmoxVE/pull/1327))\n\n## 2025-01-07\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix: Folder-Check for Updatescript Zammad [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1309](https://github.com/community-scripts/ProxmoxVE/pull/1309))\n\n### 🧰 Maintenance\n\n- fix: permissions of validate pipelines [@se-bastiaan](https://github.com/se-bastiaan) ([#1316](https://github.com/community-scripts/ProxmoxVE/pull/1316))\n- Set Execution Rights for GH-Action: Validate Scripts [@MickLesk](https://github.com/MickLesk) ([#1312](https://github.com/community-scripts/ProxmoxVE/pull/1312))\n\n## 2025-01-06\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Typesense [@tlissak](https://github.com/tlissak) ([#1291](https://github.com/community-scripts/ProxmoxVE/pull/1291))\n- New script: GLPI [@opastorello](https://github.com/opastorello) ([#1201](https://github.com/community-scripts/ProxmoxVE/pull/1201))\n\n### 🚀 Updated Scripts\n\n- Fix Tag in HyperHDR Script [@MickLesk](https://github.com/MickLesk) ([#1299](https://github.com/community-scripts/ProxmoxVE/pull/1299))\n- [Fix]: Fixed rm Bug in pf2etools [@MickLesk](https://github.com/MickLesk) ([#1292](https://github.com/community-scripts/ProxmoxVE/pull/1292))\n- Fix: Homebox Update Script  [@MickLesk](https://github.com/MickLesk) ([#1284](https://github.com/community-scripts/ProxmoxVE/pull/1284))\n- Add ca-certificates for Install (Frigate) [@MickLesk](https://github.com/MickLesk) ([#1282](https://github.com/community-scripts/ProxmoxVE/pull/1282))\n- fix: buffer from base64 in formatting pipeline [@se-bastiaan](https://github.com/se-bastiaan) ([#1285](https://github.com/community-scripts/ProxmoxVE/pull/1285))\n\n### 🧰 Maintenance\n\n- Add reapproval of Changelog-PR [@MickLesk](https://github.com/MickLesk) ([#1279](https://github.com/community-scripts/ProxmoxVE/pull/1279))\n- ci: combine header checks into workflow with PR comment [@se-bastiaan](https://github.com/se-bastiaan) ([#1257](https://github.com/community-scripts/ProxmoxVE/pull/1257))\n- ci: change filename checks into steps with PR comment [@se-bastiaan](https://github.com/se-bastiaan) ([#1255](https://github.com/community-scripts/ProxmoxVE/pull/1255))\n- ci: add pipeline for code formatting checks [@se-bastiaan](https://github.com/se-bastiaan) ([#1239](https://github.com/community-scripts/ProxmoxVE/pull/1239))\n\n## 2025-01-05\n\n### Changed\n\n### 💥 Breaking Changes\n\n- [Breaking] Update Zigbee2mqtt to v.2.0.0 (Read PR Description) [@MickLesk](https://github.com/MickLesk) ([#1221](https://github.com/community-scripts/ProxmoxVE/pull/1221))\n\n### ❔ Unlabelled\n\n- Add RAM and Disk units [@oOStroudyOo](https://github.com/oOStroudyOo) ([#1261](https://github.com/community-scripts/ProxmoxVE/pull/1261))\n\n## 2025-01-04\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Fix gpg key pf2tools & 5etools [@MickLesk](https://github.com/MickLesk) ([#1242](https://github.com/community-scripts/ProxmoxVE/pull/1242))\n- Homarr: Fix missing curl dependency [@MickLesk](https://github.com/MickLesk) ([#1238](https://github.com/community-scripts/ProxmoxVE/pull/1238))\n- Homeassistan Core: Fix Python3 and add missing dependencies [@MickLesk](https://github.com/MickLesk) ([#1236](https://github.com/community-scripts/ProxmoxVE/pull/1236))\n- Fix: Update Python for HomeAssistant [@MickLesk](https://github.com/MickLesk) ([#1227](https://github.com/community-scripts/ProxmoxVE/pull/1227))\n- OneDev: Add git-lfs [@MickLesk](https://github.com/MickLesk) ([#1225](https://github.com/community-scripts/ProxmoxVE/pull/1225))\n- Pf2eTools & 5eTools: Fixing npm build [@TheRealVira](https://github.com/TheRealVira) ([#1213](https://github.com/community-scripts/ProxmoxVE/pull/1213))\n\n### 🌐 Website\n\n- Bump next from 15.0.2 to 15.1.3 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#1212](https://github.com/community-scripts/ProxmoxVE/pull/1212))\n\n### 🧰 Maintenance\n\n- [GitHub Action] Add filename case check [@quantumryuu](https://github.com/quantumryuu) ([#1228](https://github.com/community-scripts/ProxmoxVE/pull/1228))\n\n## 2025-01-03\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Improve Homarr Installation [@MickLesk](https://github.com/MickLesk) ([#1208](https://github.com/community-scripts/ProxmoxVE/pull/1208))\n- Fix: Zabbix-Update Script [@MickLesk](https://github.com/MickLesk) ([#1205](https://github.com/community-scripts/ProxmoxVE/pull/1205))\n- Update Script: Lazylibrarian [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1190](https://github.com/community-scripts/ProxmoxVE/pull/1190))\n- Fix: Memos update function [@MickLesk](https://github.com/MickLesk) ([#1207](https://github.com/community-scripts/ProxmoxVE/pull/1207))\n- Keep Lubelogger data after update to a new version [@JcMinarro](https://github.com/JcMinarro) ([#1200](https://github.com/community-scripts/ProxmoxVE/pull/1200))\n\n### 🌐 Website\n\n- Update Nextcloud-LXC JSON [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1191](https://github.com/community-scripts/ProxmoxVE/pull/1191))\n\n### 🧰 Maintenance\n\n- Github action to check metadata lines in scripts. [@quantumryuu](https://github.com/quantumryuu) ([#1110](https://github.com/community-scripts/ProxmoxVE/pull/1110))\n\n## 2025-01-02\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Pf2eTools [@TheRealVira](https://github.com/TheRealVira) ([#1162](https://github.com/community-scripts/ProxmoxVE/pull/1162))\n- New Script: 5etools [@TheRealVira](https://github.com/TheRealVira) ([#1157](https://github.com/community-scripts/ProxmoxVE/pull/1157))\n\n### 🚀 Updated Scripts\n\n- Update config template in blocky-install.sh [@xFichtl1](https://github.com/xFichtl1) ([#1059](https://github.com/community-scripts/ProxmoxVE/pull/1059))\n\n## 2025-01-01\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Komodo [@MickLesk](https://github.com/MickLesk) ([#1167](https://github.com/community-scripts/ProxmoxVE/pull/1167))\n- New Script: Firefly [@quantumryuu](https://github.com/quantumryuu) ([#616](https://github.com/community-scripts/ProxmoxVE/pull/616))\n- New Script: Semaphore [@quantumryuu](https://github.com/quantumryuu) ([#596](https://github.com/community-scripts/ProxmoxVE/pull/596))\n\n### 🚀 Updated Scripts\n\n- Fix Script Homepage: add version during build step [@se-bastiaan](https://github.com/se-bastiaan) ([#1155](https://github.com/community-scripts/ProxmoxVE/pull/1155))\n- Happy new Year! Update Copyright to 2025 [@MickLesk](https://github.com/MickLesk) ([#1150](https://github.com/community-scripts/ProxmoxVE/pull/1150))\n- Update Kernel-Clean to new Version & Bugfixing [@MickLesk](https://github.com/MickLesk) ([#1147](https://github.com/community-scripts/ProxmoxVE/pull/1147))\n- Fix chromium installation for ArchiveBox  [@tkunzfeld](https://github.com/tkunzfeld) ([#1140](https://github.com/community-scripts/ProxmoxVE/pull/1140))\n\n### 🌐 Website\n\n- Fix Category of Semaphore [@MickLesk](https://github.com/MickLesk) ([#1148](https://github.com/community-scripts/ProxmoxVE/pull/1148))\n\n### 🧰 Maintenance\n\n- Correctly check for changed files in Shellcheck workflow [@se-bastiaan](https://github.com/se-bastiaan) ([#1156](https://github.com/community-scripts/ProxmoxVE/pull/1156))\n\n## 2024-12-31 - Happy new Year! 🎉✨\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Add ExecReload to prometheus.service [@BasixKOR](https://github.com/BasixKOR) ([#1131](https://github.com/community-scripts/ProxmoxVE/pull/1131))\n- Fix: Figlet Version & Font Check [@MickLesk](https://github.com/MickLesk) ([#1133](https://github.com/community-scripts/ProxmoxVE/pull/1133))\n\n### 🚀 Updated Scripts\n\n- Fix: Copy issue after update in Bookstack LXC [@MickLesk](https://github.com/MickLesk) ([#1137](https://github.com/community-scripts/ProxmoxVE/pull/1137))\n- Omada: Switch Base-URL to prevent issues [@MickLesk](https://github.com/MickLesk) ([#1135](https://github.com/community-scripts/ProxmoxVE/pull/1135))\n- fix: guacd service not start during Apache-Guacamole script installation process [@PhoenixEmik](https://github.com/PhoenixEmik) ([#1122](https://github.com/community-scripts/ProxmoxVE/pull/1122))\n- Fix Homepage-Script: Installation/Update [@MickLesk](https://github.com/MickLesk) ([#1129](https://github.com/community-scripts/ProxmoxVE/pull/1129))\n- Netbox: Updating URL to https [@surajsbmn](https://github.com/surajsbmn) ([#1124](https://github.com/community-scripts/ProxmoxVE/pull/1124))\n"
  },
  {
    "path": ".github/changelogs/2025/02.md",
    "content": "﻿## 2025-02-28\n\n### 🧰 Maintenance\n\n  - #### ✨ New Features\n\n    - Shell Format Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2400](https://github.com/community-scripts/ProxmoxVE/pull/2400))\n\n  - #### 📂 Github\n\n    - Update all Action to new selfhosted Runner Cluster [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2739](https://github.com/community-scripts/ProxmoxVE/pull/2739))\n    - Update Script Test Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2741](https://github.com/community-scripts/ProxmoxVE/pull/2741))\n\n## 2025-02-27\n\n### 🆕 New Scripts\n\n  - web-check [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2662](https://github.com/community-scripts/ProxmoxVE/pull/2662))\n- Pelican Panel [@bvdberg01](https://github.com/bvdberg01) ([#2678](https://github.com/community-scripts/ProxmoxVE/pull/2678))\n- Pelican Wings [@bvdberg01](https://github.com/bvdberg01) ([#2677](https://github.com/community-scripts/ProxmoxVE/pull/2677))\n- ByteStash [@tremor021](https://github.com/tremor021) ([#2680](https://github.com/community-scripts/ProxmoxVE/pull/2680))\n\n### 🚀 Updated Scripts\n\n  - ByteStash: Removed sed, app supports Node v22 now [@tremor021](https://github.com/tremor021) ([#2728](https://github.com/community-scripts/ProxmoxVE/pull/2728))\n- Keycloak: Update installation script [@tremor021](https://github.com/tremor021) ([#2714](https://github.com/community-scripts/ProxmoxVE/pull/2714))\n- ByteStash: Fix Node 22 compatibility (thanks t2lc) [@tremor021](https://github.com/tremor021) ([#2705](https://github.com/community-scripts/ProxmoxVE/pull/2705))\n\n  - #### 🐞 Bug Fixes\n\n    - EOF not detected [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2726](https://github.com/community-scripts/ProxmoxVE/pull/2726))\n    - Zitadel-install.sh: Remove one version file and update to our standard [@bvdberg01](https://github.com/bvdberg01) ([#2710](https://github.com/community-scripts/ProxmoxVE/pull/2710))\n    - Outline: Change key to hex32 [@tremor021](https://github.com/tremor021) ([#2709](https://github.com/community-scripts/ProxmoxVE/pull/2709))\n    - Typo in update scripts [@bvdberg01](https://github.com/bvdberg01) ([#2707](https://github.com/community-scripts/ProxmoxVE/pull/2707))\n    - SFTPGo Remove unneeded RELEASE variable [@MickLesk](https://github.com/MickLesk) ([#2683](https://github.com/community-scripts/ProxmoxVE/pull/2683))\n\n### 🧰 Maintenance\n\n  - #### 🐞 Bug Fixes\n\n    - Update install.func: Change Line Number for Error message. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2690](https://github.com/community-scripts/ProxmoxVE/pull/2690))\n\n  - #### 📂 Github\n\n    - New Workflow to close Script Request Discussions on PR merge [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2688](https://github.com/community-scripts/ProxmoxVE/pull/2688))\n    - Improve Script-Test Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2712](https://github.com/community-scripts/ProxmoxVE/pull/2712))\n    - Switch all actions to self-hosted Runners [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2711](https://github.com/community-scripts/ProxmoxVE/pull/2711))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Use HTML button element for copying to clipboard [@scallaway](https://github.com/scallaway) ([#2720](https://github.com/community-scripts/ProxmoxVE/pull/2720))\n    - Add basic pagination to Data Viewer [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2715](https://github.com/community-scripts/ProxmoxVE/pull/2715))\n\n  - #### 📝 Script Information\n\n    - wger - Add HTTPS instructions to the website [@tremor021](https://github.com/tremor021) ([#2695](https://github.com/community-scripts/ProxmoxVE/pull/2695))\n\n## 2025-02-26\n\n### 🆕 New Scripts\n\n  - New Script: Outline [@tremor021](https://github.com/tremor021) ([#2653](https://github.com/community-scripts/ProxmoxVE/pull/2653))\n\n### 🚀 Updated Scripts\n\n  - Fix: SABnzbd - Removed few artefacts in the code preventing the update [@tremor021](https://github.com/tremor021) ([#2670](https://github.com/community-scripts/ProxmoxVE/pull/2670))\n\n  - #### 🐞 Bug Fixes\n\n    - Fix: Homarr - Manually correct db-migration wrong-folder [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2676](https://github.com/community-scripts/ProxmoxVE/pull/2676))\n    - Kimai: add local.yaml & fix path permissions [@MickLesk](https://github.com/MickLesk) ([#2646](https://github.com/community-scripts/ProxmoxVE/pull/2646))\n    - PiHole: Fix Unbound sed for DNS [@MickLesk](https://github.com/MickLesk) ([#2647](https://github.com/community-scripts/ProxmoxVE/pull/2647))\n    - Alpine IT-Tools fix typo \"unexpected EOF while looking for matching `\"' [@MickLesk](https://github.com/MickLesk) ([#2644](https://github.com/community-scripts/ProxmoxVE/pull/2644))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - [gh] Furhter Impove Changelog Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2655](https://github.com/community-scripts/ProxmoxVE/pull/2655))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Website: PocketID Change of website and documentation links [@schneider-de-com](https://github.com/schneider-de-com) ([#2643](https://github.com/community-scripts/ProxmoxVE/pull/2643))\n\n  - #### 📝 Script Information\n\n    - Fix: Graylog - Improve application description for website [@tremor021](https://github.com/tremor021) ([#2658](https://github.com/community-scripts/ProxmoxVE/pull/2658))\n\n## 2025-02-25\n\n### Changes\n\n### ✨ New Features\n\n- Update Tailscale: Add Tag when installation is finished [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2633](https://github.com/community-scripts/ProxmoxVE/pull/2633))\n\n### 🚀 Updated Scripts\n\n  #### 🐞 Bug Fixes\n\n  - Fix Omada installer [@JcMinarro](https://github.com/JcMinarro) ([#2625](https://github.com/community-scripts/ProxmoxVE/pull/2625))\n\n### 🌐 Website\n\n- Update Tailscale-lxc Json: Add message for Supported OS [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2629](https://github.com/community-scripts/ProxmoxVE/pull/2629))\n\n### 🧰 Maintenance\n\n- [gh] Updated Changelog Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2632](https://github.com/community-scripts/ProxmoxVE/pull/2632))\n\n## 2025-02-24\n\n### Changes\n\n### 🆕 New Scripts\n\n- New Script: wger [@tremor021](https://github.com/tremor021) ([#2574](https://github.com/community-scripts/ProxmoxVE/pull/2574))\n- New Script: VictoriaMetrics [@tremor021](https://github.com/tremor021) ([#2565](https://github.com/community-scripts/ProxmoxVE/pull/2565))\n- New Script: Authelia [@thost96](https://github.com/thost96) ([#2060](https://github.com/community-scripts/ProxmoxVE/pull/2060))\n- New Script: Jupyter Notebook [@Dave-code-creater](https://github.com/Dave-code-creater) ([#2561](https://github.com/community-scripts/ProxmoxVE/pull/2561))\n\n### 🐞 Bug Fixes\n\n- Fix Docmost: default upload size and saving data when updating [@bvdberg01](https://github.com/bvdberg01) ([#2598](https://github.com/community-scripts/ProxmoxVE/pull/2598))\n- Fix: homarr db migration [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2575](https://github.com/community-scripts/ProxmoxVE/pull/2575))\n- Fix: Wireguard - Restart wgdashboard automatically after update [@LostALice](https://github.com/LostALice) ([#2587](https://github.com/community-scripts/ProxmoxVE/pull/2587))\n- Fix: Authelia Unbound Variable Argon2id [@MickLesk](https://github.com/MickLesk) ([#2604](https://github.com/community-scripts/ProxmoxVE/pull/2604))\n- Fix: Omada check for AVX Support and use the correct MongoDB Version [@MickLesk](https://github.com/MickLesk) ([#2600](https://github.com/community-scripts/ProxmoxVE/pull/2600))\n- Fix: Update-Script Firefly III based on their docs [@MickLesk](https://github.com/MickLesk) ([#2534](https://github.com/community-scripts/ProxmoxVE/pull/2534))\n\n### ✨ New Features\n\n- Feature: Template-Check, Better Handling of Downloads, Better Network… [@MickLesk](https://github.com/MickLesk) ([#2592](https://github.com/community-scripts/ProxmoxVE/pull/2592))\n- Feature: Possibility to perform updates in silent / verbose (+ logging) [@MickLesk](https://github.com/MickLesk) ([#2583](https://github.com/community-scripts/ProxmoxVE/pull/2583))\n- Feature: Use Verbose Mode for all Scripts (removed &>/dev/null) [@MickLesk](https://github.com/MickLesk) ([#2596](https://github.com/community-scripts/ProxmoxVE/pull/2596))\n\n### 🌐 Website\n\n- Fix: Authelia - Make user enter their domain manually [@tremor021](https://github.com/tremor021) ([#2618](https://github.com/community-scripts/ProxmoxVE/pull/2618))\n- Website: Change Info for PiHole Password [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2602](https://github.com/community-scripts/ProxmoxVE/pull/2602))\n- Fix: Jupyter Json (missing logo & improve name on website) [@MickLesk](https://github.com/MickLesk) ([#2584](https://github.com/community-scripts/ProxmoxVE/pull/2584))\n\n### 🧰 Maintenance\n\n- [gh] Update Script Test Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2599](https://github.com/community-scripts/ProxmoxVE/pull/2599))\n- [gh] Contributor-Guide: Update AppName.md & AppName.sh [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2603](https://github.com/community-scripts/ProxmoxVE/pull/2603))\n\n## 2025-02-23\n\n### Changes\n\n### 🆕 New Scripts\n\n- New Script: Hev socks5 server [@miviro](https://github.com/miviro) ([#2454](https://github.com/community-scripts/ProxmoxVE/pull/2454))\n- New Script: bolt.diy [@tremor021](https://github.com/tremor021) ([#2528](https://github.com/community-scripts/ProxmoxVE/pull/2528))\n\n### 🚀 Updated Scripts\n\n- Fix: Wireguard - Remove setting NAT as its already in PostUp/Down [@tremor021](https://github.com/tremor021) ([#2510](https://github.com/community-scripts/ProxmoxVE/pull/2510))\n\n### 🌐 Website\n\n- Fix: Home Assistant Core - fixed wrong text in application description on website [@TMigue](https://github.com/TMigue) ([#2576](https://github.com/community-scripts/ProxmoxVE/pull/2576))\n\n## 2025-02-22\n\n### Changes\n\n### 🌐 Website\n\n- Fix a few broken icon links [@Snarkenfaugister](https://github.com/Snarkenfaugister) ([#2548](https://github.com/community-scripts/ProxmoxVE/pull/2548))\n\n### 🧰 Maintenance\n\n- Fix: URL's in CONTRIBUTING.md [@bvdberg01](https://github.com/bvdberg01) ([#2552](https://github.com/community-scripts/ProxmoxVE/pull/2552))\n\n## 2025-02-21\n\n### Changes\n\n### 🚀 Updated Scripts\n\n- Add ZFS to Podman. Now it works on ZFS! [@jaminmc](https://github.com/jaminmc) ([#2526](https://github.com/community-scripts/ProxmoxVE/pull/2526))\n- Fix: Tianji - Downgrade Node [@MickLesk](https://github.com/MickLesk) ([#2530](https://github.com/community-scripts/ProxmoxVE/pull/2530))\n\n### 🧰 Maintenance\n\n- [gh] General Cleanup & Moving Files / Folders [@MickLesk](https://github.com/MickLesk) ([#2532](https://github.com/community-scripts/ProxmoxVE/pull/2532))\n\n## 2025-02-20\n\n### Changes\n\n### 💥 Breaking Changes\n\n- Breaking: Actual Budget Script (HTTPS / DB Migration / New Structure) - Read Description [@MickLesk](https://github.com/MickLesk) ([#2496](https://github.com/community-scripts/ProxmoxVE/pull/2496))\n- Pihole & Unbound: Installation for Pihole V6 (read description) [@MickLesk](https://github.com/MickLesk) ([#2505](https://github.com/community-scripts/ProxmoxVE/pull/2505))\n\n### ✨ New Scripts\n\n- New Script: Dolibarr [@tremor021](https://github.com/tremor021) ([#2502](https://github.com/community-scripts/ProxmoxVE/pull/2502))\n\n### 🚀 Updated Scripts\n\n- Fix: Pingvin Share - Update not copying to correct directory [@tremor021](https://github.com/tremor021) ([#2521](https://github.com/community-scripts/ProxmoxVE/pull/2521))\n- WikiJS: Prepare for Using PostgreSQL [@MickLesk](https://github.com/MickLesk) ([#2516](https://github.com/community-scripts/ProxmoxVE/pull/2516))\n\n### 🧰 Maintenance\n\n- [gh] better handling of labels [@MickLesk](https://github.com/MickLesk) ([#2517](https://github.com/community-scripts/ProxmoxVE/pull/2517))\n\n## 2025-02-19\n\n### Changes\n\n### 🚀 Updated Scripts\n\n- Fix: file replacement in Watcharr Update Script [@Clusters](https://github.com/Clusters) ([#2498](https://github.com/community-scripts/ProxmoxVE/pull/2498))\n- Fix: Kometa - fixed successful setup message and added info to json [@tremor021](https://github.com/tremor021) ([#2495](https://github.com/community-scripts/ProxmoxVE/pull/2495))\n- Fix: Actual Budget, add missing .env when updating [@MickLesk](https://github.com/MickLesk) ([#2494](https://github.com/community-scripts/ProxmoxVE/pull/2494))\n\n## 2025-02-18\n\n### Changes\n\n### ✨ New Scripts\n\n- New Script: Docmost [@MickLesk](https://github.com/MickLesk) ([#2472](https://github.com/community-scripts/ProxmoxVE/pull/2472))\n\n### 🚀 Updated Scripts\n\n- Fix: SQL Server 2022 | GPG & Install [@MickLesk](https://github.com/MickLesk) ([#2476](https://github.com/community-scripts/ProxmoxVE/pull/2476))\n- Feature: PBS Bare Metal Installation - Allow Microcode [@MickLesk](https://github.com/MickLesk) ([#2477](https://github.com/community-scripts/ProxmoxVE/pull/2477))\n- Fix: MagicMirror force Node version and fix backups [@tremor021](https://github.com/tremor021) ([#2468](https://github.com/community-scripts/ProxmoxVE/pull/2468))\n- Update BunkerWeb scripts to latest NGINX and specs [@TheophileDiot](https://github.com/TheophileDiot) ([#2466](https://github.com/community-scripts/ProxmoxVE/pull/2466))\n\n## 2025-02-17\n\n### Changes\n\n### 💥 Breaking Changes\n\n- Zipline: Prepare for Version 4.0.0 [@MickLesk](https://github.com/MickLesk) ([#2455](https://github.com/community-scripts/ProxmoxVE/pull/2455))\n\n### 🚀 Updated Scripts\n\n- Fix: Zipline increase SECRET to 42 chars [@V1d1o7](https://github.com/V1d1o7) ([#2444](https://github.com/community-scripts/ProxmoxVE/pull/2444))\n\n## 2025-02-16\n\n### Changes\n\n### 🚀 Updated Scripts\n\n- Fix: Typo in Ubuntu 24.10 VM Script [@PhoenixEmik](https://github.com/PhoenixEmik) ([#2430](https://github.com/community-scripts/ProxmoxVE/pull/2430))\n- Fix: Grist update no longer removes previous user data [@cfurrow](https://github.com/cfurrow) ([#2428](https://github.com/community-scripts/ProxmoxVE/pull/2428))\n\n### 🌐 Website\n\n- Debian icon update [@bannert1337](https://github.com/bannert1337) ([#2433](https://github.com/community-scripts/ProxmoxVE/pull/2433))\n- Update Graylog icon [@bannert1337](https://github.com/bannert1337) ([#2434](https://github.com/community-scripts/ProxmoxVE/pull/2434))\n\n## 2025-02-15\n\n### Changes\n\n### 🚀 Updated Scripts\n\n- Setup cron in install/freshrss-install.sh [@zimmra](https://github.com/zimmra) ([#2412](https://github.com/community-scripts/ProxmoxVE/pull/2412))\n- Fix: Homarr update service files [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2416](https://github.com/community-scripts/ProxmoxVE/pull/2416))\n- Update MagicMirror install and update scripts [@tremor021](https://github.com/tremor021) ([#2409](https://github.com/community-scripts/ProxmoxVE/pull/2409))\n\n### 🌐 Website\n\n- Fix RustDesk slug in json [@tremor021](https://github.com/tremor021) ([#2411](https://github.com/community-scripts/ProxmoxVE/pull/2411))\n\n### 🧰 Maintenance\n\n- [GH] Update script-test Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2415](https://github.com/community-scripts/ProxmoxVE/pull/2415))\n\n## 2025-02-14\n\n### Changes\n\n### 🚀 Updated Scripts\n\n- Fix homarr [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2369](https://github.com/community-scripts/ProxmoxVE/pull/2369))\n\n### 🌐 Website\n\n- RustDesk Server - Added configuration guide to json [@tremor021](https://github.com/tremor021) ([#2389](https://github.com/community-scripts/ProxmoxVE/pull/2389))\n\n### 🧰 Maintenance\n\n- [gh] Update script-test.yml [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2399](https://github.com/community-scripts/ProxmoxVE/pull/2399))\n- [gh] Introducing new Issue Github Template Feature (Bug, Feature, Task) [@MickLesk](https://github.com/MickLesk) ([#2394](https://github.com/community-scripts/ProxmoxVE/pull/2394))\n\n### 📡 API\n\n- [API]Add more enpoints to API [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2390](https://github.com/community-scripts/ProxmoxVE/pull/2390))\n- [API] Update api.func: Remove unwanted file creation [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2378](https://github.com/community-scripts/ProxmoxVE/pull/2378))\n\n## 2025-02-13\n\n### Changes\n\n### ✨ New Scripts\n\n- Re-Add: Pf2eTools [@MickLesk](https://github.com/MickLesk) ([#2336](https://github.com/community-scripts/ProxmoxVE/pull/2336))\n- New Script: Nx Witness [@MickLesk](https://github.com/MickLesk) ([#2350](https://github.com/community-scripts/ProxmoxVE/pull/2350))\n- New Script: RustDesk Server [@tremor021](https://github.com/tremor021) ([#2326](https://github.com/community-scripts/ProxmoxVE/pull/2326))\n- New Script: MinIO [@MickLesk](https://github.com/MickLesk) ([#2333](https://github.com/community-scripts/ProxmoxVE/pull/2333))\n\n### 🚀 Updated Scripts\n\n- Missing \";\" in ct/trilium.sh [@Scorpoon](https://github.com/Scorpoon) ([#2380](https://github.com/community-scripts/ProxmoxVE/pull/2380))\n- Fix: Element Synapse - Fixed server listening on both localhost and 0.0.0.0 [@tremor021](https://github.com/tremor021) ([#2376](https://github.com/community-scripts/ProxmoxVE/pull/2376))\n- [core] cleanup (remove # App Default Values) [@MickLesk](https://github.com/MickLesk) ([#2356](https://github.com/community-scripts/ProxmoxVE/pull/2356))\n- Fix: Kometa - Increase RAM and HDD resources [@tremor021](https://github.com/tremor021) ([#2367](https://github.com/community-scripts/ProxmoxVE/pull/2367))\n- [core] cleanup (remove base_settings & unneeded comments) [@MickLesk](https://github.com/MickLesk) ([#2351](https://github.com/community-scripts/ProxmoxVE/pull/2351))\n- Fix: Authentik Embedded Outpost Upgrade [@vidonnus](https://github.com/vidonnus) ([#2327](https://github.com/community-scripts/ProxmoxVE/pull/2327))\n- Fix HomeAsisstant LXC: Use the latest versions of runlike with --use-volume-id [@genehand](https://github.com/genehand) ([#2325](https://github.com/community-scripts/ProxmoxVE/pull/2325))\n\n### 🌐 Website\n\n- Fix: Zoraxy - now shows application as updateable on the website [@tremor021](https://github.com/tremor021) ([#2352](https://github.com/community-scripts/ProxmoxVE/pull/2352))\n- Fix script category name text alignment in ScriptAccordion [@BramSuurdje](https://github.com/BramSuurdje) ([#2342](https://github.com/community-scripts/ProxmoxVE/pull/2342))\n\n### 🧰 Maintenance\n\n- [gh] Remove unwanted output from script test workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2337](https://github.com/community-scripts/ProxmoxVE/pull/2337))\n- [gh] Workflow to change date on new json files [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2319](https://github.com/community-scripts/ProxmoxVE/pull/2319))\n\n## 2025-02-12\n\n### Changes\n\n### 💥 Breaking Changes\n\n- Frigate: Use Fixed Version 14 [@MickLesk](https://github.com/MickLesk) ([#2288](https://github.com/community-scripts/ProxmoxVE/pull/2288))\n\n### ✨ New Scripts\n\n- New Script: Kometa [@tremor021](https://github.com/tremor021) ([#2281](https://github.com/community-scripts/ProxmoxVE/pull/2281))\n- New Script: Excalidraw [@tremor021](https://github.com/tremor021) ([#2285](https://github.com/community-scripts/ProxmoxVE/pull/2285))\n- New Script: Graylog [@tremor021](https://github.com/tremor021) ([#2270](https://github.com/community-scripts/ProxmoxVE/pull/2270))\n- New Script: TasmoCompiler [@tremor021](https://github.com/tremor021) ([#2235](https://github.com/community-scripts/ProxmoxVE/pull/2235))\n- New script: cross-seed [@jmatraszek](https://github.com/jmatraszek) ([#2186](https://github.com/community-scripts/ProxmoxVE/pull/2186))\n\n### 🚀 Updated Scripts\n\n- FIX: Frigate - remove bad variable [@tremor021](https://github.com/tremor021) ([#2323](https://github.com/community-scripts/ProxmoxVE/pull/2323))\n- Fix: Kometa - Fix wrong web site address [@tremor021](https://github.com/tremor021) ([#2318](https://github.com/community-scripts/ProxmoxVE/pull/2318))\n- Fix: var_tags instead of TAGS in some CT's [@MickLesk](https://github.com/MickLesk) ([#2310](https://github.com/community-scripts/ProxmoxVE/pull/2310))\n- Update ubuntu2410-vm.sh: Fix typo in API call. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2305](https://github.com/community-scripts/ProxmoxVE/pull/2305))\n- Fix: Myspeed Installation (g++) [@MickLesk](https://github.com/MickLesk) ([#2308](https://github.com/community-scripts/ProxmoxVE/pull/2308))\n- Fix: Pingvin wrong variable used for version tracking [@alberanid](https://github.com/alberanid) ([#2302](https://github.com/community-scripts/ProxmoxVE/pull/2302))\n- Fix: SQL Server 2022 - remove unnecessary sudo [@tremor021](https://github.com/tremor021) ([#2282](https://github.com/community-scripts/ProxmoxVE/pull/2282))\n- fix: frigate pin version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2296](https://github.com/community-scripts/ProxmoxVE/pull/2296))\n- Fix changedetection: Correct Browser install [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2277](https://github.com/community-scripts/ProxmoxVE/pull/2277))\n- Paperless-AI: add dependency \"make\" [@MickLesk](https://github.com/MickLesk) ([#2289](https://github.com/community-scripts/ProxmoxVE/pull/2289))\n- Fix: Typo OPNsense VM [@chpego](https://github.com/chpego) ([#2291](https://github.com/community-scripts/ProxmoxVE/pull/2291))\n- Fix: CraftyControler fix java default [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2286](https://github.com/community-scripts/ProxmoxVE/pull/2286))\n\n### 🌐 Website\n\n- Fix: some jsons (debian instead Debian in OS) [@MickLesk](https://github.com/MickLesk) ([#2311](https://github.com/community-scripts/ProxmoxVE/pull/2311))\n- Website: Add After-Install Note for Ubuntu VM 22.04/24.04/24.10 and Debian VM [@MickLesk](https://github.com/MickLesk) ([#2307](https://github.com/community-scripts/ProxmoxVE/pull/2307))\n- Fix: duplicate 'VM' name in opnsense-vm.json [@nayzm](https://github.com/nayzm) ([#2293](https://github.com/community-scripts/ProxmoxVE/pull/2293))\n\n## 2025-02-11\n\n### Changes\n\n### ✨ New Scripts\n\n- New Script: Opnsense VM [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2097](https://github.com/community-scripts/ProxmoxVE/pull/2097))\n- New Script: Watcharr [@tremor021](https://github.com/tremor021) ([#2243](https://github.com/community-scripts/ProxmoxVE/pull/2243))\n- New Script: Suwayomi-Server [@tremor021](https://github.com/tremor021) ([#2139](https://github.com/community-scripts/ProxmoxVE/pull/2139))\n\n### 🚀 Updated Scripts\n\n- Fix Photoprism: Add defaults.yml for CLI Tool [@MickLesk](https://github.com/MickLesk) ([#2261](https://github.com/community-scripts/ProxmoxVE/pull/2261))\n- Update Checkmk: include Patch versions in Release grepping [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2264](https://github.com/community-scripts/ProxmoxVE/pull/2264))\n- Fix: Apache Guacamole Version Crawling - only latest Version [@MickLesk](https://github.com/MickLesk) ([#2258](https://github.com/community-scripts/ProxmoxVE/pull/2258))\n\n### 🌐 Website\n\n- Update Komodo icon [@bannert1337](https://github.com/bannert1337) ([#2263](https://github.com/community-scripts/ProxmoxVE/pull/2263))\n\n### 🧰 Maintenance\n\n- Add Workflow to test Scripts [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2269](https://github.com/community-scripts/ProxmoxVE/pull/2269))\n\n## 2025-02-10\n\n### Changes\n\n### 💥 Breaking Changes\n\n- [Fix] Filebrowser - Add Static Path for DB [@MickLesk](https://github.com/MickLesk) ([#2207](https://github.com/community-scripts/ProxmoxVE/pull/2207))\n\n### ✨ New Scripts\n\n- New Script: Prometheus Paperless-NGX Exporter [@andygrunwald](https://github.com/andygrunwald) ([#2153](https://github.com/community-scripts/ProxmoxVE/pull/2153))\n- New Script: Proxmox Mail Gateway [@thost96](https://github.com/thost96) ([#1906](https://github.com/community-scripts/ProxmoxVE/pull/1906))\n- New Script: FreshRSS [@bvdberg01](https://github.com/bvdberg01) ([#2226](https://github.com/community-scripts/ProxmoxVE/pull/2226))\n- New Script: Zitadel [@dave-yap](https://github.com/dave-yap) ([#2141](https://github.com/community-scripts/ProxmoxVE/pull/2141))\n\n### 🚀 Updated Scripts\n\n- Feature: Automatic Deletion of choosen LXC's (lxc-delete.sh) [@MickLesk](https://github.com/MickLesk) ([#2228](https://github.com/community-scripts/ProxmoxVE/pull/2228))\n- Quickfix: Crafty-Controller remove unnecessary \\ [@MickLesk](https://github.com/MickLesk) ([#2233](https://github.com/community-scripts/ProxmoxVE/pull/2233))\n- Fix: Crafty-Controller java versions and set default [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2199](https://github.com/community-scripts/ProxmoxVE/pull/2199))\n- Feature: Add optional Port for Filebrowser [@MickLesk](https://github.com/MickLesk) ([#2224](https://github.com/community-scripts/ProxmoxVE/pull/2224))\n- [core] Prevent double spinner [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2203](https://github.com/community-scripts/ProxmoxVE/pull/2203))\n\n### 🌐 Website\n\n- Website: Fix Zitadel Logo & Created-Date [@MickLesk](https://github.com/MickLesk) ([#2217](https://github.com/community-scripts/ProxmoxVE/pull/2217))\n- Fixed URL typo zerotier-one.json [@Divaksh](https://github.com/Divaksh) ([#2206](https://github.com/community-scripts/ProxmoxVE/pull/2206))\n- evcc.json Clarify the config file location [@mvdw](https://github.com/mvdw) ([#2193](https://github.com/community-scripts/ProxmoxVE/pull/2193))\n\n### 🧰 Maintenance\n\n- [gh]: Improve Workflows, Templates, Handling [@MickLesk](https://github.com/MickLesk) ([#2214](https://github.com/community-scripts/ProxmoxVE/pull/2214))\n- [core] Fix app-header workflow and add API [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2204](https://github.com/community-scripts/ProxmoxVE/pull/2204))\n- Fix: \"read -p\" does not support color formatting [@PhoenixEmik](https://github.com/PhoenixEmik) ([#2191](https://github.com/community-scripts/ProxmoxVE/pull/2191))\n- [API] Add API to vms [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2021](https://github.com/community-scripts/ProxmoxVE/pull/2021))\n\n## 2025-02-09\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: pbs_microcode.sh [@DonPablo1010](https://github.com/DonPablo1010) ([#2166](https://github.com/community-scripts/ProxmoxVE/pull/2166))\n\n### 🚀 Updated Scripts\n\n- Keep the same hass_config volume for Home Assistant [@genehand](https://github.com/genehand) ([#2160](https://github.com/community-scripts/ProxmoxVE/pull/2160))\n\n### 🌐 Website\n\n- Website: Set new Logo for Paperless-AI [@MickLesk](https://github.com/MickLesk) ([#2194](https://github.com/community-scripts/ProxmoxVE/pull/2194))\n- Fix: Barcode Buddy Logo & Title [@MickLesk](https://github.com/MickLesk) ([#2183](https://github.com/community-scripts/ProxmoxVE/pull/2183))\n\n## 2025-02-08\n\n### Changed\n\n### ✨ New Scripts\n\n- New script: Barcode Buddy [@bvdberg01](https://github.com/bvdberg01) ([#2167](https://github.com/community-scripts/ProxmoxVE/pull/2167))\n\n### 🚀 Updated Scripts\n\n- Fix: Actualbudget - salvage the `.migrate` file when upgrading [@bourquep](https://github.com/bourquep) ([#2173](https://github.com/community-scripts/ProxmoxVE/pull/2173))\n\n### 🌐 Website\n\n- Update cosmos.json description [@BramSuurdje](https://github.com/BramSuurdje) ([#2162](https://github.com/community-scripts/ProxmoxVE/pull/2162))\n\n### 🧰 Maintenance\n\n- fix typos in CONTRIBUTOR_GUIDE [@thomashondema](https://github.com/thomashondema) ([#2174](https://github.com/community-scripts/ProxmoxVE/pull/2174))\n\n## 2025-02-07 - 10.000 ⭐\n\n### Changed\n\n### 💥 Breaking Changes\n\n- [core]: Enhance LXC template handling and improve error recovery [@MickLesk](https://github.com/MickLesk) ([#2128](https://github.com/community-scripts/ProxmoxVE/pull/2128))\n\n### ✨ New Scripts\n\n- New Script: Cosmos [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2120](https://github.com/community-scripts/ProxmoxVE/pull/2120))\n- New Script: SearXNG [@MickLesk](https://github.com/MickLesk) ([#2123](https://github.com/community-scripts/ProxmoxVE/pull/2123))\n\n### 🚀 Updated Scripts\n\n- Fix: Trillium Update Function & Harmonize Installation [@MickLesk](https://github.com/MickLesk) ([#2148](https://github.com/community-scripts/ProxmoxVE/pull/2148))\n- Fix: Zerotier-One fixed missing dependency [@tremor021](https://github.com/tremor021) ([#2147](https://github.com/community-scripts/ProxmoxVE/pull/2147))\n- Fix: Openwrt Version checking [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2137](https://github.com/community-scripts/ProxmoxVE/pull/2137))\n- Fix: PeaNUT Increase HDD & RAM Size [@MickLesk](https://github.com/MickLesk) ([#2127](https://github.com/community-scripts/ProxmoxVE/pull/2127))\n\n### 🌐 Website\n\n- Fix: Zerotier json had a bad script path [@tremor021](https://github.com/tremor021) ([#2144](https://github.com/community-scripts/ProxmoxVE/pull/2144))\n- Fix: Cosmos logo doesnt display on website [@MickLesk](https://github.com/MickLesk) ([#2132](https://github.com/community-scripts/ProxmoxVE/pull/2132))\n- Fix JSON-Editor [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2121](https://github.com/community-scripts/ProxmoxVE/pull/2121))\n\n### 🧰 Maintenance\n\n- [gh]: Following the trend - add star-history in readme [@MickLesk](https://github.com/MickLesk) ([#2135](https://github.com/community-scripts/ProxmoxVE/pull/2135))\n\n## 2025-02-06\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Duplicati [@tremor021](https://github.com/tremor021) ([#2052](https://github.com/community-scripts/ProxmoxVE/pull/2052))\n- New Script: Paperless-AI [@MickLesk](https://github.com/MickLesk) ([#2093](https://github.com/community-scripts/ProxmoxVE/pull/2093))\n- New Script: Apache Tika [@andygrunwald](https://github.com/andygrunwald) ([#2079](https://github.com/community-scripts/ProxmoxVE/pull/2079))\n\n### 🚀 Updated Scripts\n\n- Fix: Alpine IT-Tools Update [@MickLesk](https://github.com/MickLesk) ([#2067](https://github.com/community-scripts/ProxmoxVE/pull/2067))\n- Fix: Pocket-ID Change link to GH Repo [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2082](https://github.com/community-scripts/ProxmoxVE/pull/2082))\n\n### 🌐 Website\n\n- Refactor JSON generator buttons layout for better alignment and consistency [@BramSuurdje](https://github.com/BramSuurdje) ([#2106](https://github.com/community-scripts/ProxmoxVE/pull/2106))\n- Website: Refactor Footer for improved layout and styling consistency [@BramSuurdje](https://github.com/BramSuurdje) ([#2107](https://github.com/community-scripts/ProxmoxVE/pull/2107))\n- Website: Update Footer for Json-Editor & Api [@MickLesk](https://github.com/MickLesk) ([#2100](https://github.com/community-scripts/ProxmoxVE/pull/2100))\n- Website: Add Download for json-editor [@MickLesk](https://github.com/MickLesk) ([#2099](https://github.com/community-scripts/ProxmoxVE/pull/2099))\n- Radicale: Provide additional information about configuration [@tremor021](https://github.com/tremor021) ([#2072](https://github.com/community-scripts/ProxmoxVE/pull/2072))\n\n## 2025-02-05\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Zerotier Controller [@tremor021](https://github.com/tremor021) ([#1928](https://github.com/community-scripts/ProxmoxVE/pull/1928))\n- New Script: Radicale [@tremor021](https://github.com/tremor021) ([#1941](https://github.com/community-scripts/ProxmoxVE/pull/1941))\n- New Script: seelf [@tremor021](https://github.com/tremor021) ([#2023](https://github.com/community-scripts/ProxmoxVE/pull/2023))\n- New Script: Crafty-Controller [@CrazyWolf13](https://github.com/CrazyWolf13) ([#1926](https://github.com/community-scripts/ProxmoxVE/pull/1926))\n- New script: Koillection [@bvdberg01](https://github.com/bvdberg01) ([#2031](https://github.com/community-scripts/ProxmoxVE/pull/2031))\n\n### 🚀 Updated Scripts\n\n- Bugfix: Jellyseerr pnpm Version [@vidonnus](https://github.com/vidonnus) ([#2033](https://github.com/community-scripts/ProxmoxVE/pull/2033))\n- Radicale: Fixed missing htpasswd flag [@tremor021](https://github.com/tremor021) ([#2065](https://github.com/community-scripts/ProxmoxVE/pull/2065))\n- [API] Update build.func / Improve error messages #2 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2050](https://github.com/community-scripts/ProxmoxVE/pull/2050))\n- [API] Update create-lxc.sh / Improve error messages #1 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2049](https://github.com/community-scripts/ProxmoxVE/pull/2049))\n- Feature: Element Synapse: add option to enter server name during LXC installation [@tremor021](https://github.com/tremor021) ([#2038](https://github.com/community-scripts/ProxmoxVE/pull/2038))\n\n### 🌐 Website\n\n- Paperless NGX: Mark it as updateable [@andygrunwald](https://github.com/andygrunwald) ([#2070](https://github.com/community-scripts/ProxmoxVE/pull/2070))\n- Bump vitest from 2.1.6 to 2.1.9 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#2042](https://github.com/community-scripts/ProxmoxVE/pull/2042))\n\n### 🧰 Maintenance\n\n- [API] Add API backend code [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2040](https://github.com/community-scripts/ProxmoxVE/pull/2040))\n- Update auto-update-app-headers.yml: Enable auto approval [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2057](https://github.com/community-scripts/ProxmoxVE/pull/2057))\n\n## 2025-02-04\n\n### Changed\n\n### 💥 Breaking Changes\n\n- Rename & Optimize: Proxmox Backup Server (Renaming & Update fix) [@thost96](https://github.com/thost96) ([#2012](https://github.com/community-scripts/ProxmoxVE/pull/2012))\n\n### 🚀 Updated Scripts\n\n- Fix: Authentik - Remove deprecated GO-Remove in Footer [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2020](https://github.com/community-scripts/ProxmoxVE/pull/2020))\n- Fix: Authentik Fix wrong HDD Size [@thost96](https://github.com/thost96) ([#2001](https://github.com/community-scripts/ProxmoxVE/pull/2001))\n- Fix: Tandoor - node Version [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2010](https://github.com/community-scripts/ProxmoxVE/pull/2010))\n- Fix actual update - missing hidden files, downloaded release cleanup [@maciejmatczak](https://github.com/maciejmatczak) ([#2027](https://github.com/community-scripts/ProxmoxVE/pull/2027))\n- Fix Script: post-pmg-install.sh [@thost96](https://github.com/thost96) ([#2022](https://github.com/community-scripts/ProxmoxVE/pull/2022))\n- Fix Tianji: Add heap-space value for nodejs [@MickLesk](https://github.com/MickLesk) ([#2011](https://github.com/community-scripts/ProxmoxVE/pull/2011))\n- Fix: Ghost LXC - Use Node20 [@MickLesk](https://github.com/MickLesk) ([#2006](https://github.com/community-scripts/ProxmoxVE/pull/2006))\n\n### 🌐 Website\n\n- [API] Massive update to api (remove many, optimize website for users) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1990](https://github.com/community-scripts/ProxmoxVE/pull/1990))\n\n### 🧰 Maintenance\n\n- Fix header comments on contributor templates [@tremor021](https://github.com/tremor021) ([#2029](https://github.com/community-scripts/ProxmoxVE/pull/2029))\n- [Fix]: Headername of Proxmox-Datacenter-Manager not in CamelCase [@MickLesk](https://github.com/MickLesk) ([#2017](https://github.com/community-scripts/ProxmoxVE/pull/2017))\n- [Fix] Header breaks at long title - add width for figlet github action [@MickLesk](https://github.com/MickLesk) ([#2015](https://github.com/community-scripts/ProxmoxVE/pull/2015))\n\n## 2025-02-03\n\n### Changed\n\n### ✨ New Scripts\n\n- New Script: Element Synapse [@tremor021](https://github.com/tremor021) ([#1955](https://github.com/community-scripts/ProxmoxVE/pull/1955))\n- New Script: Privatebin [@opastorello](https://github.com/opastorello) ([#1925](https://github.com/community-scripts/ProxmoxVE/pull/1925))\n\n### 🚀 Updated Scripts\n\n- Fix: Monica Install with nodejs [@MickLesk](https://github.com/MickLesk) ([#1996](https://github.com/community-scripts/ProxmoxVE/pull/1996))\n- Element Synapse sed fix [@tremor021](https://github.com/tremor021) ([#1994](https://github.com/community-scripts/ProxmoxVE/pull/1994))\n- Fix Hoarder corepack install/update error [@vhsdream](https://github.com/vhsdream) ([#1957](https://github.com/community-scripts/ProxmoxVE/pull/1957))\n- [Security & Maintenance] Update NodeJS Repo to 22 for new Installs [@MickLesk](https://github.com/MickLesk) ([#1984](https://github.com/community-scripts/ProxmoxVE/pull/1984))\n- [Standardization]: Same Setup for GoLang on all LXC's & Clear Tarball [@MickLesk](https://github.com/MickLesk) ([#1977](https://github.com/community-scripts/ProxmoxVE/pull/1977))\n- Feature: urbackupserver Include fuse&nesting features during install [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1968](https://github.com/community-scripts/ProxmoxVE/pull/1968))\n- Fix: MSSQL-Server: Better gpg handling [@MickLesk](https://github.com/MickLesk) ([#1962](https://github.com/community-scripts/ProxmoxVE/pull/1962))\n- Fix: Grist ran into a heap space during the update [@MickLesk](https://github.com/MickLesk) ([#1964](https://github.com/community-scripts/ProxmoxVE/pull/1964))\n- Fix: FS-Trim Cancel / Error-Button [@MickLesk](https://github.com/MickLesk) ([#1965](https://github.com/community-scripts/ProxmoxVE/pull/1965))\n- Fix: Increase HDD Space for Hoarder [@MickLesk](https://github.com/MickLesk) ([#1970](https://github.com/community-scripts/ProxmoxVE/pull/1970))\n- Feature: Clean Orphan LVM without CEPH [@MickLesk](https://github.com/MickLesk) ([#1974](https://github.com/community-scripts/ProxmoxVE/pull/1974))\n- [Standardization] Fix Spelling for \"Setup Python3\"  [@MickLesk](https://github.com/MickLesk) ([#1975](https://github.com/community-scripts/ProxmoxVE/pull/1975))\n\n### 🌐 Website\n\n- [Website] update data/page.tsx [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1969](https://github.com/community-scripts/ProxmoxVE/pull/1969))\n- Prometheus Proxmox VE Exporter: Set correct website slug [@andygrunwald](https://github.com/andygrunwald) ([#1961](https://github.com/community-scripts/ProxmoxVE/pull/1961))\n\n### 🧰 Maintenance\n\n- [API] Remove Hostname, Verbose, SSH and TAGS [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1967](https://github.com/community-scripts/ProxmoxVE/pull/1967))\n\n## 2025-02-02\n\n### Changed\n\n### 🚀 Updated Scripts\n\n- Prometheus PVE Exporter: Add `--default-timeout=300` to pip install commands [@andygrunwald](https://github.com/andygrunwald) ([#1950](https://github.com/community-scripts/ProxmoxVE/pull/1950))\n- fix z2m update function to 2.1.0 [@MickLesk](https://github.com/MickLesk) ([#1938](https://github.com/community-scripts/ProxmoxVE/pull/1938))\n\n### 🧰 Maintenance\n\n- VSCode: Add Shellscript Syntax highlighting for *.func files [@andygrunwald](https://github.com/andygrunwald) ([#1948](https://github.com/community-scripts/ProxmoxVE/pull/1948))\n\n## 2025-02-01\n\n### Changed\n\n### 💥 Breaking Changes\n\n- [DCMA] Delete scripts 5etools and pf2etools - Copyright abuse [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1922](https://github.com/community-scripts/ProxmoxVE/pull/1922))\n\n### ✨ New Scripts\n\n- New script: Baïkal [@bvdberg01](https://github.com/bvdberg01) ([#1913](https://github.com/community-scripts/ProxmoxVE/pull/1913))\n\n### 🚀 Updated Scripts\n\n- Bug fix: Paymenter [@opastorello](https://github.com/opastorello) ([#1917](https://github.com/community-scripts/ProxmoxVE/pull/1917))\n"
  },
  {
    "path": ".github/changelogs/2025/03.md",
    "content": "﻿## 2025-03-31\n\n### 🆕 New Scripts\n\n  - slskd [@vhsdream](https://github.com/vhsdream) ([#3516](https://github.com/community-scripts/ProxmoxVE/pull/3516))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - The Lounge: Fix sqlite3 failing to build [@tremor021](https://github.com/tremor021) ([#3542](https://github.com/community-scripts/ProxmoxVE/pull/3542))\n    - 2FAuth: Update PHP to 8.3 [@BrockHumblet](https://github.com/BrockHumblet) ([#3510](https://github.com/community-scripts/ProxmoxVE/pull/3510))\n    - GoMFT: Update Curl Path [@MickLesk](https://github.com/MickLesk) ([#3537](https://github.com/community-scripts/ProxmoxVE/pull/3537))\n    - slskd: fix broken curl for soularr [@MickLesk](https://github.com/MickLesk) ([#3533](https://github.com/community-scripts/ProxmoxVE/pull/3533))\n    - Docmost: Bump NodeJS to 22 & fixed pnpm [@MickLesk](https://github.com/MickLesk) ([#3521](https://github.com/community-scripts/ProxmoxVE/pull/3521))\n    - Tianji: Bump NodeJS to V22 [@MickLesk](https://github.com/MickLesk) ([#3519](https://github.com/community-scripts/ProxmoxVE/pull/3519))\n\n  - #### ✨ New Features\n\n    - NPMPlus: update function & better create handling (user/password) [@MickLesk](https://github.com/MickLesk) ([#3520](https://github.com/community-scripts/ProxmoxVE/pull/3520))\n\n  - #### 🔧 Refactor\n\n    - Remove old `.jar` versions of Stirling-PDF [@JcMinarro](https://github.com/JcMinarro) ([#3512](https://github.com/community-scripts/ProxmoxVE/pull/3512))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - core: fix empty header if header in repo exist [@MickLesk](https://github.com/MickLesk) ([#3536](https://github.com/community-scripts/ProxmoxVE/pull/3536))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Change Frontend Version Info [@MickLesk](https://github.com/MickLesk) ([#3527](https://github.com/community-scripts/ProxmoxVE/pull/3527))\n\n  - #### 📝 Script Information\n\n    - HomeAssistant (Container): Better Portainer explanation [@MickLesk](https://github.com/MickLesk) ([#3518](https://github.com/community-scripts/ProxmoxVE/pull/3518))\n\n## 2025-03-30\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Open WebUI: Fix Ollama update logic [@tremor021](https://github.com/tremor021) ([#3506](https://github.com/community-scripts/ProxmoxVE/pull/3506))\n    - GoMFT: Add frontend build procedure [@tremor021](https://github.com/tremor021) ([#3499](https://github.com/community-scripts/ProxmoxVE/pull/3499))\n\n  - #### ✨ New Features\n\n    - Open WebUI: Add Ollama update check [@tremor021](https://github.com/tremor021) ([#3478](https://github.com/community-scripts/ProxmoxVE/pull/3478))\n\n## 2025-03-29\n\n### 🆕 New Scripts\n\n  - Alpine MariaDB [@MickLesk](https://github.com/MickLesk) ([#3456](https://github.com/community-scripts/ProxmoxVE/pull/3456))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Komodo: Fix wrong sed text [@tremor021](https://github.com/tremor021) ([#3491](https://github.com/community-scripts/ProxmoxVE/pull/3491))\n    - GoMFT: Fix release archive naming [@tremor021](https://github.com/tremor021) ([#3483](https://github.com/community-scripts/ProxmoxVE/pull/3483))\n    - Homepage: Fix release parsing [@tremor021](https://github.com/tremor021) ([#3484](https://github.com/community-scripts/ProxmoxVE/pull/3484))\n    - Netdata: Fix debian-keyring dependency missing [@tremor021](https://github.com/tremor021) ([#3477](https://github.com/community-scripts/ProxmoxVE/pull/3477))\n    - ErsatzTV: Fix temp file reference [@tremor021](https://github.com/tremor021) ([#3476](https://github.com/community-scripts/ProxmoxVE/pull/3476))\n    - Komodo: Fix compose.env [@tremor021](https://github.com/tremor021) ([#3466](https://github.com/community-scripts/ProxmoxVE/pull/3466))\n\n## 2025-03-28\n\n### 🆕 New Scripts\n\n  - Alpine Node-RED [@MickLesk](https://github.com/MickLesk) ([#3457](https://github.com/community-scripts/ProxmoxVE/pull/3457))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GoMFT: Fix release grep [@tremor021](https://github.com/tremor021) ([#3462](https://github.com/community-scripts/ProxmoxVE/pull/3462))\n    - ErsatzTV: Fix path in update function [@tremor021](https://github.com/tremor021) ([#3463](https://github.com/community-scripts/ProxmoxVE/pull/3463))\n\n## 2025-03-27\n\n### 🚀 Updated Scripts\n\n  - [core]: add functions for Alpine (update / core deps) [@MickLesk](https://github.com/MickLesk) ([#3437](https://github.com/community-scripts/ProxmoxVE/pull/3437))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - [core]: Refactor Spinner/MSG Function (support now alpine / performance / handling) [@MickLesk](https://github.com/MickLesk) ([#3436](https://github.com/community-scripts/ProxmoxVE/pull/3436))\n\n## 2025-03-26\n\n### 🆕 New Scripts\n\n  - Alpine: Gitea [@MickLesk](https://github.com/MickLesk) ([#3424](https://github.com/community-scripts/ProxmoxVE/pull/3424))\n\n### 🚀 Updated Scripts\n\n  - FlowiseAI: Fix dependencies [@tremor021](https://github.com/tremor021) ([#3427](https://github.com/community-scripts/ProxmoxVE/pull/3427))\n\n  - #### 🐞 Bug Fixes\n\n    - fluid-calendar: Fix failed build during updates [@vhsdream](https://github.com/vhsdream) ([#3417](https://github.com/community-scripts/ProxmoxVE/pull/3417))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Remove coredeps in CONTRIBUTOR_AND_GUIDES [@bvdberg01](https://github.com/bvdberg01) ([#3420](https://github.com/community-scripts/ProxmoxVE/pull/3420))\n\n## 2025-03-25\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Discord invite link updated [@MickLesk](https://github.com/MickLesk) ([#3412](https://github.com/community-scripts/ProxmoxVE/pull/3412))\n\n## 2025-03-24\n\n### 🆕 New Scripts\n\n  - fileflows [@kkroboth](https://github.com/kkroboth) ([#3392](https://github.com/community-scripts/ProxmoxVE/pull/3392))\n- wazuh [@omiinaya](https://github.com/omiinaya) ([#3381](https://github.com/community-scripts/ProxmoxVE/pull/3381))\n- yt-dlp-webui [@CrazyWolf13](https://github.com/CrazyWolf13) ([#3364](https://github.com/community-scripts/ProxmoxVE/pull/3364))\n- Extension/New Script: Redis Alpine Installation [@MickLesk](https://github.com/MickLesk) ([#3367](https://github.com/community-scripts/ProxmoxVE/pull/3367))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Extend HOME Env for Kubo [@MickLesk](https://github.com/MickLesk) ([#3397](https://github.com/community-scripts/ProxmoxVE/pull/3397))\n\n  - #### ✨ New Features\n\n    - [core] Rebase Scripts (formatting, highlighting & remove old deps) [@MickLesk](https://github.com/MickLesk) ([#3378](https://github.com/community-scripts/ProxmoxVE/pull/3378))\n\n  - #### 🔧 Refactor\n\n    - qBittorrent: Switch to static builds for faster updating/upgrading [@tremor021](https://github.com/tremor021) ([#3405](https://github.com/community-scripts/ProxmoxVE/pull/3405))\n    - Refactor: ErsatzTV Script [@MickLesk](https://github.com/MickLesk) ([#3365](https://github.com/community-scripts/ProxmoxVE/pull/3365))\n\n### 🧰 Maintenance\n\n  - #### ✨ New Features\n\n    - [core] install core deps (debian / ubuntu) [@MickLesk](https://github.com/MickLesk) ([#3366](https://github.com/community-scripts/ProxmoxVE/pull/3366))\n\n  - #### 💾 Core\n\n    - [core] extend hardware transcoding for fileflows #3392 [@MickLesk](https://github.com/MickLesk) ([#3396](https://github.com/community-scripts/ProxmoxVE/pull/3396))\n\n  - #### 📂 Github\n\n    - Refactor Changelog Workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3371](https://github.com/community-scripts/ProxmoxVE/pull/3371))\n\n### 🌐 Website\n\n  - Update siteConfig.tsx to use new analytics code [@BramSuurdje](https://github.com/BramSuurdje) ([#3389](https://github.com/community-scripts/ProxmoxVE/pull/3389))\n\n  - #### 🐞 Bug Fixes\n\n    - Better Text for Version Date [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3388](https://github.com/community-scripts/ProxmoxVE/pull/3388))\n\n## 2025-03-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GoMFT: Check if build-essential is present before updating, if not then install it [@tremor021](https://github.com/tremor021) ([#3358](https://github.com/community-scripts/ProxmoxVE/pull/3358))\n\n## 2025-03-22\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - revealjs: Fix update process [@tremor021](https://github.com/tremor021) ([#3341](https://github.com/community-scripts/ProxmoxVE/pull/3341))\n    - Cronicle: add missing gnupg package [@MickLesk](https://github.com/MickLesk) ([#3323](https://github.com/community-scripts/ProxmoxVE/pull/3323))\n\n  - #### ✨ New Features\n\n    - Update nextcloud-vm.sh to 18.1 ISO [@0xN0BADC0FF33](https://github.com/0xN0BADC0FF33) ([#3333](https://github.com/community-scripts/ProxmoxVE/pull/3333))\n\n## 2025-03-21\n\n### 🚀 Updated Scripts\n\n  - Omada jdk to jre [@bvdberg01](https://github.com/bvdberg01) ([#3319](https://github.com/community-scripts/ProxmoxVE/pull/3319))\n\n  - #### 🐞 Bug Fixes\n\n    - Omada zulu 8 to 21 [@bvdberg01](https://github.com/bvdberg01) ([#3318](https://github.com/community-scripts/ProxmoxVE/pull/3318))\n    - MySQL: Correctly add repo to mysql.list [@tremor021](https://github.com/tremor021) ([#3315](https://github.com/community-scripts/ProxmoxVE/pull/3315))\n    - GoMFT: Fix build dependencies [@tremor021](https://github.com/tremor021) ([#3313](https://github.com/community-scripts/ProxmoxVE/pull/3313))\n    - GoMFT: Don't rely on binaries from github [@tremor021](https://github.com/tremor021) ([#3303](https://github.com/community-scripts/ProxmoxVE/pull/3303))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - Clarify MTU in advanced Settings. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3296](https://github.com/community-scripts/ProxmoxVE/pull/3296))\n\n### 🌐 Website\n\n  - Bump next from 15.1.3 to 15.2.3 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3316](https://github.com/community-scripts/ProxmoxVE/pull/3316))\n\n  - #### 📝 Script Information\n\n    - Proxmox, rather than Promox [@gringocl](https://github.com/gringocl) ([#3293](https://github.com/community-scripts/ProxmoxVE/pull/3293))\n    - Audiobookshelf: Fix category on website [@jaykup26](https://github.com/jaykup26) ([#3304](https://github.com/community-scripts/ProxmoxVE/pull/3304))\n    - Threadfin: add port for website [@MickLesk](https://github.com/MickLesk) ([#3295](https://github.com/community-scripts/ProxmoxVE/pull/3295))\n\n## 2025-03-20\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Netdata: Update to newer deb File [@MickLesk](https://github.com/MickLesk) ([#3276](https://github.com/community-scripts/ProxmoxVE/pull/3276))\n\n### 🧰 Maintenance\n\n  - #### ✨ New Features\n\n    - [core] add gitignore to prevent big pulls [@MickLesk](https://github.com/MickLesk) ([#3278](https://github.com/community-scripts/ProxmoxVE/pull/3278))\n\n## 2025-03-19\n\n### 🚀 Updated Scripts\n\n  - License url VED to VE [@bvdberg01](https://github.com/bvdberg01) ([#3258](https://github.com/community-scripts/ProxmoxVE/pull/3258))\n\n  - #### 🐞 Bug Fixes\n\n    - Snipe-IT: Remove composer update & add no interaction for install [@MickLesk](https://github.com/MickLesk) ([#3256](https://github.com/community-scripts/ProxmoxVE/pull/3256))\n    - Fluid-Calendar: Remove unneeded $STD in update [@MickLesk](https://github.com/MickLesk) ([#3250](https://github.com/community-scripts/ProxmoxVE/pull/3250))\n\n  - #### 💥 Breaking Changes\n\n    - FluidCalendar: Switch to safer DB operations [@vhsdream](https://github.com/vhsdream) ([#3270](https://github.com/community-scripts/ProxmoxVE/pull/3270))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - JSON editor note fix [@bvdberg01](https://github.com/bvdberg01) ([#3260](https://github.com/community-scripts/ProxmoxVE/pull/3260))\n\n## 2025-03-18\n\n### 🆕 New Scripts\n\n  - CryptPad [@MickLesk](https://github.com/MickLesk) ([#3205](https://github.com/community-scripts/ProxmoxVE/pull/3205))\n- GoMFT [@tremor021](https://github.com/tremor021) ([#3157](https://github.com/community-scripts/ProxmoxVE/pull/3157))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update omada download url [@bvdberg01](https://github.com/bvdberg01) ([#3245](https://github.com/community-scripts/ProxmoxVE/pull/3245))\n    - Wikijs: Remove Dev Message & Performance-Boost [@bvdberg01](https://github.com/bvdberg01) ([#3232](https://github.com/community-scripts/ProxmoxVE/pull/3232))\n    - Fix openwebui update script when backup directory already exists [@chrisdoc](https://github.com/chrisdoc) ([#3213](https://github.com/community-scripts/ProxmoxVE/pull/3213))\n\n  - #### 💥 Breaking Changes\n\n    - Tandoor: Extend needed dependencies (Read for Update-Functionality)  [@MickLesk](https://github.com/MickLesk) ([#3207](https://github.com/community-scripts/ProxmoxVE/pull/3207))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - [core] cleanup - remove old backups of workflow files [@MickLesk](https://github.com/MickLesk) ([#3247](https://github.com/community-scripts/ProxmoxVE/pull/3247))\n    - Add worflow to crawl APP verisons [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3192](https://github.com/community-scripts/ProxmoxVE/pull/3192))\n    - Update pr template and WF [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3200](https://github.com/community-scripts/ProxmoxVE/pull/3200))\n    - Update Workflow Context [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3171](https://github.com/community-scripts/ProxmoxVE/pull/3171))\n    - Change json path CONTRIBUTING.md [@bvdberg01](https://github.com/bvdberg01) ([#3187](https://github.com/community-scripts/ProxmoxVE/pull/3187))\n    - Relocate the Json Files [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3184](https://github.com/community-scripts/ProxmoxVE/pull/3184))\n    - Update Workflow to Close Discussion [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3185](https://github.com/community-scripts/ProxmoxVE/pull/3185))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Move cryptpad files to right folders [@bvdberg01](https://github.com/bvdberg01) ([#3242](https://github.com/community-scripts/ProxmoxVE/pull/3242))\n    - Update Frontend Version Logic [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3223](https://github.com/community-scripts/ProxmoxVE/pull/3223))\n\n  - #### ✨ New Features\n\n    - Add Latest Change Date to Frontend [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3231](https://github.com/community-scripts/ProxmoxVE/pull/3231))\n    - Show Version Information on Frontend [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3216](https://github.com/community-scripts/ProxmoxVE/pull/3216))\n\n  - #### 📝 Script Information\n\n    - CrowdSec: Add debian only warning to website [@tremor021](https://github.com/tremor021) ([#3210](https://github.com/community-scripts/ProxmoxVE/pull/3210))\n    - Debian VM: Update webpage with login info [@tremor021](https://github.com/tremor021) ([#3215](https://github.com/community-scripts/ProxmoxVE/pull/3215))\n    - Heimdall Dashboard: Fix missing logo on website [@tremor021](https://github.com/tremor021) ([#3227](https://github.com/community-scripts/ProxmoxVE/pull/3227))\n    - Seafile: lowercase slug for Install/Update-Source [@MickLesk](https://github.com/MickLesk) ([#3209](https://github.com/community-scripts/ProxmoxVE/pull/3209))\n    - Website: Lowercase Zitadel-Slug [@MickLesk](https://github.com/MickLesk) ([#3222](https://github.com/community-scripts/ProxmoxVE/pull/3222))\n    - VictoriaMetrics: Fix Wrong Slug [@MickLesk](https://github.com/MickLesk) ([#3225](https://github.com/community-scripts/ProxmoxVE/pull/3225))\n    - Update Pimox Logo [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3233](https://github.com/community-scripts/ProxmoxVE/pull/3233))\n    - [AUTOMATIC PR]Update versions.json [@community-scripts-pr-app[bot]](https://github.com/community-scripts-pr-app[bot]) ([#3201](https://github.com/community-scripts/ProxmoxVE/pull/3201))\n    - GoMFT: Update Logo [@MickLesk](https://github.com/MickLesk) ([#3188](https://github.com/community-scripts/ProxmoxVE/pull/3188))\n\n## 2025-03-17\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - TriliumNotes: Fix release handling [@tremor021](https://github.com/tremor021) ([#3160](https://github.com/community-scripts/ProxmoxVE/pull/3160))\n    - TriliumNext: Fix release file name/path, preventing install and update [@tremor021](https://github.com/tremor021) ([#3152](https://github.com/community-scripts/ProxmoxVE/pull/3152))\n    - qBittorrent: Accept legal notice in config file [@tremor021](https://github.com/tremor021) ([#3150](https://github.com/community-scripts/ProxmoxVE/pull/3150))\n    - Tandoor: Switch Repo to new Link [@MickLesk](https://github.com/MickLesk) ([#3140](https://github.com/community-scripts/ProxmoxVE/pull/3140))\n    - Fixed wrong PHP values to match default part-db size (100M) [@dMopp](https://github.com/dMopp) ([#3143](https://github.com/community-scripts/ProxmoxVE/pull/3143))\n    - Kimai: Fix Permission Issue on new Timerecords [@MickLesk](https://github.com/MickLesk) ([#3136](https://github.com/community-scripts/ProxmoxVE/pull/3136))\n\n  - #### ✨ New Features\n\n    - InfluxDB: Add Ports as Info / Script-End [@MickLesk](https://github.com/MickLesk) ([#3141](https://github.com/community-scripts/ProxmoxVE/pull/3141))\n    - ByteStash: Add option for multiple accounts and generate JWT secret [@tremor021](https://github.com/tremor021) ([#3132](https://github.com/community-scripts/ProxmoxVE/pull/3132))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Paperless-ngx: Fix example on website [@tremor021](https://github.com/tremor021) ([#3155](https://github.com/community-scripts/ProxmoxVE/pull/3155))\n\n## 2025-03-16\n\n### 🚀 Updated Scripts\n\n  - Typo Enviroment > Environment [@MathijsG](https://github.com/MathijsG) ([#3115](https://github.com/community-scripts/ProxmoxVE/pull/3115))\n- Paperless-ngx: Add additional information to website on how to install OCR languages [@tremor021](https://github.com/tremor021) ([#3111](https://github.com/community-scripts/ProxmoxVE/pull/3111))\n- Prometheus PVE Exporter: Rightsizing RAM and Disk [@andygrunwald](https://github.com/andygrunwald) ([#3098](https://github.com/community-scripts/ProxmoxVE/pull/3098))\n\n  - #### 🐞 Bug Fixes\n\n    - Jellyseerr: Fix dependencies [@tremor021](https://github.com/tremor021) ([#3125](https://github.com/community-scripts/ProxmoxVE/pull/3125))\n    - wger: Fix build.func path [@tremor021](https://github.com/tremor021) ([#3121](https://github.com/community-scripts/ProxmoxVE/pull/3121))\n    - Filebrowser: Fix hardcoded port in Debian service file [@Xerovoxx98](https://github.com/Xerovoxx98) ([#3105](https://github.com/community-scripts/ProxmoxVE/pull/3105))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Website: Fix alpine-it-tools \"undefined\" Link [@CrazyWolf13](https://github.com/CrazyWolf13) ([#3110](https://github.com/community-scripts/ProxmoxVE/pull/3110))\n\n## 2025-03-15\n\n### 🚀 Updated Scripts\n\n  - #### 💥 Breaking Changes\n\n    - Homepage: Bugfix for v1.0.0 [@vhsdream](https://github.com/vhsdream) ([#3092](https://github.com/community-scripts/ProxmoxVE/pull/3092))\n\n## 2025-03-14\n\n### 🚀 Updated Scripts\n\n  - Memos: Increase RAM Usage and max space [@MickLesk](https://github.com/MickLesk) ([#3072](https://github.com/community-scripts/ProxmoxVE/pull/3072))\n- Seafile - Minor bug fix: domain.sh script fix [@dave-yap](https://github.com/dave-yap) ([#3046](https://github.com/community-scripts/ProxmoxVE/pull/3046))\n\n  - #### 🐞 Bug Fixes\n\n    - openwrt: fix typo netmask [@qzydustin](https://github.com/qzydustin) ([#3084](https://github.com/community-scripts/ProxmoxVE/pull/3084))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - NPMplus: Add info about docker use. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3085](https://github.com/community-scripts/ProxmoxVE/pull/3085))\n\n## 2025-03-13\n\n### 🆕 New Scripts\n\n  - NPMplus [@MickLesk](https://github.com/MickLesk) ([#3051](https://github.com/community-scripts/ProxmoxVE/pull/3051))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - OpenWebUI check if there are stashed changes before poping [@tremor021](https://github.com/tremor021) ([#3064](https://github.com/community-scripts/ProxmoxVE/pull/3064))\n    - Update Fluid Calendar for v1.2.0 [@vhsdream](https://github.com/vhsdream) ([#3053](https://github.com/community-scripts/ProxmoxVE/pull/3053))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - alpine-Install (core) add timezone (tz) check [@MickLesk](https://github.com/MickLesk) ([#3057](https://github.com/community-scripts/ProxmoxVE/pull/3057))\n\n  - #### 📂 Github\n\n    - New Workflow: Close Issues in DEV Repo when new Script is merged [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3042](https://github.com/community-scripts/ProxmoxVE/pull/3042))\n\n### 🌐 Website\n\n  - Bump @babel/runtime from 7.26.0 to 7.26.10 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3044](https://github.com/community-scripts/ProxmoxVE/pull/3044))\n\n  - #### 📝 Script Information\n\n    - Update Vaultwarden Source [@MickLesk](https://github.com/MickLesk) ([#3036](https://github.com/community-scripts/ProxmoxVE/pull/3036))\n    - Website: Fix Alpine \"undefined\" Link [@MickLesk](https://github.com/MickLesk) ([#3048](https://github.com/community-scripts/ProxmoxVE/pull/3048))\n\n## 2025-03-12\n\n### 🆕 New Scripts\n\n  - Fluid Calendar [@vhsdream](https://github.com/vhsdream) ([#2869](https://github.com/community-scripts/ProxmoxVE/pull/2869))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Feature: Filebrowser: support now alpine [@MickLesk](https://github.com/MickLesk) ([#2997](https://github.com/community-scripts/ProxmoxVE/pull/2997))\n\n## 2025-03-11\n\n### 🆕 New Scripts\n\n  - Plant-it [@MickLesk](https://github.com/MickLesk) ([#3000](https://github.com/community-scripts/ProxmoxVE/pull/3000))\n- Seafile [@dave-yap](https://github.com/dave-yap) ([#2987](https://github.com/community-scripts/ProxmoxVE/pull/2987))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Headscale: Re-enable Service after Update [@Cerothen](https://github.com/Cerothen) ([#3012](https://github.com/community-scripts/ProxmoxVE/pull/3012))\n    - SnipeIT: Harmonize composer install to Project-Dockerfile [@MickLesk](https://github.com/MickLesk) ([#3009](https://github.com/community-scripts/ProxmoxVE/pull/3009))\n    - Teddycloud: fix update function [@tremor021](https://github.com/tremor021) ([#2996](https://github.com/community-scripts/ProxmoxVE/pull/2996))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Cleanup Old Project Files (figlet, app-header, images) [@MickLesk](https://github.com/MickLesk) ([#3004](https://github.com/community-scripts/ProxmoxVE/pull/3004))\n    - Additions and amends to the CONTIRBUTOR docs [@tremor021](https://github.com/tremor021) ([#2983](https://github.com/community-scripts/ProxmoxVE/pull/2983))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Jellyseer not labeled as updateable even though update function exists [@tremor021](https://github.com/tremor021) ([#2991](https://github.com/community-scripts/ProxmoxVE/pull/2991))\n    - Fix Website - Show correct wget path for alpine [@MickLesk](https://github.com/MickLesk) ([#2998](https://github.com/community-scripts/ProxmoxVE/pull/2998))\n\n## 2025-03-10\n\n### 🆕 New Scripts\n\n  - Paperless-GPT [@MickLesk](https://github.com/MickLesk) ([#2965](https://github.com/community-scripts/ProxmoxVE/pull/2965))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Rework SnipeIT: Tarball & Tempfile [@MickLesk](https://github.com/MickLesk) ([#2963](https://github.com/community-scripts/ProxmoxVE/pull/2963))\n    - pihole: fix path when accessing pihole using `pct enter` [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2964](https://github.com/community-scripts/ProxmoxVE/pull/2964))\n    - Hoarder: v0.23.0 dependency update [@vhsdream](https://github.com/vhsdream) ([#2958](https://github.com/community-scripts/ProxmoxVE/pull/2958))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Update autolabeler.yml: Set Labels correctly [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2968](https://github.com/community-scripts/ProxmoxVE/pull/2968))\n\n### 🌐 Website\n\n  - Add warnings about externaly sourced scripts [@tremor021](https://github.com/tremor021) ([#2975](https://github.com/community-scripts/ProxmoxVE/pull/2975))\n\n## 2025-03-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix wikijs update issue while backing up data [@AdelRefaat](https://github.com/AdelRefaat) ([#2950](https://github.com/community-scripts/ProxmoxVE/pull/2950))\n\n### 🧰 Maintenance\n\n  - #### 🐞 Bug Fixes\n\n    - Improve Release-Action (awk function) [@MickLesk](https://github.com/MickLesk) ([#2934](https://github.com/community-scripts/ProxmoxVE/pull/2934))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Pi-hole interface port in documentation [@la7eralus](https://github.com/la7eralus) ([#2953](https://github.com/community-scripts/ProxmoxVE/pull/2953))\n\n## 2025-03-08\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Update slug to lowercase in pf2etools.json  [@PhoenixEmik](https://github.com/PhoenixEmik) ([#2942](https://github.com/community-scripts/ProxmoxVE/pull/2942))\n\n## 2025-03-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - JupyterNotebook: Fix APP Variable [@MickLesk](https://github.com/MickLesk) ([#2924](https://github.com/community-scripts/ProxmoxVE/pull/2924))\n\n  - #### ✨ New Features\n\n    - Beszel: restarting service after update [@C0pywriting](https://github.com/C0pywriting) ([#2915](https://github.com/community-scripts/ProxmoxVE/pull/2915))\n\n  - #### 💥 Breaking Changes\n\n    - ActualBudget: Update Script with new Repo [@MickLesk](https://github.com/MickLesk) ([#2907](https://github.com/community-scripts/ProxmoxVE/pull/2907))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Improve Nextcloud(pi) docu and Name to NextcloudPi [@MickLesk](https://github.com/MickLesk) ([#2930](https://github.com/community-scripts/ProxmoxVE/pull/2930))\n    - fix jupyternotebook slug [@MickLesk](https://github.com/MickLesk) ([#2922](https://github.com/community-scripts/ProxmoxVE/pull/2922))\n    - Improve Trilium Description and Name to TriliumNext [@MickLesk](https://github.com/MickLesk) ([#2929](https://github.com/community-scripts/ProxmoxVE/pull/2929))\n    - Prowlarr icon [@bannert1337](https://github.com/bannert1337) ([#2906](https://github.com/community-scripts/ProxmoxVE/pull/2906))\n    - Update Apache Tika icon to SVG [@bannert1337](https://github.com/bannert1337) ([#2904](https://github.com/community-scripts/ProxmoxVE/pull/2904))\n    - Update Prometheus Alertmanager Icon [@bannert1337](https://github.com/bannert1337) ([#2905](https://github.com/community-scripts/ProxmoxVE/pull/2905))\n\n## 2025-03-06\n\n### 🆕 New Scripts\n\n  - InvenTree [@tremor021](https://github.com/tremor021) ([#2890](https://github.com/community-scripts/ProxmoxVE/pull/2890))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Homarr: Optional Reboot after update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2876](https://github.com/community-scripts/ProxmoxVE/pull/2876))\n    - Fix Tag \"community-scripts\" for ArchLinux / OPNSense [@MickLesk](https://github.com/MickLesk) ([#2875](https://github.com/community-scripts/ProxmoxVE/pull/2875))\n\n  - #### ✨ New Features\n\n    - Wastebin: Update Script for Version 3.0.0 [@MickLesk](https://github.com/MickLesk) ([#2885](https://github.com/community-scripts/ProxmoxVE/pull/2885))\n\n## 2025-03-05\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Kimai: Better Handling of Updates (backup var / env / yaml) [@MickLesk](https://github.com/MickLesk) ([#2862](https://github.com/community-scripts/ProxmoxVE/pull/2862))\n    - Fix NextcloudPi-Installation [@MickLesk](https://github.com/MickLesk) ([#2853](https://github.com/community-scripts/ProxmoxVE/pull/2853))\n\n## 2025-03-04\n\n### 🆕 New Scripts\n\n  - Reveal.js [@tremor021](https://github.com/tremor021) ([#2806](https://github.com/community-scripts/ProxmoxVE/pull/2806))\n- Apache Tomcat [@MickLesk](https://github.com/MickLesk) ([#2797](https://github.com/community-scripts/ProxmoxVE/pull/2797))\n- Pterodactyl Wings [@bvdberg01](https://github.com/bvdberg01) ([#2800](https://github.com/community-scripts/ProxmoxVE/pull/2800))\n- Pterodactyl Panel [@bvdberg01](https://github.com/bvdberg01) ([#2801](https://github.com/community-scripts/ProxmoxVE/pull/2801))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - reveal.js: Update function now backs up index and config [@tremor021](https://github.com/tremor021) ([#2845](https://github.com/community-scripts/ProxmoxVE/pull/2845))\n    - Changedetection: Increase RAM & Disk-Space [@MickLesk](https://github.com/MickLesk) ([#2838](https://github.com/community-scripts/ProxmoxVE/pull/2838))\n    - Linkwarden: Optimze RUST Installation [@MickLesk](https://github.com/MickLesk) ([#2817](https://github.com/community-scripts/ProxmoxVE/pull/2817))\n    - Nginx: Fix $STD for tar [@MickLesk](https://github.com/MickLesk) ([#2813](https://github.com/community-scripts/ProxmoxVE/pull/2813))\n\n  - #### ✨ New Features\n\n    - Add source to install scripts and make license one line [@bvdberg01](https://github.com/bvdberg01) ([#2842](https://github.com/community-scripts/ProxmoxVE/pull/2842))\n\n### 🧰 Maintenance\n\n  - #### 🐞 Bug Fixes\n\n    - Better handling of create release [@MickLesk](https://github.com/MickLesk) ([#2818](https://github.com/community-scripts/ProxmoxVE/pull/2818))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Json file update [@bvdberg01](https://github.com/bvdberg01) ([#2824](https://github.com/community-scripts/ProxmoxVE/pull/2824))\n    - Prometheus-paperless-ngx-exporter: Fix wrong Interface Port [@schneider-de-com](https://github.com/schneider-de-com) ([#2812](https://github.com/community-scripts/ProxmoxVE/pull/2812))\n\n  - #### ✨ New Features\n\n    - Feature: Update Icons (selfhst repo) [@bannert1337](https://github.com/bannert1337) ([#2834](https://github.com/community-scripts/ProxmoxVE/pull/2834))\n    - Website: Add Mikrotik to Network too, OPNSense & OpenWRT to OS [@MickLesk](https://github.com/MickLesk) ([#2823](https://github.com/community-scripts/ProxmoxVE/pull/2823))\n\n## 2025-03-03\n\n### 🆕 New Scripts\n\n  - Habitica [@tremor021](https://github.com/tremor021) ([#2779](https://github.com/community-scripts/ProxmoxVE/pull/2779))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zigbee2Mqtt Use fixed pnpm Version 10.4.1 [@MickLesk](https://github.com/MickLesk) ([#2805](https://github.com/community-scripts/ProxmoxVE/pull/2805))\n    - Linkwarden: Fix & Update Monolith-Installation [@MickLesk](https://github.com/MickLesk) ([#2787](https://github.com/community-scripts/ProxmoxVE/pull/2787))\n\n  - #### ✨ New Features\n\n    - Feature: MinIO use now static port 9001 [@MickLesk](https://github.com/MickLesk) ([#2786](https://github.com/community-scripts/ProxmoxVE/pull/2786))\n    - Feature Template Path for Mountings [@MickLesk](https://github.com/MickLesk) ([#2785](https://github.com/community-scripts/ProxmoxVE/pull/2785))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Feature: Website - show default OS [@MickLesk](https://github.com/MickLesk) ([#2790](https://github.com/community-scripts/ProxmoxVE/pull/2790))\n\n  - #### 📝 Script Information\n\n    - Update zigbee2mqtt.json - make sure link is clickable [@gurtjun](https://github.com/gurtjun) ([#2802](https://github.com/community-scripts/ProxmoxVE/pull/2802))\n\n## 2025-03-02\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix gpg Repo for nzbget [@flatlinebb](https://github.com/flatlinebb) ([#2774](https://github.com/community-scripts/ProxmoxVE/pull/2774))\n\n## 2025-03-01\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Firefly III: FIx Ownership for OAuth Key [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#2759](https://github.com/community-scripts/ProxmoxVE/pull/2759))\n    - homarr: double restart to fix homarr migration [@CrazyWolf13](https://github.com/CrazyWolf13) ([#2757](https://github.com/community-scripts/ProxmoxVE/pull/2757))\n\n  - #### ✨ New Features\n\n    - ActualBudget: New Installation Script with new Repo [@MickLesk](https://github.com/MickLesk) ([#2770](https://github.com/community-scripts/ProxmoxVE/pull/2770))\n\n  - #### 💥 Breaking Changes\n\n    - Breaking: Remove Update Function for Actual Budget until it fixed [@MickLesk](https://github.com/MickLesk) ([#2768](https://github.com/community-scripts/ProxmoxVE/pull/2768))\n\n### 🧰 Maintenance\n\n  - #### 🐞 Bug Fixes\n\n    - Remove Note on Changelog [@MickLesk](https://github.com/MickLesk) ([#2758](https://github.com/community-scripts/ProxmoxVE/pull/2758))\n    - Fix Release Creation if Changelog.md to long [@MickLesk](https://github.com/MickLesk) ([#2752](https://github.com/community-scripts/ProxmoxVE/pull/2752))\n"
  },
  {
    "path": ".github/changelogs/2025/04.md",
    "content": "﻿## 2025-04-30\n\n### 🚀 Updated Scripts\n\n  - Refactor: Matterbridge [@MickLesk](https://github.com/MickLesk) ([#4148](https://github.com/community-scripts/ProxmoxVE/pull/4148))\n- Refactor: Ollama & Adding to Website [@MickLesk](https://github.com/MickLesk) ([#4147](https://github.com/community-scripts/ProxmoxVE/pull/4147))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - mark Caddy as updateable [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4154](https://github.com/community-scripts/ProxmoxVE/pull/4154))\n    - Website: Add missing docs and config paths [@tremor021](https://github.com/tremor021) ([#4131](https://github.com/community-scripts/ProxmoxVE/pull/4131))\n\n## 2025-04-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Umlautadaptarr Service File [@MickLesk](https://github.com/MickLesk) ([#4124](https://github.com/community-scripts/ProxmoxVE/pull/4124))\n    - CheckMK added filter to not install beta versions [@briodan](https://github.com/briodan) ([#4118](https://github.com/community-scripts/ProxmoxVE/pull/4118))\n\n  - #### 🔧 Refactor\n\n    - Refactor: sabnzbd [@MickLesk](https://github.com/MickLesk) ([#4127](https://github.com/community-scripts/ProxmoxVE/pull/4127))\n    - Refactor: Navidrome [@MickLesk](https://github.com/MickLesk) ([#4120](https://github.com/community-scripts/ProxmoxVE/pull/4120))\n\n### 🧰 Maintenance\n\n  - #### ✨ New Features\n\n    - core: add setup_uv() function to automate installation and updating of uv [@MickLesk](https://github.com/MickLesk) ([#4129](https://github.com/community-scripts/ProxmoxVE/pull/4129))\n    - core: persist /usr/local/bin via profile.d helper [@MickLesk](https://github.com/MickLesk) ([#4133](https://github.com/community-scripts/ProxmoxVE/pull/4133))\n\n  - #### 📝 Documentation\n\n    - SECURITY.md: add pve 8.4 as supported [@MickLesk](https://github.com/MickLesk) ([#4123](https://github.com/community-scripts/ProxmoxVE/pull/4123))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Feat: Random Script picker for Website [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4090](https://github.com/community-scripts/ProxmoxVE/pull/4090))\n\n## 2025-04-28\n\n### 🆕 New Scripts\n\n  - umlautadaptarr ([#4093](https://github.com/community-scripts/ProxmoxVE/pull/4093))\n- documenso ([#4080](https://github.com/community-scripts/ProxmoxVE/pull/4080))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Install rsync in ct/commafeed.sh [@mzhaodev](https://github.com/mzhaodev) ([#4086](https://github.com/community-scripts/ProxmoxVE/pull/4086))\n    - fstrim: cancel/no whiptail support [@PonyXplosion](https://github.com/PonyXplosion) ([#4101](https://github.com/community-scripts/ProxmoxVE/pull/4101))\n    - clean-lxc.sh: cancel/no whiptail support [@PonyXplosion](https://github.com/PonyXplosion) ([#4102](https://github.com/community-scripts/ProxmoxVE/pull/4102))\n\n  - #### ✨ New Features\n\n    - karakeep: add cli and mcp build commands [@vhsdream](https://github.com/vhsdream) ([#4112](https://github.com/community-scripts/ProxmoxVE/pull/4112))\n    - Make apt-cacher-ng a client of its own server [@pgcudahy](https://github.com/pgcudahy) ([#4092](https://github.com/community-scripts/ProxmoxVE/pull/4092))\n\n### 🧰 Maintenance\n\n  - #### ✨ New Features\n\n    - Add: tools.func [@MickLesk](https://github.com/MickLesk) ([#4100](https://github.com/community-scripts/ProxmoxVE/pull/4100))\n\n  - #### 💾 Core\n\n    - core: remove unneeded logging [@MickLesk](https://github.com/MickLesk) ([#4103](https://github.com/community-scripts/ProxmoxVE/pull/4103))\n\n### 🌐 Website\n\n  - Website: Fix frontend path in footer [@tremor021](https://github.com/tremor021) ([#4108](https://github.com/community-scripts/ProxmoxVE/pull/4108))\n\n  - #### 📝 Script Information\n\n    - GoMFT: Move configuration info into config_path [@tremor021](https://github.com/tremor021) ([#4106](https://github.com/community-scripts/ProxmoxVE/pull/4106))\n\n## 2025-04-27\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Re-Add DeamonSync Package [@MickLesk](https://github.com/MickLesk) ([#4079](https://github.com/community-scripts/ProxmoxVE/pull/4079))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - add default configuration file location for Caddy LXC  [@aly-yvette](https://github.com/aly-yvette) ([#4074](https://github.com/community-scripts/ProxmoxVE/pull/4074))\n\n## 2025-04-26\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Element Synapse: Fix install script cd command error [@thegeorgeliu](https://github.com/thegeorgeliu) ([#4066](https://github.com/community-scripts/ProxmoxVE/pull/4066))\n\n## 2025-04-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Element Synapse: Fix update for older versions [@tremor021](https://github.com/tremor021) ([#4050](https://github.com/community-scripts/ProxmoxVE/pull/4050))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Only show update source when app is marked updateable [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4059](https://github.com/community-scripts/ProxmoxVE/pull/4059))\n\n  - #### 📝 Script Information\n\n    - Filebrowser: Add Category Files & Donwloads [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4055](https://github.com/community-scripts/ProxmoxVE/pull/4055))\n\n### 💥 Breaking Changes\n\n  - Removal of Seafile due to recurring problems [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4058](https://github.com/community-scripts/ProxmoxVE/pull/4058))\n\n## 2025-04-24\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Element Synapse: Fix Admin UI install and update procedure [@tremor021](https://github.com/tremor021) ([#4041](https://github.com/community-scripts/ProxmoxVE/pull/4041))\n    - SLSKD: always check for soularr update [@vhsdream](https://github.com/vhsdream) ([#4012](https://github.com/community-scripts/ProxmoxVE/pull/4012))\n\n  - #### ✨ New Features\n\n    - Element Synapse: Add Synapse-Admin web UI to manage Matrix [@tremor021](https://github.com/tremor021) ([#4010](https://github.com/community-scripts/ProxmoxVE/pull/4010))\n    - Pi-Hole: Fix Unbound update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4026](https://github.com/community-scripts/ProxmoxVE/pull/4026))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Feat: Add config path to website [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4005](https://github.com/community-scripts/ProxmoxVE/pull/4005))\n\n  - #### 📝 Script Information\n\n    - Jellyfin: Mark as updateable [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4029](https://github.com/community-scripts/ProxmoxVE/pull/4029))\n    - Prepare JSON files for new website feature [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4004](https://github.com/community-scripts/ProxmoxVE/pull/4004))\n\n## 2025-04-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zipline: Add new ENV Variable and Change Update [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3997](https://github.com/community-scripts/ProxmoxVE/pull/3997))\n    - karakeep: use nightly channel for yt-dlp [@vhsdream](https://github.com/vhsdream) ([#3992](https://github.com/community-scripts/ProxmoxVE/pull/3992))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Fix Workflow to close discussions [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3999](https://github.com/community-scripts/ProxmoxVE/pull/3999))\n\n## 2025-04-22\n\n### 🆕 New Scripts\n\n  - reactive-resume ([#3980](https://github.com/community-scripts/ProxmoxVE/pull/3980))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - wger: Fix a bug in update procedure and general code maintenance [@tremor021](https://github.com/tremor021) ([#3974](https://github.com/community-scripts/ProxmoxVE/pull/3974))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Add workflow to close ttek Repo relatate issues [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3981](https://github.com/community-scripts/ProxmoxVE/pull/3981))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Fix Turnkey Source Link in Button Component [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3978](https://github.com/community-scripts/ProxmoxVE/pull/3978))\n\n  - #### 📝 Script Information\n\n    - qBittorrent: Update web page [@tremor021](https://github.com/tremor021) ([#3969](https://github.com/community-scripts/ProxmoxVE/pull/3969))\n\n## 2025-04-19\n\n### 🆕 New Scripts\n\n  - LXC Iptag [@DesertGamer](https://github.com/DesertGamer) ([#3531](https://github.com/community-scripts/ProxmoxVE/pull/3531))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - seelf: Add missing gpg dependency [@tremor021](https://github.com/tremor021) ([#3953](https://github.com/community-scripts/ProxmoxVE/pull/3953))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Tailscale: Clarify tailscale script instruction on website [@tremor021](https://github.com/tremor021) ([#3952](https://github.com/community-scripts/ProxmoxVE/pull/3952))\n\n## 2025-04-18\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Changedetection: Increase connection timeout for older systems [@tremor021](https://github.com/tremor021) ([#3935](https://github.com/community-scripts/ProxmoxVE/pull/3935))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - VaultWarden: Update json with additonal information [@uSlackr](https://github.com/uSlackr) ([#3929](https://github.com/community-scripts/ProxmoxVE/pull/3929))\n\n## 2025-04-17\n\n### 🚀 Updated Scripts\n\n  - fix minor grammatical error in several scripts [@jordanpatton](https://github.com/jordanpatton) ([#3921](https://github.com/community-scripts/ProxmoxVE/pull/3921))\n\n  - #### 🐞 Bug Fixes\n\n    - PeaNUT: Fix tar command [@tremor021](https://github.com/tremor021) ([#3925](https://github.com/community-scripts/ProxmoxVE/pull/3925))\n    - GoMFT: Fix install and update process (final time) [@tremor021](https://github.com/tremor021) ([#3922](https://github.com/community-scripts/ProxmoxVE/pull/3922))\n\n## 2025-04-15\n\n### 🚀 Updated Scripts\n\n  - [core] remove unneeded vars from shellcheck [@MickLesk](https://github.com/MickLesk) ([#3899](https://github.com/community-scripts/ProxmoxVE/pull/3899))\n\n  - #### 🐞 Bug Fixes\n\n    - Outline: Installation and update fixes [@tremor021](https://github.com/tremor021) ([#3895](https://github.com/community-scripts/ProxmoxVE/pull/3895))\n    - SABnzbd: Fix update error caused by externaly managed message [@tremor021](https://github.com/tremor021) ([#3892](https://github.com/community-scripts/ProxmoxVE/pull/3892))\n\n### 🧰 Maintenance\n\n  - #### 📡 API\n\n    - Bump golang.org/x/crypto from 0.32.0 to 0.35.0 in /api [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3887](https://github.com/community-scripts/ProxmoxVE/pull/3887))\n\n  - #### 📂 Github\n\n    - shrink & minimalize report templates [@MickLesk](https://github.com/MickLesk) ([#3902](https://github.com/community-scripts/ProxmoxVE/pull/3902))\n\n## 2025-04-14\n\n### 🆕 New Scripts\n\n  - openziti-controller ([#3880](https://github.com/community-scripts/ProxmoxVE/pull/3880))\n- Alpine-AdGuardHome [@MickLesk](https://github.com/MickLesk) ([#3875](https://github.com/community-scripts/ProxmoxVE/pull/3875))\n\n### 🚀 Updated Scripts\n\n  - Paymenter: bump php to 8.3 [@opastorello](https://github.com/opastorello) ([#3825](https://github.com/community-scripts/ProxmoxVE/pull/3825))\n\n  - #### 🐞 Bug Fixes\n\n    - Neo4j: Add Java dependency [@tremor021](https://github.com/tremor021) ([#3871](https://github.com/community-scripts/ProxmoxVE/pull/3871))\n\n  - #### 🔧 Refactor\n\n    - Refactor Cockpit update_script part [@MickLesk](https://github.com/MickLesk) ([#3878](https://github.com/community-scripts/ProxmoxVE/pull/3878))\n\n## 2025-04-12\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Add \"Not Updateable\" tooltip to scripts [@BramSuurdje](https://github.com/BramSuurdje) ([#3852](https://github.com/community-scripts/ProxmoxVE/pull/3852))\n\n## 2025-04-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - slskd: fix missing -o for curl [@MickLesk](https://github.com/MickLesk) ([#3828](https://github.com/community-scripts/ProxmoxVE/pull/3828))\n    - 2FAuth: Fix php dependencies [@tremor021](https://github.com/tremor021) ([#3820](https://github.com/community-scripts/ProxmoxVE/pull/3820))\n    - Komodo: Update Repository link [@sendyputra](https://github.com/sendyputra) ([#3823](https://github.com/community-scripts/ProxmoxVE/pull/3823))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - Enlarge the size of the menu in build.func [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3833](https://github.com/community-scripts/ProxmoxVE/pull/3833))\n\n### 🌐 Website\n\n  - Bump vite from 6.2.5 to 6.2.6 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3842](https://github.com/community-scripts/ProxmoxVE/pull/3842))\n\n  - #### 📝 Script Information\n\n    - SQLServer: fix some typos in notes [@stiny861](https://github.com/stiny861) ([#3838](https://github.com/community-scripts/ProxmoxVE/pull/3838))\n    - Radicale: move to misc category [@tremor021](https://github.com/tremor021) ([#3830](https://github.com/community-scripts/ProxmoxVE/pull/3830))\n\n## 2025-04-10\n\n### 🆕 New Scripts\n\n  - openproject ([#3637](https://github.com/community-scripts/ProxmoxVE/pull/3637))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix: NodeJS Check (Tianji/Docmost) [@MickLesk](https://github.com/MickLesk) ([#3813](https://github.com/community-scripts/ProxmoxVE/pull/3813))\n\n  - #### ✨ New Features\n\n    - change var in ct files to new standard [@MickLesk](https://github.com/MickLesk) ([#3804](https://github.com/community-scripts/ProxmoxVE/pull/3804))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - New Feature: Config File [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3808](https://github.com/community-scripts/ProxmoxVE/pull/3808))\n\n### 💥 Breaking Changes\n\n  - Remove Actualbudget [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3801](https://github.com/community-scripts/ProxmoxVE/pull/3801))\n\n## 2025-04-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Paperless-NGX: Extend Granian Service Env [@MickLesk](https://github.com/MickLesk) ([#3790](https://github.com/community-scripts/ProxmoxVE/pull/3790))\n    - Paperless-NGX: remove gunicorn, use python3 for webserver [@MickLesk](https://github.com/MickLesk) ([#3785](https://github.com/community-scripts/ProxmoxVE/pull/3785))\n    - HomeAssistantOS: allow Proxmox version 8.4 [@quentinvnk](https://github.com/quentinvnk) ([#3773](https://github.com/community-scripts/ProxmoxVE/pull/3773))\n    - Tandoor: Add xmlsec as dependency [@tremor021](https://github.com/tremor021) ([#3762](https://github.com/community-scripts/ProxmoxVE/pull/3762))\n\n  - #### 🔧 Refactor\n\n    - harmonize pve versions check & vm vars [@MickLesk](https://github.com/MickLesk) ([#3779](https://github.com/community-scripts/ProxmoxVE/pull/3779))\n\n### 🧰 Maintenance\n\n  - #### 💥 Breaking Changes\n\n    - core: Removal of OS/Version Selection from Advanced Settings [@MickLesk](https://github.com/MickLesk) ([#3771](https://github.com/community-scripts/ProxmoxVE/pull/3771))\n    - core: move misc scripts to structured addon/pve paths | Refactor JSON Editor & Script Mapping [@MickLesk](https://github.com/MickLesk) ([#3765](https://github.com/community-scripts/ProxmoxVE/pull/3765))\n\n## 2025-04-08\n\n### 🆕 New Scripts\n\n  - Alpine-PostgreSQL [@MickLesk](https://github.com/MickLesk) ([#3751](https://github.com/community-scripts/ProxmoxVE/pull/3751))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-Wireguard: Fix for sysctl and ip_forward [@juronja](https://github.com/juronja) ([#3744](https://github.com/community-scripts/ProxmoxVE/pull/3744))\n    - TriliumNext: fix dump-db [@MickLesk](https://github.com/MickLesk) ([#3741](https://github.com/community-scripts/ProxmoxVE/pull/3741))\n    - Actual: Reduce RAM to 4GB and old space to 3072MB  [@dannyellis](https://github.com/dannyellis) ([#3730](https://github.com/community-scripts/ProxmoxVE/pull/3730))\n\n  - #### ✨ New Features\n\n    - Alpine-MariaDB: better handling of adminer installation [@MickLesk](https://github.com/MickLesk) ([#3739](https://github.com/community-scripts/ProxmoxVE/pull/3739))\n    - Paperless-GPT: Add logging to service file [@tremor021](https://github.com/tremor021) ([#3738](https://github.com/community-scripts/ProxmoxVE/pull/3738))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Meilisearch: Fix Typo [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3749](https://github.com/community-scripts/ProxmoxVE/pull/3749))\n\n## 2025-04-07\n\n### 🆕 New Scripts\n\n  - Breaking: Hoarder > Karakeep [@MickLesk](https://github.com/MickLesk) ([#3699](https://github.com/community-scripts/ProxmoxVE/pull/3699))\n\n### 🚀 Updated Scripts\n\n  - Actual: Increase RAM and add heap-space var for nodejs [@MickLesk](https://github.com/MickLesk) ([#3713](https://github.com/community-scripts/ProxmoxVE/pull/3713))\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-MariaDB: Fix Install Service startup [@MickLesk](https://github.com/MickLesk) ([#3701](https://github.com/community-scripts/ProxmoxVE/pull/3701))\n    - Zitadel: Fix release tarball crawling [@tremor021](https://github.com/tremor021) ([#3716](https://github.com/community-scripts/ProxmoxVE/pull/3716))\n\n  - #### ✨ New Features\n\n    - Kimai: bump php to 8.4 [@MickLesk](https://github.com/MickLesk) ([#3724](https://github.com/community-scripts/ProxmoxVE/pull/3724))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Zabbix, get always latest version [@MickLesk](https://github.com/MickLesk) ([#3720](https://github.com/community-scripts/ProxmoxVE/pull/3720))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Changed the category of Channels DVR and NextPVR [@johnsturgeon](https://github.com/johnsturgeon) ([#3729](https://github.com/community-scripts/ProxmoxVE/pull/3729))\n\n## 2025-04-06\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Actual: Add git dependency & change yarn commands [@MickLesk](https://github.com/MickLesk) ([#3703](https://github.com/community-scripts/ProxmoxVE/pull/3703))\n    - Pelican-Panel: Fix PHP 8.4 Repository [@MickLesk](https://github.com/MickLesk) ([#3700](https://github.com/community-scripts/ProxmoxVE/pull/3700))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Implement FAQ component and integrate it into the main page [@BramSuurdje](https://github.com/BramSuurdje) ([#3709](https://github.com/community-scripts/ProxmoxVE/pull/3709))\n\n## 2025-04-05\n\n### 🌐 Website\n\n  - Bump vite from 6.2.4 to 6.2.5 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3668](https://github.com/community-scripts/ProxmoxVE/pull/3668))\n\n## 2025-04-04\n\n### 🆕 New Scripts\n\n  - meilisearch [@MickLesk](https://github.com/MickLesk) ([#3638](https://github.com/community-scripts/ProxmoxVE/pull/3638))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pelican Panel: Bump php to 8.4 [@bvdberg01](https://github.com/bvdberg01) ([#3669](https://github.com/community-scripts/ProxmoxVE/pull/3669))\n    - Pterodactyl: Bump php to 8.4 [@MickLesk](https://github.com/MickLesk) ([#3655](https://github.com/community-scripts/ProxmoxVE/pull/3655))\n\n  - #### ✨ New Features\n\n    - Caddy: add git for xcaddy [@MickLesk](https://github.com/MickLesk) ([#3657](https://github.com/community-scripts/ProxmoxVE/pull/3657))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - core: fix raw path  [@MickLesk](https://github.com/MickLesk) ([#3656](https://github.com/community-scripts/ProxmoxVE/pull/3656))\n\n## 2025-04-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Prowlarr: Fix Typo in URL (update_function) [@ribera96](https://github.com/ribera96) ([#3640](https://github.com/community-scripts/ProxmoxVE/pull/3640))\n    - Prowlarr: Fix typo in release URL [@tremor021](https://github.com/tremor021) ([#3636](https://github.com/community-scripts/ProxmoxVE/pull/3636))\n    - GoMFT: Fix the node_modules deletion command [@tremor021](https://github.com/tremor021) ([#3624](https://github.com/community-scripts/ProxmoxVE/pull/3624))\n    - BookStack: Fix path to downloaded release file [@tremor021](https://github.com/tremor021) ([#3627](https://github.com/community-scripts/ProxmoxVE/pull/3627))\n\n  - #### ✨ New Features\n\n    - VM: show progress bar while downloading [@MickLesk](https://github.com/MickLesk) ([#3634](https://github.com/community-scripts/ProxmoxVE/pull/3634))\n    - *Arr: Move Arr apps to github release crawling and provide update functionality [@tremor021](https://github.com/tremor021) ([#3625](https://github.com/community-scripts/ProxmoxVE/pull/3625))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Correct URL in contributing docs [@verbumfeit](https://github.com/verbumfeit) ([#3648](https://github.com/community-scripts/ProxmoxVE/pull/3648))\n\n### 🌐 Website\n\n  - Bump next from 15.2.3 to 15.2.4 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3628](https://github.com/community-scripts/ProxmoxVE/pull/3628))\n\n  - #### 📝 Script Information\n\n    - slskd: fix typo for config note [@MickLesk](https://github.com/MickLesk) ([#3633](https://github.com/community-scripts/ProxmoxVE/pull/3633))\n\n## 2025-04-02\n\n### 🆕 New Scripts\n\n  - openziti-tunnel [@emoscardini](https://github.com/emoscardini) ([#3610](https://github.com/community-scripts/ProxmoxVE/pull/3610))\n- Alpine-Wireguard [@MickLesk](https://github.com/MickLesk) ([#3611](https://github.com/community-scripts/ProxmoxVE/pull/3611))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Authelia: fix incorrect rights for email.txt [@MickLesk](https://github.com/MickLesk) ([#3612](https://github.com/community-scripts/ProxmoxVE/pull/3612))\n    - Photoprism: harmonize curl  [@MickLesk](https://github.com/MickLesk) ([#3601](https://github.com/community-scripts/ProxmoxVE/pull/3601))\n    - Fix link in clean-lxcs.sh [@thalatamsainath](https://github.com/thalatamsainath) ([#3593](https://github.com/community-scripts/ProxmoxVE/pull/3593))\n    - Fileflows: Add ImageMagick dependecy [@tremor021](https://github.com/tremor021) ([#3589](https://github.com/community-scripts/ProxmoxVE/pull/3589))\n    - General fixes for several scripts [@tremor021](https://github.com/tremor021) ([#3587](https://github.com/community-scripts/ProxmoxVE/pull/3587))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - UI-Fix: verbose without useless space in header [@MickLesk](https://github.com/MickLesk) ([#3598](https://github.com/community-scripts/ProxmoxVE/pull/3598))\n\n## 2025-04-01\n\n### 🆕 New Scripts\n\n  - Alpine Prometheus [@MickLesk](https://github.com/MickLesk) ([#3547](https://github.com/community-scripts/ProxmoxVE/pull/3547))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Flaresolverr: Fix curl command [@tremor021](https://github.com/tremor021) ([#3583](https://github.com/community-scripts/ProxmoxVE/pull/3583))\n    - Authentik - Fix YQ_LATEST regex [@ceres-c](https://github.com/ceres-c) ([#3565](https://github.com/community-scripts/ProxmoxVE/pull/3565))\n    - Fileflows: Fix update dependencies [@tremor021](https://github.com/tremor021) ([#3577](https://github.com/community-scripts/ProxmoxVE/pull/3577))\n    - CheckMK: Increase Disk size [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3559](https://github.com/community-scripts/ProxmoxVE/pull/3559))\n    - switch arr lxc's (lidarr,-prowlarr,-radarr,-readarr,-whisparr) to curl -fsSL [@MickLesk](https://github.com/MickLesk) ([#3554](https://github.com/community-scripts/ProxmoxVE/pull/3554))\n\n  - #### 💥 Breaking Changes\n\n    - Replace wget with curl -fsSL, normalize downloads, and prep for IPv6 [@MickLesk](https://github.com/MickLesk) ([#3455](https://github.com/community-scripts/ProxmoxVE/pull/3455))\n\n  - #### 🔧 Refactor\n\n    - Fixes and standard enforcement [@tremor021](https://github.com/tremor021) ([#3564](https://github.com/community-scripts/ProxmoxVE/pull/3564))\n\n### 🌐 Website\n\n  - Update metadata inside layout.tsx for better SEO [@BramSuurdje](https://github.com/BramSuurdje) ([#3570](https://github.com/community-scripts/ProxmoxVE/pull/3570))\n- Bump vite from 5.4.14 to 5.4.16 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3549](https://github.com/community-scripts/ProxmoxVE/pull/3549))\n\n  - #### ✨ New Features\n\n    - Refactor ScriptItem and Buttons components to enhance layout and integrate dropdown for links. Update InterFaces component for improved styling and structure. [@BramSuurdje](https://github.com/BramSuurdje) ([#3567](https://github.com/community-scripts/ProxmoxVE/pull/3567))\n"
  },
  {
    "path": ".github/changelogs/2025/05.md",
    "content": "﻿## 2025-05-31\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Silverbullet: Fix Installation (wrong path) [@MickLesk](https://github.com/MickLesk) ([#4873](https://github.com/community-scripts/ProxmoxVE/pull/4873))\n    - ActualBudget: fix update check (file instead of folder check) [@MickLesk](https://github.com/MickLesk) ([#4874](https://github.com/community-scripts/ProxmoxVE/pull/4874))\n    - Omada Controller: Fix libssl url [@tremor021](https://github.com/tremor021) ([#4868](https://github.com/community-scripts/ProxmoxVE/pull/4868))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Revert \"Update package dependencies in package.json and package-lock.json (#4845) [@BramSuurdje](https://github.com/BramSuurdje) ([#4869](https://github.com/community-scripts/ProxmoxVE/pull/4869))\n\n### 💥 Breaking Changes\n\n  - Remove Authentik script [@tremor021](https://github.com/tremor021) ([#4867](https://github.com/community-scripts/ProxmoxVE/pull/4867))\n\n## 2025-05-30\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - slskd: add space in sed command [@vhsdream](https://github.com/vhsdream) ([#4853](https://github.com/community-scripts/ProxmoxVE/pull/4853))\n    - Alpine Traefik: Fix working directory and plugins [@tremor021](https://github.com/tremor021) ([#4838](https://github.com/community-scripts/ProxmoxVE/pull/4838))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Update package dependencies in package.json and package-lock.json [@enough-jainil](https://github.com/enough-jainil) ([#4845](https://github.com/community-scripts/ProxmoxVE/pull/4845))\n\n## 2025-05-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - SearXNG fix limiter [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4834](https://github.com/community-scripts/ProxmoxVE/pull/4834))\n    - Docmost: add jq before nodejs install [@MickLesk](https://github.com/MickLesk) ([#4831](https://github.com/community-scripts/ProxmoxVE/pull/4831))\n    - Alpine Traefik: Fix Dashboard not beign accessible [@tremor021](https://github.com/tremor021) ([#4828](https://github.com/community-scripts/ProxmoxVE/pull/4828))\n    - MySQL: Fix Wrong Command [@MickLesk](https://github.com/MickLesk) ([#4820](https://github.com/community-scripts/ProxmoxVE/pull/4820))\n    - docs: fix casing of OpenWrt [@GoetzGoerisch](https://github.com/GoetzGoerisch) ([#4805](https://github.com/community-scripts/ProxmoxVE/pull/4805))\n\n  - #### ✨ New Features\n\n    - Docker-VM: set individual Hostname / Disk-Space formatting [@MickLesk](https://github.com/MickLesk) ([#4821](https://github.com/community-scripts/ProxmoxVE/pull/4821))\n\n## 2025-05-28\n\n### 🆕 New Scripts\n\n  - Umbrel-OS [@MickLesk](https://github.com/MickLesk) ([#4788](https://github.com/community-scripts/ProxmoxVE/pull/4788))\n- oauth2-proxy ([#4784](https://github.com/community-scripts/ProxmoxVE/pull/4784))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Habitica: Use Node20 [@MickLesk](https://github.com/MickLesk) ([#4796](https://github.com/community-scripts/ProxmoxVE/pull/4796))\n    - Alpine-Node-RED: add service to rc [@MickLesk](https://github.com/MickLesk) ([#4783](https://github.com/community-scripts/ProxmoxVE/pull/4783))\n\n  - #### ✨ New Features\n\n    - Pulse: use prebuild tarball file / remove unneeded npm actions [@MickLesk](https://github.com/MickLesk) ([#4776](https://github.com/community-scripts/ProxmoxVE/pull/4776))\n\n  - #### 💥 Breaking Changes\n\n    - Refactor: Linkwarden + OS Upgrade [@MickLesk](https://github.com/MickLesk) ([#4756](https://github.com/community-scripts/ProxmoxVE/pull/4756))\n\n  - #### 🔧 Refactor\n\n    - refactor: use binary and randomize credentials in tinyauth [@steveiliop56](https://github.com/steveiliop56) ([#4632](https://github.com/community-scripts/ProxmoxVE/pull/4632))\n    - MariaDB CLI Update, Go Install Helper & Minor Cleanup [@MickLesk](https://github.com/MickLesk) ([#4793](https://github.com/community-scripts/ProxmoxVE/pull/4793))\n    - Refactor: Remove redundant dependencies & unify unzip usage [@MickLesk](https://github.com/MickLesk) ([#4780](https://github.com/community-scripts/ProxmoxVE/pull/4780))\n    - Refactor: Remove gpg / gnupg from script base [@MickLesk](https://github.com/MickLesk) ([#4775](https://github.com/community-scripts/ProxmoxVE/pull/4775))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - pulse: correct url in note [@xb00tt](https://github.com/xb00tt) ([#4809](https://github.com/community-scripts/ProxmoxVE/pull/4809))\n\n## 2025-05-27\n\n### 🆕 New Scripts\n\n  - Backrest ([#4766](https://github.com/community-scripts/ProxmoxVE/pull/4766))\n- Pulse ([#4728](https://github.com/community-scripts/ProxmoxVE/pull/4728))\n\n### 🚀 Updated Scripts\n\n  - Alpine-Vaultwarden: Increase min disk requirements to 1GB [@neyzm](https://github.com/neyzm) ([#4764](https://github.com/community-scripts/ProxmoxVE/pull/4764))\n\n  - #### 🐞 Bug Fixes\n\n    - lldap: fix update-check [@MickLesk](https://github.com/MickLesk) ([#4742](https://github.com/community-scripts/ProxmoxVE/pull/4742))\n\n  - #### ✨ New Features\n\n    - Big NodeJS Update: Use Helper Function on all Install-Scripts [@MickLesk](https://github.com/MickLesk) ([#4744](https://github.com/community-scripts/ProxmoxVE/pull/4744))\n\n  - #### 🔧 Refactor\n\n    - merge MariaDB to tools.func Installer [@MickLesk](https://github.com/MickLesk) ([#4753](https://github.com/community-scripts/ProxmoxVE/pull/4753))\n    - merge PostgreSQL to tools.func Installer [@MickLesk](https://github.com/MickLesk) ([#4752](https://github.com/community-scripts/ProxmoxVE/pull/4752))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Increase default RAM allocation for BunkerWeb to 8192MB [@TheophileDiot](https://github.com/TheophileDiot) ([#4762](https://github.com/community-scripts/ProxmoxVE/pull/4762))\n\n## 2025-05-26\n\n### 🆕 New Scripts\n\n  - Argus ([#4717](https://github.com/community-scripts/ProxmoxVE/pull/4717))\n- Kasm ([#4716](https://github.com/community-scripts/ProxmoxVE/pull/4716))\n\n### 🚀 Updated Scripts\n\n  - Excalidraw: increase HDD to 10GB [@MickLesk](https://github.com/MickLesk) ([#4718](https://github.com/community-scripts/ProxmoxVE/pull/4718))\n\n  - #### 🐞 Bug Fixes\n\n    - BREAKING CHANGE: Fix PocketID for v1.0.0 [@vhsdream](https://github.com/vhsdream) ([#4711](https://github.com/community-scripts/ProxmoxVE/pull/4711))\n    - InspIRCd: Fix release name in release url [@tremor021](https://github.com/tremor021) ([#4720](https://github.com/community-scripts/ProxmoxVE/pull/4720))\n\n## 2025-05-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pelican-panel: back-up database if using sqlite [@bvdberg01](https://github.com/bvdberg01) ([#4700](https://github.com/community-scripts/ProxmoxVE/pull/4700))\n\n  - #### 🔧 Refactor\n\n    - Pterodactyl panel read typo [@bvdberg01](https://github.com/bvdberg01) ([#4701](https://github.com/community-scripts/ProxmoxVE/pull/4701))\n\n## 2025-05-24\n\n\n\n## 2025-05-23\n\n### 🚀 Updated Scripts\n\n  - #### 🔧 Refactor\n\n    - TYPO: Fix nexcloud to nextcloud (VM) [@Stoufiler](https://github.com/Stoufiler) ([#4670](https://github.com/community-scripts/ProxmoxVE/pull/4670))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update Icons to selfhst/icons (FreePBX & Configarr) [@MickLesk](https://github.com/MickLesk) ([#4680](https://github.com/community-scripts/ProxmoxVE/pull/4680))\n\n### 💥 Breaking Changes\n\n  - Remove rtsptoweb (deprecated) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4686](https://github.com/community-scripts/ProxmoxVE/pull/4686))\n\n## 2025-05-22\n\n### 🆕 New Scripts\n\n  - FreePBX ([#4648](https://github.com/community-scripts/ProxmoxVE/pull/4648))\n- cloudflare-ddns ([#4647](https://github.com/community-scripts/ProxmoxVE/pull/4647))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - slskd: fix #4649 [@vhsdream](https://github.com/vhsdream) ([#4651](https://github.com/community-scripts/ProxmoxVE/pull/4651))\n\n  - #### ✨ New Features\n\n    - Paperless-AI: Add RAG chat [@tremor021](https://github.com/tremor021) ([#4635](https://github.com/community-scripts/ProxmoxVE/pull/4635))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - [gh]: Feature: Header-Generation for vm | tools | addon [@MickLesk](https://github.com/MickLesk) ([#4643](https://github.com/community-scripts/ProxmoxVE/pull/4643))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Commafeed: move to Documents category [@diemade](https://github.com/diemade) ([#4665](https://github.com/community-scripts/ProxmoxVE/pull/4665))\n\n## 2025-05-21\n\n### 🆕 New Scripts\n\n  - configarr ([#4620](https://github.com/community-scripts/ProxmoxVE/pull/4620))\n- babybuddy ([#4619](https://github.com/community-scripts/ProxmoxVE/pull/4619))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-Node-RED: Update Service File [@MickLesk](https://github.com/MickLesk) ([#4628](https://github.com/community-scripts/ProxmoxVE/pull/4628))\n    - RustDesk Server: Fix update for older installs [@tremor021](https://github.com/tremor021) ([#4612](https://github.com/community-scripts/ProxmoxVE/pull/4612))\n\n  - #### ✨ New Features\n\n    - Tandoor Recipes: Capture version information when installing [@jbolla](https://github.com/jbolla) ([#4633](https://github.com/community-scripts/ProxmoxVE/pull/4633))\n\n## 2025-05-20\n\n### 🚀 Updated Scripts\n\n  - [tools.func]: Update fetch_and_deploy_gh_release function [@tremor021](https://github.com/tremor021) ([#4605](https://github.com/community-scripts/ProxmoxVE/pull/4605))\n- [core] New Features for Config File function [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4601](https://github.com/community-scripts/ProxmoxVE/pull/4601))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Website: harmonize all Logos | use jsDelivr CDN links for icons from selfhst/icons repo [@MickLesk](https://github.com/MickLesk) ([#4603](https://github.com/community-scripts/ProxmoxVE/pull/4603))\n\n## 2025-05-19\n\n### 🆕 New Scripts\n\n  - rclone ([#4579](https://github.com/community-scripts/ProxmoxVE/pull/4579))\n\n### 🚀 Updated Scripts\n\n  - increase ressources of Homarr (3 vCPU / 6GB RAM) [@MickLesk](https://github.com/MickLesk) ([#4583](https://github.com/community-scripts/ProxmoxVE/pull/4583))\n\n  - #### 🐞 Bug Fixes\n\n    - Various unrelated fixes to kimai update script [@jamezpolley](https://github.com/jamezpolley) ([#4549](https://github.com/community-scripts/ProxmoxVE/pull/4549))\n\n  - #### ✨ New Features\n\n    - RustDesk Server: Add WebUI [@tremor021](https://github.com/tremor021) ([#4590](https://github.com/community-scripts/ProxmoxVE/pull/4590))\n\n## 2025-05-18\n\n### 🚀 Updated Scripts\n\n  - tools.func - Add function to create self-signed certificates [@tremor021](https://github.com/tremor021) ([#4562](https://github.com/community-scripts/ProxmoxVE/pull/4562))\n\n  - #### 🐞 Bug Fixes\n\n    - Homarr: fix the build [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4569](https://github.com/community-scripts/ProxmoxVE/pull/4569))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Fix Dashy Config Path on Frontend [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4570](https://github.com/community-scripts/ProxmoxVE/pull/4570))\n\n## 2025-05-17\n\n\n\n## 2025-05-16\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - [core] Refactor Config File function [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4528](https://github.com/community-scripts/ProxmoxVE/pull/4528))\n    - [core] Fix Bridge detection in Advanced Mode [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4522](https://github.com/community-scripts/ProxmoxVE/pull/4522))\n    - [core] Enable SSH_KEY and SSH without password [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4523](https://github.com/community-scripts/ProxmoxVE/pull/4523))\n\n  - #### 📂 Github\n\n    - Updates to contributor docs/guide [@tremor021](https://github.com/tremor021) ([#4518](https://github.com/community-scripts/ProxmoxVE/pull/4518))\n\n### 🌐 Website\n\n  - Remove bolt.diy script [@tremor021](https://github.com/tremor021) ([#4541](https://github.com/community-scripts/ProxmoxVE/pull/4541))\n\n## 2025-05-15\n\n### 🆕 New Scripts\n\n  - bitmagnet ([#4493](https://github.com/community-scripts/ProxmoxVE/pull/4493))\n\n### 🚀 Updated Scripts\n\n  - core: Add TAB3 formatting var to core [@tremor021](https://github.com/tremor021) ([#4496](https://github.com/community-scripts/ProxmoxVE/pull/4496))\n- Update scripts that use \"read -p\" to properly indent text [@tremor021](https://github.com/tremor021) ([#4498](https://github.com/community-scripts/ProxmoxVE/pull/4498))\n\n  - #### ✨ New Features\n\n    - tools.func: fix some things & add ruby default function [@MickLesk](https://github.com/MickLesk) ([#4507](https://github.com/community-scripts/ProxmoxVE/pull/4507))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - core: fix bridge detection for OVS  [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4495](https://github.com/community-scripts/ProxmoxVE/pull/4495))\n\n## 2025-05-14\n\n### 🆕 New Scripts\n\n  - odoo ([#4477](https://github.com/community-scripts/ProxmoxVE/pull/4477))\n- asterisk ([#4468](https://github.com/community-scripts/ProxmoxVE/pull/4468))\n\n### 🚀 Updated Scripts\n\n  - fix: fetch_release_and_deploy function [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4478](https://github.com/community-scripts/ProxmoxVE/pull/4478))\n- Website: re-add documenso & some little bugfixes [@MickLesk](https://github.com/MickLesk) ([#4456](https://github.com/community-scripts/ProxmoxVE/pull/4456))\n\n  - #### 🐞 Bug Fixes\n\n    - Add make installation dependency to Actual Budget script [@maciejmatczak](https://github.com/maciejmatczak) ([#4485](https://github.com/community-scripts/ProxmoxVE/pull/4485))\n    - Bookstack: fix copy of themes/uploads/storage [@MickLesk](https://github.com/MickLesk) ([#4457](https://github.com/community-scripts/ProxmoxVE/pull/4457))\n    - Alpine-Rclone: Fix location of passwords file [@tremor021](https://github.com/tremor021) ([#4465](https://github.com/community-scripts/ProxmoxVE/pull/4465))\n\n  - #### ✨ New Features\n\n    - monitor-all: improvements - tag based filtering [@grizmin](https://github.com/grizmin) ([#4437](https://github.com/community-scripts/ProxmoxVE/pull/4437))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Add Github app for auto PR merge [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4461](https://github.com/community-scripts/ProxmoxVE/pull/4461))\n\n## 2025-05-13\n\n### 🆕 New Scripts\n\n  - gatus ([#4443](https://github.com/community-scripts/ProxmoxVE/pull/4443))\n- alpine-gatus ([#4442](https://github.com/community-scripts/ProxmoxVE/pull/4442))\n\n### 🚀 Updated Scripts\n\n  - update some improvements from dev (tools.func) [@MickLesk](https://github.com/MickLesk) ([#4430](https://github.com/community-scripts/ProxmoxVE/pull/4430))\n\n  - #### 🐞 Bug Fixes\n\n    - openhab: use zulu17-jdk [@moodyblue](https://github.com/moodyblue) ([#4438](https://github.com/community-scripts/ProxmoxVE/pull/4438))\n\n  - #### 🔧 Refactor\n\n    - openhab. correct some typos [@moodyblue](https://github.com/moodyblue) ([#4448](https://github.com/community-scripts/ProxmoxVE/pull/4448))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - fix: improve bridge detection in all network interface configuration files [@filippolauria](https://github.com/filippolauria) ([#4413](https://github.com/community-scripts/ProxmoxVE/pull/4413))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Jellyfin Media Server: Update configuration path [@tremor021](https://github.com/tremor021) ([#4434](https://github.com/community-scripts/ProxmoxVE/pull/4434))\n    - Pingvin Share: Added explanation on how to add/edit environment variables [@tremor021](https://github.com/tremor021) ([#4432](https://github.com/community-scripts/ProxmoxVE/pull/4432))\n    - pingvin.json: fix typo [@warmbo](https://github.com/warmbo) ([#4426](https://github.com/community-scripts/ProxmoxVE/pull/4426))\n\n## 2025-05-12\n\n### 🆕 New Scripts\n\n  - Alpine-Traefik [@MickLesk](https://github.com/MickLesk) ([#4412](https://github.com/community-scripts/ProxmoxVE/pull/4412))\n\n### 🚀 Updated Scripts\n\n  - Alpine: Use onliner for updates [@tremor021](https://github.com/tremor021) ([#4414](https://github.com/community-scripts/ProxmoxVE/pull/4414))\n\n  - #### 🐞 Bug Fixes\n\n    - homarr: fetch versions dynamically from source repo [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4409](https://github.com/community-scripts/ProxmoxVE/pull/4409))\n\n  - #### ✨ New Features\n\n    - Feature: LXC-Delete (pve helper): add \"all items\" [@MickLesk](https://github.com/MickLesk) ([#4296](https://github.com/community-scripts/ProxmoxVE/pull/4296))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - Config file Function in build.func [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4411](https://github.com/community-scripts/ProxmoxVE/pull/4411))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Navidrome - Fix config path (use /etc/ instead of /var/lib) [@quake1508](https://github.com/quake1508) ([#4406](https://github.com/community-scripts/ProxmoxVE/pull/4406))\n\n## 2025-05-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zammad: Enable ElasticSearch service [@tremor021](https://github.com/tremor021) ([#4391](https://github.com/community-scripts/ProxmoxVE/pull/4391))\n\n## 2025-05-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - (fix) Documenso: fix build failures [@vhsdream](https://github.com/vhsdream) ([#4382](https://github.com/community-scripts/ProxmoxVE/pull/4382))\n    - Jellyseerr: better handling of node and pnpm [@MickLesk](https://github.com/MickLesk) ([#4365](https://github.com/community-scripts/ProxmoxVE/pull/4365))\n\n## 2025-05-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Authentik: change install to UV & increase resources to 10GB RAM [@MickLesk](https://github.com/MickLesk) ([#4364](https://github.com/community-scripts/ProxmoxVE/pull/4364))\n\n  - #### ✨ New Features\n\n    - HomeAssistant-Core: update script for 2025.5+ [@MickLesk](https://github.com/MickLesk) ([#4363](https://github.com/community-scripts/ProxmoxVE/pull/4363))\n    - Feature: autologin for Alpine [@MickLesk](https://github.com/MickLesk) ([#4344](https://github.com/community-scripts/ProxmoxVE/pull/4344))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - fix: detect all bridge types, not just vmbr prefix [@filippolauria](https://github.com/filippolauria) ([#4351](https://github.com/community-scripts/ProxmoxVE/pull/4351))\n\n  - #### 📂 Github\n\n    - Add a Repo check to all Workflows [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4339](https://github.com/community-scripts/ProxmoxVE/pull/4339))\n    - Auto-Merge Automatic PR [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4343](https://github.com/community-scripts/ProxmoxVE/pull/4343))\n\n## 2025-05-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - SearXNG: fix to resolve yaml dependency error [@Biendeo](https://github.com/Biendeo) ([#4322](https://github.com/community-scripts/ProxmoxVE/pull/4322))\n    - Bugfix: Mikrotik & Pimox HAOS VM (NEXTID) [@MickLesk](https://github.com/MickLesk) ([#4313](https://github.com/community-scripts/ProxmoxVE/pull/4313))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - build.func Change the menu for Bridge Selection [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4326](https://github.com/community-scripts/ProxmoxVE/pull/4326))\n\n### 🌐 Website\n\n  - FAQ: Explanation \"updatable\" [@tremor021](https://github.com/tremor021) ([#4300](https://github.com/community-scripts/ProxmoxVE/pull/4300))\n\n## 2025-05-07\n\n### 🚀 Updated Scripts\n\n  - Alpine scripts: Set minimum disk space to 0.5GB [@tremor021](https://github.com/tremor021) ([#4288](https://github.com/community-scripts/ProxmoxVE/pull/4288))\n\n  - #### 🐞 Bug Fixes\n\n    - SuwayomiServer: Bump Java to v21, code formating [@tremor021](https://github.com/tremor021) ([#3987](https://github.com/community-scripts/ProxmoxVE/pull/3987))\n\n  - #### ✨ New Features\n\n    - Feature: get correct next VMID [@MickLesk](https://github.com/MickLesk) ([#4292](https://github.com/community-scripts/ProxmoxVE/pull/4292))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - OpenWebUI: Update docs link [@tremor021](https://github.com/tremor021) ([#4298](https://github.com/community-scripts/ProxmoxVE/pull/4298))\n\n## 2025-05-06\n\n### 🆕 New Scripts\n\n  - alpine-transmission ([#4277](https://github.com/community-scripts/ProxmoxVE/pull/4277))\n- streamlink-webui ([#4262](https://github.com/community-scripts/ProxmoxVE/pull/4262))\n- Fumadocs ([#4263](https://github.com/community-scripts/ProxmoxVE/pull/4263))\n- alpine-rclone ([#4265](https://github.com/community-scripts/ProxmoxVE/pull/4265))\n- alpine-tinyauth ([#4264](https://github.com/community-scripts/ProxmoxVE/pull/4264))\n- Re-Add: ActualBudget [@MickLesk](https://github.com/MickLesk) ([#4228](https://github.com/community-scripts/ProxmoxVE/pull/4228))\n\n### 🧰 Maintenance\n\n  - #### 🐞 Bug Fixes\n\n    - whiptail menu - cancel button now exists the advanced menu [@MickLesk](https://github.com/MickLesk) ([#4259](https://github.com/community-scripts/ProxmoxVE/pull/4259))\n\n## 2025-05-05\n\n### 🆕 New Scripts\n\n  - Alpine-Komodo [@MickLesk](https://github.com/MickLesk) ([#4234](https://github.com/community-scripts/ProxmoxVE/pull/4234))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Docker VM: Fix variable doublequoting [@tremor021](https://github.com/tremor021) ([#4245](https://github.com/community-scripts/ProxmoxVE/pull/4245))\n    - Alpine-Vaultwarden: Fix sed and better cert generation [@tremor021](https://github.com/tremor021) ([#4232](https://github.com/community-scripts/ProxmoxVE/pull/4232))\n    - Apache Guacamole: Fix Version Grepping [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4229](https://github.com/community-scripts/ProxmoxVE/pull/4229))\n\n  - #### ✨ New Features\n\n    - Docker-VM: Add Disk Size choice [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4241](https://github.com/community-scripts/ProxmoxVE/pull/4241))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Komodo update logic [@MickLesk](https://github.com/MickLesk) ([#4231](https://github.com/community-scripts/ProxmoxVE/pull/4231))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - tools.func: better function handling + gs as new helper [@MickLesk](https://github.com/MickLesk) ([#4238](https://github.com/community-scripts/ProxmoxVE/pull/4238))\n\n## 2025-05-04\n\n### 🌐 Website\n\n  - Code Server: Update misleading name, description and icon. [@ArmainAP](https://github.com/ArmainAP) ([#4211](https://github.com/community-scripts/ProxmoxVE/pull/4211))\n\n## 2025-05-03\n\n### 🚀 Updated Scripts\n\n  - Vaultwarden: Enable HTTPS by default [@tremor021](https://github.com/tremor021) ([#4197](https://github.com/community-scripts/ProxmoxVE/pull/4197))\n\n  - #### 🐞 Bug Fixes\n\n    - Vaultwarden: Fix access URL [@tremor021](https://github.com/tremor021) ([#4199](https://github.com/community-scripts/ProxmoxVE/pull/4199))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - SFTPGo: Switch updatable to true on website [@tremor021](https://github.com/tremor021) ([#4186](https://github.com/community-scripts/ProxmoxVE/pull/4186))\n\n## 2025-05-02\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - NetBox: Fix typo in sed command, preventing install [@tremor021](https://github.com/tremor021) ([#4179](https://github.com/community-scripts/ProxmoxVE/pull/4179))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Changed the random script button to be the same as all the other buttons [@BramSuurdje](https://github.com/BramSuurdje) ([#4183](https://github.com/community-scripts/ProxmoxVE/pull/4183))\n\n  - #### 📝 Script Information\n\n    - Habitica: correct config path [@DrDonoso](https://github.com/DrDonoso) ([#4181](https://github.com/community-scripts/ProxmoxVE/pull/4181))\n\n## 2025-05-01\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Readeck: Fix release crawling [@tremor021](https://github.com/tremor021) ([#4172](https://github.com/community-scripts/ProxmoxVE/pull/4172))\n\n  - #### ✨ New Features\n\n    - homepage: Add build time var [@burgerga](https://github.com/burgerga) ([#4167](https://github.com/community-scripts/ProxmoxVE/pull/4167))\n\n### 🌐 Website\n\n  - Bump vite from 6.2.6 to 6.3.4 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#4159](https://github.com/community-scripts/ProxmoxVE/pull/4159))\n\n  - #### 📝 Script Information\n\n    - Grafana: add config path & documentation  [@JamborJan](https://github.com/JamborJan) ([#4162](https://github.com/community-scripts/ProxmoxVE/pull/4162))\n"
  },
  {
    "path": ".github/changelogs/2025/06.md",
    "content": "﻿## 2025-06-30\n\n### 🆕 New Scripts\n\n  - Alpine Syncthing [@MickLesk](https://github.com/MickLesk) ([#5586](https://github.com/community-scripts/ProxmoxVE/pull/5586))\n- Kapowarr ([#5584](https://github.com/community-scripts/ProxmoxVE/pull/5584))\n\n### 🚀 Updated Scripts\n\n  - Fixing Cloudflare DDNS - lack of resources [@meszolym](https://github.com/meszolym) ([#5600](https://github.com/community-scripts/ProxmoxVE/pull/5600))\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: make changes to automatically enable QuickSync [@vhsdream](https://github.com/vhsdream) ([#5560](https://github.com/community-scripts/ProxmoxVE/pull/5560))\n    - Apache Guacamole: Install auth-jdbc component that matches release version [@tremor021](https://github.com/tremor021) ([#5563](https://github.com/community-scripts/ProxmoxVE/pull/5563))\n\n  - #### ✨ New Features\n\n    - tools.func: optimize binary installs with fetch_and_deploy helper [@MickLesk](https://github.com/MickLesk) ([#5588](https://github.com/community-scripts/ProxmoxVE/pull/5588))\n    - [core]: add ipv6 configuration support [@MickLesk](https://github.com/MickLesk) ([#5575](https://github.com/community-scripts/ProxmoxVE/pull/5575))\n\n## 2025-06-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Linkwarden: Add backing up of data folder to the update function [@tremor021](https://github.com/tremor021) ([#5548](https://github.com/community-scripts/ProxmoxVE/pull/5548))\n\n  - #### ✨ New Features\n\n    - Add cron-job api-key env variable to homarr script [@Meierschlumpf](https://github.com/Meierschlumpf) ([#5204](https://github.com/community-scripts/ProxmoxVE/pull/5204))\n\n### 🧰 Maintenance\n\n  - #### 📝 Documentation\n\n    - update readme with valid discord link. other one expired [@BramSuurdje](https://github.com/BramSuurdje) ([#5567](https://github.com/community-scripts/ProxmoxVE/pull/5567))\n\n### 🌐 Website\n\n  - Update script-item.tsx [@ape364](https://github.com/ape364) ([#5549](https://github.com/community-scripts/ProxmoxVE/pull/5549))\n\n  - #### 🐞 Bug Fixes\n\n    - fix bug in tooltip that would always render 'updateable' [@BramSuurdje](https://github.com/BramSuurdje) ([#5552](https://github.com/community-scripts/ProxmoxVE/pull/5552))\n\n## 2025-06-28\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Ollama: Clean up old Ollama files before running update [@tremor021](https://github.com/tremor021) ([#5534](https://github.com/community-scripts/ProxmoxVE/pull/5534))\n    - ONLYOFFICE: Update install script to manually create RabbitMQ user [@tremor021](https://github.com/tremor021) ([#5535](https://github.com/community-scripts/ProxmoxVE/pull/5535))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Booklore: Correct documentation and website  [@pieman3000](https://github.com/pieman3000) ([#5528](https://github.com/community-scripts/ProxmoxVE/pull/5528))\n\n## 2025-06-27\n\n### 🆕 New Scripts\n\n  - BookLore ([#5524](https://github.com/community-scripts/ProxmoxVE/pull/5524))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - wizarr: remove unneeded tmp file [@MickLesk](https://github.com/MickLesk) ([#5517](https://github.com/community-scripts/ProxmoxVE/pull/5517))\n\n### 🧰 Maintenance\n\n  - #### 🐞 Bug Fixes\n\n    - Remove npm legacy errors, created single source of truth for ESlint. updated analytics url. updated script background [@BramSuurdje](https://github.com/BramSuurdje) ([#5498](https://github.com/community-scripts/ProxmoxVE/pull/5498))\n\n  - #### 📂 Github\n\n    - New workflow to push to gitea and change links to gitea [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5510](https://github.com/community-scripts/ProxmoxVE/pull/5510))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Wireguard, Update Link to Documentation. [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5514](https://github.com/community-scripts/ProxmoxVE/pull/5514))\n\n## 2025-06-26\n\n### 🆕 New Scripts\n\n  - ConvertX ([#5484](https://github.com/community-scripts/ProxmoxVE/pull/5484))\n\n### 🚀 Updated Scripts\n\n  - [tools] Update setup_nodejs function [@tremor021](https://github.com/tremor021) ([#5488](https://github.com/community-scripts/ProxmoxVE/pull/5488))\n- [tools] Fix setup_mongodb function [@tremor021](https://github.com/tremor021) ([#5486](https://github.com/community-scripts/ProxmoxVE/pull/5486))\n\n## 2025-06-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Docmost: Increase resources [@tremor021](https://github.com/tremor021) ([#5458](https://github.com/community-scripts/ProxmoxVE/pull/5458))\n\n  - #### ✨ New Features\n\n    - tools.func: new helper for imagemagick [@MickLesk](https://github.com/MickLesk) ([#5452](https://github.com/community-scripts/ProxmoxVE/pull/5452))\n    - YunoHost: add Update-Function [@MickLesk](https://github.com/MickLesk) ([#5450](https://github.com/community-scripts/ProxmoxVE/pull/5450))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Tailscale  [@MickLesk](https://github.com/MickLesk) ([#5454](https://github.com/community-scripts/ProxmoxVE/pull/5454))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Update Tooltips component to conditionally display updateable status based on item type [@BramSuurdje](https://github.com/BramSuurdje) ([#5461](https://github.com/community-scripts/ProxmoxVE/pull/5461))\n    - Refactor CommandMenu to prevent duplicate scripts across categories [@BramSuurdje](https://github.com/BramSuurdje) ([#5463](https://github.com/community-scripts/ProxmoxVE/pull/5463))\n\n  - #### ✨ New Features\n\n    - Enhance InstallCommand component to support Gitea as an alternative source for installation scripts. [@BramSuurdje](https://github.com/BramSuurdje) ([#5464](https://github.com/community-scripts/ProxmoxVE/pull/5464))\n\n  - #### 📝 Script Information\n\n    - Website: mark VM's and \"OS\"-LXC's as updatable [@MickLesk](https://github.com/MickLesk) ([#5453](https://github.com/community-scripts/ProxmoxVE/pull/5453))\n\n## 2025-06-24\n\n### 🆕 New Scripts\n\n  - ONLYOFFICE Docs ([#5420](https://github.com/community-scripts/ProxmoxVE/pull/5420))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GoMFT: tmpl bugfix to work with current version until a new release pushed [@MickLesk](https://github.com/MickLesk) ([#5435](https://github.com/community-scripts/ProxmoxVE/pull/5435))\n    - Update all Alpine Scripts to atleast 1GB HDD [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5418](https://github.com/community-scripts/ProxmoxVE/pull/5418))\n\n  - #### ✨ New Features\n\n    - general: update all alpine scripts to version 3.22 [@MickLesk](https://github.com/MickLesk) ([#5428](https://github.com/community-scripts/ProxmoxVE/pull/5428))\n    - Minio: use latest version or latest feature rich version [@MickLesk](https://github.com/MickLesk) ([#5423](https://github.com/community-scripts/ProxmoxVE/pull/5423))\n    - [core]: Improve GitHub release fetch robustness with split timeouts and retry logic [@MickLesk](https://github.com/MickLesk) ([#5422](https://github.com/community-scripts/ProxmoxVE/pull/5422))\n\n  - #### 💥 Breaking Changes\n\n    - bump scripts (Installer) from Ubuntu 22.04 to Ubuntu 24.04 (agentdvr, emby, jellyfin, plex, shinobi) [@MickLesk](https://github.com/MickLesk) ([#5434](https://github.com/community-scripts/ProxmoxVE/pull/5434))\n\n  - #### 🔧 Refactor\n\n    - Refactor: MeTube to uv based install [@MickLesk](https://github.com/MickLesk) ([#5411](https://github.com/community-scripts/ProxmoxVE/pull/5411))\n    - Refactor: Prometheus PVE Exporter to uv based install [@MickLesk](https://github.com/MickLesk) ([#5412](https://github.com/community-scripts/ProxmoxVE/pull/5412))\n    - Refactor: ESPHome to uv based install [@MickLesk](https://github.com/MickLesk) ([#5413](https://github.com/community-scripts/ProxmoxVE/pull/5413))\n\n## 2025-06-23\n\n### 🆕 New Scripts\n\n  - Alpine-Forgejo by @Johann3s-H [@MickLesk](https://github.com/MickLesk) ([#5396](https://github.com/community-scripts/ProxmoxVE/pull/5396))\n\n### 🚀 Updated Scripts\n\n  - [core]: tools.func -> autoupdate npm to newest version on install [@MickLesk](https://github.com/MickLesk) ([#5397](https://github.com/community-scripts/ProxmoxVE/pull/5397))\n\n  - #### 🐞 Bug Fixes\n\n    - PLANKA: Fix the update procedure [@tremor021](https://github.com/tremor021) ([#5391](https://github.com/community-scripts/ProxmoxVE/pull/5391))\n    - changed trilium github repo [@miggi92](https://github.com/miggi92) ([#5390](https://github.com/community-scripts/ProxmoxVE/pull/5390))\n    - changedetection: fix: hermetic msedge [@CrazyWolf13](https://github.com/CrazyWolf13) ([#5388](https://github.com/community-scripts/ProxmoxVE/pull/5388))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - MariaDB: Add information about Adminer on website [@tremor021](https://github.com/tremor021) ([#5400](https://github.com/community-scripts/ProxmoxVE/pull/5400))\n\n## 2025-06-22\n\n### 🚀 Updated Scripts\n\n  - [core]: fix timing issues while template update & timezone setup at create new LXC [@MickLesk](https://github.com/MickLesk) ([#5358](https://github.com/community-scripts/ProxmoxVE/pull/5358))\n- alpine: increase hdd to 1gb [@MickLesk](https://github.com/MickLesk) ([#5377](https://github.com/community-scripts/ProxmoxVE/pull/5377))\n\n  - #### 🐞 Bug Fixes\n\n    - fix: casing and naming error after #5254 [@GoetzGoerisch](https://github.com/GoetzGoerisch) ([#5380](https://github.com/community-scripts/ProxmoxVE/pull/5380))\n    - fix: install_adminer > setup_adminer [@MickLesk](https://github.com/MickLesk) ([#5356](https://github.com/community-scripts/ProxmoxVE/pull/5356))\n    - gitea: Update gitea.sh to stop update failures [@tystuyfzand](https://github.com/tystuyfzand) ([#5361](https://github.com/community-scripts/ProxmoxVE/pull/5361))\n\n  - #### 🔧 Refactor\n\n    - Immich: unpin release; use fetch & deploy function for update [@vhsdream](https://github.com/vhsdream) ([#5355](https://github.com/community-scripts/ProxmoxVE/pull/5355))\n\n## 2025-06-21\n\n\n\n## 2025-06-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: remove unneeded tmp_file [@MickLesk](https://github.com/MickLesk) ([#5332](https://github.com/community-scripts/ProxmoxVE/pull/5332))\n    - Huntarr: Fix duplicate update status messages [@tremor021](https://github.com/tremor021) ([#5336](https://github.com/community-scripts/ProxmoxVE/pull/5336))\n    - fix planka Tags [@CrazyWolf13](https://github.com/CrazyWolf13) ([#5311](https://github.com/community-scripts/ProxmoxVE/pull/5311))\n    - PLANKA: Better DB password generate [@tremor021](https://github.com/tremor021) ([#5313](https://github.com/community-scripts/ProxmoxVE/pull/5313))\n    - Immich: Hotfix for #5299 [@vhsdream](https://github.com/vhsdream) ([#5300](https://github.com/community-scripts/ProxmoxVE/pull/5300))\n    - changedetection: add msedge as Browser dependency [@Niklas04](https://github.com/Niklas04) ([#5301](https://github.com/community-scripts/ProxmoxVE/pull/5301))\n\n  - #### ✨ New Features\n\n    - (turnkey) Add OpenLDAP as a TurnKey option [@mhaligowski](https://github.com/mhaligowski) ([#5305](https://github.com/community-scripts/ProxmoxVE/pull/5305))\n\n  - #### 🔧 Refactor\n\n    - [core]: unify misc/*.func scripts with centralized logic from core.func [@MickLesk](https://github.com/MickLesk) ([#5316](https://github.com/community-scripts/ProxmoxVE/pull/5316))\n    - Refactor: migrate AdventureLog update to uv and GitHub release logic [@MickLesk](https://github.com/MickLesk) ([#5318](https://github.com/community-scripts/ProxmoxVE/pull/5318))\n    - Refactor: migrate Jupyter Notebook to uv-based installation with update support [@MickLesk](https://github.com/MickLesk) ([#5320](https://github.com/community-scripts/ProxmoxVE/pull/5320))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Argus: fix wrong port on website [@MickLesk](https://github.com/MickLesk) ([#5322](https://github.com/community-scripts/ProxmoxVE/pull/5322))\n\n## 2025-06-19\n\n### 🆕 New Scripts\n\n  - Ubuntu 25.04 VM [@MickLesk](https://github.com/MickLesk) ([#5284](https://github.com/community-scripts/ProxmoxVE/pull/5284))\n- PLANKA ([#5277](https://github.com/community-scripts/ProxmoxVE/pull/5277))\n- Wizarr ([#5273](https://github.com/community-scripts/ProxmoxVE/pull/5273))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - immich-install.sh: restore pgvector module install [@vhsdream](https://github.com/vhsdream) ([#5286](https://github.com/community-scripts/ProxmoxVE/pull/5286))\n    - Immich: prepare for v1.135.0 [@vhsdream](https://github.com/vhsdream) ([#5025](https://github.com/community-scripts/ProxmoxVE/pull/5025))\n\n### 🧰 Maintenance\n\n  - #### ✨ New Features\n\n    - [core]: Feature - Check Quorum Status in create_lxc to prevent issues [@MickLesk](https://github.com/MickLesk) ([#5278](https://github.com/community-scripts/ProxmoxVE/pull/5278))\n    - [core]: add validation and replace recursion for invalid inputs in adv. settings [@MickLesk](https://github.com/MickLesk) ([#5291](https://github.com/community-scripts/ProxmoxVE/pull/5291))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - cloudflare-ddns: fix typo in info-text [@LukaZagar](https://github.com/LukaZagar) ([#5263](https://github.com/community-scripts/ProxmoxVE/pull/5263))\n\n## 2025-06-18\n\n### 🆕 New Scripts\n\n  - FileBrowser Quantum [@MickLesk](https://github.com/MickLesk) ([#5248](https://github.com/community-scripts/ProxmoxVE/pull/5248))\n- Huntarr ([#5249](https://github.com/community-scripts/ProxmoxVE/pull/5249))\n\n### 🚀 Updated Scripts\n\n  - tools.func: Standardized and Renamed Setup Functions [@MickLesk](https://github.com/MickLesk) ([#5241](https://github.com/community-scripts/ProxmoxVE/pull/5241))\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: fix prompt clobber issue [@vhsdream](https://github.com/vhsdream) ([#5231](https://github.com/community-scripts/ProxmoxVE/pull/5231))\n\n  - #### 🔧 Refactor\n\n    - Refactor all VM's to same logic & functions [@MickLesk](https://github.com/MickLesk) ([#5254](https://github.com/community-scripts/ProxmoxVE/pull/5254))\n    - upgrade old Scriptcalls to new tools.func calls [@MickLesk](https://github.com/MickLesk) ([#5242](https://github.com/community-scripts/ProxmoxVE/pull/5242))\n\n## 2025-06-17\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - gitea-mirror: increase build ressources  [@CrazyWolf13](https://github.com/CrazyWolf13) ([#5235](https://github.com/community-scripts/ProxmoxVE/pull/5235))\n    - Immich: ensure in proper working dir when updating [@vhsdream](https://github.com/vhsdream) ([#5227](https://github.com/community-scripts/ProxmoxVE/pull/5227))\n    - Update IP-Tag [@DesertGamer](https://github.com/DesertGamer) ([#5226](https://github.com/community-scripts/ProxmoxVE/pull/5226))\n    - trilium: fix update function after db changes folder [@tjcomserv](https://github.com/tjcomserv) ([#5207](https://github.com/community-scripts/ProxmoxVE/pull/5207))\n\n  - #### ✨ New Features\n\n    - LibreTranslate: Add .env for easier configuration [@tremor021](https://github.com/tremor021) ([#5216](https://github.com/community-scripts/ProxmoxVE/pull/5216))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - IPTag: Better explanation [@MickLesk](https://github.com/MickLesk) ([#5213](https://github.com/community-scripts/ProxmoxVE/pull/5213))\n\n## 2025-06-16\n\n### 🆕 New Scripts\n\n  - Intel NIC offload Fix by @rcastley [@MickLesk](https://github.com/MickLesk) ([#5155](https://github.com/community-scripts/ProxmoxVE/pull/5155))\n\n### 🚀 Updated Scripts\n\n  - [core] Move install_php() from VED to main [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5182](https://github.com/community-scripts/ProxmoxVE/pull/5182))\n- Firefly: Add Data Importer to LXC [@tremor021](https://github.com/tremor021) ([#5159](https://github.com/community-scripts/ProxmoxVE/pull/5159))\n\n  - #### 🐞 Bug Fixes\n\n    - Wastebin: Fix missing dependencies [@tremor021](https://github.com/tremor021) ([#5185](https://github.com/community-scripts/ProxmoxVE/pull/5185))\n    - Kasm: Storing Creds Fix [@omiinaya](https://github.com/omiinaya) ([#5162](https://github.com/community-scripts/ProxmoxVE/pull/5162))\n\n  - #### ✨ New Features\n\n    - add optional Cloud-init support to Debian VM script  [@koendiender](https://github.com/koendiender) ([#5137](https://github.com/community-scripts/ProxmoxVE/pull/5137))\n\n  - #### 🔧 Refactor\n\n    - Refactor: 2FAuth [@tremor021](https://github.com/tremor021) ([#5184](https://github.com/community-scripts/ProxmoxVE/pull/5184))\n\n### 🌐 Website\n\n  - Refactor layout and component styles for improved responsiveness [@BramSuurdje](https://github.com/BramSuurdje) ([#5195](https://github.com/community-scripts/ProxmoxVE/pull/5195))\n\n  - #### 🐞 Bug Fixes\n\n    - Refactor ScriptItem and ConfigFile components to conditionally render config file location. Update ConfigFile to accept configPath prop instead of item. [@BramSuurdje](https://github.com/BramSuurdje) ([#5197](https://github.com/community-scripts/ProxmoxVE/pull/5197))\n    - Update default image asset in the public directory and update api route to only search for files that end with .json [@BramSuurdje](https://github.com/BramSuurdje) ([#5179](https://github.com/community-scripts/ProxmoxVE/pull/5179))\n\n  - #### ✨ New Features\n\n    - Update default image asset in the public directory [@BramSuurdje](https://github.com/BramSuurdje) ([#5189](https://github.com/community-scripts/ProxmoxVE/pull/5189))\n\n## 2025-06-15\n\n### 🆕 New Scripts\n\n  - LibreTranslate ([#5154](https://github.com/community-scripts/ProxmoxVE/pull/5154))\n\n## 2025-06-14\n\n### 🚀 Updated Scripts\n\n  - [core] Update install_mariadb func [@tremor021](https://github.com/tremor021) ([#5138](https://github.com/community-scripts/ProxmoxVE/pull/5138))\n\n  - #### 🐞 Bug Fixes\n\n    - flowiseai: set NodeJS to Version 20 [@MickLesk](https://github.com/MickLesk) ([#5130](https://github.com/community-scripts/ProxmoxVE/pull/5130))\n    - Update dolibarr-install.sh - Get largest version number [@tjcomserv](https://github.com/tjcomserv) ([#5127](https://github.com/community-scripts/ProxmoxVE/pull/5127))\n\n## 2025-06-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zigbee2MQTT: Fix missing directory [@tremor021](https://github.com/tremor021) ([#5120](https://github.com/community-scripts/ProxmoxVE/pull/5120))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Umbrel OS: Fix bad disk size shown on website [@tremor021](https://github.com/tremor021) ([#5125](https://github.com/community-scripts/ProxmoxVE/pull/5125))\n\n## 2025-06-12\n\n### 🆕 New Scripts\n\n  - Manage my Damn Life ([#5100](https://github.com/community-scripts/ProxmoxVE/pull/5100))\n\n### 🚀 Updated Scripts\n\n  - Kasm: Increase Ressources & Hint for Fuse / Swap [@MickLesk](https://github.com/MickLesk) ([#5112](https://github.com/community-scripts/ProxmoxVE/pull/5112))\n\n## 2025-06-11\n\n\n\n## 2025-06-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Element Synapse: switched from development to production mode and fixed update [@Frankmaaan](https://github.com/Frankmaaan) ([#5066](https://github.com/community-scripts/ProxmoxVE/pull/5066))\n    - Tinyauth: Fix creation of service file [@tremor021](https://github.com/tremor021) ([#5090](https://github.com/community-scripts/ProxmoxVE/pull/5090))\n    - Dolibarr: Fix typo in SQL command [@tremor021](https://github.com/tremor021) ([#5091](https://github.com/community-scripts/ProxmoxVE/pull/5091))\n\n### 🧰 Maintenance\n\n  - #### 📡 API\n\n    - [core] Prevent API form sending Data when disabled [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5080](https://github.com/community-scripts/ProxmoxVE/pull/5080))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Immich: Update JSON [@vhsdream](https://github.com/vhsdream) ([#5085](https://github.com/community-scripts/ProxmoxVE/pull/5085))\n\n## 2025-06-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Authelia: Fix the URL of the container [@tremor021](https://github.com/tremor021) ([#5064](https://github.com/community-scripts/ProxmoxVE/pull/5064))\n\n### 🌐 Website\n\n  - GoMFT: Remove from website temporarily [@tremor021](https://github.com/tremor021) ([#5065](https://github.com/community-scripts/ProxmoxVE/pull/5065))\n\n## 2025-06-08\n\n### 🆕 New Scripts\n\n  - Minarca ([#5058](https://github.com/community-scripts/ProxmoxVE/pull/5058))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - zot: fix missing var (Dev -> Main) [@MickLesk](https://github.com/MickLesk) ([#5056](https://github.com/community-scripts/ProxmoxVE/pull/5056))\n\n  - #### ✨ New Features\n\n    - karakeep: Add more configuration defaults [@vhsdream](https://github.com/vhsdream) ([#5054](https://github.com/community-scripts/ProxmoxVE/pull/5054))\n\n## 2025-06-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - alpine-it-tools fix update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#5039](https://github.com/community-scripts/ProxmoxVE/pull/5039))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - Fix typo in build.func [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5041](https://github.com/community-scripts/ProxmoxVE/pull/5041))\n\n## 2025-06-06\n\n### 🆕 New Scripts\n\n  - Zot-Registry ([#5016](https://github.com/community-scripts/ProxmoxVE/pull/5016))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - zipline: fix old upload copy from v3 to v4 [@MickLesk](https://github.com/MickLesk) ([#5015](https://github.com/community-scripts/ProxmoxVE/pull/5015))\n\n## 2025-06-05\n\n### 🆕 New Scripts\n\n  - Lyrion Music Server ([#4992](https://github.com/community-scripts/ProxmoxVE/pull/4992))\n- gitea-mirror ([#4967](https://github.com/community-scripts/ProxmoxVE/pull/4967))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zipline: Fix PostgreSQL install [@tremor021](https://github.com/tremor021) ([#4989](https://github.com/community-scripts/ProxmoxVE/pull/4989))\n    - Homarr: add nodejs upgrade [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4974](https://github.com/community-scripts/ProxmoxVE/pull/4974))\n    - add FUSE to rclone [@Frankmaaan](https://github.com/Frankmaaan) ([#4972](https://github.com/community-scripts/ProxmoxVE/pull/4972))\n\n  - #### ✨ New Features\n\n    - Zitadel: Upgrade Install to PSQL 17 [@MickLesk](https://github.com/MickLesk) ([#5000](https://github.com/community-scripts/ProxmoxVE/pull/5000))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Fix clean-lxcs.sh type categorization [@bitspill](https://github.com/bitspill) ([#4980](https://github.com/community-scripts/ProxmoxVE/pull/4980))\n\n## 2025-06-04\n\n### 🚀 Updated Scripts\n\n  - Pulse: add polkit for sudoless web updates [@rcourtman](https://github.com/rcourtman) ([#4970](https://github.com/community-scripts/ProxmoxVE/pull/4970))\n- Pulse: add correct Port for URL output  [@rcourtman](https://github.com/rcourtman) ([#4951](https://github.com/community-scripts/ProxmoxVE/pull/4951))\n\n  - #### 🐞 Bug Fixes\n\n    - [refactor] Seelf [@tremor021](https://github.com/tremor021) ([#4954](https://github.com/community-scripts/ProxmoxVE/pull/4954))\n\n## 2025-06-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Kasm: Swap fix [@omiinaya](https://github.com/omiinaya) ([#4937](https://github.com/community-scripts/ProxmoxVE/pull/4937))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - netbox: correct website URL  [@theincrediblenoone](https://github.com/theincrediblenoone) ([#4952](https://github.com/community-scripts/ProxmoxVE/pull/4952))\n\n## 2025-06-02\n\n### 🆕 New Scripts\n\n  - PVE-Privilege-Converter [@MickLesk](https://github.com/MickLesk) ([#4906](https://github.com/community-scripts/ProxmoxVE/pull/4906))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix(wastebin): use tar asset [@dbeg](https://github.com/dbeg) ([#4934](https://github.com/community-scripts/ProxmoxVE/pull/4934))\n    - MySQL/MariaDB: fix create user with password [@MickLesk](https://github.com/MickLesk) ([#4918](https://github.com/community-scripts/ProxmoxVE/pull/4918))\n    - Fix alpine-tinyauth env configuration parsing logic [@gokussjx](https://github.com/gokussjx) ([#4901](https://github.com/community-scripts/ProxmoxVE/pull/4901))\n\n  - #### 💥 Breaking Changes\n\n    - make Pulse installation non-interactive [@rcourtman](https://github.com/rcourtman) ([#4848](https://github.com/community-scripts/ProxmoxVE/pull/4848))\n\n### 🧰 Maintenance\n\n  - #### 💾 Core\n\n    - [core] add hw-accelerated for immich, openwebui / remove scrypted [@MickLesk](https://github.com/MickLesk) ([#4927](https://github.com/community-scripts/ProxmoxVE/pull/4927))\n    - [core] tools.func: Bugfix old gpg key for mysql & little improvements [@MickLesk](https://github.com/MickLesk) ([#4916](https://github.com/community-scripts/ProxmoxVE/pull/4916))\n    - [core] Varius fixes to Config file feature [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4924](https://github.com/community-scripts/ProxmoxVE/pull/4924))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Display default password even if there isn't a default username [@0risc](https://github.com/0risc) ([#4900](https://github.com/community-scripts/ProxmoxVE/pull/4900))\n\n## 2025-06-01\n\n### 🆕 New Scripts\n\n  - immich ([#4886](https://github.com/community-scripts/ProxmoxVE/pull/4886))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - AdventureLog: add login credentials info [@tremor021](https://github.com/tremor021) ([#4887](https://github.com/community-scripts/ProxmoxVE/pull/4887))\n"
  },
  {
    "path": ".github/changelogs/2025/07.md",
    "content": "﻿## 2025-07-31\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - OpenObserve: Fix release fetching [@tremor021](https://github.com/tremor021) ([#6409](https://github.com/community-scripts/ProxmoxVE/pull/6409))\n\n  - #### 💥 Breaking Changes\n\n    - Remove temp. Tandoor during Install & Update Issues [@MickLesk](https://github.com/MickLesk) ([#6438](https://github.com/community-scripts/ProxmoxVE/pull/6438))\n\n  - #### 🔧 Refactor\n\n    - Refactor: listmonk [@tremor021](https://github.com/tremor021) ([#6399](https://github.com/community-scripts/ProxmoxVE/pull/6399))\n    - Refactor: Neo4j [@tremor021](https://github.com/tremor021) ([#6418](https://github.com/community-scripts/ProxmoxVE/pull/6418))\n    - Refactor: Monica [@tremor021](https://github.com/tremor021) ([#6416](https://github.com/community-scripts/ProxmoxVE/pull/6416))\n    - Refactor: Memos [@tremor021](https://github.com/tremor021) ([#6415](https://github.com/community-scripts/ProxmoxVE/pull/6415))\n    - Refactor: Opengist [@tremor021](https://github.com/tremor021) ([#6423](https://github.com/community-scripts/ProxmoxVE/pull/6423))\n    - Refactor: Ombi [@tremor021](https://github.com/tremor021) ([#6422](https://github.com/community-scripts/ProxmoxVE/pull/6422))\n    - Refactor: MySpeed [@tremor021](https://github.com/tremor021) ([#6417](https://github.com/community-scripts/ProxmoxVE/pull/6417))\n\n## 2025-07-30\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Strip SD and NS prefixes before writing to config file [@mattv8](https://github.com/mattv8) ([#6356](https://github.com/community-scripts/ProxmoxVE/pull/6356))\n    - [core] fix: expand $CACHER_IP in apt-proxy-detect.sh (revert quoted heredoc) [@MickLesk](https://github.com/MickLesk) ([#6385](https://github.com/community-scripts/ProxmoxVE/pull/6385))\n    - PiAlert: bugfix dependencies [@leiweibau](https://github.com/leiweibau) ([#6396](https://github.com/community-scripts/ProxmoxVE/pull/6396))\n    - Librespeed-Rust: Fix service name and RELEASE var fetching [@tremor021](https://github.com/tremor021) ([#6378](https://github.com/community-scripts/ProxmoxVE/pull/6378))\n    - n8n: add build-essential as dependency [@MickLesk](https://github.com/MickLesk) ([#6392](https://github.com/community-scripts/ProxmoxVE/pull/6392))\n    - Habitica: Fix trusted domains [@tremor021](https://github.com/tremor021) ([#6380](https://github.com/community-scripts/ProxmoxVE/pull/6380))\n    - Mafl: Fix undeclared var [@tremor021](https://github.com/tremor021) ([#6371](https://github.com/community-scripts/ProxmoxVE/pull/6371))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Lidarr [@tremor021](https://github.com/tremor021) ([#6379](https://github.com/community-scripts/ProxmoxVE/pull/6379))\n    - Refactor: Kubo [@tremor021](https://github.com/tremor021) ([#6376](https://github.com/community-scripts/ProxmoxVE/pull/6376))\n    - Refactor: Komga [@tremor021](https://github.com/tremor021) ([#6374](https://github.com/community-scripts/ProxmoxVE/pull/6374))\n    - Refactor: Koillection [@tremor021](https://github.com/tremor021) ([#6373](https://github.com/community-scripts/ProxmoxVE/pull/6373))\n    - Refactor: Karakeep [@tremor021](https://github.com/tremor021) ([#6372](https://github.com/community-scripts/ProxmoxVE/pull/6372))\n\n## 2025-07-29\n\n### 🆕 New Scripts\n\n  - Jeedom ([#6336](https://github.com/community-scripts/ProxmoxVE/pull/6336))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - PiAlert: add new dependencies [@leiweibau](https://github.com/leiweibau) ([#6337](https://github.com/community-scripts/ProxmoxVE/pull/6337))\n    - Element Synapse CT RAM increased from 1024 to 2048 [@tjcomserv](https://github.com/tjcomserv) ([#6330](https://github.com/community-scripts/ProxmoxVE/pull/6330))\n\n  - #### 🔧 Refactor\n\n    - Refactor: HomeBox [@tremor021](https://github.com/tremor021) ([#6338](https://github.com/community-scripts/ProxmoxVE/pull/6338))\n    - Refactor: InspIRCd [@tremor021](https://github.com/tremor021) ([#6343](https://github.com/community-scripts/ProxmoxVE/pull/6343))\n    - Refactor: Jackett [@tremor021](https://github.com/tremor021) ([#6344](https://github.com/community-scripts/ProxmoxVE/pull/6344))\n    - Update keycloak script to support configuration of latest release (v26) [@remz1337](https://github.com/remz1337) ([#6322](https://github.com/community-scripts/ProxmoxVE/pull/6322))\n    - Refactor: Photoprism [@MickLesk](https://github.com/MickLesk) ([#6335](https://github.com/community-scripts/ProxmoxVE/pull/6335))\n    - Refactor: Autobrr [@tremor021](https://github.com/tremor021) ([#6302](https://github.com/community-scripts/ProxmoxVE/pull/6302))\n\n## 2025-07-28\n\n### 🚀 Updated Scripts\n\n  - Refactor: Cronicle [@tremor021](https://github.com/tremor021) ([#6314](https://github.com/community-scripts/ProxmoxVE/pull/6314))\n- Refactor: HiveMQ [@tremor021](https://github.com/tremor021) ([#6313](https://github.com/community-scripts/ProxmoxVE/pull/6313))\n\n  - #### 🐞 Bug Fixes\n\n    - fix: SSH authorized keys not added in Alpine install script [@enihsyou](https://github.com/enihsyou) ([#6316](https://github.com/community-scripts/ProxmoxVE/pull/6316))\n    - fix: removing \",gw=\" from GATE var when reading/writing from/to config. [@teohz](https://github.com/teohz) ([#6177](https://github.com/community-scripts/ProxmoxVE/pull/6177))\n    - add 'g++' to actualbudget-install.sh [@saivishnu725](https://github.com/saivishnu725) ([#6293](https://github.com/community-scripts/ProxmoxVE/pull/6293))\n\n  - #### ✨ New Features\n\n    - [core]: create_lxc: better handling, fix lock handling, improve template validation & storage selection UX [@MickLesk](https://github.com/MickLesk) ([#6296](https://github.com/community-scripts/ProxmoxVE/pull/6296))\n    - ProxmoxVE 9.0 Beta: add BETA Version as test in pve_check [@MickLesk](https://github.com/MickLesk) ([#6295](https://github.com/community-scripts/ProxmoxVE/pull/6295))\n    - karakeep: Run workers in prod without tsx [@vhsdream](https://github.com/vhsdream) ([#6285](https://github.com/community-scripts/ProxmoxVE/pull/6285))\n\n  - #### 🔧 Refactor\n\n    - Refactor: grocy [@tremor021](https://github.com/tremor021) ([#6307](https://github.com/community-scripts/ProxmoxVE/pull/6307))\n    - Refactor: Navidrome [@tremor021](https://github.com/tremor021) ([#6300](https://github.com/community-scripts/ProxmoxVE/pull/6300))\n    - Refactor: Gotify [@tremor021](https://github.com/tremor021) ([#6301](https://github.com/community-scripts/ProxmoxVE/pull/6301))\n    - Refactor: Grafana [@tremor021](https://github.com/tremor021) ([#6306](https://github.com/community-scripts/ProxmoxVE/pull/6306))\n    - Refactor: Argus [@tremor021](https://github.com/tremor021) ([#6305](https://github.com/community-scripts/ProxmoxVE/pull/6305))\n    - n8n: refactor environmentfile [@CrazyWolf13](https://github.com/CrazyWolf13) ([#6297](https://github.com/community-scripts/ProxmoxVE/pull/6297))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - temp change the analytics url to one that works untill community one is fixed [@BramSuurdje](https://github.com/BramSuurdje) ([#6319](https://github.com/community-scripts/ProxmoxVE/pull/6319))\n\n## 2025-07-27\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - karakeep: export DATA_DIR from user config in update [@vhsdream](https://github.com/vhsdream) ([#6283](https://github.com/community-scripts/ProxmoxVE/pull/6283))\n    - go2rtc: Fix release download handling [@tremor021](https://github.com/tremor021) ([#6280](https://github.com/community-scripts/ProxmoxVE/pull/6280))\n\n## 2025-07-26\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - PiAlert: Update dependencies [@leiweibau](https://github.com/leiweibau) ([#6251](https://github.com/community-scripts/ProxmoxVE/pull/6251))\n\n## 2025-07-25\n\n### 🆕 New Scripts\n\n  - Cleanuparr ([#6238](https://github.com/community-scripts/ProxmoxVE/pull/6238))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: fix #6236 [@vhsdream](https://github.com/vhsdream) ([#6243](https://github.com/community-scripts/ProxmoxVE/pull/6243))\n    - Wizarr: use absolute path to uv [@vhsdream](https://github.com/vhsdream) ([#6221](https://github.com/community-scripts/ProxmoxVE/pull/6221))\n    - Immich v1.136.0 [@vhsdream](https://github.com/vhsdream) ([#6219](https://github.com/community-scripts/ProxmoxVE/pull/6219))\n\n## 2025-07-24\n\n### 🆕 New Scripts\n\n  - Alpine TeamSpeak Server [@tremor021](https://github.com/tremor021) ([#6201](https://github.com/community-scripts/ProxmoxVE/pull/6201))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Pin Version to v1.135.3 [@MickLesk](https://github.com/MickLesk) ([#6212](https://github.com/community-scripts/ProxmoxVE/pull/6212))\n    - Habitica: force npm to 10 [@MickLesk](https://github.com/MickLesk) ([#6192](https://github.com/community-scripts/ProxmoxVE/pull/6192))\n    - sabnzbd: add uv setup in update [@MickLesk](https://github.com/MickLesk) ([#6191](https://github.com/community-scripts/ProxmoxVE/pull/6191))\n\n  - #### ✨ New Features\n\n    - SnipeIT - Update dependencies  [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#6217](https://github.com/community-scripts/ProxmoxVE/pull/6217))\n    - Refactor: VictoriaMetrics [@tremor021](https://github.com/tremor021) ([#6210](https://github.com/community-scripts/ProxmoxVE/pull/6210))\n    - Headscale: Add headscale-admin UI as option [@tremor021](https://github.com/tremor021) ([#6205](https://github.com/community-scripts/ProxmoxVE/pull/6205))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Gokapi [@tremor021](https://github.com/tremor021) ([#6197](https://github.com/community-scripts/ProxmoxVE/pull/6197))\n    - Refactor: duplicati [@tremor021](https://github.com/tremor021) ([#6202](https://github.com/community-scripts/ProxmoxVE/pull/6202))\n    - Refactor: go2rtc [@tremor021](https://github.com/tremor021) ([#6198](https://github.com/community-scripts/ProxmoxVE/pull/6198))\n    - Refactor: Headscale [@tremor021](https://github.com/tremor021) ([#6180](https://github.com/community-scripts/ProxmoxVE/pull/6180))\n\n## 2025-07-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - documenso: remove customerId by creating initial user [@MickLesk](https://github.com/MickLesk) ([#6171](https://github.com/community-scripts/ProxmoxVE/pull/6171))\n\n## 2025-07-22\n\n### 🆕 New Scripts\n\n  - Salt ([#6116](https://github.com/community-scripts/ProxmoxVE/pull/6116))\n- LinkStack ([#6137](https://github.com/community-scripts/ProxmoxVE/pull/6137))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func - fix typo for target_file [@tjcomserv](https://github.com/tjcomserv) ([#6156](https://github.com/community-scripts/ProxmoxVE/pull/6156))\n    - fix(nginxproxymanager.sh): Set the version number before build. [@JMarcosHP](https://github.com/JMarcosHP) ([#6139](https://github.com/community-scripts/ProxmoxVE/pull/6139))\n\n  - #### ✨ New Features\n\n    - Fixed the previous fix of the anti-nag hook and propagated fixes everywhere [@imcrazytwkr](https://github.com/imcrazytwkr) ([#6162](https://github.com/community-scripts/ProxmoxVE/pull/6162))\n    - [core]: Improved LXC Container Startup Handling [@MickLesk](https://github.com/MickLesk) ([#6142](https://github.com/community-scripts/ProxmoxVE/pull/6142))\n    - wallos: add cron in installer for yearly cost [@CrazyWolf13](https://github.com/CrazyWolf13) ([#6133](https://github.com/community-scripts/ProxmoxVE/pull/6133))\n\n  - #### 💥 Breaking Changes\n\n    - gitea-mirror: add: migration to 3.0 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#6138](https://github.com/community-scripts/ProxmoxVE/pull/6138))\n\n  - #### 🔧 Refactor\n\n    - [core]: tools.func: increase setup_php function [@MickLesk](https://github.com/MickLesk) ([#6141](https://github.com/community-scripts/ProxmoxVE/pull/6141))\n\n### 🌐 Website\n\n  - Bump form-data from 4.0.3 to 4.0.4 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#6150](https://github.com/community-scripts/ProxmoxVE/pull/6150))\n\n## 2025-07-21\n\n### 🆕 New Scripts\n\n  - Teamspeak-Server ([#6121](https://github.com/community-scripts/ProxmoxVE/pull/6121))\n\n### 🚀 Updated Scripts\n\n  - pve-post-installer: remove Nag-File if already exist [@luckman212](https://github.com/luckman212) ([#6098](https://github.com/community-scripts/ProxmoxVE/pull/6098))\n\n  - #### 🐞 Bug Fixes\n\n    - firefly: fix permissions at update [@MickLesk](https://github.com/MickLesk) ([#6119](https://github.com/community-scripts/ProxmoxVE/pull/6119))\n    - nginxproxymanager: remove injected footer link (tteck) [@MickLesk](https://github.com/MickLesk) ([#6117](https://github.com/community-scripts/ProxmoxVE/pull/6117))\n\n## 2025-07-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix OpenWebUI install/update scripts [@karamanliev](https://github.com/karamanliev) ([#6093](https://github.com/community-scripts/ProxmoxVE/pull/6093))\n\n  - #### ✨ New Features\n\n    - karakeep: add DB_WAL_MODE; suppress test output [@vhsdream](https://github.com/vhsdream) ([#6101](https://github.com/community-scripts/ProxmoxVE/pull/6101))\n\n## 2025-07-19\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fixed nag script on ProxMox 8.4.5 [@imcrazytwkr](https://github.com/imcrazytwkr) ([#6084](https://github.com/community-scripts/ProxmoxVE/pull/6084))\n\n## 2025-07-18\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - AdventureLog: add backup folder before update [@MickLesk](https://github.com/MickLesk) ([#6066](https://github.com/community-scripts/ProxmoxVE/pull/6066))\n\n  - #### ✨ New Features\n\n    - Bar-Assistant: add Cocktail database [@MickLesk](https://github.com/MickLesk) ([#6068](https://github.com/community-scripts/ProxmoxVE/pull/6068))\n    - ErsatzTV: use project prebuild ffmpeg version [@MickLesk](https://github.com/MickLesk) ([#6067](https://github.com/community-scripts/ProxmoxVE/pull/6067))\n\n## 2025-07-17\n\n### 🆕 New Scripts\n\n  - Cloudreve ([#6044](https://github.com/community-scripts/ProxmoxVE/pull/6044))\n\n### 🚀 Updated Scripts\n\n  - config-file: set GATE [@ahmaddxb](https://github.com/ahmaddxb) ([#6042](https://github.com/community-scripts/ProxmoxVE/pull/6042))\n\n  - #### 🐞 Bug Fixes\n\n    - add \"setup_composer\" in update_script (baikal, bar-assistant, firefly) [@MickLesk](https://github.com/MickLesk) ([#6047](https://github.com/community-scripts/ProxmoxVE/pull/6047))\n    - PLANKA: Fix update procedure [@tremor021](https://github.com/tremor021) ([#6031](https://github.com/community-scripts/ProxmoxVE/pull/6031))\n\n  - #### ✨ New Features\n\n    - Reactive Resume: switch source to community-maintained fork [@vhsdream](https://github.com/vhsdream) ([#6051](https://github.com/community-scripts/ProxmoxVE/pull/6051))\n\n## 2025-07-16\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - homepage.sh: resolves #6028 [@vhsdream](https://github.com/vhsdream) ([#6032](https://github.com/community-scripts/ProxmoxVE/pull/6032))\n    - karakeep-install: Disable Playwright browser download, remove MCP build [@vhsdream](https://github.com/vhsdream) ([#5833](https://github.com/community-scripts/ProxmoxVE/pull/5833))\n\n  - #### 🔧 Refactor\n\n    - chore: reorganize nginxproxymanager update script [@Kirbo](https://github.com/Kirbo) ([#5971](https://github.com/community-scripts/ProxmoxVE/pull/5971))\n\n## 2025-07-15\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - checkmk: change password crawling based on there docs [@MickLesk](https://github.com/MickLesk) ([#6001](https://github.com/community-scripts/ProxmoxVE/pull/6001))\n    - Whiptail: Improve Dialogue to work with ESC [@MickLesk](https://github.com/MickLesk) ([#6003](https://github.com/community-scripts/ProxmoxVE/pull/6003))\n    - 2FAuth: Improve Update-Check [@MickLesk](https://github.com/MickLesk) ([#5998](https://github.com/community-scripts/ProxmoxVE/pull/5998))\n\n  - #### 💥 Breaking Changes\n\n    - EMQX: Purge Old Install (remove acl.conf too!) [@MickLesk](https://github.com/MickLesk) ([#5999](https://github.com/community-scripts/ProxmoxVE/pull/5999))\n\n  - #### 🔧 Refactor\n\n    - Refactor: PeaNUT [@MickLesk](https://github.com/MickLesk) ([#6002](https://github.com/community-scripts/ProxmoxVE/pull/6002))\n\n## 2025-07-14\n\n### 🆕 New Scripts\n\n  - Bar Assistant ([#5977](https://github.com/community-scripts/ProxmoxVE/pull/5977))\n- Mealie ([#5968](https://github.com/community-scripts/ProxmoxVE/pull/5968))\n\n### 🚀 Updated Scripts\n\n  - Config-File: Some Addons, Bugfixes...  [@MickLesk](https://github.com/MickLesk) ([#5978](https://github.com/community-scripts/ProxmoxVE/pull/5978))\n\n  - #### 🐞 Bug Fixes\n\n    - add --break-system-packages certbot-dns-cloudflare to the nginxproxym… [@tug-benson](https://github.com/tug-benson) ([#5957](https://github.com/community-scripts/ProxmoxVE/pull/5957))\n    - Dashy: remove unbound variable (RELEASE) [@MickLesk](https://github.com/MickLesk) ([#5974](https://github.com/community-scripts/ProxmoxVE/pull/5974))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update nic-offloading-fix: add Intel as search Text  [@calvin-li-developer](https://github.com/calvin-li-developer) ([#5954](https://github.com/community-scripts/ProxmoxVE/pull/5954))\n\n## 2025-07-12\n\n\n\n## 2025-07-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - immich: hotfix #5921 [@vhsdream](https://github.com/vhsdream) ([#5938](https://github.com/community-scripts/ProxmoxVE/pull/5938))\n    - bookstack: add setup_composer in update [@MickLesk](https://github.com/MickLesk) ([#5935](https://github.com/community-scripts/ProxmoxVE/pull/5935))\n    - Quickfix: Immich: revert install sequence [@vhsdream](https://github.com/vhsdream) ([#5932](https://github.com/community-scripts/ProxmoxVE/pull/5932))\n\n  - #### ✨ New Features\n\n    - Refactor & Function Bump: Docker [@MickLesk](https://github.com/MickLesk) ([#5889](https://github.com/community-scripts/ProxmoxVE/pull/5889))\n\n  - #### 🔧 Refactor\n\n    - Immich: handle custom library dependency updates; other fixes [@vhsdream](https://github.com/vhsdream) ([#5896](https://github.com/community-scripts/ProxmoxVE/pull/5896))\n\n## 2025-07-10\n\n### 🚀 Updated Scripts\n\n  - Refactor: Habitica [@MickLesk](https://github.com/MickLesk) ([#5911](https://github.com/community-scripts/ProxmoxVE/pull/5911))\n\n  - #### 🐞 Bug Fixes\n\n    - core: fix breaking re-download of lxc containers  [@MickLesk](https://github.com/MickLesk) ([#5906](https://github.com/community-scripts/ProxmoxVE/pull/5906))\n    - PLANKA: Fix paths to application directory [@tremor021](https://github.com/tremor021) ([#5900](https://github.com/community-scripts/ProxmoxVE/pull/5900))\n\n  - #### 🔧 Refactor\n\n    - Refactor: EMQX + Update-Function + Improved NodeJS Crawling [@MickLesk](https://github.com/MickLesk) ([#5907](https://github.com/community-scripts/ProxmoxVE/pull/5907))\n\n## 2025-07-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Omada Update: add missing exit  [@MickLesk](https://github.com/MickLesk) ([#5894](https://github.com/community-scripts/ProxmoxVE/pull/5894))\n    - FreshRSS: fix needed php modules [@MickLesk](https://github.com/MickLesk) ([#5886](https://github.com/community-scripts/ProxmoxVE/pull/5886))\n    - core: Fix VAAPI passthrough for unprivileged LXC containers via devX  [@MickLesk](https://github.com/MickLesk) ([#5875](https://github.com/community-scripts/ProxmoxVE/pull/5875))\n    - tools.func: fix an bug while php libapache2-mod breaks [@MickLesk](https://github.com/MickLesk) ([#5857](https://github.com/community-scripts/ProxmoxVE/pull/5857))\n    - BabyBuddy: fix path issues for update [@MickLesk](https://github.com/MickLesk) ([#5856](https://github.com/community-scripts/ProxmoxVE/pull/5856))\n\n  - #### ✨ New Features\n\n    - tools.func: strip leading folders for prebuild assets [@MickLesk](https://github.com/MickLesk) ([#5865](https://github.com/community-scripts/ProxmoxVE/pull/5865))\n\n  - #### 💥 Breaking Changes\n\n    - Refactor: Stirling-PDF [@MickLesk](https://github.com/MickLesk) ([#5872](https://github.com/community-scripts/ProxmoxVE/pull/5872))\n\n  - #### 🔧 Refactor\n\n    - Refactor: EMQX [@tremor021](https://github.com/tremor021) ([#5840](https://github.com/community-scripts/ProxmoxVE/pull/5840))\n    - Refactor: Excalidraw [@tremor021](https://github.com/tremor021) ([#5841](https://github.com/community-scripts/ProxmoxVE/pull/5841))\n    - Refactor: Firefly [@tremor021](https://github.com/tremor021) ([#5844](https://github.com/community-scripts/ProxmoxVE/pull/5844))\n    - Refactor: gatus [@tremor021](https://github.com/tremor021) ([#5849](https://github.com/community-scripts/ProxmoxVE/pull/5849))\n    - Refactor: FreshRSS [@tremor021](https://github.com/tremor021) ([#5847](https://github.com/community-scripts/ProxmoxVE/pull/5847))\n    - Refactor: Fluid-Calendar [@tremor021](https://github.com/tremor021) ([#5846](https://github.com/community-scripts/ProxmoxVE/pull/5846))\n    - Refactor: Commafeed [@tremor021](https://github.com/tremor021) ([#5802](https://github.com/community-scripts/ProxmoxVE/pull/5802))\n    - Refactor: FlareSolverr [@tremor021](https://github.com/tremor021) ([#5845](https://github.com/community-scripts/ProxmoxVE/pull/5845))\n    - Refactor: Glance [@tremor021](https://github.com/tremor021) ([#5874](https://github.com/community-scripts/ProxmoxVE/pull/5874))\n    - Refactor: Gitea [@tremor021](https://github.com/tremor021) ([#5876](https://github.com/community-scripts/ProxmoxVE/pull/5876))\n    - Refactor: Ghost (use now MySQL)  [@MickLesk](https://github.com/MickLesk) ([#5871](https://github.com/community-scripts/ProxmoxVE/pull/5871))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Github: AutoLabler | ChangeLog (Refactor) [@MickLesk](https://github.com/MickLesk) ([#5868](https://github.com/community-scripts/ProxmoxVE/pull/5868))\n\n## 2025-07-08\n\n### 🚀 Updated Scripts\n\n  - Refactor: Emby [@tremor021](https://github.com/tremor021) ([#5839](https://github.com/community-scripts/ProxmoxVE/pull/5839))\n\n  - #### 🐞 Bug Fixes\n\n    - Ollama: fix update script [@lucacome](https://github.com/lucacome) ([#5819](https://github.com/community-scripts/ProxmoxVE/pull/5819))\n\n  - #### ✨ New Features\n\n    - tools.func: add ffmpeg + minor improvement [@MickLesk](https://github.com/MickLesk) ([#5834](https://github.com/community-scripts/ProxmoxVE/pull/5834))\n\n  - #### 🔧 Refactor\n\n    - Refactor: ErsatzTV [@MickLesk](https://github.com/MickLesk) ([#5835](https://github.com/community-scripts/ProxmoxVE/pull/5835))\n\n## 2025-07-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix/stirling pdf script [@JcMinarro](https://github.com/JcMinarro) ([#5803](https://github.com/community-scripts/ProxmoxVE/pull/5803))\n    - gitea-mirror: update repo-url [@CrazyWolf13](https://github.com/CrazyWolf13) ([#5794](https://github.com/community-scripts/ProxmoxVE/pull/5794))\n    - Fix unbound var in pulse.sh [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#5807](https://github.com/community-scripts/ProxmoxVE/pull/5807))\n    - Bookstack: Fix PHP Issue & Bump to PHP 8.3 [@MickLesk](https://github.com/MickLesk) ([#5779](https://github.com/community-scripts/ProxmoxVE/pull/5779))\n\n  - #### ✨ New Features\n\n    - Refactor: Threadfin (+ updatable) [@MickLesk](https://github.com/MickLesk) ([#5783](https://github.com/community-scripts/ProxmoxVE/pull/5783))\n    - tools.func: better handling when unpacking tarfiles in prebuild mode [@MickLesk](https://github.com/MickLesk) ([#5781](https://github.com/community-scripts/ProxmoxVE/pull/5781))\n    - tools.func: add AVX check for MongoDB [@MickLesk](https://github.com/MickLesk) ([#5780](https://github.com/community-scripts/ProxmoxVE/pull/5780))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Docmost [@tremor021](https://github.com/tremor021) ([#5806](https://github.com/community-scripts/ProxmoxVE/pull/5806))\n    - Refactor: Baby Buddy [@tremor021](https://github.com/tremor021) ([#5769](https://github.com/community-scripts/ProxmoxVE/pull/5769))\n    - Refactor: Changed the way we install BunkerWeb by leveraging the brand new install-bunkerweb.sh [@TheophileDiot](https://github.com/TheophileDiot) ([#5707](https://github.com/community-scripts/ProxmoxVE/pull/5707))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - PBS: add hint for advanced installs [@MickLesk](https://github.com/MickLesk) ([#5788](https://github.com/community-scripts/ProxmoxVE/pull/5788))\n    - EMQX: Add warning to website [@tremor021](https://github.com/tremor021) ([#5770](https://github.com/community-scripts/ProxmoxVE/pull/5770))\n\n## 2025-07-06\n\n### 🚀 Updated Scripts\n\n  - Refactor: Barcodebuddy [@tremor021](https://github.com/tremor021) ([#5735](https://github.com/community-scripts/ProxmoxVE/pull/5735))\n\n  - #### 🐞 Bug Fixes\n\n    - Fix update script for Mafl: ensure directory is removed recursively [@jonalbr](https://github.com/jonalbr) ([#5759](https://github.com/community-scripts/ProxmoxVE/pull/5759))\n    - BookStack: Typo fix [@tremor021](https://github.com/tremor021) ([#5746](https://github.com/community-scripts/ProxmoxVE/pull/5746))\n    - Resolves incorrect URL at end of Pocket ID script [@johnsturgeon](https://github.com/johnsturgeon) ([#5743](https://github.com/community-scripts/ProxmoxVE/pull/5743))\n\n  - #### ✨ New Features\n\n    - [Feature] Add option to expose Docker via TCP port (alpine docker) [@oformaniuk](https://github.com/oformaniuk) ([#5716](https://github.com/community-scripts/ProxmoxVE/pull/5716))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Bitmagnet [@tremor021](https://github.com/tremor021) ([#5733](https://github.com/community-scripts/ProxmoxVE/pull/5733))\n    - Refactor: Baikal [@tremor021](https://github.com/tremor021) ([#5736](https://github.com/community-scripts/ProxmoxVE/pull/5736))\n\n## 2025-07-05\n\n### 🚀 Updated Scripts\n\n  - #### 🔧 Refactor\n\n    - Refactor: BookStack [@tremor021](https://github.com/tremor021) ([#5732](https://github.com/community-scripts/ProxmoxVE/pull/5732))\n    - Refactor: Authelia [@tremor021](https://github.com/tremor021) ([#5722](https://github.com/community-scripts/ProxmoxVE/pull/5722))\n    - Refactor: Dashy [@tremor021](https://github.com/tremor021) ([#5723](https://github.com/community-scripts/ProxmoxVE/pull/5723))\n    - Refactor: CryptPad [@tremor021](https://github.com/tremor021) ([#5724](https://github.com/community-scripts/ProxmoxVE/pull/5724))\n    - Refactor: ByteStash [@tremor021](https://github.com/tremor021) ([#5725](https://github.com/community-scripts/ProxmoxVE/pull/5725))\n    - Refactor: AgentDVR [@tremor021](https://github.com/tremor021) ([#5726](https://github.com/community-scripts/ProxmoxVE/pull/5726))\n\n## 2025-07-04\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Refactor: Mafl [@tremor021](https://github.com/tremor021) ([#5702](https://github.com/community-scripts/ProxmoxVE/pull/5702))\n    - Outline: Fix sed command for v0.85.0 [@tremor021](https://github.com/tremor021) ([#5688](https://github.com/community-scripts/ProxmoxVE/pull/5688))\n    - Komodo: Update Script to use FerretDB / remove psql & sqlite options [@MickLesk](https://github.com/MickLesk) ([#5690](https://github.com/community-scripts/ProxmoxVE/pull/5690))\n    - ESPHome: Fix Linking issue to prevent version mismatch [@MickLesk](https://github.com/MickLesk) ([#5685](https://github.com/community-scripts/ProxmoxVE/pull/5685))\n    - Cloudflare-DDNS: fix unvisible read command at install [@MickLesk](https://github.com/MickLesk) ([#5682](https://github.com/community-scripts/ProxmoxVE/pull/5682))\n\n  - #### ✨ New Features\n\n    - Core layer refactor: centralized error traps and msg_* consistency [@MickLesk](https://github.com/MickLesk) ([#5705](https://github.com/community-scripts/ProxmoxVE/pull/5705))\n\n  - #### 💥 Breaking Changes\n\n    - Update Iptag [@DesertGamer](https://github.com/DesertGamer) ([#5677](https://github.com/community-scripts/ProxmoxVE/pull/5677))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - MySQL phpMyAdmin Access Information [@austinpilz](https://github.com/austinpilz) ([#5679](https://github.com/community-scripts/ProxmoxVE/pull/5679))\n\n## 2025-07-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zipline: Fix typo in uploads directory path [@tremor021](https://github.com/tremor021) ([#5662](https://github.com/community-scripts/ProxmoxVE/pull/5662))\n\n  - #### ✨ New Features\n\n    - Improve asset matching in fetch_and_deploy_gh_release for prebuild and singlefile modes [@MickLesk](https://github.com/MickLesk) ([#5669](https://github.com/community-scripts/ProxmoxVE/pull/5669))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Trilium [@MickLesk](https://github.com/MickLesk) ([#5665](https://github.com/community-scripts/ProxmoxVE/pull/5665))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Bump Icons to selfhst repo | switch svg to webp [@MickLesk](https://github.com/MickLesk) ([#5659](https://github.com/community-scripts/ProxmoxVE/pull/5659))\n\n## 2025-07-02\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Changedetection: Base64 encode the launch options [@tremor021](https://github.com/tremor021) ([#5640](https://github.com/community-scripts/ProxmoxVE/pull/5640))\n\n  - #### 🔧 Refactor\n\n    - Refactor & Bump to Node24: Zigbee2MQTT [@MickLesk](https://github.com/MickLesk) ([#5638](https://github.com/community-scripts/ProxmoxVE/pull/5638))\n\n### 🌐 Website\n\n  - #### 💥 Breaking Changes\n\n    - Remove: Pingvin-Share [@MickLesk](https://github.com/MickLesk) ([#5635](https://github.com/community-scripts/ProxmoxVE/pull/5635))\n    - Remove: Readarr [@MickLesk](https://github.com/MickLesk) ([#5636](https://github.com/community-scripts/ProxmoxVE/pull/5636))\n\n## 2025-07-01\n\n### 🆕 New Scripts\n\n  - Librespeed Rust ([#5614](https://github.com/community-scripts/ProxmoxVE/pull/5614))\n- ITSM-NG ([#5615](https://github.com/community-scripts/ProxmoxVE/pull/5615))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Open WebUI: Fix Ollama update procedure [@tremor021](https://github.com/tremor021) ([#5601](https://github.com/community-scripts/ProxmoxVE/pull/5601))\n\n  - #### ✨ New Features\n\n    - [tools]: increase fetch_and_deploy with dns pre check [@MickLesk](https://github.com/MickLesk) ([#5608](https://github.com/community-scripts/ProxmoxVE/pull/5608))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Jellyfin GPU Passthrough NVIDIA Note [@austinpilz](https://github.com/austinpilz) ([#5625](https://github.com/community-scripts/ProxmoxVE/pull/5625))\n"
  },
  {
    "path": ".github/changelogs/2025/08.md",
    "content": "﻿## 2025-08-31\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - n8n: Increase disk size [@tremor021](https://github.com/tremor021) ([#7320](https://github.com/community-scripts/ProxmoxVE/pull/7320))\n\n## 2025-08-30\n\n### 🚀 Updated Scripts\n\n  - Immich: bump version to 1.140.0 [@vhsdream](https://github.com/vhsdream) ([#7275](https://github.com/community-scripts/ProxmoxVE/pull/7275))\n\n  - #### 🔧 Refactor\n\n    - Refactor gitea-mirror env-file [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7240](https://github.com/community-scripts/ProxmoxVE/pull/7240))\n\n## 2025-08-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix version check for pocket-id migration [@MickLesk](https://github.com/MickLesk) ([#7298](https://github.com/community-scripts/ProxmoxVE/pull/7298))\n    - fix: remove file creation at release fetching and version checking logic [@MickLesk](https://github.com/MickLesk) ([#7299](https://github.com/community-scripts/ProxmoxVE/pull/7299))\n    - Zitadel: Fix initial setup [@tremor021](https://github.com/tremor021) ([#7284](https://github.com/community-scripts/ProxmoxVE/pull/7284))\n    - post-pbs: increase enterprise recognition [@MickLesk](https://github.com/MickLesk) ([#7280](https://github.com/community-scripts/ProxmoxVE/pull/7280))\n    - Fix typo where install mode was changed instead of pinned version [@Brandsma](https://github.com/Brandsma) ([#7277](https://github.com/community-scripts/ProxmoxVE/pull/7277))\n\n  - #### ✨ New Features\n\n    - [core]: feature - check_for_gh_release - version pinning [@MickLesk](https://github.com/MickLesk) ([#7279](https://github.com/community-scripts/ProxmoxVE/pull/7279))\n    - [feat]: migrate all update_scripts to new version helper (gh) [@MickLesk](https://github.com/MickLesk) ([#7262](https://github.com/community-scripts/ProxmoxVE/pull/7262))\n\n## 2025-08-28\n\n### 🆕 New Scripts\n\n  - MediaManager ([#7238](https://github.com/community-scripts/ProxmoxVE/pull/7238))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - MMDL: add build-essential as dep [@vhsdream](https://github.com/vhsdream) ([#7266](https://github.com/community-scripts/ProxmoxVE/pull/7266))\n\n  - #### ✨ New Features\n\n    - add support for multiple ip addresses in monitor-all.sh [@moshekv](https://github.com/moshekv) ([#7244](https://github.com/community-scripts/ProxmoxVE/pull/7244))\n    - [core]: feature - check_for_gh_release as update-handler [@MickLesk](https://github.com/MickLesk) ([#7254](https://github.com/community-scripts/ProxmoxVE/pull/7254))\n\n  - #### 💥 Breaking Changes\n\n    - Flaresolverr: Pin to 3.3.25 (Python Issue) [@MickLesk](https://github.com/MickLesk) ([#7248](https://github.com/community-scripts/ProxmoxVE/pull/7248))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Keycloak: Update website [@tremor021](https://github.com/tremor021) ([#7256](https://github.com/community-scripts/ProxmoxVE/pull/7256))\n\n## 2025-08-27\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - searxng: improve installation [@MickLesk](https://github.com/MickLesk) ([#7233](https://github.com/community-scripts/ProxmoxVE/pull/7233))\n    - Homebox: Fix Update Script [@MickLesk](https://github.com/MickLesk) ([#7232](https://github.com/community-scripts/ProxmoxVE/pull/7232))\n\n## 2025-08-26\n\n### 🆕 New Scripts\n\n  - tracktor ([#7190](https://github.com/community-scripts/ProxmoxVE/pull/7190))\n- PBS: Upgrade Script for v4 [@MickLesk](https://github.com/MickLesk) ([#7214](https://github.com/community-scripts/ProxmoxVE/pull/7214))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Refactor: Post-PBS-Script [@MickLesk](https://github.com/MickLesk) ([#7213](https://github.com/community-scripts/ProxmoxVE/pull/7213))\n    - Refactor: Post-PMG-Script [@MickLesk](https://github.com/MickLesk) ([#7212](https://github.com/community-scripts/ProxmoxVE/pull/7212))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - [website] Update documentation URLs [@tremor021](https://github.com/tremor021) ([#7201](https://github.com/community-scripts/ProxmoxVE/pull/7201))\n\n## 2025-08-25\n\n### 🆕 New Scripts\n\n  - Alpine-RustDesk Server [@tremor021](https://github.com/tremor021) ([#7191](https://github.com/community-scripts/ProxmoxVE/pull/7191))\n- Alpine-Redlib ([#7178](https://github.com/community-scripts/ProxmoxVE/pull/7178))\n- healthchecks ([#7177](https://github.com/community-scripts/ProxmoxVE/pull/7177))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - FileBrowser Quantum: safer update (tmp download + atomic replace + arch autodetect) [@CommanderPaladin](https://github.com/CommanderPaladin) ([#7174](https://github.com/community-scripts/ProxmoxVE/pull/7174))\n    - Immich: bump to v1.139.4 [@vhsdream](https://github.com/vhsdream) ([#7138](https://github.com/community-scripts/ProxmoxVE/pull/7138))\n    - Komodo: Fix compose.env path [@tremor021](https://github.com/tremor021) ([#7202](https://github.com/community-scripts/ProxmoxVE/pull/7202))\n    - Komodo: Fix update procedure and missing env var [@tremor021](https://github.com/tremor021) ([#7198](https://github.com/community-scripts/ProxmoxVE/pull/7198))\n    - SnipeIT: Update nginx config to v8.3 [@tremor021](https://github.com/tremor021) ([#7171](https://github.com/community-scripts/ProxmoxVE/pull/7171))\n    - Lidarr: Fix RELEASE variable fetching [@tremor021](https://github.com/tremor021) ([#7162](https://github.com/community-scripts/ProxmoxVE/pull/7162))\n\n  - #### ✨ New Features\n\n    - Komodo: Generate admin users password [@tremor021](https://github.com/tremor021) ([#7193](https://github.com/community-scripts/ProxmoxVE/pull/7193))\n    - [core]: uv uses now \"update-shell\" command [@MickLesk](https://github.com/MickLesk) ([#7172](https://github.com/community-scripts/ProxmoxVE/pull/7172))\n    - [core]: tools.func - better verbose for postgresql [@MickLesk](https://github.com/MickLesk) ([#7173](https://github.com/community-scripts/ProxmoxVE/pull/7173))\n    - n8n: Force update to NodeJS v22 [@tremor021](https://github.com/tremor021) ([#7176](https://github.com/community-scripts/ProxmoxVE/pull/7176))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - 2FAuth: Fix website and docs URLs [@tremor021](https://github.com/tremor021) ([#7199](https://github.com/community-scripts/ProxmoxVE/pull/7199))\n\n## 2025-08-24\n\n### 🚀 Updated Scripts\n\n  - Kasm: Fix install log parsing [@tremor021](https://github.com/tremor021) ([#7140](https://github.com/community-scripts/ProxmoxVE/pull/7140))\n\n  - #### 🐞 Bug Fixes\n\n    - BookLore: Fix Nginx config [@tremor021](https://github.com/tremor021) ([#7155](https://github.com/community-scripts/ProxmoxVE/pull/7155))\n\n  - #### ✨ New Features\n\n    - Syncthing: Switch to v2 stable repository [@tremor021](https://github.com/tremor021) ([#7150](https://github.com/community-scripts/ProxmoxVE/pull/7150))\n\n## 2025-08-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - qBittorrent: Fix file names [@tremor021](https://github.com/tremor021) ([#7136](https://github.com/community-scripts/ProxmoxVE/pull/7136))\n    - Tandoor: Fix env path [@tremor021](https://github.com/tremor021) ([#7130](https://github.com/community-scripts/ProxmoxVE/pull/7130))\n\n  - #### 💥 Breaking Changes\n\n    - Immich: v1.139.2 [@vhsdream](https://github.com/vhsdream) ([#7116](https://github.com/community-scripts/ProxmoxVE/pull/7116))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Pf2eTools [@tremor021](https://github.com/tremor021) ([#7096](https://github.com/community-scripts/ProxmoxVE/pull/7096))\n    - Refactor: Prowlarr [@tremor021](https://github.com/tremor021) ([#7091](https://github.com/community-scripts/ProxmoxVE/pull/7091))\n    - Refactor: Radarr [@tremor021](https://github.com/tremor021) ([#7088](https://github.com/community-scripts/ProxmoxVE/pull/7088))\n    - Refactor: Snipe-IT [@tremor021](https://github.com/tremor021) ([#7081](https://github.com/community-scripts/ProxmoxVE/pull/7081))\n\n## 2025-08-22\n\n### 🚀 Updated Scripts\n\n  - Refactor: Prometheus [@tremor021](https://github.com/tremor021) ([#7093](https://github.com/community-scripts/ProxmoxVE/pull/7093))\n\n  - #### 🐞 Bug Fixes\n\n    - paperless: nltk fix [@MickLesk](https://github.com/MickLesk) ([#7098](https://github.com/community-scripts/ProxmoxVE/pull/7098))\n    - Tududi Fix: use correct tag parsing for release during update check [@vhsdream](https://github.com/vhsdream) ([#7072](https://github.com/community-scripts/ProxmoxVE/pull/7072))\n\n  - #### 🔧 Refactor\n\n    - Refactor: phpIPAM [@tremor021](https://github.com/tremor021) ([#7095](https://github.com/community-scripts/ProxmoxVE/pull/7095))\n    - Refactor: Prometheus Paperless NGX Exporter [@tremor021](https://github.com/tremor021) ([#7092](https://github.com/community-scripts/ProxmoxVE/pull/7092))\n    - Refactor: PS5-MQTT [@tremor021](https://github.com/tremor021) ([#7090](https://github.com/community-scripts/ProxmoxVE/pull/7090))\n    - Refactor: qBittorrent [@tremor021](https://github.com/tremor021) ([#7089](https://github.com/community-scripts/ProxmoxVE/pull/7089))\n    - Refactor: RDTClient [@tremor021](https://github.com/tremor021) ([#7086](https://github.com/community-scripts/ProxmoxVE/pull/7086))\n    - Refactor: Recyclarr [@tremor021](https://github.com/tremor021) ([#7085](https://github.com/community-scripts/ProxmoxVE/pull/7085))\n    - Refactor: RevealJS [@tremor021](https://github.com/tremor021) ([#7084](https://github.com/community-scripts/ProxmoxVE/pull/7084))\n    - Refactor: Rclone [@tremor021](https://github.com/tremor021) ([#7087](https://github.com/community-scripts/ProxmoxVE/pull/7087))\n    - Refactor: Semaphore [@tremor021](https://github.com/tremor021) ([#7083](https://github.com/community-scripts/ProxmoxVE/pull/7083))\n    - Refactor: Silverbullet [@tremor021](https://github.com/tremor021) ([#7082](https://github.com/community-scripts/ProxmoxVE/pull/7082))\n    - Refactor: Plant-it [@tremor021](https://github.com/tremor021) ([#7094](https://github.com/community-scripts/ProxmoxVE/pull/7094))\n    - Refactor: TasmoAdmin [@tremor021](https://github.com/tremor021) ([#7080](https://github.com/community-scripts/ProxmoxVE/pull/7080))\n\n## 2025-08-21\n\n### 🆕 New Scripts\n\n  - LiteLLM ([#7052](https://github.com/community-scripts/ProxmoxVE/pull/7052))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - tianji: add uv deps [@MickLesk](https://github.com/MickLesk) ([#7066](https://github.com/community-scripts/ProxmoxVE/pull/7066))\n    - Zitadel: installer for v4 [@MickLesk](https://github.com/MickLesk) ([#7058](https://github.com/community-scripts/ProxmoxVE/pull/7058))\n    - Paperless-NGX: create direction for nltk [@MickLesk](https://github.com/MickLesk) ([#7064](https://github.com/community-scripts/ProxmoxVE/pull/7064))\n    - Immich: hotfix - revert 7035 [@vhsdream](https://github.com/vhsdream) ([#7054](https://github.com/community-scripts/ProxmoxVE/pull/7054))\n    - duplicati: fix release pattern [@MickLesk](https://github.com/MickLesk) ([#7049](https://github.com/community-scripts/ProxmoxVE/pull/7049))\n    - technitiumdns: fix unbound variable [@MickLesk](https://github.com/MickLesk) ([#7047](https://github.com/community-scripts/ProxmoxVE/pull/7047))\n    - [core]: improve binary globbing for gh releases [@MickLesk](https://github.com/MickLesk) ([#7044](https://github.com/community-scripts/ProxmoxVE/pull/7044))\n\n## 2025-08-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Post-Install, change pve-test for trixie [@MickLesk](https://github.com/MickLesk) ([#7031](https://github.com/community-scripts/ProxmoxVE/pull/7031))\n    - Immich: fix small issue with immich-admin \"start\" script [@vhsdream](https://github.com/vhsdream) ([#7035](https://github.com/community-scripts/ProxmoxVE/pull/7035))\n    - WasteBin: Small fixes [@tremor021](https://github.com/tremor021) ([#7018](https://github.com/community-scripts/ProxmoxVE/pull/7018))\n    - Komga: Fix update [@tremor021](https://github.com/tremor021) ([#7027](https://github.com/community-scripts/ProxmoxVE/pull/7027))\n    - Barcode Buddy: Fix missing dependency [@tremor021](https://github.com/tremor021) ([#7020](https://github.com/community-scripts/ProxmoxVE/pull/7020))\n    - PBS: ifupdown2 reload [@MickLesk](https://github.com/MickLesk) ([#7013](https://github.com/community-scripts/ProxmoxVE/pull/7013))\n\n  - #### ✨ New Features\n\n    - Feature: Netdata support PVE9 (Debian 13 Trixie) [@MickLesk](https://github.com/MickLesk) ([#7012](https://github.com/community-scripts/ProxmoxVE/pull/7012))\n\n  - #### 🔧 Refactor\n\n    - Refactor: RustDesk Server [@tremor021](https://github.com/tremor021) ([#7008](https://github.com/community-scripts/ProxmoxVE/pull/7008))\n    - ghost: fix: verbose [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7023](https://github.com/community-scripts/ProxmoxVE/pull/7023))\n    - Refactor: Paperless-ngx [@MickLesk](https://github.com/MickLesk) ([#6938](https://github.com/community-scripts/ProxmoxVE/pull/6938))\n\n## 2025-08-19\n\n### 🆕 New Scripts\n\n  - Debian 13 VM [@MickLesk](https://github.com/MickLesk) ([#6970](https://github.com/community-scripts/ProxmoxVE/pull/6970))\n- Swizzin ([#6962](https://github.com/community-scripts/ProxmoxVE/pull/6962))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [core]: create_lxc - fix offline issue with alpine packages [@MickLesk](https://github.com/MickLesk) ([#6994](https://github.com/community-scripts/ProxmoxVE/pull/6994))\n    - OpenObserve: Fix release fetching [@tremor021](https://github.com/tremor021) ([#6961](https://github.com/community-scripts/ProxmoxVE/pull/6961))\n    - Update hev-socks5-server-install.sh [@iAzamat2](https://github.com/iAzamat2) ([#6953](https://github.com/community-scripts/ProxmoxVE/pull/6953))\n\n  - #### ✨ New Features\n\n    - Refactor: Glances (+ Feature Bump) [@MickLesk](https://github.com/MickLesk) ([#6976](https://github.com/community-scripts/ProxmoxVE/pull/6976))\n    - [core]: add new features to create_lxc [@MickLesk](https://github.com/MickLesk) ([#6979](https://github.com/community-scripts/ProxmoxVE/pull/6979))\n    - [core]: extend setup_uv to work with alpine [@MickLesk](https://github.com/MickLesk) ([#6978](https://github.com/community-scripts/ProxmoxVE/pull/6978))\n    - Immich: Bump version to 1.138.1 [@vhsdream](https://github.com/vhsdream) ([#6984](https://github.com/community-scripts/ProxmoxVE/pull/6984))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Tdarr [@MickLesk](https://github.com/MickLesk) ([#6969](https://github.com/community-scripts/ProxmoxVE/pull/6969))\n    - Refactor: The Lounge [@tremor021](https://github.com/tremor021) ([#6958](https://github.com/community-scripts/ProxmoxVE/pull/6958))\n    - Refactor: TeddyCloud [@tremor021](https://github.com/tremor021) ([#6963](https://github.com/community-scripts/ProxmoxVE/pull/6963))\n    - Refactor: Technitium DNS [@tremor021](https://github.com/tremor021) ([#6968](https://github.com/community-scripts/ProxmoxVE/pull/6968))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - [web]: update logos from reactive-resume & slskd [@MickLesk](https://github.com/MickLesk) ([#6990](https://github.com/community-scripts/ProxmoxVE/pull/6990))\n\n## 2025-08-18\n\n### 🆕 New Scripts\n\n  - CopyParty [@MickLesk](https://github.com/MickLesk) ([#6929](https://github.com/community-scripts/ProxmoxVE/pull/6929))\n- Twingate-Connector ([#6921](https://github.com/community-scripts/ProxmoxVE/pull/6921))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Keycloak: fix update function [@MickLesk](https://github.com/MickLesk) ([#6943](https://github.com/community-scripts/ProxmoxVE/pull/6943))\n    - Immich: add message to indicate image-processing library update check [@vhsdream](https://github.com/vhsdream) ([#6935](https://github.com/community-scripts/ProxmoxVE/pull/6935))\n    - fix(uptimekuma): unbound env variable [@vidonnus](https://github.com/vidonnus) ([#6922](https://github.com/community-scripts/ProxmoxVE/pull/6922))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Traefik [@tremor021](https://github.com/tremor021) ([#6940](https://github.com/community-scripts/ProxmoxVE/pull/6940))\n    - Refactor: Traccar [@tremor021](https://github.com/tremor021) ([#6942](https://github.com/community-scripts/ProxmoxVE/pull/6942))\n    - Refactor: Umami [@tremor021](https://github.com/tremor021) ([#6939](https://github.com/community-scripts/ProxmoxVE/pull/6939))\n    - Refactor: GoMFT [@tremor021](https://github.com/tremor021) ([#6916](https://github.com/community-scripts/ProxmoxVE/pull/6916))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - OpenWRT: add info for VLAN-aware in frontend [@MickLesk](https://github.com/MickLesk) ([#6944](https://github.com/community-scripts/ProxmoxVE/pull/6944))\n\n## 2025-08-17\n\n\n\n## 2025-08-16\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Wireguard: Fix WGDashboard not updating [@tremor021](https://github.com/tremor021) ([#6898](https://github.com/community-scripts/ProxmoxVE/pull/6898))\n    - Tandoor Images Fix [@WarLord185](https://github.com/WarLord185) ([#6892](https://github.com/community-scripts/ProxmoxVE/pull/6892))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Uptime Kuma [@tremor021](https://github.com/tremor021) ([#6902](https://github.com/community-scripts/ProxmoxVE/pull/6902))\n    - Refactor: Wallos [@tremor021](https://github.com/tremor021) ([#6900](https://github.com/community-scripts/ProxmoxVE/pull/6900))\n\n## 2025-08-15\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: pin Vectorchord release; adjust extension update commands [@vhsdream](https://github.com/vhsdream) ([#6878](https://github.com/community-scripts/ProxmoxVE/pull/6878))\n\n  - #### ✨ New Features\n\n    - Bump Immich to v1.138.0 [@vhsdream](https://github.com/vhsdream) ([#6813](https://github.com/community-scripts/ProxmoxVE/pull/6813))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Wavelog [@tremor021](https://github.com/tremor021) ([#6869](https://github.com/community-scripts/ProxmoxVE/pull/6869))\n    - Refactor: WatchYourLAN [@tremor021](https://github.com/tremor021) ([#6871](https://github.com/community-scripts/ProxmoxVE/pull/6871))\n    - Refactor: Watcharr [@tremor021](https://github.com/tremor021) ([#6872](https://github.com/community-scripts/ProxmoxVE/pull/6872))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Add missing default user & pass for RabbitMQ [@hbenyoussef](https://github.com/hbenyoussef) ([#6883](https://github.com/community-scripts/ProxmoxVE/pull/6883))\n\n## 2025-08-14\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Bugfix Searxng Redis replaced with Valkey in installscript  [@elvito](https://github.com/elvito) ([#6831](https://github.com/community-scripts/ProxmoxVE/pull/6831))\n    - Spoolman: Use environment variables to control host and port [@tremor021](https://github.com/tremor021) ([#6825](https://github.com/community-scripts/ProxmoxVE/pull/6825))\n    - Pulse: v4.3.2+ [@vhsdream](https://github.com/vhsdream) ([#6859](https://github.com/community-scripts/ProxmoxVE/pull/6859))\n    - rustdeskserver: fix API version file [@steadfasterX](https://github.com/steadfasterX) ([#6847](https://github.com/community-scripts/ProxmoxVE/pull/6847))\n    - Immich: quickfix #6836 [@vhsdream](https://github.com/vhsdream) ([#6848](https://github.com/community-scripts/ProxmoxVE/pull/6848))\n\n  - #### 🔧 Refactor\n\n    - Refactor: WikiJS [@tremor021](https://github.com/tremor021) ([#6840](https://github.com/community-scripts/ProxmoxVE/pull/6840))\n    - Refactor: Zoraxy [@tremor021](https://github.com/tremor021) ([#6823](https://github.com/community-scripts/ProxmoxVE/pull/6823))\n    - Refactor: Zitadel [@tremor021](https://github.com/tremor021) ([#6826](https://github.com/community-scripts/ProxmoxVE/pull/6826))\n    - Refactor: WordPress [@tremor021](https://github.com/tremor021) ([#6837](https://github.com/community-scripts/ProxmoxVE/pull/6837))\n    - Refactor: WireGuard [@tremor021](https://github.com/tremor021) ([#6839](https://github.com/community-scripts/ProxmoxVE/pull/6839))\n    - Refactor: yt-dlp-webui [@tremor021](https://github.com/tremor021) ([#6832](https://github.com/community-scripts/ProxmoxVE/pull/6832))\n    - Refactor: Zipline [@tremor021](https://github.com/tremor021) ([#6829](https://github.com/community-scripts/ProxmoxVE/pull/6829))\n    - Refactor: Zot-Registry [@tremor021](https://github.com/tremor021) ([#6822](https://github.com/community-scripts/ProxmoxVE/pull/6822))\n    - Refactor: Zwave-JS-UI [@tremor021](https://github.com/tremor021) ([#6820](https://github.com/community-scripts/ProxmoxVE/pull/6820))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - ProxmoxVE svg logo [@LuisPalacios](https://github.com/LuisPalacios) ([#6846](https://github.com/community-scripts/ProxmoxVE/pull/6846))\n\n## 2025-08-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - emby: fix update output [@MickLesk](https://github.com/MickLesk) ([#6791](https://github.com/community-scripts/ProxmoxVE/pull/6791))\n    - archivebox: fix wrong formatted uv command [@MickLesk](https://github.com/MickLesk) ([#6794](https://github.com/community-scripts/ProxmoxVE/pull/6794))\n    - Outline: Fixes for install and update procedures [@tremor021](https://github.com/tremor021) ([#6806](https://github.com/community-scripts/ProxmoxVE/pull/6806))\n    - Palmr: fix release version parsing // increase RAM [@vhsdream](https://github.com/vhsdream) ([#6800](https://github.com/community-scripts/ProxmoxVE/pull/6800))\n    - myspeed: fix update process if no data exist [@MickLesk](https://github.com/MickLesk) ([#6795](https://github.com/community-scripts/ProxmoxVE/pull/6795))\n    - crafty-controller: fix update output [@MickLesk](https://github.com/MickLesk) ([#6793](https://github.com/community-scripts/ProxmoxVE/pull/6793))\n    - GLPI: Fix timezone command [@tremor021](https://github.com/tremor021) ([#6783](https://github.com/community-scripts/ProxmoxVE/pull/6783))\n\n  - #### ✨ New Features\n\n    - Docker LXC: Add Portainer info [@tremor021](https://github.com/tremor021) ([#6803](https://github.com/community-scripts/ProxmoxVE/pull/6803))\n    - AgentDVR: Added update function [@tremor021](https://github.com/tremor021) ([#6804](https://github.com/community-scripts/ProxmoxVE/pull/6804))\n\n## 2025-08-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pulse: binary path changed AGAIN; other fixes [@vhsdream](https://github.com/vhsdream) ([#6770](https://github.com/community-scripts/ProxmoxVE/pull/6770))\n    - fix alpine syncthing config not being created [@GamerHun1238](https://github.com/GamerHun1238) ([#6773](https://github.com/community-scripts/ProxmoxVE/pull/6773))\n    - change owner of hortusfox directory [@snow2k9](https://github.com/snow2k9) ([#6763](https://github.com/community-scripts/ProxmoxVE/pull/6763))\n\n## 2025-08-11\n\n### 🚀 Updated Scripts\n\n  - Reactive Resume: use new release parsing; other fixes [@vhsdream](https://github.com/vhsdream) ([#6744](https://github.com/community-scripts/ProxmoxVE/pull/6744))\n\n## 2025-08-10\n\n### 🚀 Updated Scripts\n\n  - Fix/thinpool detection as it allows to delete active thinpool with different name than \"data\" [@onethree7](https://github.com/onethree7) ([#6730](https://github.com/community-scripts/ProxmoxVE/pull/6730))\n\n  - #### 🐞 Bug Fixes\n\n    - Pulse: fix binary path [@vhsdream](https://github.com/vhsdream) ([#6740](https://github.com/community-scripts/ProxmoxVE/pull/6740))\n    - Karakeep: chromium fix [@vhsdream](https://github.com/vhsdream) ([#6729](https://github.com/community-scripts/ProxmoxVE/pull/6729))\n\n## 2025-08-09\n\n### 🚀 Updated Scripts\n\n  - Paperless-AI: increase HDD Space to 20G [@MickLesk](https://github.com/MickLesk) ([#6716](https://github.com/community-scripts/ProxmoxVE/pull/6716))\n\n  - #### 🐞 Bug Fixes\n\n    - vaultwarden: increase disk space [@CrazyWolf13](https://github.com/CrazyWolf13) ([#6712](https://github.com/community-scripts/ProxmoxVE/pull/6712))\n    - Fix: Bazarr requirements.txt file not parse-able by UV [@Xerovoxx98](https://github.com/Xerovoxx98) ([#6701](https://github.com/community-scripts/ProxmoxVE/pull/6701))\n    - Improve backup of adventurelog folder [@ThomasDetemmerman](https://github.com/ThomasDetemmerman) ([#6653](https://github.com/community-scripts/ProxmoxVE/pull/6653))\n    - HomeBox: Fixes for update procedure [@tremor021](https://github.com/tremor021) ([#6702](https://github.com/community-scripts/ProxmoxVE/pull/6702))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Tianji [@MickLesk](https://github.com/MickLesk) ([#6662](https://github.com/community-scripts/ProxmoxVE/pull/6662))\n\n## 2025-08-08\n\n### 🆕 New Scripts\n\n  - Palmr ([#6642](https://github.com/community-scripts/ProxmoxVE/pull/6642))\n- HortusFox ([#6641](https://github.com/community-scripts/ProxmoxVE/pull/6641))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Unifi: Update libssl dependency [@tremor021](https://github.com/tremor021) ([#6680](https://github.com/community-scripts/ProxmoxVE/pull/6680))\n    - HomeBox: Fix checking for existing install [@tremor021](https://github.com/tremor021) ([#6677](https://github.com/community-scripts/ProxmoxVE/pull/6677))\n    - Immich: unpin libvips revision [@vhsdream](https://github.com/vhsdream) ([#6669](https://github.com/community-scripts/ProxmoxVE/pull/6669))\n    - Meilisearch: fix wrong path switch [@MickLesk](https://github.com/MickLesk) ([#6668](https://github.com/community-scripts/ProxmoxVE/pull/6668))\n    - MariaDB: fix \"feedback\" (statistical informations) whiptail  [@MickLesk](https://github.com/MickLesk) ([#6657](https://github.com/community-scripts/ProxmoxVE/pull/6657))\n    - Karakeep: workaround/fix for #6593 [@vhsdream](https://github.com/vhsdream) ([#6648](https://github.com/community-scripts/ProxmoxVE/pull/6648))\n\n  - #### ✨ New Features\n\n    - Feature: FSTrim (Filesystem Trim) - Log / LVM Check / ZFS [@MickLesk](https://github.com/MickLesk) ([#6660](https://github.com/community-scripts/ProxmoxVE/pull/6660))\n    - IP Tag: Allow installation on PVE 9.x [@webhdx](https://github.com/webhdx) ([#6679](https://github.com/community-scripts/ProxmoxVE/pull/6679))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Alpine IT-Tools [@tremor021](https://github.com/tremor021) ([#6579](https://github.com/community-scripts/ProxmoxVE/pull/6579))\n    - Refactor: ArchiveBox [@MickLesk](https://github.com/MickLesk) ([#6670](https://github.com/community-scripts/ProxmoxVE/pull/6670))\n    - Refactor: Bazarr [@MickLesk](https://github.com/MickLesk) ([#6663](https://github.com/community-scripts/ProxmoxVE/pull/6663))\n    - Refactor: Kometa [@MickLesk](https://github.com/MickLesk) ([#6673](https://github.com/community-scripts/ProxmoxVE/pull/6673))\n\n## 2025-08-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Commafeed: Fix Backup Handling while Update [@MickLesk](https://github.com/MickLesk) ([#6629](https://github.com/community-scripts/ProxmoxVE/pull/6629))\n    - VictoriaMetrics: Fix release fetching [@tremor021](https://github.com/tremor021) ([#6632](https://github.com/community-scripts/ProxmoxVE/pull/6632))\n\n  - #### ✨ New Features\n\n    - Feature: Post-PVE-Script (PVE9 Support + some Features) [@MickLesk](https://github.com/MickLesk) ([#6626](https://github.com/community-scripts/ProxmoxVE/pull/6626))\n    - Feature: Clean-LXC now supports Alpine based containers [@MickLesk](https://github.com/MickLesk) ([#6628](https://github.com/community-scripts/ProxmoxVE/pull/6628))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Tandoor v2  [@MickLesk](https://github.com/MickLesk) ([#6635](https://github.com/community-scripts/ProxmoxVE/pull/6635))\n    - Refactor: Paymenter [@tremor021](https://github.com/tremor021) ([#6589](https://github.com/community-scripts/ProxmoxVE/pull/6589))\n\n## 2025-08-06\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [core] better y/N handling for ressource check [@MickLesk](https://github.com/MickLesk) ([#6608](https://github.com/community-scripts/ProxmoxVE/pull/6608))\n    - fix: update Pulse scripts for v4 Go rewrite support [@rcourtman](https://github.com/rcourtman) ([#6574](https://github.com/community-scripts/ProxmoxVE/pull/6574))\n    - OpenProject: Fix missing apt update [@tremor021](https://github.com/tremor021) ([#6598](https://github.com/community-scripts/ProxmoxVE/pull/6598))\n\n  - #### ✨ New Features\n\n    - PVE9: Remove Beta Whiptail / add correct version check [@MickLesk](https://github.com/MickLesk) ([#6599](https://github.com/community-scripts/ProxmoxVE/pull/6599))\n\n## 2025-08-05\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - NIC offloading: e1000 support [@rcastley](https://github.com/rcastley) ([#6575](https://github.com/community-scripts/ProxmoxVE/pull/6575))\n\n  - #### 💥 Breaking Changes\n\n    - Temporary Remove: SearXNG [@MickLesk](https://github.com/MickLesk) ([#6578](https://github.com/community-scripts/ProxmoxVE/pull/6578))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Prometheus Alertmanager [@tremor021](https://github.com/tremor021) ([#6577](https://github.com/community-scripts/ProxmoxVE/pull/6577))\n\n## 2025-08-04\n\n### 🆕 New Scripts\n\n  - Tududi ([#6534](https://github.com/community-scripts/ProxmoxVE/pull/6534))\n- ots ([#6532](https://github.com/community-scripts/ProxmoxVE/pull/6532))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - MySpeed: fix update and copy old tests back [@MickLesk](https://github.com/MickLesk) ([#6550](https://github.com/community-scripts/ProxmoxVE/pull/6550))\n    - Composer: PATH Issues when updating [@MickLesk](https://github.com/MickLesk) ([#6543](https://github.com/community-scripts/ProxmoxVE/pull/6543))\n\n  - #### ✨ New Features\n\n    - Feat: enable tun for VPN services (wireguard) [@MickLesk](https://github.com/MickLesk) ([#6562](https://github.com/community-scripts/ProxmoxVE/pull/6562))\n    - turnkey: add hostname & Fix TUN access [@masterofrpm](https://github.com/masterofrpm) ([#6512](https://github.com/community-scripts/ProxmoxVE/pull/6512))\n    - Increase: Core Network check (pre-LXC Creation)  [@MickLesk](https://github.com/MickLesk) ([#6546](https://github.com/community-scripts/ProxmoxVE/pull/6546))\n\n  - #### 🔧 Refactor\n\n    - Refactor: PrivateBin [@tremor021](https://github.com/tremor021) ([#6559](https://github.com/community-scripts/ProxmoxVE/pull/6559))\n    - Refactor: PocketID [@tremor021](https://github.com/tremor021) ([#6556](https://github.com/community-scripts/ProxmoxVE/pull/6556))\n    - Refactor: Pocketbase [@tremor021](https://github.com/tremor021) ([#6554](https://github.com/community-scripts/ProxmoxVE/pull/6554))\n    - Refactor: NocoDB [@tremor021](https://github.com/tremor021) ([#6548](https://github.com/community-scripts/ProxmoxVE/pull/6548))\n    - Refactor: PairDrop [@tremor021](https://github.com/tremor021) ([#6528](https://github.com/community-scripts/ProxmoxVE/pull/6528))\n\n## 2025-08-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - docmost: remove build step due new version [@MickLesk](https://github.com/MickLesk) ([#6513](https://github.com/community-scripts/ProxmoxVE/pull/6513))\n    - Fix: Komga uses .komga as storage / so it fails after install [@MickLesk](https://github.com/MickLesk) ([#6517](https://github.com/community-scripts/ProxmoxVE/pull/6517))\n\n  - #### 💥 Breaking Changes\n\n    - Remove: Ubuntu 24.10-VM [@MickLesk](https://github.com/MickLesk) ([#6515](https://github.com/community-scripts/ProxmoxVE/pull/6515))\n\n  - #### 🔧 Refactor\n\n    - Refactor: openHAB [@tremor021](https://github.com/tremor021) ([#6524](https://github.com/community-scripts/ProxmoxVE/pull/6524))\n    - Refactor: OpenProject [@tremor021](https://github.com/tremor021) ([#6525](https://github.com/community-scripts/ProxmoxVE/pull/6525))\n\n## 2025-08-02\n\n### 🚀 Updated Scripts\n\n  - Alternative connectivity checks for LXC [@mariano-dagostino](https://github.com/mariano-dagostino) ([#6472](https://github.com/community-scripts/ProxmoxVE/pull/6472))\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: fix copy error during install [@vhsdream](https://github.com/vhsdream) ([#6497](https://github.com/community-scripts/ProxmoxVE/pull/6497))\n    - MagicMirror: Fix install process [@tremor021](https://github.com/tremor021) ([#6492](https://github.com/community-scripts/ProxmoxVE/pull/6492))\n    - chore: BookLore repo change [@vhsdream](https://github.com/vhsdream) ([#6493](https://github.com/community-scripts/ProxmoxVE/pull/6493))\n\n  - #### ✨ New Features\n\n    - VictoriaMetrics: Make VictoriaLogs optional add-on [@tremor021](https://github.com/tremor021) ([#6489](https://github.com/community-scripts/ProxmoxVE/pull/6489))\n\n## 2025-08-01\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fumadocs: add git as dependency [@MickLesk](https://github.com/MickLesk) ([#6459](https://github.com/community-scripts/ProxmoxVE/pull/6459))\n    - Immich: Fix immich-admin script; other fixes | pin to v.137.3 [@vhsdream](https://github.com/vhsdream) ([#6443](https://github.com/community-scripts/ProxmoxVE/pull/6443))\n\n  - #### ✨ New Features\n\n    - Re-Add: Suwayomi-Server [@MickLesk](https://github.com/MickLesk) ([#6458](https://github.com/community-scripts/ProxmoxVE/pull/6458))\n\n  - #### 🔧 Refactor\n\n    - Update homepage.sh to use setup_nodejs [@burgerga](https://github.com/burgerga) ([#6462](https://github.com/community-scripts/ProxmoxVE/pull/6462))\n    - Refactor: Owncast [@tremor021](https://github.com/tremor021) ([#6434](https://github.com/community-scripts/ProxmoxVE/pull/6434))\n    - Refactor: MediaMTX [@tremor021](https://github.com/tremor021) ([#6406](https://github.com/community-scripts/ProxmoxVE/pull/6406))\n    - Refactor: LubeLogger [@tremor021](https://github.com/tremor021) ([#6400](https://github.com/community-scripts/ProxmoxVE/pull/6400))\n    - Refactor: MagicMirror [@tremor021](https://github.com/tremor021) ([#6402](https://github.com/community-scripts/ProxmoxVE/pull/6402))\n    - Refactor: Manage My Damn Life [@tremor021](https://github.com/tremor021) ([#6403](https://github.com/community-scripts/ProxmoxVE/pull/6403))\n    - Refactor: Meilisearch [@tremor021](https://github.com/tremor021) ([#6407](https://github.com/community-scripts/ProxmoxVE/pull/6407))\n    - Refactor: NodeBB [@tremor021](https://github.com/tremor021) ([#6419](https://github.com/community-scripts/ProxmoxVE/pull/6419))\n    - Refactor: oauth2-proxy [@tremor021](https://github.com/tremor021) ([#6421](https://github.com/community-scripts/ProxmoxVE/pull/6421))\n    - Refactor: Outline [@tremor021](https://github.com/tremor021) ([#6424](https://github.com/community-scripts/ProxmoxVE/pull/6424))\n    - Refactor: Overseerr [@tremor021](https://github.com/tremor021) ([#6425](https://github.com/community-scripts/ProxmoxVE/pull/6425))\n"
  },
  {
    "path": ".github/changelogs/2025/09.md",
    "content": "﻿## 2025-09-30\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - caddy: fix typo for setup_go [@MickLesk](https://github.com/MickLesk) ([#8017](https://github.com/community-scripts/ProxmoxVE/pull/8017))\n    - Changedetection: Fix Browserless installation and update process [@h-stoyanov](https://github.com/h-stoyanov) ([#8011](https://github.com/community-scripts/ProxmoxVE/pull/8011))\n    - n8n: Update procedure workaround [@tremor021](https://github.com/tremor021) ([#8004](https://github.com/community-scripts/ProxmoxVE/pull/8004))\n    - Changedetection: Bump nodejs to 24 [@MickLesk](https://github.com/MickLesk) ([#8002](https://github.com/community-scripts/ProxmoxVE/pull/8002))\n\n  - #### ✨ New Features\n\n    - Bump Guacamole to Debian 13 [@burgerga](https://github.com/burgerga) ([#8010](https://github.com/community-scripts/ProxmoxVE/pull/8010))\n\n## 2025-09-29\n\n### 🆕 New Scripts\n\n  - Ghostfolio ([#7982](https://github.com/community-scripts/ProxmoxVE/pull/7982))\n- Warracker ([#7977](https://github.com/community-scripts/ProxmoxVE/pull/7977))\n- MyIP ([#7974](https://github.com/community-scripts/ProxmoxVE/pull/7974))\n- Verdaccio ([#7967](https://github.com/community-scripts/ProxmoxVE/pull/7967))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - fix sidebar loading issues and navbar on mobile [@BramSuurdje](https://github.com/BramSuurdje) ([#7991](https://github.com/community-scripts/ProxmoxVE/pull/7991))\n\n  - #### ✨ New Features\n\n    - Improve mobile ui: added a hamburger navigation to the mobile view. [@BramSuurdje](https://github.com/BramSuurdje) ([#7987](https://github.com/community-scripts/ProxmoxVE/pull/7987))\n\n  - #### 📝 Script Information\n\n    - Remove Frigate from Website [@MickLesk](https://github.com/MickLesk) ([#7972](https://github.com/community-scripts/ProxmoxVE/pull/7972))\n\n## 2025-09-28\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Metube: remove uv flags [@vhsdream](https://github.com/vhsdream) ([#7962](https://github.com/community-scripts/ProxmoxVE/pull/7962))\n    - freshrss: fix for broken permissions after update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7953](https://github.com/community-scripts/ProxmoxVE/pull/7953))\n\n## 2025-09-27\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GoAway: Make admin password aquisition more reliable [@tremor021](https://github.com/tremor021) ([#7946](https://github.com/community-scripts/ProxmoxVE/pull/7946))\n    - MeTube: Various fixes [@vhsdream](https://github.com/vhsdream) ([#7936](https://github.com/community-scripts/ProxmoxVE/pull/7936))\n    - Oddo: Fix typo in update procedure [@tremor021](https://github.com/tremor021) ([#7941](https://github.com/community-scripts/ProxmoxVE/pull/7941))\n\n## 2025-09-26\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Odoo: Fix missing dependencies [@tremor021](https://github.com/tremor021) ([#7931](https://github.com/community-scripts/ProxmoxVE/pull/7931))\n    - OpenWebUI: Update NODE_OPTIONS to increase memory limit [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#7919](https://github.com/community-scripts/ProxmoxVE/pull/7919))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Clarify descriptions of update scripts [@tremor021](https://github.com/tremor021) ([#7929](https://github.com/community-scripts/ProxmoxVE/pull/7929))\n\n## 2025-09-25\n\n### 🆕 New Scripts\n\n  - GoAway ([#7900](https://github.com/community-scripts/ProxmoxVE/pull/7900))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - ntfy: bump to debian 13 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7895](https://github.com/community-scripts/ProxmoxVE/pull/7895))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - feat: add menu icons to website [@BramSuurdje](https://github.com/BramSuurdje) ([#7894](https://github.com/community-scripts/ProxmoxVE/pull/7894))\n\n## 2025-09-24\n\n### 🆕 New Scripts\n\n  - Add Script: Joplin Server ([#7879](https://github.com/community-scripts/ProxmoxVE/pull/7879))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Monica: Fix dependencies [@tremor021](https://github.com/tremor021) ([#7877](https://github.com/community-scripts/ProxmoxVE/pull/7877))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update name in lxc-delete.json to 'PVE LXC Deletion' [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#7872](https://github.com/community-scripts/ProxmoxVE/pull/7872))\n\n## 2025-09-23\n\n### 🆕 New Scripts\n\n  - UpSnap ([#7825](https://github.com/community-scripts/ProxmoxVE/pull/7825))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: Check for /usr/local/bin in PATH during yq setup [@vhsdream](https://github.com/vhsdream) ([#7856](https://github.com/community-scripts/ProxmoxVE/pull/7856))\n    - BookLore: increase RAM [@vhsdream](https://github.com/vhsdream) ([#7855](https://github.com/community-scripts/ProxmoxVE/pull/7855))\n    - Bump Immich to v1.143.1 [@vhsdream](https://github.com/vhsdream) ([#7864](https://github.com/community-scripts/ProxmoxVE/pull/7864))\n    - zabbix: Remove not exist admin credentials from output [@MickLesk](https://github.com/MickLesk) ([#7849](https://github.com/community-scripts/ProxmoxVE/pull/7849))\n    - Suppress wrong errors from uv shell integration in setup_uv [@MickLesk](https://github.com/MickLesk) ([#7822](https://github.com/community-scripts/ProxmoxVE/pull/7822))\n    - Refactor Caddyfile configuration for headscale-admin [@MickLesk](https://github.com/MickLesk) ([#7821](https://github.com/community-scripts/ProxmoxVE/pull/7821))\n    - Improve subscription element removal (mobile) in post-pve script [@MickLesk](https://github.com/MickLesk) ([#7814](https://github.com/community-scripts/ProxmoxVE/pull/7814))\n    - Blocky: Fix release fetching [@tremor021](https://github.com/tremor021) ([#7807](https://github.com/community-scripts/ProxmoxVE/pull/7807))\n\n  - #### ✨ New Features\n\n    - Improve globaleaks install ensuring install can proceed without user … [@evilaliv3](https://github.com/evilaliv3) ([#7860](https://github.com/community-scripts/ProxmoxVE/pull/7860))\n    - Manage My Damn Life: use NodeJS 22 [@vhsdream](https://github.com/vhsdream) ([#7861](https://github.com/community-scripts/ProxmoxVE/pull/7861))\n    - VM: Increase pv & xz functions (HA OS / Umbrel OS) [@MickLesk](https://github.com/MickLesk) ([#7838](https://github.com/community-scripts/ProxmoxVE/pull/7838))\n    - Tandoor: update for newer dependencies (psql) + bump nodejs to 22 [@MickLesk](https://github.com/MickLesk) ([#7826](https://github.com/community-scripts/ProxmoxVE/pull/7826))\n    - Update Monica and Outline to use Node.js 22 [@MickLesk](https://github.com/MickLesk) ([#7833](https://github.com/community-scripts/ProxmoxVE/pull/7833))\n    - Update Zabbix install for Debian 13 and agent selection [@MickLesk](https://github.com/MickLesk) ([#7819](https://github.com/community-scripts/ProxmoxVE/pull/7819))\n    - tracktor: bump to debian 13 | feature bump [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7818](https://github.com/community-scripts/ProxmoxVE/pull/7818))\n    - LiteLLM: Bump to Debian 13 & add deps [@MickLesk](https://github.com/MickLesk) ([#7815](https://github.com/community-scripts/ProxmoxVE/pull/7815))\n    - Immich: bump to v1.143.0 [@vhsdream](https://github.com/vhsdream) ([#7801](https://github.com/community-scripts/ProxmoxVE/pull/7801))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - gh: remove ai autolabel test [@MickLesk](https://github.com/MickLesk) ([#7817](https://github.com/community-scripts/ProxmoxVE/pull/7817))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - OpenWebUI: Add information about Ollama [@tremor021](https://github.com/tremor021) ([#7843](https://github.com/community-scripts/ProxmoxVE/pull/7843))\n    - cosmos: add info note for configuration file [@MickLesk](https://github.com/MickLesk) ([#7824](https://github.com/community-scripts/ProxmoxVE/pull/7824))\n    - ElementSynapse: add note for Bridge Install Methods [@MickLesk](https://github.com/MickLesk) ([#7820](https://github.com/community-scripts/ProxmoxVE/pull/7820))\n\n## 2025-09-22\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [core]: Update detection of current running subshell [@tremor021](https://github.com/tremor021) ([#7796](https://github.com/community-scripts/ProxmoxVE/pull/7796))\n    - Paymenter: Installation and update fixes [@tremor021](https://github.com/tremor021) ([#7792](https://github.com/community-scripts/ProxmoxVE/pull/7792))\n\n## 2025-09-21\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix openwebui update and installer [@HeedfulCrayon](https://github.com/HeedfulCrayon) ([#7788](https://github.com/community-scripts/ProxmoxVE/pull/7788))\n    - tracktor: add: cleanup before upgrade [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7782](https://github.com/community-scripts/ProxmoxVE/pull/7782))\n    - Fix regex to extract MySQL version correctly [@MickLesk](https://github.com/MickLesk) ([#7774](https://github.com/community-scripts/ProxmoxVE/pull/7774))\n    - Update Ollama Installer in OpenWebUI to resume downloads if interrupted [@HeedfulCrayon](https://github.com/HeedfulCrayon) ([#7779](https://github.com/community-scripts/ProxmoxVE/pull/7779))\n\n  - #### ✨ New Features\n\n    - Implement clean install option in tools.func (fetch_and_deploy_gh_release) [@MickLesk](https://github.com/MickLesk) ([#7785](https://github.com/community-scripts/ProxmoxVE/pull/7785))\n    - caddy: modify disk size and implement xCaddy update [@MickLesk](https://github.com/MickLesk) ([#7775](https://github.com/community-scripts/ProxmoxVE/pull/7775))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Harmonize  and shorten JSON Names for PVE/PBS/PMG [@MickLesk](https://github.com/MickLesk) ([#7773](https://github.com/community-scripts/ProxmoxVE/pull/7773))\n\n## 2025-09-20\n\n### 🚀 Updated Scripts\n\n  - checkmk.sh Update: Revert old Pr  [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#7765](https://github.com/community-scripts/ProxmoxVE/pull/7765))\n\n  - #### 🐞 Bug Fixes\n\n    - Wazuh: Increase HDD size [@tremor021](https://github.com/tremor021) ([#7759](https://github.com/community-scripts/ProxmoxVE/pull/7759))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Add Debian 13 in bug report template [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#7757](https://github.com/community-scripts/ProxmoxVE/pull/7757))\n\n## 2025-09-19\n\n### 🆕 New Scripts\n\n  - Tunarr ([#7735](https://github.com/community-scripts/ProxmoxVE/pull/7735))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - SigNoz: Fix wrong URL for Zookeeper [@tremor021](https://github.com/tremor021) ([#7742](https://github.com/community-scripts/ProxmoxVE/pull/7742))\n\n## 2025-09-18\n\n### 🆕 New Scripts\n\n  - Alpine-Caddy [@tremor021](https://github.com/tremor021) ([#7711](https://github.com/community-scripts/ProxmoxVE/pull/7711))\n- pve-tool: execute.sh by @jeroenzwart [@MickLesk](https://github.com/MickLesk) ([#7708](https://github.com/community-scripts/ProxmoxVE/pull/7708))\n- GlobaLeaks ([#7707](https://github.com/community-scripts/ProxmoxVE/pull/7707))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Delay chmod after updating beszel [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7725](https://github.com/community-scripts/ProxmoxVE/pull/7725))\n    - Remove redundant globaleaks configuration [@evilaliv3](https://github.com/evilaliv3) ([#7723](https://github.com/community-scripts/ProxmoxVE/pull/7723))\n    - Gatus: check for GO path before update [@vhsdream](https://github.com/vhsdream) ([#7705](https://github.com/community-scripts/ProxmoxVE/pull/7705))\n\n  - #### ✨ New Features\n\n    - Cloudflared: Bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#7719](https://github.com/community-scripts/ProxmoxVE/pull/7719))\n    - AdGuard Home: Bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#7720](https://github.com/community-scripts/ProxmoxVE/pull/7720))\n\n  - #### 🔧 Refactor\n\n    - Immich: Debian Trixie [@vhsdream](https://github.com/vhsdream) ([#7728](https://github.com/community-scripts/ProxmoxVE/pull/7728))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Add Warning for Containerized Home Assistant [@ZaxLofful](https://github.com/ZaxLofful) ([#7704](https://github.com/community-scripts/ProxmoxVE/pull/7704))\n\n## 2025-09-17\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - beszel: fix: binary permission after upgrade [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7697](https://github.com/community-scripts/ProxmoxVE/pull/7697))\n    - RabbitMQ: Update repositories [@tremor021](https://github.com/tremor021) ([#7689](https://github.com/community-scripts/ProxmoxVE/pull/7689))\n    - Komodo: Add docker compose pull for actually updating docker container [@hanneshier](https://github.com/hanneshier) ([#7682](https://github.com/community-scripts/ProxmoxVE/pull/7682))\n\n  - #### ✨ New Features\n\n    - Debian-LXC: Bump to Debian 13 Trixie [@MickLesk](https://github.com/MickLesk) ([#7683](https://github.com/community-scripts/ProxmoxVE/pull/7683))\n    - Bump Immich to v1.142.1 [@vhsdream](https://github.com/vhsdream) ([#7675](https://github.com/community-scripts/ProxmoxVE/pull/7675))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Grist [@tremor021](https://github.com/tremor021) ([#7681](https://github.com/community-scripts/ProxmoxVE/pull/7681))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - Improve: SECURITY.md for clarity and detail + Adding PVE9 as supported [@MickLesk](https://github.com/MickLesk) ([#7690](https://github.com/community-scripts/ProxmoxVE/pull/7690))\n\n## 2025-09-16\n\n### 🚀 Updated Scripts\n\n  - Improve OpenWrt VM boot and readiness check [@MickLesk](https://github.com/MickLesk) ([#7669](https://github.com/community-scripts/ProxmoxVE/pull/7669))\n\n  - #### 🐞 Bug Fixes\n\n    - hortusfox: fix update check [@MickLesk](https://github.com/MickLesk) ([#7667](https://github.com/community-scripts/ProxmoxVE/pull/7667))\n\n## 2025-09-15\n\n### 🆕 New Scripts\n\n  - SigNoz ([#7648](https://github.com/community-scripts/ProxmoxVE/pull/7648))\n- Scraparr ([#7644](https://github.com/community-scripts/ProxmoxVE/pull/7644))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - vm: move pv installation into ensure_pv function [@MickLesk](https://github.com/MickLesk) ([#7642](https://github.com/community-scripts/ProxmoxVE/pull/7642))\n    - Cloudflare-DDNS: Fix the IP6_PROVIDER variable [@hugodantas](https://github.com/hugodantas) ([#7660](https://github.com/community-scripts/ProxmoxVE/pull/7660))\n    - Wikijs: Bump Node.js version to 22 [@MickLesk](https://github.com/MickLesk) ([#7643](https://github.com/community-scripts/ProxmoxVE/pull/7643))\n\n## 2025-09-14\n\n\n\n## 2025-09-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Palmr: hotfix #7622 [@vhsdream](https://github.com/vhsdream) ([#7625](https://github.com/community-scripts/ProxmoxVE/pull/7625))\n    - ollama: fix: ccurl continue on interrupts [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7620](https://github.com/community-scripts/ProxmoxVE/pull/7620))\n\n  - #### 🔧 Refactor\n\n    - pdm: refactor for beta version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7619](https://github.com/community-scripts/ProxmoxVE/pull/7619))\n    - Immich: bump to v1.142.0 [@vhsdream](https://github.com/vhsdream) ([#7594](https://github.com/community-scripts/ProxmoxVE/pull/7594))\n\n### 🌐 Website\n\n  - fix: tagline grammar [@jonathanwuki](https://github.com/jonathanwuki) ([#7621](https://github.com/community-scripts/ProxmoxVE/pull/7621))\n- fix: grammar/capitalization for links and taglines [@jonathanwuki](https://github.com/jonathanwuki) ([#7609](https://github.com/community-scripts/ProxmoxVE/pull/7609))\n\n## 2025-09-12\n\n### 🆕 New Scripts\n\n  - Stylus ([#7588](https://github.com/community-scripts/ProxmoxVE/pull/7588))\n- UHF ([#7589](https://github.com/community-scripts/ProxmoxVE/pull/7589))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Tweak: post-pve-install: create folder if Not exist  [@JVKeller](https://github.com/JVKeller) ([#7598](https://github.com/community-scripts/ProxmoxVE/pull/7598))\n    - Update openwebui.sh [@webmogul1](https://github.com/webmogul1) ([#7582](https://github.com/community-scripts/ProxmoxVE/pull/7582))\n\n  - #### ✨ New Features\n\n    - [core]: add fallback if mariadb upstream unreachable [@MickLesk](https://github.com/MickLesk) ([#7599](https://github.com/community-scripts/ProxmoxVE/pull/7599))\n    - ESPHome: Increase default disk size [@tremor021](https://github.com/tremor021) ([#7600](https://github.com/community-scripts/ProxmoxVE/pull/7600))\n\n## 2025-09-11\n\n### 🆕 New Scripts\n\n  - telegraf ([#7576](https://github.com/community-scripts/ProxmoxVE/pull/7576))\n\n### 🚀 Updated Scripts\n\n  - [core] Sort tools.func functions alphabeticaly [@tremor021](https://github.com/tremor021) ([#7569](https://github.com/community-scripts/ProxmoxVE/pull/7569))\n- mobile subscription nag fix [@dvino](https://github.com/dvino) ([#7567](https://github.com/community-scripts/ProxmoxVE/pull/7567))\n\n  - #### 🐞 Bug Fixes\n\n    - alpine-install: switch to using GitHub to fetch tools when using GitHub [@burritosoftware](https://github.com/burritosoftware) ([#7566](https://github.com/community-scripts/ProxmoxVE/pull/7566))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Add margin-bottom to Most Viewed Scripts header to unifi UI [@BramSuurdje](https://github.com/BramSuurdje) ([#7572](https://github.com/community-scripts/ProxmoxVE/pull/7572))\n\n  - #### 📝 Script Information\n\n    - Fix frontend url [@r1cebank](https://github.com/r1cebank) ([#7578](https://github.com/community-scripts/ProxmoxVE/pull/7578))\n\n## 2025-09-10\n\n### 🆕 New Scripts\n\n  - Autocaliweb ([#7515](https://github.com/community-scripts/ProxmoxVE/pull/7515))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Palmr: fix #7556 [@vhsdream](https://github.com/vhsdream) ([#7558](https://github.com/community-scripts/ProxmoxVE/pull/7558))\n    - Wizarr: Fix DB migrations [@vhsdream](https://github.com/vhsdream) ([#7552](https://github.com/community-scripts/ProxmoxVE/pull/7552))\n    - fix: pmg - split no-nag script into separate config files [@MickLesk](https://github.com/MickLesk) ([#7540](https://github.com/community-scripts/ProxmoxVE/pull/7540))\n\n  - #### ✨ New Features\n\n    - Update Palmr to Support new v3.2.1 [@vhsdream](https://github.com/vhsdream) ([#7526](https://github.com/community-scripts/ProxmoxVE/pull/7526))\n    - add external installer warnings and user confirmation in several LXC's [@MickLesk](https://github.com/MickLesk) ([#7539](https://github.com/community-scripts/ProxmoxVE/pull/7539))\n    - Booklore: Add Bookdrop location to .env [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#7533](https://github.com/community-scripts/ProxmoxVE/pull/7533))\n\n  - #### 🔧 Refactor\n\n    - Refactor: audiobookshelf [@MickLesk](https://github.com/MickLesk) ([#7538](https://github.com/community-scripts/ProxmoxVE/pull/7538))\n    - Refactor: Blocky [@MickLesk](https://github.com/MickLesk) ([#7537](https://github.com/community-scripts/ProxmoxVE/pull/7537))\n    - Improve npmplus credential retrieval and messaging [@MickLesk](https://github.com/MickLesk) ([#7532](https://github.com/community-scripts/ProxmoxVE/pull/7532))\n\n### 🌐 Website\n\n  - #### 💥 Breaking Changes\n\n    - Remove Pingvin Share [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7553](https://github.com/community-scripts/ProxmoxVE/pull/7553))\n\n  - #### 📝 Script Information\n\n    - set updateable to true for several lxc JSON-configs [@MickLesk](https://github.com/MickLesk) ([#7534](https://github.com/community-scripts/ProxmoxVE/pull/7534))\n\n## 2025-09-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Tududi: v0.81 [@vhsdream](https://github.com/vhsdream) ([#7517](https://github.com/community-scripts/ProxmoxVE/pull/7517))\n    - WGDashboard: Revert back to old update method [@tremor021](https://github.com/tremor021) ([#7500](https://github.com/community-scripts/ProxmoxVE/pull/7500))\n    - AdventureLog: remove folder during update process [@MickLesk](https://github.com/MickLesk) ([#7507](https://github.com/community-scripts/ProxmoxVE/pull/7507))\n    - PLANKA: Fix backup and restore commands [@tremor021](https://github.com/tremor021) ([#7505](https://github.com/community-scripts/ProxmoxVE/pull/7505))\n    - Recyclarr: Suppress config creation output [@tremor021](https://github.com/tremor021) ([#7502](https://github.com/community-scripts/ProxmoxVE/pull/7502))\n\n  - #### 🔧 Refactor\n\n    - Pulse: standardise install/update with Pulse repo script [@vhsdream](https://github.com/vhsdream) ([#7519](https://github.com/community-scripts/ProxmoxVE/pull/7519))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Refactor GitHubStarsButton to wrap in Link component for external navigation [@BramSuurdje](https://github.com/BramSuurdje) ([#7492](https://github.com/community-scripts/ProxmoxVE/pull/7492))\n\n  - #### ✨ New Features\n\n    - Bump vite from 7.0.0 to 7.1.5 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#7522](https://github.com/community-scripts/ProxmoxVE/pull/7522))\n\n  - #### 📝 Script Information\n\n    - swizzin: Change category from nvr to media [@MickLesk](https://github.com/MickLesk) ([#7511](https://github.com/community-scripts/ProxmoxVE/pull/7511))\n\n## 2025-09-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - CT's: fix missing variable declaration (actualBudget, openziti, umlautadaptarr) [@MickLesk](https://github.com/MickLesk) ([#7483](https://github.com/community-scripts/ProxmoxVE/pull/7483))\n    - karakeep: fix service file [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7482](https://github.com/community-scripts/ProxmoxVE/pull/7482))\n    - Update searxng-install.sh [@sebguy](https://github.com/sebguy) ([#7469](https://github.com/community-scripts/ProxmoxVE/pull/7469))\n\n  - #### ✨ New Features\n\n    - Immich: bump to version 1.141.1 [@vhsdream](https://github.com/vhsdream) ([#7418](https://github.com/community-scripts/ProxmoxVE/pull/7418))\n    - [core]: switch all base_settings to variables [@MickLesk](https://github.com/MickLesk) ([#7479](https://github.com/community-scripts/ProxmoxVE/pull/7479))\n\n  - #### 💥 Breaking Changes\n\n    - RustDesk Server: Update the credentials info [@tremor021](https://github.com/tremor021) ([#7473](https://github.com/community-scripts/ProxmoxVE/pull/7473))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Format numerical values in DataFetcher component for better readability [@BramSuurdje](https://github.com/BramSuurdje) ([#7477](https://github.com/community-scripts/ProxmoxVE/pull/7477))\n\n  - #### ✨ New Features\n\n    - feat: enhance github stars button to be better looking and more compact [@BramSuurdje](https://github.com/BramSuurdje) ([#7464](https://github.com/community-scripts/ProxmoxVE/pull/7464))\n\n## 2025-09-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update ExecStart path for karakeep service [@CrazyWolf13](https://github.com/CrazyWolf13) ([#7460](https://github.com/community-scripts/ProxmoxVE/pull/7460))\n\n## 2025-09-06\n\n### 🆕 New Scripts\n\n  - Resilio Sync ([#7442](https://github.com/community-scripts/ProxmoxVE/pull/7442))\n- leantime ([#7414](https://github.com/community-scripts/ProxmoxVE/pull/7414))\n\n### 🚀 Updated Scripts\n\n  - use debian source for direct installation of MQTT [@EtlamGit](https://github.com/EtlamGit) ([#7423](https://github.com/community-scripts/ProxmoxVE/pull/7423))\n\n  - #### ✨ New Features\n\n    - feat: added mobile ui subscription nag removal [@ivan-penchev](https://github.com/ivan-penchev) ([#7164](https://github.com/community-scripts/ProxmoxVE/pull/7164))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - MediaManager Configuration Path [@austinpilz](https://github.com/austinpilz) ([#7408](https://github.com/community-scripts/ProxmoxVE/pull/7408))\n    - Paperless-NGX: Remove default credentials from json [@tremor021](https://github.com/tremor021) ([#7403](https://github.com/community-scripts/ProxmoxVE/pull/7403))\n\n## 2025-09-05\n\n### 🚀 Updated Scripts\n\n  - Tududi: Pin version to 0.80 [@vhsdream](https://github.com/vhsdream) ([#7420](https://github.com/community-scripts/ProxmoxVE/pull/7420))\n\n  - #### 🐞 Bug Fixes\n\n    - AdventureLog: Update dependencies [@tremor021](https://github.com/tremor021) ([#7404](https://github.com/community-scripts/ProxmoxVE/pull/7404))\n\n### 🌐 Website\n\n  - refactor: Enhance ScriptAccordion and Sidebar components to support selectedCategory state [@BramSuurdje](https://github.com/BramSuurdje) ([#7405](https://github.com/community-scripts/ProxmoxVE/pull/7405))\n\n## 2025-09-04\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: Syntax error in Immich scripts [@henworth](https://github.com/henworth) ([#7398](https://github.com/community-scripts/ProxmoxVE/pull/7398))\n    - Netdata: Fix pve_check for 8 [@MickLesk](https://github.com/MickLesk) ([#7392](https://github.com/community-scripts/ProxmoxVE/pull/7392))\n\n  - #### 🔧 Refactor\n\n    - Immich: pin compiled photo library revisions [@vhsdream](https://github.com/vhsdream) ([#7395](https://github.com/community-scripts/ProxmoxVE/pull/7395))\n\n## 2025-09-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Element-Synapse: Increase HDD size [@tremor021](https://github.com/tremor021) ([#7384](https://github.com/community-scripts/ProxmoxVE/pull/7384))\n    - Wizarr: fix uv lock issue; use correct output suppression [@vhsdream](https://github.com/vhsdream) ([#7378](https://github.com/community-scripts/ProxmoxVE/pull/7378))\n    - Netbox: Fix missing directory [@tremor021](https://github.com/tremor021) ([#7374](https://github.com/community-scripts/ProxmoxVE/pull/7374))\n\n  - #### 🔧 Refactor\n\n    - Enhanced IP-Tag installation script with interactive configuration, improved VM IP detection, and better visual indicators [@DesertGamer](https://github.com/DesertGamer) ([#7366](https://github.com/community-scripts/ProxmoxVE/pull/7366))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Fix navigation [@BramSuurdje](https://github.com/BramSuurdje) ([#7376](https://github.com/community-scripts/ProxmoxVE/pull/7376))\n\n## 2025-09-02\n\n### 🚀 Updated Scripts\n\n  - Increase default disk size for Apt-Cacher-NG [@MickLesk](https://github.com/MickLesk) ([#7352](https://github.com/community-scripts/ProxmoxVE/pull/7352))\n\n  - #### 🐞 Bug Fixes\n\n    - Snipe-IT: Fix Nginx configuration [@tremor021](https://github.com/tremor021) ([#7358](https://github.com/community-scripts/ProxmoxVE/pull/7358))\n    - booklore: remove folder before update [@MickLesk](https://github.com/MickLesk) ([#7351](https://github.com/community-scripts/ProxmoxVE/pull/7351))\n\n  - #### ✨ New Features\n\n    - Immich: bump version to 1.140.1 [@vhsdream](https://github.com/vhsdream) ([#7349](https://github.com/community-scripts/ProxmoxVE/pull/7349))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - pbs: increase note on website for ipv6 [@MickLesk](https://github.com/MickLesk) ([#7339](https://github.com/community-scripts/ProxmoxVE/pull/7339))\n\n## 2025-09-01\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update configarr.sh to mv backep up .env correctly [@finkerle](https://github.com/finkerle) ([#7323](https://github.com/community-scripts/ProxmoxVE/pull/7323))\n\n  - #### ✨ New Features\n\n    - Refactor + Feature Bump: HomeAssistant OS [@MickLesk](https://github.com/MickLesk) ([#7336](https://github.com/community-scripts/ProxmoxVE/pull/7336))\n    - UmbrelOS: Refactor / use q35 / better import [@MickLesk](https://github.com/MickLesk) ([#7329](https://github.com/community-scripts/ProxmoxVE/pull/7329))\n    - Harmonize GH Release Check (excl. Pre-Releases & Migrate old \"_version.txt\" [@MickLesk](https://github.com/MickLesk) ([#7328](https://github.com/community-scripts/ProxmoxVE/pull/7328))\n\n### 🌐 Website\n\n  - Bump next from 15.2.4 to 15.5.2 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#7309](https://github.com/community-scripts/ProxmoxVE/pull/7309))\n\n  - #### 📝 Script Information\n\n    - booklore: add note for start-up in frontend [@MickLesk](https://github.com/MickLesk) ([#7331](https://github.com/community-scripts/ProxmoxVE/pull/7331))\n"
  },
  {
    "path": ".github/changelogs/2025/10.md",
    "content": "﻿## 2025-10-31\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Reitti: Fix missing data directory [@tremor021](https://github.com/tremor021) ([#8787](https://github.com/community-scripts/ProxmoxVE/pull/8787))\n    - omada: fix update script with mongodb 8 [@MickLesk](https://github.com/MickLesk) ([#8724](https://github.com/community-scripts/ProxmoxVE/pull/8724))\n    - Booklore: Fix port configuration for Nginx [@tremor021](https://github.com/tremor021) ([#8780](https://github.com/community-scripts/ProxmoxVE/pull/8780))\n    - Fix paths in grist.sh [@mrinaldi](https://github.com/mrinaldi) ([#8777](https://github.com/community-scripts/ProxmoxVE/pull/8777))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Removed errant ` from wireguard.json [@AndrewDragonCh](https://github.com/AndrewDragonCh) ([#8791](https://github.com/community-scripts/ProxmoxVE/pull/8791))\n\n## 2025-10-30\n\n### 🆕 New Scripts\n\n  - Livebook ([#8739](https://github.com/community-scripts/ProxmoxVE/pull/8739))\n- Reitti ([#8736](https://github.com/community-scripts/ProxmoxVE/pull/8736))\n- BentoPDF ([#8735](https://github.com/community-scripts/ProxmoxVE/pull/8735))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Open Archiver: Fix missing daemon-reload [@tremor021](https://github.com/tremor021) ([#8768](https://github.com/community-scripts/ProxmoxVE/pull/8768))\n    - Open Archiver: Fix missing command in update procedure [@tremor021](https://github.com/tremor021) ([#8765](https://github.com/community-scripts/ProxmoxVE/pull/8765))\n    - Kimai: Fix database connection string [@tremor021](https://github.com/tremor021) ([#8758](https://github.com/community-scripts/ProxmoxVE/pull/8758))\n    - Add explicit exit calls to update_script functions [@MickLesk](https://github.com/MickLesk) ([#8752](https://github.com/community-scripts/ProxmoxVE/pull/8752))\n    - kimai: Set global SQL mode to empty in install script [@MickLesk](https://github.com/MickLesk) ([#8747](https://github.com/community-scripts/ProxmoxVE/pull/8747))\n\n  - #### ✨ New Features\n\n    - Immich: Updates for v2.2.0 [@vhsdream](https://github.com/vhsdream) ([#8770](https://github.com/community-scripts/ProxmoxVE/pull/8770))\n    - Standardize update success messages in scripts [@MickLesk](https://github.com/MickLesk) ([#8757](https://github.com/community-scripts/ProxmoxVE/pull/8757))\n    - core: add function cleanup_lxc [@MickLesk](https://github.com/MickLesk) ([#8749](https://github.com/community-scripts/ProxmoxVE/pull/8749))\n    - Asterisk: add interactive version selection to installer [@MickLesk](https://github.com/MickLesk) ([#8726](https://github.com/community-scripts/ProxmoxVE/pull/8726))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Cronicle: Update default credentials [@tremor021](https://github.com/tremor021) ([#8720](https://github.com/community-scripts/ProxmoxVE/pull/8720))\n\n## 2025-10-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Docker-VM: add workaround for libguestfs issue on Proxmox VE 9+ [@MickLesk](https://github.com/MickLesk) ([#8722](https://github.com/community-scripts/ProxmoxVE/pull/8722))\n    - Dispatcharr: add folders in installer / add more build ressources [@MickLesk](https://github.com/MickLesk) ([#8708](https://github.com/community-scripts/ProxmoxVE/pull/8708))\n    - LibreTranslate: bump torch version [@MickLesk](https://github.com/MickLesk) ([#8710](https://github.com/community-scripts/ProxmoxVE/pull/8710))\n\n  - #### ✨ New Features\n\n    - Archivebox: add Chromium and Node modules [@MickLesk](https://github.com/MickLesk) ([#8725](https://github.com/community-scripts/ProxmoxVE/pull/8725))\n\n  - #### 🔧 Refactor\n\n    - tracktor: refactor envfile [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8711](https://github.com/community-scripts/ProxmoxVE/pull/8711))\n    - Kimai / Ghost / ManageMyDamnLife: Switch to MariaDB [@MickLesk](https://github.com/MickLesk) ([#8712](https://github.com/community-scripts/ProxmoxVE/pull/8712))\n\n## 2025-10-28\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update alpine-komodo.sh fixing missing pull images command [@glopes](https://github.com/glopes) ([#8689](https://github.com/community-scripts/ProxmoxVE/pull/8689))\n\n  - #### ✨ New Features\n\n    - Update SABnzbd. Include par2cmdline-turbo [@burgerga](https://github.com/burgerga) ([#8648](https://github.com/community-scripts/ProxmoxVE/pull/8648))\n    - jotty: Add more ENV VARS (disabled) [@vhsdream](https://github.com/vhsdream) ([#8688](https://github.com/community-scripts/ProxmoxVE/pull/8688))\n    - Bump bazarr to Debian 13 [@burgerga](https://github.com/burgerga) ([#8677](https://github.com/community-scripts/ProxmoxVE/pull/8677))\n    - Update flaresolverr to Debian 13 [@burgerga](https://github.com/burgerga) ([#8672](https://github.com/community-scripts/ProxmoxVE/pull/8672))\n\n## 2025-10-27\n\n### 🆕 New Scripts\n\n  - Dispatcharr ([#8658](https://github.com/community-scripts/ProxmoxVE/pull/8658))\n- Garage | Alpine-Garage ([#8656](https://github.com/community-scripts/ProxmoxVE/pull/8656))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Add typescript and esbuild to browserless setup [@MickLesk](https://github.com/MickLesk) ([#8666](https://github.com/community-scripts/ProxmoxVE/pull/8666))\n    - jellyfin: fix: intel deps [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8657](https://github.com/community-scripts/ProxmoxVE/pull/8657))\n\n## 2025-10-26\n\n### 🆕 New Scripts\n\n  - ComfyUI ([#8633](https://github.com/community-scripts/ProxmoxVE/pull/8633))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - PiHole: Bump to Debian 12 [@MickLesk](https://github.com/MickLesk) ([#8649](https://github.com/community-scripts/ProxmoxVE/pull/8649))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Mylar3 [@tremor021](https://github.com/tremor021) ([#8642](https://github.com/community-scripts/ProxmoxVE/pull/8642))\n\n## 2025-10-25\n\n### 🆕 New Scripts\n\n  - PatchMon ([#8632](https://github.com/community-scripts/ProxmoxVE/pull/8632))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - UrBackup Server: Fix install going interactive [@tremor021](https://github.com/tremor021) ([#8622](https://github.com/community-scripts/ProxmoxVE/pull/8622))\n\n## 2025-10-24\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Fix config path for BunkerWeb [@Nonolanlan1007](https://github.com/Nonolanlan1007) ([#8618](https://github.com/community-scripts/ProxmoxVE/pull/8618))\n    - Update logo URL in guardian.json [@HydroshieldMKII](https://github.com/HydroshieldMKII) ([#8615](https://github.com/community-scripts/ProxmoxVE/pull/8615))\n\n## 2025-10-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Radicale: Update dependencies [@ilofX](https://github.com/ilofX) ([#8603](https://github.com/community-scripts/ProxmoxVE/pull/8603))\n    - Various Downgrades to Debian 12 (MySQL / OMW / Technitium)  [@MickLesk](https://github.com/MickLesk) ([#8595](https://github.com/community-scripts/ProxmoxVE/pull/8595))\n    - MeTube: Fix inserting path into .bashrc [@tremor021](https://github.com/tremor021) ([#8589](https://github.com/community-scripts/ProxmoxVE/pull/8589))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Kavita + Updated tools.func (no-same-owner) [@MickLesk](https://github.com/MickLesk) ([#8594](https://github.com/community-scripts/ProxmoxVE/pull/8594))\n    - tools.func: update update_check messages for clarity [@MickLesk](https://github.com/MickLesk) ([#8588](https://github.com/community-scripts/ProxmoxVE/pull/8588))\n\n## 2025-10-22\n\n### 🚀 Updated Scripts\n\n  - Refactor: Full Change & Feature-Bump of tools.func [@MickLesk](https://github.com/MickLesk) ([#8409](https://github.com/community-scripts/ProxmoxVE/pull/8409))\n\n  - #### 🐞 Bug Fixes\n\n    - part-db: use helper-script php function [@MickLesk](https://github.com/MickLesk) ([#8575](https://github.com/community-scripts/ProxmoxVE/pull/8575))\n    - omada: remove static mongodb install [@MickLesk](https://github.com/MickLesk) ([#8577](https://github.com/community-scripts/ProxmoxVE/pull/8577))\n\n## 2025-10-21\n\n### 🆕 New Scripts\n\n  - rwMarkable: migrate from rwMarkable => jotty [@vhsdream](https://github.com/vhsdream) ([#8554](https://github.com/community-scripts/ProxmoxVE/pull/8554))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Guardian: Added validation before copying file and fix build command error [@HydroshieldMKII](https://github.com/HydroshieldMKII) ([#8553](https://github.com/community-scripts/ProxmoxVE/pull/8553))\n    - Unifi: Bump libssl debian version to new update [@fastiuk](https://github.com/fastiuk) ([#8547](https://github.com/community-scripts/ProxmoxVE/pull/8547))\n    - Alpine-TeamSpeak-Server: Fix release version fetching [@tremor021](https://github.com/tremor021) ([#8537](https://github.com/community-scripts/ProxmoxVE/pull/8537))\n    - jellyfin: fix opencl dep for ubuntu [@MickLesk](https://github.com/MickLesk) ([#8535](https://github.com/community-scripts/ProxmoxVE/pull/8535))\n\n  - #### ✨ New Features\n\n    - Refactor: ProjectSend [@tremor021](https://github.com/tremor021) ([#8552](https://github.com/community-scripts/ProxmoxVE/pull/8552))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Open Archiver: Fix application icon [@tremor021](https://github.com/tremor021) ([#8542](https://github.com/community-scripts/ProxmoxVE/pull/8542))\n\n## 2025-10-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - jellyfin: fix: version conflict [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8520](https://github.com/community-scripts/ProxmoxVE/pull/8520))\n    - Paperless-AI: Increase CPU and RAM [@MickLesk](https://github.com/MickLesk) ([#8507](https://github.com/community-scripts/ProxmoxVE/pull/8507))\n\n  - #### ✨ New Features\n\n    - Enhance error message for container creation failure [@MickLesk](https://github.com/MickLesk) ([#8511](https://github.com/community-scripts/ProxmoxVE/pull/8511))\n    - Filebrowser-Quantum: change initial config to newer default [@MickLesk](https://github.com/MickLesk) ([#8497](https://github.com/community-scripts/ProxmoxVE/pull/8497))\n\n  - #### 💥 Breaking Changes\n\n    - Remove: GoMFT [@MickLesk](https://github.com/MickLesk) ([#8499](https://github.com/community-scripts/ProxmoxVE/pull/8499))\n\n  - #### 🔧 Refactor\n\n    - palmr: update node to v24 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8521](https://github.com/community-scripts/ProxmoxVE/pull/8521))\n    - jellyfin: add: intel dependencies [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8508](https://github.com/community-scripts/ProxmoxVE/pull/8508))\n    - Jellyfin: ensure libjemalloc is used / increase hdd space [@MickLesk](https://github.com/MickLesk) ([#8494](https://github.com/community-scripts/ProxmoxVE/pull/8494))\n\n## 2025-10-19\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - rwMarkable: Increase RAM [@vhsdream](https://github.com/vhsdream) ([#8482](https://github.com/community-scripts/ProxmoxVE/pull/8482))\n    - changedetection: fix: update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8480](https://github.com/community-scripts/ProxmoxVE/pull/8480))\n\n## 2025-10-18\n\n### 🆕 New Scripts\n\n  - Open-Archiver ([#8452](https://github.com/community-scripts/ProxmoxVE/pull/8452))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Cronicle: Dont copy init.d service file [@tremor021](https://github.com/tremor021) ([#8451](https://github.com/community-scripts/ProxmoxVE/pull/8451))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Nginx Proxy Manager [@MickLesk](https://github.com/MickLesk) ([#8453](https://github.com/community-scripts/ProxmoxVE/pull/8453))\n\n## 2025-10-17\n\n### 🚀 Updated Scripts\n\n  - Revert back to debian 12 template for various apps [@tremor021](https://github.com/tremor021) ([#8431](https://github.com/community-scripts/ProxmoxVE/pull/8431))\n\n  - #### 🐞 Bug Fixes\n\n    - [FIX]Pulse: replace policykit-1 with polkitd [@vhsdream](https://github.com/vhsdream) ([#8439](https://github.com/community-scripts/ProxmoxVE/pull/8439))\n    - MySpeed: Fix build step [@tremor021](https://github.com/tremor021) ([#8427](https://github.com/community-scripts/ProxmoxVE/pull/8427))\n\n  - #### ✨ New Features\n\n    - GLPI: Bump to Debian 13 base [@tremor021](https://github.com/tremor021) ([#8443](https://github.com/community-scripts/ProxmoxVE/pull/8443))\n\n  - #### 🔧 Refactor\n\n    - refactor: fix pve-scripts local install script [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#8418](https://github.com/community-scripts/ProxmoxVE/pull/8418))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - PLANKA: Fix config path [@tremor021](https://github.com/tremor021) ([#8422](https://github.com/community-scripts/ProxmoxVE/pull/8422))\n\n## 2025-10-16\n\n### 🚀 Updated Scripts\n\n  - post-pve/post-pbs: Disable 'pve-enterprise' and 'ceph enterprise' repositories [@MickLesk](https://github.com/MickLesk) ([#8399](https://github.com/community-scripts/ProxmoxVE/pull/8399))\n\n  - #### 🐞 Bug Fixes\n\n    - fix: changedetection: fix for tsc and esbuild not found [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8407](https://github.com/community-scripts/ProxmoxVE/pull/8407))\n    - paperless-ngx: remove unneeded deps, use static ghostscript [@MickLesk](https://github.com/MickLesk) ([#8397](https://github.com/community-scripts/ProxmoxVE/pull/8397))\n    - UmlautAdaptarr: Revert back to bookworm repo [@tremor021](https://github.com/tremor021) ([#8392](https://github.com/community-scripts/ProxmoxVE/pull/8392))\n\n  - #### 🔧 Refactor\n\n    - Enhance nginx proxy manager install script [@MickLesk](https://github.com/MickLesk) ([#8400](https://github.com/community-scripts/ProxmoxVE/pull/8400))\n\n## 2025-10-15\n\n### 🆕 New Scripts\n\n  - LimeSurvey ([#8364](https://github.com/community-scripts/ProxmoxVE/pull/8364))\n- Guardian ([#8365](https://github.com/community-scripts/ProxmoxVE/pull/8365))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update omada-install.sh to use correct libssl version [@punctualwesley](https://github.com/punctualwesley) ([#8380](https://github.com/community-scripts/ProxmoxVE/pull/8380))\n    - zigbee2mqtt: Use hardlinks for PNPM packages [@mikeage](https://github.com/mikeage) ([#8357](https://github.com/community-scripts/ProxmoxVE/pull/8357))\n\n  - #### ✨ New Features\n\n    - Bump Q to S-Scripts to Debian 13 (Trixie) [@MickLesk](https://github.com/MickLesk) ([#8366](https://github.com/community-scripts/ProxmoxVE/pull/8366))\n    - Bump O to P-Scripts to Debian 13 (Trixie) [@MickLesk](https://github.com/MickLesk) ([#8367](https://github.com/community-scripts/ProxmoxVE/pull/8367))\n    - Bump L to N-Scripts to Debian 13 (Trixie) [@MickLesk](https://github.com/MickLesk) ([#8368](https://github.com/community-scripts/ProxmoxVE/pull/8368))\n    - Immich: v2.1.0 - VectorChord 0.5+ support [@vhsdream](https://github.com/vhsdream) ([#8348](https://github.com/community-scripts/ProxmoxVE/pull/8348))\n\n## 2025-10-14\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - MediaManager: Use managed Python 3.13 [@vhsdream](https://github.com/vhsdream) ([#8343](https://github.com/community-scripts/ProxmoxVE/pull/8343))\n\n  - #### 🔧 Refactor\n\n    - Update cockpit installation/update [@burgerga](https://github.com/burgerga) ([#8346](https://github.com/community-scripts/ProxmoxVE/pull/8346))\n\n## 2025-10-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GLPI: fix version 11 [@opastorello](https://github.com/opastorello) ([#8238](https://github.com/community-scripts/ProxmoxVE/pull/8238))\n    - Keycloak: Fix typo in update function [@tremor021](https://github.com/tremor021) ([#8316](https://github.com/community-scripts/ProxmoxVE/pull/8316))\n\n  - #### 🔧 Refactor\n\n    - fix: adjust configarr to use binaries [@BlackDark](https://github.com/BlackDark) ([#8254](https://github.com/community-scripts/ProxmoxVE/pull/8254))\n\n## 2025-10-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: add Debian Testing repo [@vhsdream](https://github.com/vhsdream) ([#8310](https://github.com/community-scripts/ProxmoxVE/pull/8310))\n    - Tinyauth: Fix install issues for v4 [@tremor021](https://github.com/tremor021) ([#8309](https://github.com/community-scripts/ProxmoxVE/pull/8309))\n\n## 2025-10-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zabbix: various bugfixes agent1/agent2 [@MickLesk](https://github.com/MickLesk) ([#8294](https://github.com/community-scripts/ProxmoxVE/pull/8294))\n    - wger: fix python and pip install [@MickLesk](https://github.com/MickLesk) ([#8295](https://github.com/community-scripts/ProxmoxVE/pull/8295))\n    - searxng: add msgspec as dependency [@MickLesk](https://github.com/MickLesk) ([#8293](https://github.com/community-scripts/ProxmoxVE/pull/8293))\n    - keycloak: fix update check [@MickLesk](https://github.com/MickLesk) ([#8275](https://github.com/community-scripts/ProxmoxVE/pull/8275))\n    - komga: fix update check [@MickLesk](https://github.com/MickLesk) ([#8285](https://github.com/community-scripts/ProxmoxVE/pull/8285))\n\n  - #### ✨ New Features\n\n    - host-backup.sh: Added \"ALL\" option and include timestamp in backup filename [@stumpyofpain](https://github.com/stumpyofpain) ([#8276](https://github.com/community-scripts/ProxmoxVE/pull/8276))\n    - Komga: Update dependencies and enable RAR5 support [@tremor021](https://github.com/tremor021) ([#8257](https://github.com/community-scripts/ProxmoxVE/pull/8257))\n\n### 🌐 Website\n\n  - Update script count in metadata and page content from 300+ to 400+ [@BramSuurdje](https://github.com/BramSuurdje) ([#8279](https://github.com/community-scripts/ProxmoxVE/pull/8279))\n- Refactor CI workflow to use Bun instead of Node.js. [@BramSuurdje](https://github.com/BramSuurdje) ([#8277](https://github.com/community-scripts/ProxmoxVE/pull/8277))\n\n## 2025-10-10\n\n### 🆕 New Scripts\n\n  - Prometheus-Blackbox-Exporter ([#8255](https://github.com/community-scripts/ProxmoxVE/pull/8255))\n- SonarQube ([#8256](https://github.com/community-scripts/ProxmoxVE/pull/8256))\n\n### 🚀 Updated Scripts\n\n  - Unifi installation script fix [@knightfall](https://github.com/knightfall) ([#8242](https://github.com/community-scripts/ProxmoxVE/pull/8242))\n\n  - #### 🐞 Bug Fixes\n\n    - Docmost: Fix env variables [@tremor021](https://github.com/tremor021) ([#8244](https://github.com/community-scripts/ProxmoxVE/pull/8244))\n\n  - #### 🔧 Refactor\n\n    - Harmonize Service MSG-Blocks [@MickLesk](https://github.com/MickLesk) ([#8233](https://github.com/community-scripts/ProxmoxVE/pull/8233))\n\n## 2025-10-09\n\n### 🆕 New Scripts\n\n  - New Script: rwMarkable ([#8215](https://github.com/community-scripts/ProxmoxVE/pull/8215))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-Tinyauth: Fixes for v4 release [@tremor021](https://github.com/tremor021) ([#8225](https://github.com/community-scripts/ProxmoxVE/pull/8225))\n\n  - #### ✨ New Features\n\n    - Bump U-T Scripts to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#8227](https://github.com/community-scripts/ProxmoxVE/pull/8227))\n\n## 2025-10-08\n\n### 🚀 Updated Scripts\n\n  - MyIP: Increase resources [@tremor021](https://github.com/tremor021) ([#8199](https://github.com/community-scripts/ProxmoxVE/pull/8199))\n\n  - #### 🐞 Bug Fixes\n\n    - Wireguard: Fix sysctl for Trixie [@tremor021](https://github.com/tremor021) ([#8209](https://github.com/community-scripts/ProxmoxVE/pull/8209))\n    - Update prompt for Stirling-PDF login option [@EarMaster](https://github.com/EarMaster) ([#8196](https://github.com/community-scripts/ProxmoxVE/pull/8196))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Fixed incorrect tag variables in several scripts [@tremor021](https://github.com/tremor021) ([#8182](https://github.com/community-scripts/ProxmoxVE/pull/8182))\n    - ZeroTier One: Fix install output [@tremor021](https://github.com/tremor021) ([#8179](https://github.com/community-scripts/ProxmoxVE/pull/8179))\n\n## 2025-10-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-Caddy: remove functions [@MickLesk](https://github.com/MickLesk) ([#8177](https://github.com/community-scripts/ProxmoxVE/pull/8177))\n    - Palmr: Fix NodeJS setup [@tremor021](https://github.com/tremor021) ([#8173](https://github.com/community-scripts/ProxmoxVE/pull/8173))\n    - GLPI: Fix UNBOUND variable [@tremor021](https://github.com/tremor021) ([#8167](https://github.com/community-scripts/ProxmoxVE/pull/8167))\n    - BookLore: upgrade to Java 25/Gradle 9 [@vhsdream](https://github.com/vhsdream) ([#8165](https://github.com/community-scripts/ProxmoxVE/pull/8165))\n    - Alpine-Wireguard: Fix for update failing in normal mode [@tremor021](https://github.com/tremor021) ([#8160](https://github.com/community-scripts/ProxmoxVE/pull/8160))\n\n  - #### ✨ New Features\n\n    - Bump W-V Scripts to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#8176](https://github.com/community-scripts/ProxmoxVE/pull/8176))\n    - Bump Z-Y Scripts to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#8174](https://github.com/community-scripts/ProxmoxVE/pull/8174))\n    - Docmost: Fixes and updates [@tremor021](https://github.com/tremor021) ([#8158](https://github.com/community-scripts/ProxmoxVE/pull/8158))\n\n## 2025-10-06\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GLPI: Revert fix for v11 [@tremor021](https://github.com/tremor021) ([#8148](https://github.com/community-scripts/ProxmoxVE/pull/8148))\n\n  - #### ✨ New Features\n\n    - Node-Red: bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#8141](https://github.com/community-scripts/ProxmoxVE/pull/8141))\n    - NocoDB: bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#8140](https://github.com/community-scripts/ProxmoxVE/pull/8140))\n    - Navidrome: bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#8139](https://github.com/community-scripts/ProxmoxVE/pull/8139))\n    - pve-scripts-local: add update function [@MickLesk](https://github.com/MickLesk) ([#8138](https://github.com/community-scripts/ProxmoxVE/pull/8138))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update config_path for Zigbee2MQTT configuration [@MickLesk](https://github.com/MickLesk) ([#8153](https://github.com/community-scripts/ProxmoxVE/pull/8153))\n\n## 2025-10-05\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - ActualBudget: bump to debian 13 [@MickLesk](https://github.com/MickLesk) ([#8124](https://github.com/community-scripts/ProxmoxVE/pull/8124))\n    - 2fauth: bump to debian 13 [@MickLesk](https://github.com/MickLesk) ([#8123](https://github.com/community-scripts/ProxmoxVE/pull/8123))\n    - AdventureLog: bump to debian 13 [@MickLesk](https://github.com/MickLesk) ([#8125](https://github.com/community-scripts/ProxmoxVE/pull/8125))\n    - Update cockpit to Debian 13 [@burgerga](https://github.com/burgerga) ([#8119](https://github.com/community-scripts/ProxmoxVE/pull/8119))\n\n## 2025-10-04\n\n### 🚀 Updated Scripts\n\n  - immich: guard /dev/dri permissions so CPU-only installs don’t fail [@mlongwell](https://github.com/mlongwell) ([#8094](https://github.com/community-scripts/ProxmoxVE/pull/8094))\n\n  - #### ✨ New Features\n\n    - PosgreSQL: Add version choice [@tremor021](https://github.com/tremor021) ([#8103](https://github.com/community-scripts/ProxmoxVE/pull/8103))\n\n## 2025-10-03\n\n### 🆕 New Scripts\n\n  - pve-scripts-local ([#8083](https://github.com/community-scripts/ProxmoxVE/pull/8083))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GLPI: Pin version to v10.0.20 [@tremor021](https://github.com/tremor021) ([#8092](https://github.com/community-scripts/ProxmoxVE/pull/8092))\n    - GLPI: Fix database setup [@tremor021](https://github.com/tremor021) ([#8074](https://github.com/community-scripts/ProxmoxVE/pull/8074))\n    - Overseerr: Increase resources [@tremor021](https://github.com/tremor021) ([#8086](https://github.com/community-scripts/ProxmoxVE/pull/8086))\n    - FIX: post-pve-install.sh just quitting [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#8070](https://github.com/community-scripts/ProxmoxVE/pull/8070))\n    - fix: ensure /etc/pulse exists before chown in update script [@rcourtman](https://github.com/rcourtman) ([#8068](https://github.com/community-scripts/ProxmoxVE/pull/8068))\n    - grist: remove unneeded var [@MickLesk](https://github.com/MickLesk) ([#8060](https://github.com/community-scripts/ProxmoxVE/pull/8060))\n\n  - #### 🔧 Refactor\n\n    - Immich: bump version to 2.0.1 [@vhsdream](https://github.com/vhsdream) ([#8090](https://github.com/community-scripts/ProxmoxVE/pull/8090))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Adjust navbar layout for large screen [@BramSuurdje](https://github.com/BramSuurdje) ([#8087](https://github.com/community-scripts/ProxmoxVE/pull/8087))\n\n## 2025-10-02\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - EMQX: removal logic in emqx update [@MickLesk](https://github.com/MickLesk) ([#8050](https://github.com/community-scripts/ProxmoxVE/pull/8050))\n    - fix FlareSolverr version check to v3.3.25 [@MickLesk](https://github.com/MickLesk) ([#8051](https://github.com/community-scripts/ProxmoxVE/pull/8051))\n\n## 2025-10-01\n\n### 🆕 New Scripts\n\n  - New Script: PhpMyAdmin (Addon) [@MickLesk](https://github.com/MickLesk) ([#8030](https://github.com/community-scripts/ProxmoxVE/pull/8030))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - openwrt: Add conditional logic for EFI disk allocation [@MickLesk](https://github.com/MickLesk) ([#8024](https://github.com/community-scripts/ProxmoxVE/pull/8024))\n    - Plant-IT: Pin version to v0.10.0 [@tremor021](https://github.com/tremor021) ([#8023](https://github.com/community-scripts/ProxmoxVE/pull/8023))\n\n  - #### ✨ New Features\n\n    - Immich: bump version to 2.0.0 stable [@vhsdream](https://github.com/vhsdream) ([#8041](https://github.com/community-scripts/ProxmoxVE/pull/8041))\n\n  - #### 🔧 Refactor\n\n    - Immich: bump version to 1.144.1 [@vhsdream](https://github.com/vhsdream) ([#7994](https://github.com/community-scripts/ProxmoxVE/pull/7994))\n"
  },
  {
    "path": ".github/changelogs/2025/11.md",
    "content": "﻿## 2025-11-30\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix(recyclarr): remove update script systemctl commands [@vidonnus](https://github.com/vidonnus) ([#9522](https://github.com/community-scripts/ProxmoxVE/pull/9522))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Actual Budget [@tremor021](https://github.com/tremor021) ([#9518](https://github.com/community-scripts/ProxmoxVE/pull/9518))\n\n## 2025-11-29\n\n### 🆕 New Scripts\n\n  - Valkey ([#9510](https://github.com/community-scripts/ProxmoxVE/pull/9510))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix duplicate ORIGIN in .env for OpenArchiver install script [@Copilot](https://github.com/Copilot) ([#9503](https://github.com/community-scripts/ProxmoxVE/pull/9503))\n\n  - #### 💥 Breaking Changes\n\n    - Remove: Documenso [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#9507](https://github.com/community-scripts/ProxmoxVE/pull/9507))\n\n### 🌐 Website\n\n  - Update Discord link on website [@tremor021](https://github.com/tremor021) ([#9499](https://github.com/community-scripts/ProxmoxVE/pull/9499))\n\n## 2025-11-28\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Apache-guacamole: fixed to early rm [@mtorazzi](https://github.com/mtorazzi) ([#9492](https://github.com/community-scripts/ProxmoxVE/pull/9492))\n\n  - #### 💥 Breaking Changes\n\n    - Remove: Habitica [@MickLesk](https://github.com/MickLesk) ([#9489](https://github.com/community-scripts/ProxmoxVE/pull/9489))\n\n## 2025-11-27\n\n### 🆕 New Scripts\n\n  - Qdrant ([#9465](https://github.com/community-scripts/ProxmoxVE/pull/9465))\n\n### 🚀 Updated Scripts\n\n  - #### 💥 Breaking Changes\n\n    - Upgrade pve-scripts-local to node 24 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#9457](https://github.com/community-scripts/ProxmoxVE/pull/9457))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - PBS: fix typo [@joshuaharmsen845](https://github.com/joshuaharmsen845) ([#9482](https://github.com/community-scripts/ProxmoxVE/pull/9482))\n\n## 2025-11-26\n\n### 🚀 Updated Scripts\n\n  - Joplin Server: Increase RAM for LXC [@tremor021](https://github.com/tremor021) ([#9460](https://github.com/community-scripts/ProxmoxVE/pull/9460))\n\n  - #### 🐞 Bug Fixes\n\n    - Fix Open WebUI update logic (swap upgrade/install) [@camcop](https://github.com/camcop) ([#9461](https://github.com/community-scripts/ProxmoxVE/pull/9461))\n\n## 2025-11-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Open WebUI: Change install command to upgrade for Open-WebUI [@tremor021](https://github.com/tremor021) ([#9448](https://github.com/community-scripts/ProxmoxVE/pull/9448))\n    - core: set default LANG in locale configuration [@MickLesk](https://github.com/MickLesk) ([#9440](https://github.com/community-scripts/ProxmoxVE/pull/9440))\n    - documenso: switch to npm peer-.deps to get build running [@MickLesk](https://github.com/MickLesk) ([#9441](https://github.com/community-scripts/ProxmoxVE/pull/9441))\n    - Refactor Asterisk installation process [@MickLesk](https://github.com/MickLesk) ([#9429](https://github.com/community-scripts/ProxmoxVE/pull/9429))\n    - Fix the mikrotik VM installer after they reformatted their downloads page [@paul-ridgway](https://github.com/paul-ridgway) ([#9434](https://github.com/community-scripts/ProxmoxVE/pull/9434))\n    - paperless: patch consume to uv [@MickLesk](https://github.com/MickLesk) ([#9425](https://github.com/community-scripts/ProxmoxVE/pull/9425))\n\n  - #### ✨ New Features\n\n    - add Zabbix version selection to install and update scripts [@MickLesk](https://github.com/MickLesk) ([#9430](https://github.com/community-scripts/ProxmoxVE/pull/9430))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - gh: update supported PVE Version [@MickLesk](https://github.com/MickLesk) ([#9422](https://github.com/community-scripts/ProxmoxVE/pull/9422))\n\n## 2025-11-24\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - core: remove uv cache clean command [@MickLesk](https://github.com/MickLesk) ([#9413](https://github.com/community-scripts/ProxmoxVE/pull/9413))\n    - Joplin-Server: Bump Node.js version from 22 to 24 [@tremor021](https://github.com/tremor021) ([#9405](https://github.com/community-scripts/ProxmoxVE/pull/9405))\n\n  - #### 🔧 Refactor\n\n    - [Fix]: Wizarr DB error during install [@vhsdream](https://github.com/vhsdream) ([#9415](https://github.com/community-scripts/ProxmoxVE/pull/9415))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Gitea: Update website [@tremor021](https://github.com/tremor021) ([#9406](https://github.com/community-scripts/ProxmoxVE/pull/9406))\n    - huntarr: disable on website during install issues [@MickLesk](https://github.com/MickLesk) ([#9403](https://github.com/community-scripts/ProxmoxVE/pull/9403))\n\n## 2025-11-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - core: remove journal log rotation [@MickLesk](https://github.com/MickLesk) ([#9392](https://github.com/community-scripts/ProxmoxVE/pull/9392))\n    - [LibreNMS] Correcting mariadb sed string for Debian 13 default in install/librenms-install.sh, website config for Debian 13 #9369 [@htmlspinnr](https://github.com/htmlspinnr) ([#9370](https://github.com/community-scripts/ProxmoxVE/pull/9370))\n    - fix: Snipe-IT update check failure [@ruanmed](https://github.com/ruanmed) ([#9371](https://github.com/community-scripts/ProxmoxVE/pull/9371))\n\n  - #### ✨ New Features\n\n    - PVE Kernel Clean: Add info about currently running kernel [@tremor021](https://github.com/tremor021) ([#9388](https://github.com/community-scripts/ProxmoxVE/pull/9388))\n\n  - #### 🔧 Refactor\n\n    - Update glpi-install.sh to remove install.php [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9378](https://github.com/community-scripts/ProxmoxVE/pull/9378))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - fix: enhance back navigation in NotFoundPage component and remove unused deps [@BramSuurdje](https://github.com/BramSuurdje) ([#9341](https://github.com/community-scripts/ProxmoxVE/pull/9341))\n\n  - #### ✨ New Features\n\n    - feat(frontend): add script disable functionality with visual indicators [@AlphaLawless](https://github.com/AlphaLawless) ([#9374](https://github.com/community-scripts/ProxmoxVE/pull/9374))\n\n## 2025-11-22\n\n### 🆕 New Scripts\n\n  - Upgopher ([#9360](https://github.com/community-scripts/ProxmoxVE/pull/9360))\n\n### 🚀 Updated Scripts\n\n  - Expand support to Proxmox VE 9.1 in VM scripts [@MickLesk](https://github.com/MickLesk) ([#9351](https://github.com/community-scripts/ProxmoxVE/pull/9351))\n\n  - #### 🐞 Bug Fixes\n\n    - fix: Snipe-IT install and update failure due to new repository url [@ruanmed](https://github.com/ruanmed) ([#9362](https://github.com/community-scripts/ProxmoxVE/pull/9362))\n    - glpi - allow migration of existing databases [@moodyblue](https://github.com/moodyblue) ([#9353](https://github.com/community-scripts/ProxmoxVE/pull/9353))\n\n  - #### ✨ New Features\n\n    - Refactor cleanup steps to use cleanup_lxc function (install/ Folder) [@MickLesk](https://github.com/MickLesk) ([#9354](https://github.com/community-scripts/ProxmoxVE/pull/9354))\n    - Remove redundant cleanup steps from update scripts (ct/ Folder)  [@MickLesk](https://github.com/MickLesk) ([#9359](https://github.com/community-scripts/ProxmoxVE/pull/9359))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Refactor /data page [@BramSuurdje](https://github.com/BramSuurdje) ([#9343](https://github.com/community-scripts/ProxmoxVE/pull/9343))\n\n## 2025-11-21\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - plex: prevent [] syntax issue [@MickLesk](https://github.com/MickLesk) ([#9318](https://github.com/community-scripts/ProxmoxVE/pull/9318))\n    - fix: karakeep strip \"v\" from release version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9324](https://github.com/community-scripts/ProxmoxVE/pull/9324))\n    - NetVisor: fix grep in update [@vhsdream](https://github.com/vhsdream) ([#9334](https://github.com/community-scripts/ProxmoxVE/pull/9334))\n    - Immich: pin correct version [@vhsdream](https://github.com/vhsdream) ([#9332](https://github.com/community-scripts/ProxmoxVE/pull/9332))\n\n  - #### 🔧 Refactor\n\n    - Refactor IPv6 disable logic and add 'disable' option [@MickLesk](https://github.com/MickLesk) ([#9326](https://github.com/community-scripts/ProxmoxVE/pull/9326))\n\n## 2025-11-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - core: change 'uv cache clear' to 'uv cache clean' [@MickLesk](https://github.com/MickLesk) ([#9299](https://github.com/community-scripts/ProxmoxVE/pull/9299))\n\n  - #### ✨ New Features\n\n    - Immich v2.3.1: OpenVINO tuning, OCR fixes, Maintenance mode, workflows/plugin framework [@vhsdream](https://github.com/vhsdream) ([#9310](https://github.com/community-scripts/ProxmoxVE/pull/9310))\n    - kasm: add: update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9253](https://github.com/community-scripts/ProxmoxVE/pull/9253))\n    - tools/pve: expand PVE support to 9.0–9.1 (post-install & netdata) [@MickLesk](https://github.com/MickLesk) ([#9298](https://github.com/community-scripts/ProxmoxVE/pull/9298))\n\n  - #### 💥 Breaking Changes\n\n    - Omada - AVX-only support [@MickLesk](https://github.com/MickLesk) ([#9295](https://github.com/community-scripts/ProxmoxVE/pull/9295))\n\n## 2025-11-19\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - HotFix: Fix NetVisor env var [@vhsdream](https://github.com/vhsdream) ([#9286](https://github.com/community-scripts/ProxmoxVE/pull/9286))\n    - Jotty: reduce RAM requirement [@vhsdream](https://github.com/vhsdream) ([#9272](https://github.com/community-scripts/ProxmoxVE/pull/9272))\n    - Nginx Proxy Manager: Pin version to v2.13.4 [@tremor021](https://github.com/tremor021) ([#9259](https://github.com/community-scripts/ProxmoxVE/pull/9259))\n\n  - #### ✨ New Features\n\n    - PVE 9.1 version support [@MickLesk](https://github.com/MickLesk) ([#9280](https://github.com/community-scripts/ProxmoxVE/pull/9280))\n    - force disable IPv6 if IPV6_METHOD = none [@MickLesk](https://github.com/MickLesk) ([#9277](https://github.com/community-scripts/ProxmoxVE/pull/9277))\n\n  - #### 💥 Breaking Changes\n\n    - NetVisor: v0.10.0 fixes [@vhsdream](https://github.com/vhsdream) ([#9255](https://github.com/community-scripts/ProxmoxVE/pull/9255))\n\n## 2025-11-18\n\n### 🚀 Updated Scripts\n\n  - librenms: Fix password to short [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#9236](https://github.com/community-scripts/ProxmoxVE/pull/9236))\n\n  - #### 🐞 Bug Fixes\n\n    - Huntarr: Downgrade Python to 3.12 [@MickLesk](https://github.com/MickLesk) ([#9246](https://github.com/community-scripts/ProxmoxVE/pull/9246))\n    - kasm: fix release fetching [@MickLesk](https://github.com/MickLesk) ([#9244](https://github.com/community-scripts/ProxmoxVE/pull/9244))\n\n## 2025-11-17\n\n### 🆕 New Scripts\n\n  - Passbolt ([#9226](https://github.com/community-scripts/ProxmoxVE/pull/9226))\n- Domain-Locker ([#9214](https://github.com/community-scripts/ProxmoxVE/pull/9214))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Domain Monitor: Fix encryption key length in install script [@tremor021](https://github.com/tremor021) ([#9239](https://github.com/community-scripts/ProxmoxVE/pull/9239))\n    - NetVisor: add build deps, increase RAM [@vhsdream](https://github.com/vhsdream) ([#9205](https://github.com/community-scripts/ProxmoxVE/pull/9205))\n    - fix: restart apache2 after installing zabbix config [@AlphaLawless](https://github.com/AlphaLawless) ([#9206](https://github.com/community-scripts/ProxmoxVE/pull/9206))\n\n  - #### ✨ New Features\n\n    - [core]: harmonize app_name for creds [@MickLesk](https://github.com/MickLesk) ([#9224](https://github.com/community-scripts/ProxmoxVE/pull/9224))\n\n  - #### 💥 Breaking Changes\n\n    - Refactor: paperless-ngx (Breaking Change Inside) [@MickLesk](https://github.com/MickLesk) ([#9223](https://github.com/community-scripts/ProxmoxVE/pull/9223))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - github: add verbose mode check to bug report template [@MickLesk](https://github.com/MickLesk) ([#9234](https://github.com/community-scripts/ProxmoxVE/pull/9234))\n\n## 2025-11-16\n\n### 🆕 New Scripts\n\n  - Metabase ([#9190](https://github.com/community-scripts/ProxmoxVE/pull/9190))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Change backup directory to /opt for paperless-ngx [@ProfDrYoMan](https://github.com/ProfDrYoMan) ([#9195](https://github.com/community-scripts/ProxmoxVE/pull/9195))\n    - Kimai: remove deprecated admin_lte section [@MickLesk](https://github.com/MickLesk) ([#9182](https://github.com/community-scripts/ProxmoxVE/pull/9182))\n    - healthchecks: bump python to 3.13 [@MickLesk](https://github.com/MickLesk) ([#9175](https://github.com/community-scripts/ProxmoxVE/pull/9175))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - fixed config_path for donetick [@TazztheMonster](https://github.com/TazztheMonster) ([#9203](https://github.com/community-scripts/ProxmoxVE/pull/9203))\n\n## 2025-11-15\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - privatebin: fix: syntax error in chmod command [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9169](https://github.com/community-scripts/ProxmoxVE/pull/9169))\n    - phpIPHAM: patch db and add fping [@MickLesk](https://github.com/MickLesk) ([#9177](https://github.com/community-scripts/ProxmoxVE/pull/9177))\n    - changedetection: fix: increase ressources [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9171](https://github.com/community-scripts/ProxmoxVE/pull/9171))\n    - 2fauth: update composer command [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9168](https://github.com/community-scripts/ProxmoxVE/pull/9168))\n\n  - #### 🔧 Refactor\n\n    - firefly: refactor update_script and add dataimporter update [@MickLesk](https://github.com/MickLesk) ([#9178](https://github.com/community-scripts/ProxmoxVE/pull/9178))\n\n## 2025-11-14\n\n### 🆕 New Scripts\n\n  - LibreNMS ([#9148](https://github.com/community-scripts/ProxmoxVE/pull/9148))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - karakeep: clean install after every update [@MickLesk](https://github.com/MickLesk) ([#9144](https://github.com/community-scripts/ProxmoxVE/pull/9144))\n\n  - #### ✨ New Features\n\n    - bump grafana to debian 13 [@mschabhuettl](https://github.com/mschabhuettl) ([#9141](https://github.com/community-scripts/ProxmoxVE/pull/9141))\n\n## 2025-11-13\n\n### 🆕 New Scripts\n\n  - Netvisor ([#9133](https://github.com/community-scripts/ProxmoxVE/pull/9133))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Domain Monitor: Add domain checking cron [@tremor021](https://github.com/tremor021) ([#9129](https://github.com/community-scripts/ProxmoxVE/pull/9129))\n    - Kimai: Fix for MariaDB connection URL [@tremor021](https://github.com/tremor021) ([#9124](https://github.com/community-scripts/ProxmoxVE/pull/9124))\n    - Fix: filebrowser-quantum update [@MickLesk](https://github.com/MickLesk) ([#9115](https://github.com/community-scripts/ProxmoxVE/pull/9115))\n    - tools.func: fix wrong output for setup_java (error token is \"0\") [@snow2k9](https://github.com/snow2k9) ([#9110](https://github.com/community-scripts/ProxmoxVE/pull/9110))\n\n  - #### ✨ New Features\n\n    - tools.func: improve Rust setup and crate installation logic [@MickLesk](https://github.com/MickLesk) ([#9120](https://github.com/community-scripts/ProxmoxVE/pull/9120))\n\n  - #### 💥 Breaking Changes\n\n    - Remove Barcodebuddy [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#9135](https://github.com/community-scripts/ProxmoxVE/pull/9135))\n    - Downgrade Swizzin to Debian 12 Bookworm [@MickLesk](https://github.com/MickLesk) ([#9116](https://github.com/community-scripts/ProxmoxVE/pull/9116))\n\n## 2025-11-12\n\n### 🆕 New Scripts\n\n  - Miniflux ([#9091](https://github.com/community-scripts/ProxmoxVE/pull/9091))\n- Splunk Enterprise ([#9090](https://github.com/community-scripts/ProxmoxVE/pull/9090))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - evcc: add missing fi in update  [@MichaelVetter1979](https://github.com/MichaelVetter1979) ([#9107](https://github.com/community-scripts/ProxmoxVE/pull/9107))\n    - PeaNUT: use clean install flag during update [@vhsdream](https://github.com/vhsdream) ([#9100](https://github.com/community-scripts/ProxmoxVE/pull/9100))\n    - Tududi: Create new env file from example; fix installation & update [@vhsdream](https://github.com/vhsdream) ([#9097](https://github.com/community-scripts/ProxmoxVE/pull/9097))\n    - openwebui: Python version usage | core: zsh completion install [@MickLesk](https://github.com/MickLesk) ([#9079](https://github.com/community-scripts/ProxmoxVE/pull/9079))\n    - Refactor: evcc [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9057](https://github.com/community-scripts/ProxmoxVE/pull/9057))\n\n  - #### ✨ New Features\n\n    - Bump K to H-Scripts to Debian 13 (Trixie) [@MickLesk](https://github.com/MickLesk) ([#8597](https://github.com/community-scripts/ProxmoxVE/pull/8597))\n\n  - #### 🔧 Refactor\n\n    - Refactor: web-check [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9055](https://github.com/community-scripts/ProxmoxVE/pull/9055))\n\n### 🌐 Website\n\n  - Refactor web analytics to use Rybbit instead of Umami [@BramSuurdje](https://github.com/BramSuurdje) ([#9072](https://github.com/community-scripts/ProxmoxVE/pull/9072))\n\n## 2025-11-11\n\n### 🆕 New Scripts\n\n  - Domain-Monitor ([#9029](https://github.com/community-scripts/ProxmoxVE/pull/9029))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: fix JDK count variable initialization in setup_java [@MickLesk](https://github.com/MickLesk) ([#9058](https://github.com/community-scripts/ProxmoxVE/pull/9058))\n    - flaresolverr: unpin - use latest version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9046](https://github.com/community-scripts/ProxmoxVE/pull/9046))\n    - Part-DB: Increase amount of RAM [@tremor021](https://github.com/tremor021) ([#9039](https://github.com/community-scripts/ProxmoxVE/pull/9039))\n\n  - #### 🔧 Refactor\n\n    - Refactor: openHAB [@MickLesk](https://github.com/MickLesk) ([#9060](https://github.com/community-scripts/ProxmoxVE/pull/9060))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - [docs / gh]: modernize README | Change Version Support in SECURITY.md | Shoutout to selfhst\\icons [@MickLesk](https://github.com/MickLesk) ([#9049](https://github.com/community-scripts/ProxmoxVE/pull/9049))\n\n## 2025-11-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Plex: extend checking for deb822 source [@Matt17000](https://github.com/Matt17000) ([#9036](https://github.com/community-scripts/ProxmoxVE/pull/9036))\n\n  - #### ✨ New Features\n\n    - tools.func: add helper functions for MariaDB and PostgreSQL setup [@MickLesk](https://github.com/MickLesk) ([#9026](https://github.com/community-scripts/ProxmoxVE/pull/9026))\n    - core: update message for no available updates scenario (if pinned) [@MickLesk](https://github.com/MickLesk) ([#9021](https://github.com/community-scripts/ProxmoxVE/pull/9021))\n    - Migrate Open WebUI to uv-based installation [@MickLesk](https://github.com/MickLesk) ([#9019](https://github.com/community-scripts/ProxmoxVE/pull/9019))\n\n  - #### 🔧 Refactor\n\n    - Refactor: phpIPAM [@MickLesk](https://github.com/MickLesk) ([#9027](https://github.com/community-scripts/ProxmoxVE/pull/9027))\n\n## 2025-11-09\n\n### 🚀 Updated Scripts\n\n  - core: improve log cleaning [@MickLesk](https://github.com/MickLesk) ([#8999](https://github.com/community-scripts/ProxmoxVE/pull/8999))\n\n  - #### 🐞 Bug Fixes\n\n    - Add wkhtmltopdf to Odoo installation dependencies [@akileos](https://github.com/akileos) ([#9010](https://github.com/community-scripts/ProxmoxVE/pull/9010))\n    - fix(jotty): Comments removed from variables, as they are interpreted. [@schneider-de-com](https://github.com/schneider-de-com) ([#9002](https://github.com/community-scripts/ProxmoxVE/pull/9002))\n    - fix(n8n): Add python3-setuptools dependency for Debian 13 [@chrikodo](https://github.com/chrikodo) ([#9007](https://github.com/community-scripts/ProxmoxVE/pull/9007))\n    - Paperless-ngx: hotfix config path [@vhsdream](https://github.com/vhsdream) ([#9003](https://github.com/community-scripts/ProxmoxVE/pull/9003))\n    - Paperless-NGX: Move config backup outside of app folder [@vhsdream](https://github.com/vhsdream) ([#8996](https://github.com/community-scripts/ProxmoxVE/pull/8996))\n\n## 2025-11-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Technitium DNS: Fix update [@tremor021](https://github.com/tremor021) ([#8980](https://github.com/community-scripts/ProxmoxVE/pull/8980))\n    - MediaManager: add LOG_FILE to start.sh script; fix BASE_PATH and PUBLIC_API_URL [@vhsdream](https://github.com/vhsdream) ([#8981](https://github.com/community-scripts/ProxmoxVE/pull/8981))\n    - Firefly: Fix missing command in update script [@tremor021](https://github.com/tremor021) ([#8972](https://github.com/community-scripts/ProxmoxVE/pull/8972))\n    - MongoDB: Remove unused message [@tremor021](https://github.com/tremor021) ([#8969](https://github.com/community-scripts/ProxmoxVE/pull/8969))\n    - Set TZ=Etc/UTC in Ghostfolio installation script [@LuloDev](https://github.com/LuloDev) ([#8961](https://github.com/community-scripts/ProxmoxVE/pull/8961))\n\n  - #### 🔧 Refactor\n\n    - paperless: refactor - remove backup after update and enable clean install [@MickLesk](https://github.com/MickLesk) ([#8988](https://github.com/community-scripts/ProxmoxVE/pull/8988))\n    - Refactor setup_deb822_repo for optional architectures [@MickLesk](https://github.com/MickLesk) ([#8983](https://github.com/community-scripts/ProxmoxVE/pull/8983))\n\n## 2025-11-07\n\n### 🆕 New Scripts\n\n  - infisical ([#8926](https://github.com/community-scripts/ProxmoxVE/pull/8926))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update script URLs to ProxmoxVE repository [@MickLesk](https://github.com/MickLesk) ([#8946](https://github.com/community-scripts/ProxmoxVE/pull/8946))\n    - tools.func: fix amd64 arm64 mismatch [@MickLesk](https://github.com/MickLesk) ([#8943](https://github.com/community-scripts/ProxmoxVE/pull/8943))\n    - ghostfolio: refactor CoinGecko key prompts in installer [@MickLesk](https://github.com/MickLesk) ([#8935](https://github.com/community-scripts/ProxmoxVE/pull/8935))\n    - flaresolverr: pin release to 3.4.3 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8937](https://github.com/community-scripts/ProxmoxVE/pull/8937))\n\n  - #### ✨ New Features\n\n    - Pangolin: Add Traefik proxy [@tremor021](https://github.com/tremor021) ([#8952](https://github.com/community-scripts/ProxmoxVE/pull/8952))\n\n## 2025-11-06\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - OpenProject: Remove duplicate server_path_prefix configuration [@tremor021](https://github.com/tremor021) ([#8919](https://github.com/community-scripts/ProxmoxVE/pull/8919))\n    - Grist: Fix change directory to /opt/grist before build steps [@tremor021](https://github.com/tremor021) ([#8913](https://github.com/community-scripts/ProxmoxVE/pull/8913))\n    - Jotty hotfix: SSO_FALLBACK_LOCAL value [@vhsdream](https://github.com/vhsdream) ([#8907](https://github.com/community-scripts/ProxmoxVE/pull/8907))\n    - npm: add Debian version check to update script [@MickLesk](https://github.com/MickLesk) ([#8901](https://github.com/community-scripts/ProxmoxVE/pull/8901))\n\n  - #### ✨ New Features\n\n    - MongoDB: install script now use setup_mongodb [@MickLesk](https://github.com/MickLesk) ([#8897](https://github.com/community-scripts/ProxmoxVE/pull/8897))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Graylog [@tremor021](https://github.com/tremor021) ([#8912](https://github.com/community-scripts/ProxmoxVE/pull/8912))\n\n## 2025-11-05\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Pin version to 2.2.3 [@vhsdream](https://github.com/vhsdream) ([#8861](https://github.com/community-scripts/ProxmoxVE/pull/8861))\n    - Jotty: increase RAM to 4GB [@vhsdream](https://github.com/vhsdream) ([#8887](https://github.com/community-scripts/ProxmoxVE/pull/8887))\n    - Zabbix: fix agent service recognition in update [@MickLesk](https://github.com/MickLesk) ([#8881](https://github.com/community-scripts/ProxmoxVE/pull/8881))\n\n  - #### 💥 Breaking Changes\n\n    - fix: npm: refactor for v2.13.x [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8870](https://github.com/community-scripts/ProxmoxVE/pull/8870))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Open WebUI [@tremor021](https://github.com/tremor021) ([#8874](https://github.com/community-scripts/ProxmoxVE/pull/8874))\n    - Refactor(tools.func): Add Retry Logic, OS-Upgrade Safety, Smart Version Detection + 10 Critical Bugfixes [@MickLesk](https://github.com/MickLesk) ([#8871](https://github.com/community-scripts/ProxmoxVE/pull/8871))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - npm: Increase RAM and HDD, update Certbot notes [@MickLesk](https://github.com/MickLesk) ([#8882](https://github.com/community-scripts/ProxmoxVE/pull/8882))\n    - Update config_path in donetick.json [@fyxtro](https://github.com/fyxtro) ([#8872](https://github.com/community-scripts/ProxmoxVE/pull/8872))\n\n## 2025-11-04\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - stirling-pdf: add native jbig2 dep to installation script [@MickLesk](https://github.com/MickLesk) ([#8858](https://github.com/community-scripts/ProxmoxVE/pull/8858))\n\n## 2025-11-03\n\n### 🆕 New Scripts\n\n  - Donetick ([#8835](https://github.com/community-scripts/ProxmoxVE/pull/8835))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Pin version to 2.2.2 [@vhsdream](https://github.com/vhsdream) ([#8848](https://github.com/community-scripts/ProxmoxVE/pull/8848))\n    - Asterisk: handle errors in version retrieval commands [@MickLesk](https://github.com/MickLesk) ([#8844](https://github.com/community-scripts/ProxmoxVE/pull/8844))\n    - linkstack: fix wrong directory installation [@omertahaoztop](https://github.com/omertahaoztop) ([#8814](https://github.com/community-scripts/ProxmoxVE/pull/8814))\n    - Remove BOM from shebang lines in ct scripts [@MickLesk](https://github.com/MickLesk) ([#8833](https://github.com/community-scripts/ProxmoxVE/pull/8833))\n\n  - #### 💥 Breaking Changes\n\n    - Removed: MeTube [@MickLesk](https://github.com/MickLesk) ([#8830](https://github.com/community-scripts/ProxmoxVE/pull/8830))\n\n## 2025-11-02\n\n### 🚀 Updated Scripts\n\n  - Zigbee2MQTT: fix: pnpm workspace in update [@fkroeger](https://github.com/fkroeger) ([#8825](https://github.com/community-scripts/ProxmoxVE/pull/8825))\n\n  - #### 🐞 Bug Fixes\n\n    - Pangolin: Fix install and database migration [@tremor021](https://github.com/tremor021) ([#8828](https://github.com/community-scripts/ProxmoxVE/pull/8828))\n    - MediaManager: fix BASE_PATH error preventing main page load [@vhsdream](https://github.com/vhsdream) ([#8821](https://github.com/community-scripts/ProxmoxVE/pull/8821))\n\n## 2025-11-01\n\n### 🆕 New Scripts\n\n  - Pangolin ([#8809](https://github.com/community-scripts/ProxmoxVE/pull/8809))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - VictoriaMetrics: Fix release fetching for Victori Logs add-on [@tremor021](https://github.com/tremor021) ([#8807](https://github.com/community-scripts/ProxmoxVE/pull/8807))\n    - Immich: Pin version to 2.2.1 [@vhsdream](https://github.com/vhsdream) ([#8800](https://github.com/community-scripts/ProxmoxVE/pull/8800))\n    - jellyfin: fix: initial update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8784](https://github.com/community-scripts/ProxmoxVE/pull/8784))\n\n### 🌐 Website\n\n  - frontend: chore: bump debian OS [@CrazyWolf13](https://github.com/CrazyWolf13) ([#8798](https://github.com/community-scripts/ProxmoxVE/pull/8798))\n"
  },
  {
    "path": ".github/changelogs/2025/12.md",
    "content": "﻿## 2025-12-31\n\n### 🚀 Updated Scripts\n\n  - fix(wazuh): add LXC rootcheck exclusion to prevent false positives [@brettlyons](https://github.com/brettlyons) ([#10436](https://github.com/community-scripts/ProxmoxVE/pull/10436))\n\n  - #### 🐞 Bug Fixes\n\n    - Increase BentoPDF RAM requirement from 2GB to 4GB [@Copilot](https://github.com/Copilot) ([#10449](https://github.com/community-scripts/ProxmoxVE/pull/10449))\n    - fix(swizzin): Use HTTPS and add curl error handling [@fmcglinn](https://github.com/fmcglinn) ([#10440](https://github.com/community-scripts/ProxmoxVE/pull/10440))\n\n## 2025-12-30\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Unlink default nginx config [@iLikeToCode](https://github.com/iLikeToCode) ([#10432](https://github.com/community-scripts/ProxmoxVE/pull/10432))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Firefly [@tremor021](https://github.com/tremor021) ([#10421](https://github.com/community-scripts/ProxmoxVE/pull/10421))\n\n### 🗑️ Deleted Scripts\n\n  - Remove: GoAway [@MickLesk](https://github.com/MickLesk) ([#10429](https://github.com/community-scripts/ProxmoxVE/pull/10429))\n\n## 2025-12-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - syncthing: check for deb822 source [@MickLesk](https://github.com/MickLesk) ([#10414](https://github.com/community-scripts/ProxmoxVE/pull/10414))\n    - speedtest-tracker: add external IP URL and internet check hostname in .env [@MickLesk](https://github.com/MickLesk) ([#10078](https://github.com/community-scripts/ProxmoxVE/pull/10078))\n    - Pelican-panel: prevent composer superuser prompt [@MickLesk](https://github.com/MickLesk) ([#10418](https://github.com/community-scripts/ProxmoxVE/pull/10418))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - add libmfx-gen1.2 for intel gpu hwaccel [@jcnix](https://github.com/jcnix) ([#10400](https://github.com/community-scripts/ProxmoxVE/pull/10400))\n\n## 2025-12-28\n\n### 🆕 New Scripts\n\n  - Mail-Archiver ([#10393](https://github.com/community-scripts/ProxmoxVE/pull/10393))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix mongodb update logic [@durzo](https://github.com/durzo) ([#10388](https://github.com/community-scripts/ProxmoxVE/pull/10388))\n    - fix pulse downloading incorrect tarball [@durzo](https://github.com/durzo) ([#10383](https://github.com/community-scripts/ProxmoxVE/pull/10383))\n\n  - #### 🔧 Refactor\n\n    - Linkwarden: enable Corepack and prepare Yarn v4 before running yarn [@MickLesk](https://github.com/MickLesk) ([#10390](https://github.com/community-scripts/ProxmoxVE/pull/10390))\n    - metube: use pnpm + corepack for frontend build [@MickLesk](https://github.com/MickLesk) ([#10392](https://github.com/community-scripts/ProxmoxVE/pull/10392))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - Set default LANG in locale configuration [@jamezpolley](https://github.com/jamezpolley) ([#10378](https://github.com/community-scripts/ProxmoxVE/pull/10378))\n\n### ❔ Uncategorized\n\n  - Updated Frontend Debian and Ubuntu VM notes so links can be copied quickly. [@mzb2xeo](https://github.com/mzb2xeo) ([#10379](https://github.com/community-scripts/ProxmoxVE/pull/10379))\n\n## 2025-12-27\n\n### 🆕 New Scripts\n\n  - nextcloud-exporter ([#10314](https://github.com/community-scripts/ProxmoxVE/pull/10314))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Dotnet ASP Web API: Fix need for verbose [@tremor021](https://github.com/tremor021) ([#10368](https://github.com/community-scripts/ProxmoxVE/pull/10368))\n    - Npm: fix build for 2.13.5 [@durzo](https://github.com/durzo) ([#10340](https://github.com/community-scripts/ProxmoxVE/pull/10340))\n    - Outline: Fix for database connection string [@tremor021](https://github.com/tremor021) ([#10359](https://github.com/community-scripts/ProxmoxVE/pull/10359))\n\n## 2025-12-26\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - phpipam: use PHP 8.4 with correct mysql module for PDO support [@MickLesk](https://github.com/MickLesk) ([#10348](https://github.com/community-scripts/ProxmoxVE/pull/10348))\n    - hyperion: increase disk to 4GB and tools.func: fix /root/. path error [@MickLesk](https://github.com/MickLesk) ([#10349](https://github.com/community-scripts/ProxmoxVE/pull/10349))\n\n### ❔ Uncategorized\n\n  - fix: zoraxy: category [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10344](https://github.com/community-scripts/ProxmoxVE/pull/10344))\n- categorize valkey as database [@pshankinclarke](https://github.com/pshankinclarke) ([#10331](https://github.com/community-scripts/ProxmoxVE/pull/10331))\n\n## 2025-12-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - InfluxDB: Fixes [@tremor021](https://github.com/tremor021) ([#10308](https://github.com/community-scripts/ProxmoxVE/pull/10308))\n    - Increase Zot Default Memory, Recategorize [@chrismuzyn](https://github.com/chrismuzyn) ([#10311](https://github.com/community-scripts/ProxmoxVE/pull/10311))\n\n  - #### 🔧 Refactor\n\n    - Refactor: OpenObserve [@tremor021](https://github.com/tremor021) ([#10279](https://github.com/community-scripts/ProxmoxVE/pull/10279))\n    - Refactor: NZBGet [@tremor021](https://github.com/tremor021) ([#10302](https://github.com/community-scripts/ProxmoxVE/pull/10302))\n    - Refactor: ntfy [@tremor021](https://github.com/tremor021) ([#10303](https://github.com/community-scripts/ProxmoxVE/pull/10303))\n    - Refactor: Notifiarr [@tremor021](https://github.com/tremor021) ([#10304](https://github.com/community-scripts/ProxmoxVE/pull/10304))\n\n### 🌐 Website\n\n  - Fix horizontal scroll on website [@mateossh](https://github.com/mateossh) ([#10317](https://github.com/community-scripts/ProxmoxVE/pull/10317))\n\n## 2025-12-24\n\n### 🚀 Updated Scripts\n\n  - recyclarr: increase cron path [@Uncloak2](https://github.com/Uncloak2) ([#10272](https://github.com/community-scripts/ProxmoxVE/pull/10272))\n\n  - #### 🐞 Bug Fixes\n\n    - fix: technitium: service migration [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10300](https://github.com/community-scripts/ProxmoxVE/pull/10300))\n\n  - #### 🔧 Refactor\n\n    - Overseerr: Update dependencies [@tremor021](https://github.com/tremor021) ([#10275](https://github.com/community-scripts/ProxmoxVE/pull/10275))\n    - Refactor: Paperless-GPT [@tremor021](https://github.com/tremor021) ([#10274](https://github.com/community-scripts/ProxmoxVE/pull/10274))\n    - Refactor: Outline [@tremor021](https://github.com/tremor021) ([#10276](https://github.com/community-scripts/ProxmoxVE/pull/10276))\n    - Refactor: OTS [@tremor021](https://github.com/tremor021) ([#10277](https://github.com/community-scripts/ProxmoxVE/pull/10277))\n    - Refactor: OpenProject [@tremor021](https://github.com/tremor021) ([#10278](https://github.com/community-scripts/ProxmoxVE/pull/10278))\n    - Refactor: Open Archiver [@tremor021](https://github.com/tremor021) ([#10280](https://github.com/community-scripts/ProxmoxVE/pull/10280))\n    - Refactor: Tautulli [@tremor021](https://github.com/tremor021) ([#10241](https://github.com/community-scripts/ProxmoxVE/pull/10241))\n    - Refactor: PrivateBin [@tremor021](https://github.com/tremor021) ([#10256](https://github.com/community-scripts/ProxmoxVE/pull/10256))\n    - Refactor: Podman-Home Assistant [@tremor021](https://github.com/tremor021) ([#10258](https://github.com/community-scripts/ProxmoxVE/pull/10258))\n    - Refactor: Plant-it [@tremor021](https://github.com/tremor021) ([#10259](https://github.com/community-scripts/ProxmoxVE/pull/10259))\n    - Refactor: PatchMon [@tremor021](https://github.com/tremor021) ([#10260](https://github.com/community-scripts/ProxmoxVE/pull/10260))\n    - Refactor: Part-DB [@tremor021](https://github.com/tremor021) ([#10262](https://github.com/community-scripts/ProxmoxVE/pull/10262))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: correct local template discovery regex pattern [@MickLesk](https://github.com/MickLesk) ([#10282](https://github.com/community-scripts/ProxmoxVE/pull/10282))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - pihole-exporter fix: unbound var [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10307](https://github.com/community-scripts/ProxmoxVE/pull/10307))\n\n### ❔ Uncategorized\n\n  - Pocketbase: Add note for superuser account creation [@tremor021](https://github.com/tremor021) ([#10245](https://github.com/community-scripts/ProxmoxVE/pull/10245))\n\n## 2025-12-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Technitium DNS: Migrate service [@tremor021](https://github.com/tremor021) ([#10240](https://github.com/community-scripts/ProxmoxVE/pull/10240))\n    - Update forgejo to debian13 and fix env var [@burgerga](https://github.com/burgerga) ([#10242](https://github.com/community-scripts/ProxmoxVE/pull/10242))\n\n  - #### 🔧 Refactor\n\n    - Passbolt: Small fixes [@tremor021](https://github.com/tremor021) ([#10261](https://github.com/community-scripts/ProxmoxVE/pull/10261))\n    - Refactor: ProjectSend [@tremor021](https://github.com/tremor021) ([#10255](https://github.com/community-scripts/ProxmoxVE/pull/10255))\n    - Prometheus Paperless NGX Exporter: Small fix [@tremor021](https://github.com/tremor021) ([#10254](https://github.com/community-scripts/ProxmoxVE/pull/10254))\n    - Podman: Fixes [@tremor021](https://github.com/tremor021) ([#10257](https://github.com/community-scripts/ProxmoxVE/pull/10257))\n    - Refactor: Beszel [@tremor021](https://github.com/tremor021) ([#10195](https://github.com/community-scripts/ProxmoxVE/pull/10195))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - fix: pihole-exporter: unknown function [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10249](https://github.com/community-scripts/ProxmoxVE/pull/10249))\n\n### ❔ Uncategorized\n\n  - Fix Recyclarr page TypeError: schema mismatch in notes field [@Copilot](https://github.com/Copilot) ([#10253](https://github.com/community-scripts/ProxmoxVE/pull/10253))\n\n## 2025-12-22\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - InvoiceNinja: add chromium dependencies for PDF generation [@MickLesk](https://github.com/MickLesk) ([#10230](https://github.com/community-scripts/ProxmoxVE/pull/10230))\n    - MediaManager) use npm install [@MickLesk](https://github.com/MickLesk) ([#10228](https://github.com/community-scripts/ProxmoxVE/pull/10228))\n    - Kometa: Fix update procedure [@tremor021](https://github.com/tremor021) ([#10217](https://github.com/community-scripts/ProxmoxVE/pull/10217))\n\n  - #### 💥 Breaking Changes\n\n    - refactor: reitti: v3.0.0 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10196](https://github.com/community-scripts/ProxmoxVE/pull/10196))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func - hwaccel: skip setup without GPU passthrough and fix Ubuntu AMD firmware [@MickLesk](https://github.com/MickLesk) ([#10225](https://github.com/community-scripts/ProxmoxVE/pull/10225))\n\n### 📚 Documentation\n\n  - contribution docs: update templates with modern patterns [@MickLesk](https://github.com/MickLesk) ([#10227](https://github.com/community-scripts/ProxmoxVE/pull/10227))\n\n### ❔ Uncategorized\n\n  - InvoiceNinja: switch category [@DragoQC](https://github.com/DragoQC) ([#10223](https://github.com/community-scripts/ProxmoxVE/pull/10223))\n\n## 2025-12-21\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Typo fix in Heimdall install script [@Turcid-uwu](https://github.com/Turcid-uwu) ([#10187](https://github.com/community-scripts/ProxmoxVE/pull/10187))\n\n  - #### ✨ New Features\n\n    - recyclarr: add default daily cron job for recyclarr sync [@MickLesk](https://github.com/MickLesk) ([#10208](https://github.com/community-scripts/ProxmoxVE/pull/10208))\n\n  - #### 🔧 Refactor\n\n    - Optimize Jotty installation with standalone mode [@MickLesk](https://github.com/MickLesk) ([#10207](https://github.com/community-scripts/ProxmoxVE/pull/10207))\n    - unifi: remove mongodb 4.4 support | bump to java 21 [@MickLesk](https://github.com/MickLesk) ([#10206](https://github.com/community-scripts/ProxmoxVE/pull/10206))\n    - Refactor: Backrest [@tremor021](https://github.com/tremor021) ([#10193](https://github.com/community-scripts/ProxmoxVE/pull/10193))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - Fix AMD GPU firmware installation by adding non-free repositories [@MickLesk](https://github.com/MickLesk) ([#10205](https://github.com/community-scripts/ProxmoxVE/pull/10205))\n\n### 🧰 Tools\n\n  - pihole-exporter ([#10091](https://github.com/community-scripts/ProxmoxVE/pull/10091))\n\n## 2025-12-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update Technitium DNS and Restart Service [@DrEVILish](https://github.com/DrEVILish) ([#10181](https://github.com/community-scripts/ProxmoxVE/pull/10181))\n    - bump: ersatztv: deb13 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10174](https://github.com/community-scripts/ProxmoxVE/pull/10174))\n\n## 2025-12-19\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Update Reitti to Java 25 for 3.0.0 compatibility [@Copilot](https://github.com/Copilot) ([#10164](https://github.com/community-scripts/ProxmoxVE/pull/10164))\n    - Bump Bar-Assistant to php 8.4 [@MickLesk](https://github.com/MickLesk) ([#10138](https://github.com/community-scripts/ProxmoxVE/pull/10138))\n    - Zabbix: Add version-specific SQL script path for 7.0 LTS [@MickLesk](https://github.com/MickLesk) ([#10142](https://github.com/community-scripts/ProxmoxVE/pull/10142))\n    - InfluxDB: Fix update function [@Liganic](https://github.com/Liganic) ([#10151](https://github.com/community-scripts/ProxmoxVE/pull/10151))\n\n  - #### ✨ New Features\n\n    - Bump Immich to v2.4.1 [@vhsdream](https://github.com/vhsdream) ([#10154](https://github.com/community-scripts/ProxmoxVE/pull/10154))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Cosmos: + Upgrade to Debian 13  [@MickLesk](https://github.com/MickLesk) ([#10147](https://github.com/community-scripts/ProxmoxVE/pull/10147))\n    - Refactor: Proxmox-Mail-Gateway [@tremor021](https://github.com/tremor021) ([#10070](https://github.com/community-scripts/ProxmoxVE/pull/10070))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: Auto-cleanup after all update_script executions [@MickLesk](https://github.com/MickLesk) ([#10141](https://github.com/community-scripts/ProxmoxVE/pull/10141))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - fix: removed verbose option to avoid unnecessary output [@wolle604](https://github.com/wolle604) ([#10144](https://github.com/community-scripts/ProxmoxVE/pull/10144))\n\n### ❔ Uncategorized\n\n  - Update paymenter.json(#10133) [@DragoQC](https://github.com/DragoQC) ([#10134](https://github.com/community-scripts/ProxmoxVE/pull/10134))\n\n## 2025-12-18\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [HOTFIX] Fix Scanopy release check [@vhsdream](https://github.com/vhsdream) ([#10097](https://github.com/community-scripts/ProxmoxVE/pull/10097))\n    - Fix cleanup issues in npm cache and rustup toolchain [@MickLesk](https://github.com/MickLesk) ([#10107](https://github.com/community-scripts/ProxmoxVE/pull/10107))\n    - Fix Zabbix 7.0 repository URL structure [@MickLesk](https://github.com/MickLesk) ([#10106](https://github.com/community-scripts/ProxmoxVE/pull/10106))\n\n  - #### ✨ New Features\n\n    - bump pihole to debian 13 [@mschabhuettl](https://github.com/mschabhuettl) ([#10118](https://github.com/community-scripts/ProxmoxVE/pull/10118))\n    - Immich: v2.4.0 [@vhsdream](https://github.com/vhsdream) ([#10095](https://github.com/community-scripts/ProxmoxVE/pull/10095))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - tools.func: hardening/Improve error handling and cleanup in shell functions [@MickLesk](https://github.com/MickLesk) ([#10116](https://github.com/community-scripts/ProxmoxVE/pull/10116))\n\n### 🧰 Tools\n\n  - qbittorrent-exporter ([#10090](https://github.com/community-scripts/ProxmoxVE/pull/10090))\n\n  - #### 🐞 Bug Fixes\n\n    - Improved error handling when a command does not exist [@wolle604](https://github.com/wolle604) ([#10089](https://github.com/community-scripts/ProxmoxVE/pull/10089))\n\n## 2025-12-17\n\n### 🚀 Updated Scripts\n\n  - Tracktor: updated environment variables for latest release [@javedh-dev](https://github.com/javedh-dev) ([#10067](https://github.com/community-scripts/ProxmoxVE/pull/10067))\n\n  - #### 🐞 Bug Fixes\n\n    - Semaphore: Fix release binary package fetching [@tremor021](https://github.com/tremor021) ([#10055](https://github.com/community-scripts/ProxmoxVE/pull/10055))\n    - update github repo for endurain [@johanngrobe](https://github.com/johanngrobe) ([#10074](https://github.com/community-scripts/ProxmoxVE/pull/10074))\n\n  - #### ✨ New Features\n\n    - use setup_hwaccel for robust hardware acceleration [@MickLesk](https://github.com/MickLesk) ([#10054](https://github.com/community-scripts/ProxmoxVE/pull/10054))\n    - add hardware acceleration support for 17 additional apps [@MickLesk](https://github.com/MickLesk) ([#10061](https://github.com/community-scripts/ProxmoxVE/pull/10061))\n\n  - #### 🔧 Refactor\n\n    - Telegraf: Small refactor [@tremor021](https://github.com/tremor021) ([#10056](https://github.com/community-scripts/ProxmoxVE/pull/10056))\n    - Refactor: Salt [@tremor021](https://github.com/tremor021) ([#10057](https://github.com/community-scripts/ProxmoxVE/pull/10057))\n    - Refactor: Resilio Sync [@tremor021](https://github.com/tremor021) ([#10058](https://github.com/community-scripts/ProxmoxVE/pull/10058))\n    - Refactor: Reitti [@tremor021](https://github.com/tremor021) ([#10059](https://github.com/community-scripts/ProxmoxVE/pull/10059))\n    - Refactor: Redis [@tremor021](https://github.com/tremor021) ([#10060](https://github.com/community-scripts/ProxmoxVE/pull/10060))\n    - Refactor: Reactive-Resume [@tremor021](https://github.com/tremor021) ([#10062](https://github.com/community-scripts/ProxmoxVE/pull/10062))\n    - Refactor: RDTClient [@tremor021](https://github.com/tremor021) ([#10064](https://github.com/community-scripts/ProxmoxVE/pull/10064))\n    - Refactor: RabbitMQ [@tremor021](https://github.com/tremor021) ([#10065](https://github.com/community-scripts/ProxmoxVE/pull/10065))\n    - Qdrant: Code cleanup [@tremor021](https://github.com/tremor021) ([#10066](https://github.com/community-scripts/ProxmoxVE/pull/10066))\n    - Refactor: Pterodactyl Wings [@tremor021](https://github.com/tremor021) ([#10069](https://github.com/community-scripts/ProxmoxVE/pull/10069))\n\n## 2025-12-16\n\n### 🆕 New Scripts\n\n  - [REFACTOR]: NetVisor => Scanopy [@vhsdream](https://github.com/vhsdream) ([#10011](https://github.com/community-scripts/ProxmoxVE/pull/10011))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - zabbix: fix repo url after change [@MickLesk](https://github.com/MickLesk) ([#10042](https://github.com/community-scripts/ProxmoxVE/pull/10042))\n    - Fix: mariadb repo in update_scripts [@MickLesk](https://github.com/MickLesk) ([#10034](https://github.com/community-scripts/ProxmoxVE/pull/10034))\n    - 2fauth: update PHP version from 8.3 to 8.4 in update_script [@MickLesk](https://github.com/MickLesk) ([#10035](https://github.com/community-scripts/ProxmoxVE/pull/10035))\n    - pdm: add rsyslog to fix /dev/log Connection refused errors [@MickLesk](https://github.com/MickLesk) ([#10018](https://github.com/community-scripts/ProxmoxVE/pull/10018))\n    - 2fauth: bump to php8.4 [@MickLesk](https://github.com/MickLesk) ([#10019](https://github.com/community-scripts/ProxmoxVE/pull/10019))\n    - Miniflux: use correct systemctl to check service instead of file path [@MickLesk](https://github.com/MickLesk) ([#10024](https://github.com/community-scripts/ProxmoxVE/pull/10024))\n    - PhotoPrism: export env variables for CLI tools [@MickLesk](https://github.com/MickLesk) ([#10023](https://github.com/community-scripts/ProxmoxVE/pull/10023))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: IP-Range-Scan Support (app.vars / default.vars)  [@MickLesk](https://github.com/MickLesk) ([#10038](https://github.com/community-scripts/ProxmoxVE/pull/10038))\n    - tools.func: add optional enabled parameter to setup_deb822_repo [@MickLesk](https://github.com/MickLesk) ([#10017](https://github.com/community-scripts/ProxmoxVE/pull/10017))\n    - core: map Etc/* timezones to 'host' for pct compatibility [@MickLesk](https://github.com/MickLesk) ([#10020](https://github.com/community-scripts/ProxmoxVE/pull/10020))\n\n### 🌐 Website\n\n  - website: bump deps & prevent security issues [@MickLesk](https://github.com/MickLesk) ([#10045](https://github.com/community-scripts/ProxmoxVE/pull/10045))\n\n## 2025-12-15\n\n### 🆕 New Scripts\n\n  - Koel ([#9972](https://github.com/community-scripts/ProxmoxVE/pull/9972))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix DiscoPanel build [@PouletteMC](https://github.com/PouletteMC) ([#10009](https://github.com/community-scripts/ProxmoxVE/pull/10009))\n    - fix:ct/openwebui.sh adding progressbar and minimize service downtime [@jobben-2025](https://github.com/jobben-2025) ([#9894](https://github.com/community-scripts/ProxmoxVE/pull/9894))\n    - homarr: add: temp note aboute deb13 requirement [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9992](https://github.com/community-scripts/ProxmoxVE/pull/9992))\n    - paperless-ai: backup data and recreate venv during update [@MickLesk](https://github.com/MickLesk) ([#9987](https://github.com/community-scripts/ProxmoxVE/pull/9987))\n    - fix(booklore): add setup_yq to update script [@MickLesk](https://github.com/MickLesk) ([#9989](https://github.com/community-scripts/ProxmoxVE/pull/9989))\n    - fix(pangolin-install): add network-online dependency [@worried-networking](https://github.com/worried-networking) ([#9984](https://github.com/community-scripts/ProxmoxVE/pull/9984))\n\n  - #### ✨ New Features\n\n    - OPNsense: dynamic crawl latest stable FreeBSD [@austindsmith](https://github.com/austindsmith) ([#9831](https://github.com/community-scripts/ProxmoxVE/pull/9831))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Heimdall Dashboard [@tremor021](https://github.com/tremor021) ([#9959](https://github.com/community-scripts/ProxmoxVE/pull/9959))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools: prevent awk errors in setup_rust on restricted containers [@MickLesk](https://github.com/MickLesk) ([#9985](https://github.com/community-scripts/ProxmoxVE/pull/9985))\n    - core: App Defaults force mode and prevent unbound variables [@MickLesk](https://github.com/MickLesk) ([#9971](https://github.com/community-scripts/ProxmoxVE/pull/9971))\n    - core: load app defaults before applying base_settings / fix composer cleanup after install/update [@MickLesk](https://github.com/MickLesk) ([#9965](https://github.com/community-scripts/ProxmoxVE/pull/9965))\n\n  - #### ✨ New Features\n\n    - tools: handle flat repositories in setup_deb822_repo [@MickLesk](https://github.com/MickLesk) ([#9994](https://github.com/community-scripts/ProxmoxVE/pull/9994))\n\n### 📚 Documentation\n\n  - (github) remove old files and assets [@MickLesk](https://github.com/MickLesk) ([#9991](https://github.com/community-scripts/ProxmoxVE/pull/9991))\n- README; add project statistics / formatting [@MickLesk](https://github.com/MickLesk) ([#9967](https://github.com/community-scripts/ProxmoxVE/pull/9967))\n\n## 2025-12-14\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - SonarQube: Fix database variables [@tremor021](https://github.com/tremor021) ([#9946](https://github.com/community-scripts/ProxmoxVE/pull/9946))\n\n  - #### 💥 Breaking Changes\n\n    - refactor: homarr  [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9948](https://github.com/community-scripts/ProxmoxVE/pull/9948))\n\n### 🌐 Website\n\n  - Update dependencies and remove unused files [@BramSuurdje](https://github.com/BramSuurdje) ([#9945](https://github.com/community-scripts/ProxmoxVE/pull/9945))\n\n## 2025-12-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Umami: Use `pnpm` [@tremor021](https://github.com/tremor021) ([#9937](https://github.com/community-scripts/ProxmoxVE/pull/9937))\n    - Tunarr: Switch to prebuild archive [@tremor021](https://github.com/tremor021) ([#9920](https://github.com/community-scripts/ProxmoxVE/pull/9920))\n    - [HOTFIX] NetVisor: backup OIDC config before update [@vhsdream](https://github.com/vhsdream) ([#9895](https://github.com/community-scripts/ProxmoxVE/pull/9895))\n    - Update OPNsense download URL to version 14.3 [@jaredcarling42-design](https://github.com/jaredcarling42-design) ([#9899](https://github.com/community-scripts/ProxmoxVE/pull/9899))\n\n  - #### ✨ New Features\n\n    - Add optional TLS setup to Valkey installer [@pshankinclarke](https://github.com/pshankinclarke) ([#9789](https://github.com/community-scripts/ProxmoxVE/pull/9789))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Spoolman [@tremor021](https://github.com/tremor021) ([#9873](https://github.com/community-scripts/ProxmoxVE/pull/9873))\n\n### 🧰 Tools\n\n  - AdGuardHome-Sync ([#9783](https://github.com/community-scripts/ProxmoxVE/pull/9783))\n\n### ❔ Uncategorized\n\n  - Update category value in glance.json and adguard-home.json [@Bensonheimer992](https://github.com/Bensonheimer992) ([#9932](https://github.com/community-scripts/ProxmoxVE/pull/9932))\n- Change category ID from 6 to 3 in coolify.json and dokploy.json [@Bensonheimer992](https://github.com/Bensonheimer992) ([#9930](https://github.com/community-scripts/ProxmoxVE/pull/9930))\n\n## 2025-12-12\n\n### 🆕 New Scripts\n\n  - Wallabag ([#9904](https://github.com/community-scripts/ProxmoxVE/pull/9904))\n- InvoiceNinja ([#9905](https://github.com/community-scripts/ProxmoxVE/pull/9905))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pangolin: URL fixes [@tremor021](https://github.com/tremor021) ([#9902](https://github.com/community-scripts/ProxmoxVE/pull/9902))\n\n## 2025-12-11\n\n### 🆕 New Scripts\n\n  - Speedtest-Tracker ([#9802](https://github.com/community-scripts/ProxmoxVE/pull/9802))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - dokploy: require unprivileged LXC environment [@MickLesk](https://github.com/MickLesk) ([#9891](https://github.com/community-scripts/ProxmoxVE/pull/9891))\n    - Update NetVisor repo information [@vhsdream](https://github.com/vhsdream) ([#9864](https://github.com/community-scripts/ProxmoxVE/pull/9864))\n\n  - #### 🔧 Refactor\n\n    - Syncthing: Various fixes [@tremor021](https://github.com/tremor021) ([#9872](https://github.com/community-scripts/ProxmoxVE/pull/9872))\n    - Sonarr: Fix standard [@tremor021](https://github.com/tremor021) ([#9874](https://github.com/community-scripts/ProxmoxVE/pull/9874))\n    - Refactor: Snipe-IT [@tremor021](https://github.com/tremor021) ([#9876](https://github.com/community-scripts/ProxmoxVE/pull/9876))\n    - Technitium DNS: Various fixes [@tremor021](https://github.com/tremor021) ([#9863](https://github.com/community-scripts/ProxmoxVE/pull/9863))\n    - SonarQube: Fixes [@tremor021](https://github.com/tremor021) ([#9875](https://github.com/community-scripts/ProxmoxVE/pull/9875))\n    - endurain: remove unneeded deps [@johanngrobe](https://github.com/johanngrobe) ([#9855](https://github.com/community-scripts/ProxmoxVE/pull/9855))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: skip -features flag when empty [@MickLesk](https://github.com/MickLesk) ([#9871](https://github.com/community-scripts/ProxmoxVE/pull/9871))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - paperless: add note on website (uv usage) [@MickLesk](https://github.com/MickLesk) ([#9833](https://github.com/community-scripts/ProxmoxVE/pull/9833))\n\n## 2025-12-10\n\n### 🆕 New Scripts\n\n  - DiscoPanel ([#9847](https://github.com/community-scripts/ProxmoxVE/pull/9847))\n\n### 🚀 Updated Scripts\n\n  - #### 🔧 Refactor\n\n    - Refactor: UmlautAdaptarr [@tremor021](https://github.com/tremor021) ([#9839](https://github.com/community-scripts/ProxmoxVE/pull/9839))\n    - Verdaccio: Small fixes [@tremor021](https://github.com/tremor021) ([#9836](https://github.com/community-scripts/ProxmoxVE/pull/9836))\n    - Refactor: WaveLog [@tremor021](https://github.com/tremor021) ([#9835](https://github.com/community-scripts/ProxmoxVE/pull/9835))\n    - Refactor: Unifi Network Server [@tremor021](https://github.com/tremor021) ([#9838](https://github.com/community-scripts/ProxmoxVE/pull/9838))\n    - Refactor: Umami [@tremor021](https://github.com/tremor021) ([#9840](https://github.com/community-scripts/ProxmoxVE/pull/9840))\n    - Refactor: UrBackup Server [@tremor021](https://github.com/tremor021) ([#9837](https://github.com/community-scripts/ProxmoxVE/pull/9837))\n    - Refactor: Tianji [@tremor021](https://github.com/tremor021) ([#9842](https://github.com/community-scripts/ProxmoxVE/pull/9842))\n    - Tracktor: Remove unused variable [@tremor021](https://github.com/tremor021) ([#9841](https://github.com/community-scripts/ProxmoxVE/pull/9841))\n\n### ❔ Uncategorized\n\n  - Update icon URLs from master to main branch [@MickLesk](https://github.com/MickLesk) ([#9834](https://github.com/community-scripts/ProxmoxVE/pull/9834))\n\n## 2025-12-09\n\n### 🆕 New Scripts\n\n  - Dokploy ([#9793](https://github.com/community-scripts/ProxmoxVE/pull/9793))\n- Coolify ([#9792](https://github.com/community-scripts/ProxmoxVE/pull/9792))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Refactor: Zerotier-One [@tremor021](https://github.com/tremor021) ([#9804](https://github.com/community-scripts/ProxmoxVE/pull/9804))\n    - Refactor: Zabbix [@tremor021](https://github.com/tremor021) ([#9807](https://github.com/community-scripts/ProxmoxVE/pull/9807))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Zigbee2MQTT [@tremor021](https://github.com/tremor021) ([#9803](https://github.com/community-scripts/ProxmoxVE/pull/9803))\n    - Refactor: Wordpress [@tremor021](https://github.com/tremor021) ([#9808](https://github.com/community-scripts/ProxmoxVE/pull/9808))\n    - Wizarr: Various fixes [@tremor021](https://github.com/tremor021) ([#9809](https://github.com/community-scripts/ProxmoxVE/pull/9809))\n    - Refactor: Wiki.js [@tremor021](https://github.com/tremor021) ([#9810](https://github.com/community-scripts/ProxmoxVE/pull/9810))\n    - Zammad: Various fixes [@tremor021](https://github.com/tremor021) ([#9805](https://github.com/community-scripts/ProxmoxVE/pull/9805))\n    - Refactor: Zipline [@tremor021](https://github.com/tremor021) ([#9801](https://github.com/community-scripts/ProxmoxVE/pull/9801))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - fix(tools): handle repos with 30+ pre-releases in check_for_gh_release [@vidonnus](https://github.com/vidonnus) ([#9786](https://github.com/community-scripts/ProxmoxVE/pull/9786))\n\n  - #### ✨ New Features\n\n    - Feature: extend advanced settings with more options & inherit app defaults [@MickLesk](https://github.com/MickLesk) ([#9776](https://github.com/community-scripts/ProxmoxVE/pull/9776))\n\n### 📚 Documentation\n\n  - website: fix/check updateable flags [@MickLesk](https://github.com/MickLesk) ([#9777](https://github.com/community-scripts/ProxmoxVE/pull/9777))\n- fixed grammar on alert that pops up when you copy the curl command [@Sarthak-Sidhant](https://github.com/Sarthak-Sidhant) ([#9799](https://github.com/community-scripts/ProxmoxVE/pull/9799))\n\n### ❔ Uncategorized\n\n  - Website: Remove Palmr script [@tremor021](https://github.com/tremor021) ([#9824](https://github.com/community-scripts/ProxmoxVE/pull/9824))\n\n## 2025-12-08\n\n### 🚀 Updated Scripts\n\n  - typo: tandoor instead of trandoor [@Neonize](https://github.com/Neonize) ([#9771](https://github.com/community-scripts/ProxmoxVE/pull/9771))\n\n  - #### 🐞 Bug Fixes\n\n    - Tandoor: Remove postgres17-contrib package [@tremor021](https://github.com/tremor021) ([#9781](https://github.com/community-scripts/ProxmoxVE/pull/9781))\n\n  - #### ✨ New Features\n\n    - feat: Add var_gpu flag for GPU passthrough configuration [@MickLesk](https://github.com/MickLesk) ([#9764](https://github.com/community-scripts/ProxmoxVE/pull/9764))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - fix: always show SSH access dialog in advanced settings [@MickLesk](https://github.com/MickLesk) ([#9765](https://github.com/community-scripts/ProxmoxVE/pull/9765))\n\n## 2025-12-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - wanderer: add meilisearch dumpless upgrade for database migration [@MickLesk](https://github.com/MickLesk) ([#9749](https://github.com/community-scripts/ProxmoxVE/pull/9749))\n\n  - #### 💥 Breaking Changes\n\n    - Refactor: Inventree (uses now ubuntu 24.04) [@MickLesk](https://github.com/MickLesk) ([#9752](https://github.com/community-scripts/ProxmoxVE/pull/9752))\n    - Revert Zammad: use Debian 12 and dynamic APT source version [@MickLesk](https://github.com/MickLesk) ([#9750](https://github.com/community-scripts/ProxmoxVE/pull/9750))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: handle empty grep results in stop_all_services [@MickLesk](https://github.com/MickLesk) ([#9748](https://github.com/community-scripts/ProxmoxVE/pull/9748))\n    - Remove Debian from GPU passthrough [@MickLesk](https://github.com/MickLesk) ([#9754](https://github.com/community-scripts/ProxmoxVE/pull/9754))\n\n  - #### ✨ New Features\n\n    - core: motd - dynamically read OS version on each login [@MickLesk](https://github.com/MickLesk) ([#9751](https://github.com/community-scripts/ProxmoxVE/pull/9751))\n\n### 🌐 Website\n\n  - FAQ update [@tremor021](https://github.com/tremor021) ([#9742](https://github.com/community-scripts/ProxmoxVE/pull/9742))\n\n## 2025-12-06\n\n### 🚀 Updated Scripts\n\n  - Update domain-locker-install.sh to enable auto-start after reboot [@alexindigo](https://github.com/alexindigo) ([#9715](https://github.com/community-scripts/ProxmoxVE/pull/9715))\n\n  - #### 🐞 Bug Fixes\n\n    - InfluxDB: Remove InfluxData source list post-installation [@tremor021](https://github.com/tremor021) ([#9723](https://github.com/community-scripts/ProxmoxVE/pull/9723))\n    - InfluxDB: Update InfluxDB repository key URL [@tremor021](https://github.com/tremor021) ([#9720](https://github.com/community-scripts/ProxmoxVE/pull/9720))\n\n  - #### ✨ New Features\n\n    - pin Portainer Update to CE Version only [@sgaert](https://github.com/sgaert) ([#9710](https://github.com/community-scripts/ProxmoxVE/pull/9710))\n\n## 2025-12-05\n\n### 🆕 New Scripts\n\n  - Endurain ([#9681](https://github.com/community-scripts/ProxmoxVE/pull/9681))\n- MeTube ([#9671](https://github.com/community-scripts/ProxmoxVE/pull/9671))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - libretranslate: pin uv python to 3.12 (pytorch fix) [@MickLesk](https://github.com/MickLesk) ([#9699](https://github.com/community-scripts/ProxmoxVE/pull/9699))\n    - alpine: (mariadb/postgresql): correct php-cgi path for php83 (adminer) [@MickLesk](https://github.com/MickLesk) ([#9698](https://github.com/community-scripts/ProxmoxVE/pull/9698))\n    - fix(librespeed-rs): use correct service name [@jniles](https://github.com/jniles) ([#9683](https://github.com/community-scripts/ProxmoxVE/pull/9683))\n    - NetVisor: fix daemon auto-config [@vhsdream](https://github.com/vhsdream) ([#9682](https://github.com/community-scripts/ProxmoxVE/pull/9682))\n    - Improve NVIDIA device detection for container passthrough [@MickLesk](https://github.com/MickLesk) ([#9670](https://github.com/community-scripts/ProxmoxVE/pull/9670))\n    - Fix AdventureLog installation failure: missing postgis extension permissions [@Copilot](https://github.com/Copilot) ([#9674](https://github.com/community-scripts/ProxmoxVE/pull/9674))\n    - paperless: ASGI interface typo [@MickLesk](https://github.com/MickLesk) ([#9668](https://github.com/community-scripts/ProxmoxVE/pull/9668))\n    - var. core fixes (bash to sh in fix_gpu_gids ...)  [@MickLesk](https://github.com/MickLesk) ([#9666](https://github.com/community-scripts/ProxmoxVE/pull/9666))\n\n  - #### ✨ New Features\n\n    - tools.func: handle GitHub 300 Multiple Choices in tarball mode [@MickLesk](https://github.com/MickLesk) ([#9697](https://github.com/community-scripts/ProxmoxVE/pull/9697))\n\n  - #### 🔧 Refactor\n\n    - Refactor: OneDev [@MickLesk](https://github.com/MickLesk) ([#9597](https://github.com/community-scripts/ProxmoxVE/pull/9597))\n\n### 📂 Github\n\n  - chore(github): improve PR template and cleanup obsolete references | move contribution guide [@MickLesk](https://github.com/MickLesk) ([#9700](https://github.com/community-scripts/ProxmoxVE/pull/9700))\n\n## 2025-12-04\n\n### 🛠️ Core Overhaul\n\n  - Major refactor of the entire `/misc` subsystem introducing a secure, modular and fully extensible foundation for all future scripts.  \n    Includes the new three-tier defaults architecture (ENV → App → User), strict variable whitelisting, safe `.vars` parsing without `source/eval`, centralized `error_handler.func`, structured logging, an improved 19-step advanced wizard, unified container creation, dedicated storage selector, updated sysctl handling, IPv6 disable mode, cloud-init library, SSH key auto-discovery, and a complete cleanup of legacy components.  \n    Documentation added under `/docs/guides`.  \n    [@MickLesk](https://github.com/MickLesk) ([#9540](https://github.com/community-scripts/ProxmoxVE/pull/9540))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix kimai.sh update script path typo for local.yaml [@Copilot](https://github.com/Copilot) ([#9645](https://github.com/community-scripts/ProxmoxVE/pull/9645))\n\n  - #### ✨ New Features\n\n    - core: extend storage type support (rbd, nfs, cifs) and validation (iscidirect, isci, zfs, cephfs, pbs) [@MickLesk](https://github.com/MickLesk) ([#9646](https://github.com/community-scripts/ProxmoxVE/pull/9646))\n\n  - #### 🔧 Refactor\n\n    - update pdm repo to stable [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9648](https://github.com/community-scripts/ProxmoxVE/pull/9648))\n\n## 2025-12-03\n\n### 🚀 Updated Scripts\n\n  - fix(opnsense-vm): improve script  and add single-interface mode [@AlphaLawless](https://github.com/AlphaLawless) ([#9614](https://github.com/community-scripts/ProxmoxVE/pull/9614))\n\n  - #### 🐞 Bug Fixes\n\n    - Fix Homebridge update detection for Debian 13 DEB822 format [@Copilot](https://github.com/Copilot) ([#9629](https://github.com/community-scripts/ProxmoxVE/pull/9629))\n    - go2rtc: Add WorkingDirectory to go2rtc service configuration [@tremor021](https://github.com/tremor021) ([#9618](https://github.com/community-scripts/ProxmoxVE/pull/9618))\n\n  - #### 🔧 Refactor\n\n    - explicit node versions [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9594](https://github.com/community-scripts/ProxmoxVE/pull/9594))\n\n### 🌐 Website\n\n  - Bump next from 15.5.2 to 15.5.7 in /frontend in the npm_and_yarn group across 1 directory [@dependabot[bot]](https://github.com/dependabot[bot]) ([#9632](https://github.com/community-scripts/ProxmoxVE/pull/9632))\n\n  - #### 📝 Script Information\n\n    - Update logo URL in swizzin.json [@MickLesk](https://github.com/MickLesk) ([#9627](https://github.com/community-scripts/ProxmoxVE/pull/9627))\n\n## 2025-12-02\n\n### 🆕 New Scripts\n\n  - Snowshare ([#9578](https://github.com/community-scripts/ProxmoxVE/pull/9578))\n\n### 🚀 Updated Scripts\n\n  - NetVisor: patch systemd file to fix new OIDC config [@vhsdream](https://github.com/vhsdream) ([#9562](https://github.com/community-scripts/ProxmoxVE/pull/9562))\n- Refactor: BookStack [@tremor021](https://github.com/tremor021) ([#9567](https://github.com/community-scripts/ProxmoxVE/pull/9567))\n\n  - #### 🐞 Bug Fixes\n\n    - Matterbridge: Fix ExecStart command in service install script to allow childbridge mode [@jonalbr](https://github.com/jonalbr) ([#9603](https://github.com/community-scripts/ProxmoxVE/pull/9603))\n    - Open-webui add .env backup and restore functionality from older versions [@DrDonoso](https://github.com/DrDonoso) ([#9592](https://github.com/community-scripts/ProxmoxVE/pull/9592))\n    - Booklore: Downgrad Java from 25 to 21 [@Pr0mises](https://github.com/Pr0mises) ([#9566](https://github.com/community-scripts/ProxmoxVE/pull/9566))\n\n  - #### ✨ New Features\n\n    - Set Valkey memory and eviction defaults [@pshankinclarke](https://github.com/pshankinclarke) ([#9602](https://github.com/community-scripts/ProxmoxVE/pull/9602))\n    - Add auth via requirepass to Valkey [@pshankinclarke](https://github.com/pshankinclarke) ([#9570](https://github.com/community-scripts/ProxmoxVE/pull/9570))\n\n  - #### 🔧 Refactor\n\n    - Refactor: 2FAuth [@tremor021](https://github.com/tremor021) ([#9582](https://github.com/community-scripts/ProxmoxVE/pull/9582))\n    - Refactor: Paperless-AI [@MickLesk](https://github.com/MickLesk) ([#9588](https://github.com/community-scripts/ProxmoxVE/pull/9588))\n    - Refactor: AdventureLog [@tremor021](https://github.com/tremor021) ([#9583](https://github.com/community-scripts/ProxmoxVE/pull/9583))\n    - CommaFeed: Bump Java and service file [@tremor021](https://github.com/tremor021) ([#9564](https://github.com/community-scripts/ProxmoxVE/pull/9564))\n    - Refactor: Docmost [@tremor021](https://github.com/tremor021) ([#9563](https://github.com/community-scripts/ProxmoxVE/pull/9563))\n    - Cloudflared: Add repo via helper function [@tremor021](https://github.com/tremor021) ([#9565](https://github.com/community-scripts/ProxmoxVE/pull/9565))\n\n### 🧰 Maintenance\n\n  - #### 📝 Documentation\n\n    - add configuration and deployment guides to docs [@MickLesk](https://github.com/MickLesk) ([#9591](https://github.com/community-scripts/ProxmoxVE/pull/9591))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update category for \"Wanderer\" [@Lorondos](https://github.com/Lorondos) ([#9607](https://github.com/community-scripts/ProxmoxVE/pull/9607))\n\n## 2025-12-01\n\n### 🆕 New Scripts\n\n  - Wanderer ([#9556](https://github.com/community-scripts/ProxmoxVE/pull/9556))\n- core: add cloud-init.func library for VM configuration [@MickLesk](https://github.com/MickLesk) ([#9538](https://github.com/community-scripts/ProxmoxVE/pull/9538))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - core: sanitize appname for certificate generation [@tremor021](https://github.com/tremor021) ([#9552](https://github.com/community-scripts/ProxmoxVE/pull/9552))\n    - Fix Django superuser creation failing with ImproperlyConfigured error [@Copilot](https://github.com/Copilot) ([#9554](https://github.com/community-scripts/ProxmoxVE/pull/9554))\n\n  - #### ✨ New Features\n\n    - Bump Baikal to deb13 [@MickLesk](https://github.com/MickLesk) ([#9544](https://github.com/community-scripts/ProxmoxVE/pull/9544))\n    - Enhance MariaDB version fallback logic [@MickLesk](https://github.com/MickLesk) ([#9545](https://github.com/community-scripts/ProxmoxVE/pull/9545))\n\n  - #### 💥 Breaking Changes\n\n    - Refactor: Healthchecks [@MickLesk](https://github.com/MickLesk) ([#9188](https://github.com/community-scripts/ProxmoxVE/pull/9188))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Mealie [@MickLesk](https://github.com/MickLesk) ([#9308](https://github.com/community-scripts/ProxmoxVE/pull/9308))\n\n### 🧰 Maintenance\n\n  - #### 📂 Github\n\n    - add comprehensive documentation (core, develop, functions, technical guide, contributor guide) [@MickLesk](https://github.com/MickLesk) ([#9537](https://github.com/community-scripts/ProxmoxVE/pull/9537))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - update selfhst icon-URLs to use @master path [@MickLesk](https://github.com/MickLesk) ([#9543](https://github.com/community-scripts/ProxmoxVE/pull/9543))\n\n\n## 2025-12-31\n\n### 🚀 Updated Scripts\n\n  - fix(wazuh): add LXC rootcheck exclusion to prevent false positives [@brettlyons](https://github.com/brettlyons) ([#10436](https://github.com/community-scripts/ProxmoxVE/pull/10436))\n\n  - #### 🐞 Bug Fixes\n\n    - Increase BentoPDF RAM requirement from 2GB to 4GB [@Copilot](https://github.com/Copilot) ([#10449](https://github.com/community-scripts/ProxmoxVE/pull/10449))\n    - fix(swizzin): Use HTTPS and add curl error handling [@fmcglinn](https://github.com/fmcglinn) ([#10440](https://github.com/community-scripts/ProxmoxVE/pull/10440))"
  },
  {
    "path": ".github/changelogs/2026/01.md",
    "content": "## 2026-01-31\n\n### 🆕 New Scripts\n\n  - shelfmark ([#11371](https://github.com/community-scripts/ProxmoxVE/pull/11371))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: yubal: add git [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11394](https://github.com/community-scripts/ProxmoxVE/pull/11394))\n\n## 2026-01-30\n\n### 🆕 New Scripts\n\n  - languagetool ([#11370](https://github.com/community-scripts/ProxmoxVE/pull/11370))\n  - Ampache ([#11369](https://github.com/community-scripts/ProxmoxVE/pull/11369))\n\n### 🚀 Updated Scripts\n\n  - #### 🔧 Refactor\n\n    - Refactor: remove redundant PHP_MODULE entries in several scripts [@MickLesk](https://github.com/MickLesk) ([#11362](https://github.com/community-scripts/ProxmoxVE/pull/11362))\n    - Refactor: Koillection [@MickLesk](https://github.com/MickLesk) ([#11361](https://github.com/community-scripts/ProxmoxVE/pull/11361))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: meilisearch - add data migration for version upgrades [@MickLesk](https://github.com/MickLesk) ([#11356](https://github.com/community-scripts/ProxmoxVE/pull/11356))\n\n  - #### ✨ New Features\n\n    - [tools] Add `fetch_and_deploy_from_url()` [@tremor021](https://github.com/tremor021) ([#11376](https://github.com/community-scripts/ProxmoxVE/pull/11376))\n    - core: php - improve module handling and prevent installation failures [@MickLesk](https://github.com/MickLesk) ([#11358](https://github.com/community-scripts/ProxmoxVE/pull/11358))\n\n## 2026-01-29\n\n### 🆕 New Scripts\n\n  - Alpine-Valkey [@MickLesk](https://github.com/MickLesk) ([#11320](https://github.com/community-scripts/ProxmoxVE/pull/11320))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Pin version to 2.5.2 [@vhsdream](https://github.com/vhsdream) ([#11335](https://github.com/community-scripts/ProxmoxVE/pull/11335))\n    - Kollection: Update to php 8.5 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11315](https://github.com/community-scripts/ProxmoxVE/pull/11315))\n    - Notifiarr: change installation check from apt to systemd service [@MickLesk](https://github.com/MickLesk) ([#11319](https://github.com/community-scripts/ProxmoxVE/pull/11319))\n\n  - #### ✨ New Features\n\n    - [FEAT] Immich: Enable Maintenance Mode before update [@vhsdream](https://github.com/vhsdream) ([#11342](https://github.com/community-scripts/ProxmoxVE/pull/11342))\n    - jellyfin: add logrotate instead of reducing log level [@MickLesk](https://github.com/MickLesk) ([#11326](https://github.com/community-scripts/ProxmoxVE/pull/11326))\n    - core: Add config file handling options | Fix Vikunja update with interactive overwrite [@MickLesk](https://github.com/MickLesk) ([#11317](https://github.com/community-scripts/ProxmoxVE/pull/11317))\n    - Immich: v2.5.0 [@vhsdream](https://github.com/vhsdream) ([#11240](https://github.com/community-scripts/ProxmoxVE/pull/11240))\n\n  - #### 💥 Breaking Changes\n\n    - fix: vikunja v1 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11308](https://github.com/community-scripts/ProxmoxVE/pull/11308))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Byparr [@vhsdream](https://github.com/vhsdream) ([#11338](https://github.com/community-scripts/ProxmoxVE/pull/11338))\n    - cloudflare: Remove deprecated DNS-over-HTTPS proxy option [@MickLesk](https://github.com/MickLesk) ([#11068](https://github.com/community-scripts/ProxmoxVE/pull/11068))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - build.func: Replace storage variable with searchdomain variable [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11322](https://github.com/community-scripts/ProxmoxVE/pull/11322))\n\n### 📂 Github\n\n  - Add workflow to lock closed issues [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11316](https://github.com/community-scripts/ProxmoxVE/pull/11316))\n\n## 2026-01-28\n\n### 🆕 New Scripts\n\n  - nodecast-tv ([#11287](https://github.com/community-scripts/ProxmoxVE/pull/11287))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Ubuntu 25.04 VM - Change default start from yes to no [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11292](https://github.com/community-scripts/ProxmoxVE/pull/11292))\n\n  - #### ✨ New Features\n\n    - various scripts: use setup_meilisearch function [@MickLesk](https://github.com/MickLesk) ([#11259](https://github.com/community-scripts/ProxmoxVE/pull/11259))\n\n  - #### 🔧 Refactor\n\n    - Refactor: NPMPlus / Default Login [@MickLesk](https://github.com/MickLesk) ([#11262](https://github.com/community-scripts/ProxmoxVE/pull/11262))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: sed patch for ram [@lavacano](https://github.com/lavacano) ([#11285](https://github.com/community-scripts/ProxmoxVE/pull/11285))\n    - Fix installer loop caused by invalid whiptail menu separator [@Mesteriis](https://github.com/Mesteriis) ([#11237](https://github.com/community-scripts/ProxmoxVE/pull/11237))\n    - core: fix Debian 13 LXC template root ownership bug [@MickLesk](https://github.com/MickLesk) ([#11277](https://github.com/community-scripts/ProxmoxVE/pull/11277))\n    - tools.func: prevent systemd-tmpfiles failure in unprivileged LXC during deb install [@MickLesk](https://github.com/MickLesk) ([#11271](https://github.com/community-scripts/ProxmoxVE/pull/11271))\n    - tools.func: fix php \"wait_for\" hint [@MickLesk](https://github.com/MickLesk) ([#11254](https://github.com/community-scripts/ProxmoxVE/pull/11254))\n\n  - #### ✨ New Features\n\n    - core: update dynamic values in LXC profile on update_motd_ip [@MickLesk](https://github.com/MickLesk) ([#11268](https://github.com/community-scripts/ProxmoxVE/pull/11268))\n    - tools.func: add new function - setup_meilisearch [@MickLesk](https://github.com/MickLesk) ([#11258](https://github.com/community-scripts/ProxmoxVE/pull/11258))\n\n### 📂 Github\n\n  - github: add GitHub-based versions.json updater [@MickLesk](https://github.com/MickLesk) ([#10021](https://github.com/community-scripts/ProxmoxVE/pull/10021))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - Frontend: use github-versions.json for version display [@MickLesk](https://github.com/MickLesk) ([#11281](https://github.com/community-scripts/ProxmoxVE/pull/11281))\n\n  - #### 📝 Script Information\n\n    - fix: homarr: conf location [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11253](https://github.com/community-scripts/ProxmoxVE/pull/11253))\n\n## 2026-01-27\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [FIX] Jotty: backup and restore custom config [@vhsdream](https://github.com/vhsdream) ([#11212](https://github.com/community-scripts/ProxmoxVE/pull/11212))\n    - Immich: update libraw [@vhsdream](https://github.com/vhsdream) ([#11233](https://github.com/community-scripts/ProxmoxVE/pull/11233))\n\n  - #### ✨ New Features\n\n    - grist: enable optional enterprise features toggle [@MickLesk](https://github.com/MickLesk) ([#11239](https://github.com/community-scripts/ProxmoxVE/pull/11239))\n\n  - #### 🔧 Refactor\n\n    - Termix: use nginx.conf from upstream repo [@MickLesk](https://github.com/MickLesk) ([#11228](https://github.com/community-scripts/ProxmoxVE/pull/11228))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - feat: add NVIDIA driver install prompt for GPU-enabled containers [@devdecrux](https://github.com/devdecrux) ([#11184](https://github.com/community-scripts/ProxmoxVE/pull/11184))\n\n### 📚 Documentation\n\n  - doc setup_deb822_repo arg order [@chrnie](https://github.com/chrnie) ([#11215](https://github.com/community-scripts/ProxmoxVE/pull/11215))\n  - changelog: archive old entries to year/month files [@MickLesk](https://github.com/MickLesk) ([#11225](https://github.com/community-scripts/ProxmoxVE/pull/11225))\n\n## 2026-01-26\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Ghost: Fix missing dependency [@tremor021](https://github.com/tremor021) ([#11196](https://github.com/community-scripts/ProxmoxVE/pull/11196))\n    - tracearr: fix install check and update node to version 24 [@durzo](https://github.com/durzo) ([#11188](https://github.com/community-scripts/ProxmoxVE/pull/11188))\n\n  - #### ✨ New Features\n\n    - jotty: full refactor / prebuild package [@MickLesk](https://github.com/MickLesk) ([#11059](https://github.com/community-scripts/ProxmoxVE/pull/11059))\n\n  - #### 💥 Breaking Changes\n\n    - Termix: Fixing Nginx configuration for 1.11.0 installs (read description for fix!) [@8b1th3r0](https://github.com/8b1th3r0) ([#11207](https://github.com/community-scripts/ProxmoxVE/pull/11207))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: refine cleanup_lxc to safely clear caches [@MickLesk](https://github.com/MickLesk) ([#11197](https://github.com/community-scripts/ProxmoxVE/pull/11197))\n\n  - #### ✨ New Features\n\n    - core: add nesting warning for systemd-based distributions [@MickLesk](https://github.com/MickLesk) ([#11208](https://github.com/community-scripts/ProxmoxVE/pull/11208))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - jellystat: correct WorkingDirectory to /backend [@MickLesk](https://github.com/MickLesk) ([#11201](https://github.com/community-scripts/ProxmoxVE/pull/11201))\n\n## 2026-01-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [FIX] Tautulli: ensure virtualenv is recreated during update; backup tautulli.db [@vhsdream](https://github.com/vhsdream) ([#11182](https://github.com/community-scripts/ProxmoxVE/pull/11182))\n    - [Fix] Pangolin: ensure additional JSON files are in place [@vhsdream](https://github.com/vhsdream) ([#11183](https://github.com/community-scripts/ProxmoxVE/pull/11183))\n    - Manyfold: fix permissions error [@vhsdream](https://github.com/vhsdream) ([#11165](https://github.com/community-scripts/ProxmoxVE/pull/11165))\n    - Termix: recreate nginx dirs and backup uploads on update [@MickLesk](https://github.com/MickLesk) ([#11169](https://github.com/community-scripts/ProxmoxVE/pull/11169))\n    - Deluge: correct service paths to /usr/local/bin [@MickLesk](https://github.com/MickLesk) ([#11170](https://github.com/community-scripts/ProxmoxVE/pull/11170))\n\n  - #### ✨ New Features\n\n    - Karakeep: Add the FFmpeg option to the installation script [@vonhyou](https://github.com/vonhyou) ([#11157](https://github.com/community-scripts/ProxmoxVE/pull/11157))\n    - apt-cacher-ng: add avahi-daemon for mDNS service discovery [@MickLesk](https://github.com/MickLesk) ([#11140](https://github.com/community-scripts/ProxmoxVE/pull/11140))\n\n## 2026-01-24\n\n### 🆕 New Scripts\n\n  - Manyfold ([#11143](https://github.com/community-scripts/ProxmoxVE/pull/11143))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - elementsynapse: correct parameter order in fetch_and_deploy_gh_release [@MickLesk](https://github.com/MickLesk) ([#11145](https://github.com/community-scripts/ProxmoxVE/pull/11145))\n    - leantime: fix backup file naming [@MickLesk](https://github.com/MickLesk) ([#11137](https://github.com/community-scripts/ProxmoxVE/pull/11137))\n    - [Hotfix] Element Synapse [@vhsdream](https://github.com/vhsdream) ([#11135](https://github.com/community-scripts/ProxmoxVE/pull/11135))\n    - authelia: use POSIX-safe arithmetic to avoid exit code 1 with set -e in subshells [@MickLesk](https://github.com/MickLesk) ([#11125](https://github.com/community-scripts/ProxmoxVE/pull/11125))\n    - Bitmagnet: PostgreSQL and environment variable fixes [@tremor021](https://github.com/tremor021) ([#11119](https://github.com/community-scripts/ProxmoxVE/pull/11119))\n    - Spoolman: move to uv [@vhsdream](https://github.com/vhsdream) ([#11121](https://github.com/community-scripts/ProxmoxVE/pull/11121))\n\n  - #### 🔧 Refactor\n\n    - bump crafty-controller to debian 13 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11094](https://github.com/community-scripts/ProxmoxVE/pull/11094))\n    - Netbox: Refactor [@vhsdream](https://github.com/vhsdream) ([#11126](https://github.com/community-scripts/ProxmoxVE/pull/11126))\n    - Flatnotes: Standard enforcing [@tremor021](https://github.com/tremor021) ([#11109](https://github.com/community-scripts/ProxmoxVE/pull/11109))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - nvidia: use versioned nvidia-utils package for Ubuntu fallback [@MickLesk](https://github.com/MickLesk) ([#11139](https://github.com/community-scripts/ProxmoxVE/pull/11139))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Byparr: Add config file path to website [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11120](https://github.com/community-scripts/ProxmoxVE/pull/11120))\n\n## 2026-01-23\n\n### 🆕 New Scripts\n\n  - Tracearr ([#11079](https://github.com/community-scripts/ProxmoxVE/pull/11079))\n  - Dawarich ([#11075](https://github.com/community-scripts/ProxmoxVE/pull/11075))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: homarr: more ram [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11102](https://github.com/community-scripts/ProxmoxVE/pull/11102))\n    - plant-it: re-add JWT_SECRET [@MickLesk](https://github.com/MickLesk) ([#11098](https://github.com/community-scripts/ProxmoxVE/pull/11098))\n    - Tautulli: fix config backup and restore logic [@MickLesk](https://github.com/MickLesk) ([#11099](https://github.com/community-scripts/ProxmoxVE/pull/11099))\n    - Scanopy: remove integrated daemon script [@vhsdream](https://github.com/vhsdream) ([#11100](https://github.com/community-scripts/ProxmoxVE/pull/11100))\n    - fix: reitti start nginx [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11095](https://github.com/community-scripts/ProxmoxVE/pull/11095))\n    - add: uptime-kuma: chromium [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11081](https://github.com/community-scripts/ProxmoxVE/pull/11081))\n    - fix(install): Add typing_extensions to SearXNG Python dependencies [@ZarenOFF](https://github.com/ZarenOFF) ([#11074](https://github.com/community-scripts/ProxmoxVE/pull/11074))\n\n  - #### ✨ New Features\n\n    - Bump various scripts to Debian 13 (Trixie) [@MickLesk](https://github.com/MickLesk) ([#11093](https://github.com/community-scripts/ProxmoxVE/pull/11093))\n    - several scripts: bump default Alpine version to 3.23 [@MickLesk](https://github.com/MickLesk) ([#11082](https://github.com/community-scripts/ProxmoxVE/pull/11082))\n    - PDM: avoid installing useless package [@LongQT-sea](https://github.com/LongQT-sea) ([#10833](https://github.com/community-scripts/ProxmoxVE/pull/10833))\n\n  - #### 🔧 Refactor\n\n    - FHEM: Bump to Debian 13 [@tremor021](https://github.com/tremor021) ([#11061](https://github.com/community-scripts/ProxmoxVE/pull/11061))\n    - Duplicati: Bump to Debian 13 [@tremor021](https://github.com/tremor021) ([#11060](https://github.com/community-scripts/ProxmoxVE/pull/11060))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: add IPv6 fallback support to get_current_ip functions | add check for SSH_KEYS_FILE in user_defaults [@MickLesk](https://github.com/MickLesk) ([#11067](https://github.com/community-scripts/ProxmoxVE/pull/11067))\n\n## 2026-01-22\n\n### 🆕 New Scripts\n\n  - Loki |  Alpine-Loki ([#11048](https://github.com/community-scripts/ProxmoxVE/pull/11048))\n\n### 🚀 Updated Scripts\n\n  - Immich: Increase RAM to 6GB [@vhsdream](https://github.com/vhsdream) ([#10965](https://github.com/community-scripts/ProxmoxVE/pull/10965))\n\n  - #### 🐞 Bug Fixes\n\n    - Jotty: Increase default disk size from 6 to 8 [@tremor021](https://github.com/tremor021) ([#11056](https://github.com/community-scripts/ProxmoxVE/pull/11056))\n    - Fix tags in several scripts [@s4dmach1ne](https://github.com/s4dmach1ne) ([#11050](https://github.com/community-scripts/ProxmoxVE/pull/11050))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools: use distro packages for MariaDB by default [@MickLesk](https://github.com/MickLesk) ([#11049](https://github.com/community-scripts/ProxmoxVE/pull/11049))\n\n## 2026-01-21\n\n### 🆕 New Scripts\n\n  - Byparr ([#11039](https://github.com/community-scripts/ProxmoxVE/pull/11039))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: Snipe-IT update missing all user uploads (#11032) [@ruanmed](https://github.com/ruanmed) ([#11033](https://github.com/community-scripts/ProxmoxVE/pull/11033))\n    - yubal: fix for v0.2 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11006](https://github.com/community-scripts/ProxmoxVE/pull/11006))\n    - Joplin-Server: use yarn workspaces focus for faster builds [@MickLesk](https://github.com/MickLesk) ([#11027](https://github.com/community-scripts/ProxmoxVE/pull/11027))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools: add ubuntu PHP repository setup [@MickLesk](https://github.com/MickLesk) ([#11034](https://github.com/community-scripts/ProxmoxVE/pull/11034))\n\n  - #### 🔧 Refactor\n\n    - core: allow empty tags & improve template search [@MickLesk](https://github.com/MickLesk) ([#11020](https://github.com/community-scripts/ProxmoxVE/pull/11020))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Joplin Server: Set disable flag to true in joplin-server.json [@tremor021](https://github.com/tremor021) ([#11008](https://github.com/community-scripts/ProxmoxVE/pull/11008))\n\n## 2026-01-20\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - dolibarr: switch mirror [@MickLesk](https://github.com/MickLesk) ([#11004](https://github.com/community-scripts/ProxmoxVE/pull/11004))\n    - checkmk: reordner base function [@MickLesk](https://github.com/MickLesk) ([#10990](https://github.com/community-scripts/ProxmoxVE/pull/10990))\n    - Homepage: preserve config directory during updates [@MickLesk](https://github.com/MickLesk) ([#10993](https://github.com/community-scripts/ProxmoxVE/pull/10993))\n    - DiscoPanel: add go for update build process [@miausalvaje](https://github.com/miausalvaje) ([#10991](https://github.com/community-scripts/ProxmoxVE/pull/10991))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: add retry logic for template lock in LXC container creation [@MickLesk](https://github.com/MickLesk) ([#11002](https://github.com/community-scripts/ProxmoxVE/pull/11002))\n    - core: implement ensure_profile_loaded function [@MickLesk](https://github.com/MickLesk) ([#10999](https://github.com/community-scripts/ProxmoxVE/pull/10999))\n    - core: add input validations for several functions [@MickLesk](https://github.com/MickLesk) ([#10995](https://github.com/community-scripts/ProxmoxVE/pull/10995))\n\n## 2026-01-19\n\n### 🆕 New Scripts\n\n  - yubal ([#10955](https://github.com/community-scripts/ProxmoxVE/pull/10955))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Apache-Guacamole: move jdbc cleanup after schema upgrade [@MickLesk](https://github.com/MickLesk) ([#10974](https://github.com/community-scripts/ProxmoxVE/pull/10974))\n    - Outline: prevent corepack interactive prompt blocking installation [@MickLesk](https://github.com/MickLesk) ([#10973](https://github.com/community-scripts/ProxmoxVE/pull/10973))\n    - firefly: prevent nested storage directories during update (#10967) [@MickLesk](https://github.com/MickLesk) ([#10972](https://github.com/community-scripts/ProxmoxVE/pull/10972))\n    - PeaNUT: change default port [@vhsdream](https://github.com/vhsdream) ([#10962](https://github.com/community-scripts/ProxmoxVE/pull/10962))\n    - Update/splunk enterprise [@rcastley](https://github.com/rcastley) ([#10949](https://github.com/community-scripts/ProxmoxVE/pull/10949))\n\n  - #### ✨ New Features\n\n    - Pangolin: use dynamic badger plugin version [@MickLesk](https://github.com/MickLesk) ([#10975](https://github.com/community-scripts/ProxmoxVE/pull/10975))\n    - Tautulli: add version detection and add proper update script [@MickLesk](https://github.com/MickLesk) ([#10976](https://github.com/community-scripts/ProxmoxVE/pull/10976))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Remove custom IP fetching in scripts [@tremor021](https://github.com/tremor021) ([#10954](https://github.com/community-scripts/ProxmoxVE/pull/10954))\n    - Refactor: Homepage [@tremor021](https://github.com/tremor021) ([#10950](https://github.com/community-scripts/ProxmoxVE/pull/10950))\n    - Refactor: hev-socks5-server [@tremor021](https://github.com/tremor021) ([#10945](https://github.com/community-scripts/ProxmoxVE/pull/10945))\n\n### 🗑️ Deleted Scripts\n\n  - Remove: phpIPAM [@MickLesk](https://github.com/MickLesk) ([#10939](https://github.com/community-scripts/ProxmoxVE/pull/10939))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: add RFC 1123/952 compliant hostname/FQDN validation [@MickLesk](https://github.com/MickLesk) ([#10977](https://github.com/community-scripts/ProxmoxVE/pull/10977))\n    - [core]: Make LXC IP a global variable [@tremor021](https://github.com/tremor021) ([#10951](https://github.com/community-scripts/ProxmoxVE/pull/10951))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - Refactor: copyparty [@MickLesk](https://github.com/MickLesk) ([#10941](https://github.com/community-scripts/ProxmoxVE/pull/10941))\n\n## 2026-01-18\n\n### 🆕 New Scripts\n\n  - Termix ([#10887](https://github.com/community-scripts/ProxmoxVE/pull/10887))\n  - ThingsBoard ([#10904](https://github.com/community-scripts/ProxmoxVE/pull/10904))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix Patchmon install script (escaping) [@christiaangoossens](https://github.com/christiaangoossens) ([#10920](https://github.com/community-scripts/ProxmoxVE/pull/10920))\n    - refactor: peanut entrypoint [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10902](https://github.com/community-scripts/ProxmoxVE/pull/10902))\n\n  - #### 💥 Breaking Changes\n\n    - Update Patchmon default Nginx config (IPv6 and correct scheme) [@christiaangoossens](https://github.com/christiaangoossens) ([#10917](https://github.com/community-scripts/ProxmoxVE/pull/10917))\n\n  - #### 🔧 Refactor\n\n    - Refactor: FluidCalendar [@tremor021](https://github.com/tremor021) ([#10928](https://github.com/community-scripts/ProxmoxVE/pull/10928))\n\n### 🗑️ Deleted Scripts\n\n  - Remove iVentoy script [@tremor021](https://github.com/tremor021) ([#10924](https://github.com/community-scripts/ProxmoxVE/pull/10924))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: improve password handling and validation logic [@MickLesk](https://github.com/MickLesk) ([#10925](https://github.com/community-scripts/ProxmoxVE/pull/10925))\n\n  - #### 🔧 Refactor\n\n    - hwaccel: improve NVIDIA version matching and GPU selection UI [@MickLesk](https://github.com/MickLesk) ([#10901](https://github.com/community-scripts/ProxmoxVE/pull/10901))\n\n### 📂 Github\n\n  - Fix typo in the New Script request template [@tremor021](https://github.com/tremor021) ([#10891](https://github.com/community-scripts/ProxmoxVE/pull/10891))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - fix: preserve newest scripts pagination [@jgrubiox](https://github.com/jgrubiox) ([#10882](https://github.com/community-scripts/ProxmoxVE/pull/10882))\n\n### ❔ Uncategorized\n\n  - Update qui.json [@GalaxyCatD3v](https://github.com/GalaxyCatD3v) ([#10896](https://github.com/community-scripts/ProxmoxVE/pull/10896))\n\n## 2026-01-17\n\n### 🆕 New Scripts\n\n  - TRIP ([#10864](https://github.com/community-scripts/ProxmoxVE/pull/10864))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix sonarqube update version info (#10870) [@Karlito83](https://github.com/Karlito83) ([#10871](https://github.com/community-scripts/ProxmoxVE/pull/10871))\n    - WGDashboard: Update repo URL [@tremor021](https://github.com/tremor021) ([#10872](https://github.com/community-scripts/ProxmoxVE/pull/10872))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Disable Palmer [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#10889](https://github.com/community-scripts/ProxmoxVE/pull/10889))\n\n## 2026-01-16\n\n### 🆕 New Scripts\n\n  - Flatnotes ([#10857](https://github.com/community-scripts/ProxmoxVE/pull/10857))\n  - Unifi OS Server ([#10856](https://github.com/community-scripts/ProxmoxVE/pull/10856))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Jotty: increase RAM; cap heap size at 3GB during build [@vhsdream](https://github.com/vhsdream) ([#10868](https://github.com/community-scripts/ProxmoxVE/pull/10868))\n    - SnowShare: Increase default resources [@TuroYT](https://github.com/TuroYT) ([#10865](https://github.com/community-scripts/ProxmoxVE/pull/10865))\n    - postgresql: name of sources file fixed (update check) [@JamborJan](https://github.com/JamborJan) ([#10854](https://github.com/community-scripts/ProxmoxVE/pull/10854))\n    - immich: use dpkg-query to get intel-opencl-icd version [@MickLesk](https://github.com/MickLesk) ([#10848](https://github.com/community-scripts/ProxmoxVE/pull/10848))\n    - domain-monitor: fix: cron user [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10846](https://github.com/community-scripts/ProxmoxVE/pull/10846))\n    - pihole/unbound: create unbound config before apt install to prevent port conflicts [@MickLesk](https://github.com/MickLesk) ([#10839](https://github.com/community-scripts/ProxmoxVE/pull/10839))\n    - zammad: use ln -sf to avoid failure when symlink exists [@MickLesk](https://github.com/MickLesk) ([#10840](https://github.com/community-scripts/ProxmoxVE/pull/10840))\n\n### ❔ Uncategorized\n\n  - qui: fix: category [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10847](https://github.com/community-scripts/ProxmoxVE/pull/10847))\n\n## 2026-01-15\n\n### 🆕 New Scripts\n\n  - Qui ([#10829](https://github.com/community-scripts/ProxmoxVE/pull/10829))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Refactor: FreshRSS + Bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#10824](https://github.com/community-scripts/ProxmoxVE/pull/10824))\n\n## 2026-01-14\n\n### 🆕 New Scripts\n\n  - Kutt ([#10812](https://github.com/community-scripts/ProxmoxVE/pull/10812))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Switch Ollama install to .tar.zst and add zstd dependency [@MickLesk](https://github.com/MickLesk) ([#10814](https://github.com/community-scripts/ProxmoxVE/pull/10814))\n    - Immich: Install libde265-dev from Debian Testing [@vhsdream](https://github.com/vhsdream) ([#10810](https://github.com/community-scripts/ProxmoxVE/pull/10810))\n    - nginxproxymanager: allow updates now the build is fixed [@durzo](https://github.com/durzo) ([#10796](https://github.com/community-scripts/ProxmoxVE/pull/10796))\n    - Fixed Apache Guacamole installer [@horvatbenjamin](https://github.com/horvatbenjamin) ([#10798](https://github.com/community-scripts/ProxmoxVE/pull/10798))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: Improve NVIDIA GPU setup (5000x Series) [@MickLesk](https://github.com/MickLesk) ([#10807](https://github.com/community-scripts/ProxmoxVE/pull/10807))\n\n### 🧰 Tools\n\n  - Fix whiptail dialog hanging in Proxmox web console [@comk22](https://github.com/comk22) ([#10794](https://github.com/community-scripts/ProxmoxVE/pull/10794))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Add search filtering to CommandDialog for improved script search functionality [@BramSuurdje](https://github.com/BramSuurdje) ([#10800](https://github.com/community-scripts/ProxmoxVE/pull/10800))\n\n## 2026-01-13\n\n### 🆕 New Scripts\n\n  - Investbrain ([#10774](https://github.com/community-scripts/ProxmoxVE/pull/10774))\n  - Fladder ([#10768](https://github.com/community-scripts/ProxmoxVE/pull/10768))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Fix Intel version check; install legacy Intel packages during new install [@vhsdream](https://github.com/vhsdream) ([#10787](https://github.com/community-scripts/ProxmoxVE/pull/10787))\n    - Openwrt: Remove default VLAN for LAN [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#10782](https://github.com/community-scripts/ProxmoxVE/pull/10782))\n    - Refactor: Joplin Server [@tremor021](https://github.com/tremor021) ([#10769](https://github.com/community-scripts/ProxmoxVE/pull/10769))\n    - Fix Zammad nginx configuration causing installation failure [@Copilot](https://github.com/Copilot) ([#10757](https://github.com/community-scripts/ProxmoxVE/pull/10757))\n\n  - #### 🔧 Refactor\n\n    - Backrest: Bump to Trixie [@tremor021](https://github.com/tremor021) ([#10758](https://github.com/community-scripts/ProxmoxVE/pull/10758))\n    - Refactor: Caddy [@tremor021](https://github.com/tremor021) ([#10759](https://github.com/community-scripts/ProxmoxVE/pull/10759))\n    - Refactor: Leantime [@tremor021](https://github.com/tremor021) ([#10760](https://github.com/community-scripts/ProxmoxVE/pull/10760))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - update_lxcs.sh: Add the option to skip stopped LXC [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#10783](https://github.com/community-scripts/ProxmoxVE/pull/10783))\n\n## 2026-01-12\n\n### 🆕 New Scripts\n\n  - Jellystat ([#10628](https://github.com/community-scripts/ProxmoxVE/pull/10628))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - InfluxSB: fix If / fi [@chrnie](https://github.com/chrnie) ([#10753](https://github.com/community-scripts/ProxmoxVE/pull/10753))\n    - Cockpit: Downgrade to Debian 12 Bookworm (45Drives Issue) [@MickLesk](https://github.com/MickLesk) ([#10717](https://github.com/community-scripts/ProxmoxVE/pull/10717))\n\n  - #### ✨ New Features\n\n    - InfluxDB: add setup for influxdb v3 [@victorlap](https://github.com/victorlap) ([#10736](https://github.com/community-scripts/ProxmoxVE/pull/10736))\n    - Apache Guacamole: add schema upgrades and extension updates [@MickLesk](https://github.com/MickLesk) ([#10746](https://github.com/community-scripts/ProxmoxVE/pull/10746))\n    - Apache Tomcat: update support and refactor install script + debian 13 [@MickLesk](https://github.com/MickLesk) ([#10739](https://github.com/community-scripts/ProxmoxVE/pull/10739))\n    - Apache Guacamole: Function Bump + update_script [@MickLesk](https://github.com/MickLesk) ([#10728](https://github.com/community-scripts/ProxmoxVE/pull/10728))\n    - Apache CouchDB: bump to debian 13 and add update support [@MickLesk](https://github.com/MickLesk) ([#10721](https://github.com/community-scripts/ProxmoxVE/pull/10721))\n    - Apache Cassandra: bump to debian 13 and add update support [@MickLesk](https://github.com/MickLesk) ([#10720](https://github.com/community-scripts/ProxmoxVE/pull/10720))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Booklore [@MickLesk](https://github.com/MickLesk) ([#10742](https://github.com/community-scripts/ProxmoxVE/pull/10742))\n    - Bump Argus to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#10718](https://github.com/community-scripts/ProxmoxVE/pull/10718))\n    - Refactor Docker/Dockge & Bump to Debian 13 [@MickLesk](https://github.com/MickLesk) ([#10719](https://github.com/community-scripts/ProxmoxVE/pull/10719))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: remove duplicated pve_version in advanced installs [@MickLesk](https://github.com/MickLesk) ([#10743](https://github.com/community-scripts/ProxmoxVE/pull/10743))\n\n  - #### ✨ New Features\n\n    - core: add storage validation & fix GB/MB display [@MickLesk](https://github.com/MickLesk) ([#10745](https://github.com/community-scripts/ProxmoxVE/pull/10745))\n    - core: validate container ID before pct create to prevent failures [@MickLesk](https://github.com/MickLesk) ([#10729](https://github.com/community-scripts/ProxmoxVE/pull/10729))\n\n  - #### 🔧 Refactor\n\n    - Enforce non-interactive apt mode in DB setup scripts [@MickLesk](https://github.com/MickLesk) ([#10714](https://github.com/community-scripts/ProxmoxVE/pull/10714))\n\n## 2026-01-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix Invoice Ninja Error 500 by restoring file ownership after artisan commands [@Copilot](https://github.com/Copilot) ([#10709](https://github.com/community-scripts/ProxmoxVE/pull/10709))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Infisical [@tremor021](https://github.com/tremor021) ([#10693](https://github.com/community-scripts/ProxmoxVE/pull/10693))\n    - Refactor: HortusFox [@tremor021](https://github.com/tremor021) ([#10697](https://github.com/community-scripts/ProxmoxVE/pull/10697))\n    - Refactor: Homer [@tremor021](https://github.com/tremor021) ([#10698](https://github.com/community-scripts/ProxmoxVE/pull/10698))\n\n## 2026-01-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [Endurain] Increase default RAM from 2048 to 4096 [@FutureCow](https://github.com/FutureCow) ([#10690](https://github.com/community-scripts/ProxmoxVE/pull/10690))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: hwaccel - make beignet-opencl-icd optional for legacy Intel GPUs [@MickLesk](https://github.com/MickLesk) ([#10677](https://github.com/community-scripts/ProxmoxVE/pull/10677))\n\n## 2026-01-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Jenkins: Fix application repository setup [@tremor021](https://github.com/tremor021) ([#10671](https://github.com/community-scripts/ProxmoxVE/pull/10671))\n    - deCONZ: Fix sources check in update script [@tremor021](https://github.com/tremor021) ([#10664](https://github.com/community-scripts/ProxmoxVE/pull/10664))\n    - Remove '--cpu' option from ExecStart command [@sethgregory](https://github.com/sethgregory) ([#10659](https://github.com/community-scripts/ProxmoxVE/pull/10659))\n\n### 💾 Core\n\n  - #### 💥 Breaking Changes\n\n    - fix: setup_mariadb hangs on [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10672](https://github.com/community-scripts/ProxmoxVE/pull/10672))\n\n## 2026-01-08\n\n### 🆕 New Scripts\n\n  - GWN-Manager ([#10642](https://github.com/community-scripts/ProxmoxVE/pull/10642))\n\n### 🚀 Updated Scripts\n\n  - Fix line continuation for vlc-bin installation [@chinedu40](https://github.com/chinedu40) ([#10654](https://github.com/community-scripts/ProxmoxVE/pull/10654))\n\n  - #### 🐞 Bug Fixes\n\n    - outline: use corepack yarn module [@MickLesk](https://github.com/MickLesk) ([#10652](https://github.com/community-scripts/ProxmoxVE/pull/10652))\n    - Remove unnecessary quotes from variable expansions in VM scripts  [@MickLesk](https://github.com/MickLesk) ([#10649](https://github.com/community-scripts/ProxmoxVE/pull/10649))\n    - Monica: Fix database variable names [@tremor021](https://github.com/tremor021) ([#10634](https://github.com/community-scripts/ProxmoxVE/pull/10634))\n    - Tianji: Fix PostrgreSQL vars [@tremor021](https://github.com/tremor021) ([#10633](https://github.com/community-scripts/ProxmoxVE/pull/10633))\n\n  - #### 🔧 Refactor\n\n    - deCONZ: Bump to Trixie base [@tremor021](https://github.com/tremor021) ([#10643](https://github.com/community-scripts/ProxmoxVE/pull/10643))\n\n## 2026-01-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - pve-scripts-local: fix missing exit in update [@MickLesk](https://github.com/MickLesk) ([#10630](https://github.com/community-scripts/ProxmoxVE/pull/10630))\n\n  - #### ✨ New Features\n\n    - Upgrade ESPHome LXC to Debian 13 [@heinemannj](https://github.com/heinemannj) ([#10624](https://github.com/community-scripts/ProxmoxVE/pull/10624))\n\n  - #### 🔧 Refactor\n\n    - Explicitly state installation method [@tremor021](https://github.com/tremor021) ([#10608](https://github.com/community-scripts/ProxmoxVE/pull/10608))\n\n### 🧰 Tools\n\n  - Modify Debian sources list for trixie updates (as 4.1.0-1 config) [@maiux](https://github.com/maiux) ([#10505](https://github.com/community-scripts/ProxmoxVE/pull/10505))\n\n## 2026-01-06\n\n### 🆕 New Scripts\n\n  - Sportarr ([#10600](https://github.com/community-scripts/ProxmoxVE/pull/10600))\n\n### 🚀 Updated Scripts\n\n  - chore: fix update msg [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10593](https://github.com/community-scripts/ProxmoxVE/pull/10593))\n\n  - #### 🐞 Bug Fixes\n\n    - InspIRCd: Fix release fetching [@tremor021](https://github.com/tremor021) ([#10578](https://github.com/community-scripts/ProxmoxVE/pull/10578))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Sonarr [@tremor021](https://github.com/tremor021) ([#10573](https://github.com/community-scripts/ProxmoxVE/pull/10573))\n    - Refactor: Dispatcharr [@tremor021](https://github.com/tremor021) ([#10599](https://github.com/community-scripts/ProxmoxVE/pull/10599))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - hwaccel: rewrite of GPU hardware acceleration support [@MickLesk](https://github.com/MickLesk) ([#10597](https://github.com/community-scripts/ProxmoxVE/pull/10597))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - iptag: fix syntax error in VM config file parsing [@MickLesk](https://github.com/MickLesk) ([#10598](https://github.com/community-scripts/ProxmoxVE/pull/10598))\n\n  - #### ✨ New Features\n\n    - Update clean-lxcs.sh to support Red Hat compatible distros [@jabofh](https://github.com/jabofh) ([#10583](https://github.com/community-scripts/ProxmoxVE/pull/10583))\n\n### 📚 Documentation\n\n  - [gh] New Script template update [@tremor021](https://github.com/tremor021) ([#10607](https://github.com/community-scripts/ProxmoxVE/pull/10607))\n  - chore: bump copyright to 2026 - happy new year [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10585](https://github.com/community-scripts/ProxmoxVE/pull/10585))\n\n### 📂 Github\n\n  - re-add shellcheck exclusions [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10586](https://github.com/community-scripts/ProxmoxVE/pull/10586))\n\n## 2026-01-05\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - reitti: add postgis extension to PostgreSQL DB setup [@MickLesk](https://github.com/MickLesk) ([#10555](https://github.com/community-scripts/ProxmoxVE/pull/10555))\n    - openWRT: separate disk attachment and resizing in VM setup [@MickLesk](https://github.com/MickLesk) ([#10557](https://github.com/community-scripts/ProxmoxVE/pull/10557))\n    - paperless-ai: Set TMPDIR for pip to use disk during install [@MickLesk](https://github.com/MickLesk) ([#10559](https://github.com/community-scripts/ProxmoxVE/pull/10559))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Monica [@tremor021](https://github.com/tremor021) ([#10552](https://github.com/community-scripts/ProxmoxVE/pull/10552))\n    - Upgrade Wazuh LXC Container to Debian 13 [@heinemannj](https://github.com/heinemannj) ([#10551](https://github.com/community-scripts/ProxmoxVE/pull/10551))\n    - Upgrade evcc LXC to Debian 13 [@heinemannj](https://github.com/heinemannj) ([#10548](https://github.com/community-scripts/ProxmoxVE/pull/10548))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - Harden setup_hwaccel for old Intel GPUs [@MickLesk](https://github.com/MickLesk) ([#10556](https://github.com/community-scripts/ProxmoxVE/pull/10556))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - Refactor: IP-Tag (Multiple IP / Performance / Execution Time)  [@MickLesk](https://github.com/MickLesk) ([#10558](https://github.com/community-scripts/ProxmoxVE/pull/10558))\n\n## 2026-01-04\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - PocketID: Update PocketID for 2.x [@tremor021](https://github.com/tremor021) ([#10506](https://github.com/community-scripts/ProxmoxVE/pull/10506))\n    - fix: reitti: nginx [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10511](https://github.com/community-scripts/ProxmoxVE/pull/10511))\n    - MagicMirror: bump to nodejs 24 [@MickLesk](https://github.com/MickLesk) ([#10534](https://github.com/community-scripts/ProxmoxVE/pull/10534))\n\n  - #### 🔧 Refactor\n\n    - Refactor: SFTPGo [@tremor021](https://github.com/tremor021) ([#10518](https://github.com/community-scripts/ProxmoxVE/pull/10518))\n    - Refactor: Pelican Wings [@tremor021](https://github.com/tremor021) ([#10517](https://github.com/community-scripts/ProxmoxVE/pull/10517))\n    - Refactor: Pelican Panel [@tremor021](https://github.com/tremor021) ([#10516](https://github.com/community-scripts/ProxmoxVE/pull/10516))\n    - Refactor: Audiobookshelf [@tremor021](https://github.com/tremor021) ([#10519](https://github.com/community-scripts/ProxmoxVE/pull/10519))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - Export IPV6_METHOD to trigger verb_ip6() function [@remz1337](https://github.com/remz1337) ([#10538](https://github.com/community-scripts/ProxmoxVE/pull/10538))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Prowlarr: Update config_path [@tremor021](https://github.com/tremor021) ([#10504](https://github.com/community-scripts/ProxmoxVE/pull/10504))\n\n## 2026-01-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix ownership and permissions for InvoiceNinja setup [@twinzdragonz](https://github.com/twinzdragonz) ([#10298](https://github.com/community-scripts/ProxmoxVE/pull/10298))\n    - Fix headscale Caddyfile to pass non-API URLs [@IlyaSemenov](https://github.com/IlyaSemenov) ([#10493](https://github.com/community-scripts/ProxmoxVE/pull/10493))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - [core]: Preserve log files [@tremor021](https://github.com/tremor021) ([#10509](https://github.com/community-scripts/ProxmoxVE/pull/10509))\n\n### ❔ Uncategorized\n\n  - Wireguard: Update WGDashboard notes URL to the new link [@tremor021](https://github.com/tremor021) ([#10496](https://github.com/community-scripts/ProxmoxVE/pull/10496))\n  - InvoiceNinja: Update database credentias information [@tremor021](https://github.com/tremor021) ([#10497](https://github.com/community-scripts/ProxmoxVE/pull/10497))\n\n## 2026-01-02\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix Intel Level Zero package conflict on Debian 13 [@Copilot](https://github.com/Copilot) ([#10467](https://github.com/community-scripts/ProxmoxVE/pull/10467))\n\n### ❔ Uncategorized\n\n  - Extend guidance for changing the immich upload location for #10447 [@jshprentz](https://github.com/jshprentz) ([#10475](https://github.com/community-scripts/ProxmoxVE/pull/10475))\n\n## 2026-01-01\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix(sabnzbd): update script now migrates old service files to use venv Python [@vidonnus](https://github.com/vidonnus) ([#10466](https://github.com/community-scripts/ProxmoxVE/pull/10466))\n    - fix(bazarr): update script now migrates old service files to use venv Python [@vidonnus](https://github.com/vidonnus) ([#10459](https://github.com/community-scripts/ProxmoxVE/pull/10459))\n    - fix #10453 broken sonarqube update [@Karlito83](https://github.com/Karlito83) ([#10456](https://github.com/community-scripts/ProxmoxVE/pull/10456))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - Fix MariaDB runtime directory persistence on container reboot [@Copilot](https://github.com/Copilot) ([#10468](https://github.com/community-scripts/ProxmoxVE/pull/10468))\n"
  },
  {
    "path": ".github/changelogs/2026/02.md",
    "content": "## 2026-02-28\n\n### 🚀 Updated Scripts\n\n  - Update Reactive Resume install script with useful .env information for reverse proxy setup [@Mazianni](https://github.com/Mazianni) ([#12401](https://github.com/community-scripts/ProxmoxVE/pull/12401))\n\n  - #### 🐞 Bug Fixes\n\n    - gramps-web: install addons (FilterRules) for relationship diagram [@MickLesk](https://github.com/MickLesk) ([#12387](https://github.com/community-scripts/ProxmoxVE/pull/12387))\n    - [Fix] Immich: Change `sed` command to fully replace line in postgresql.conf [@vhsdream](https://github.com/vhsdream) ([#12429](https://github.com/community-scripts/ProxmoxVE/pull/12429))\n    - [FIX] Immich: fix Openvino memory leak during OCR; improve HW-accelerated ML performance [@vhsdream](https://github.com/vhsdream) ([#12426](https://github.com/community-scripts/ProxmoxVE/pull/12426))\n    - Fix default tag for ioBroker LXC install [@josefglatz](https://github.com/josefglatz) ([#12423](https://github.com/community-scripts/ProxmoxVE/pull/12423))\n    - Ombi: Add database.json [@hraphael](https://github.com/hraphael) ([#12412](https://github.com/community-scripts/ProxmoxVE/pull/12412))\n    - Dawarich: add missing build deps and handle seed failure [@MickLesk](https://github.com/MickLesk) ([#12410](https://github.com/community-scripts/ProxmoxVE/pull/12410))\n    - pangolin: increase hdd to 10G [@MickLesk](https://github.com/MickLesk) ([#12409](https://github.com/community-scripts/ProxmoxVE/pull/12409))\n\n  - #### ✨ New Features\n\n    - BookLore: add additional JVM flags [@vhsdream](https://github.com/vhsdream) ([#12421](https://github.com/community-scripts/ProxmoxVE/pull/12421))\n\n### 🗑️ Deleted Scripts\n\n  - Delete Palmr [@vhsdream](https://github.com/vhsdream) ([#12399](https://github.com/community-scripts/ProxmoxVE/pull/12399))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: read from /dev/tty in all interactive prompts | fix empty or cropped logs due build process [@MickLesk](https://github.com/MickLesk) ([#12406](https://github.com/community-scripts/ProxmoxVE/pull/12406))\n\n## 2026-02-27\n\n### 🆕 New Scripts\n\n  - Strapi ([#12320](https://github.com/community-scripts/ProxmoxVE/pull/12320))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - TrueNAS VM: filter out new nightlies with MASTER [@juronja](https://github.com/juronja) ([#12355](https://github.com/community-scripts/ProxmoxVE/pull/12355))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: graceful fallback for apt-get update failures [@MickLesk](https://github.com/MickLesk) ([#12386](https://github.com/community-scripts/ProxmoxVE/pull/12386))\n    - core: Improve error outputs across core functions [@MickLesk](https://github.com/MickLesk) ([#12378](https://github.com/community-scripts/ProxmoxVE/pull/12378))\n\n## 2026-02-26\n\n### 🆕 New Scripts\n\n  - Kima-Hub ([#12319](https://github.com/community-scripts/ProxmoxVE/pull/12319))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: update glx alternatives / nvidia alternative if nvidia glx are missing [@MickLesk](https://github.com/MickLesk) ([#12372](https://github.com/community-scripts/ProxmoxVE/pull/12372))\n    - hotfix: overseer version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12366](https://github.com/community-scripts/ProxmoxVE/pull/12366))\n\n  - #### ✨ New Features\n\n    - Add ffmpeg for booklore (ffprobe) [@MickLesk](https://github.com/MickLesk) ([#12371](https://github.com/community-scripts/ProxmoxVE/pull/12371))\n    - [QOL] Immich: add warning regarding library compilation time [@vhsdream](https://github.com/vhsdream) ([#12345](https://github.com/community-scripts/ProxmoxVE/pull/12345))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - Improves adguardhome-sync addon when running on alpine LXCs [@Darkangeel-hd](https://github.com/Darkangeel-hd) ([#12362](https://github.com/community-scripts/ProxmoxVE/pull/12362))\n\n  - #### ✨ New Features\n\n    - Add Alpine support and improve Tailscale install [@MickLesk](https://github.com/MickLesk) ([#12370](https://github.com/community-scripts/ProxmoxVE/pull/12370))\n\n### 📚 Documentation\n\n  - fix wrong link on contributions README.md [@Darkangeel-hd](https://github.com/Darkangeel-hd) ([#12363](https://github.com/community-scripts/ProxmoxVE/pull/12363))\n\n### 📂 Github\n\n  - github: add workflow to autom. close unauthorized new-script PRs [@MickLesk](https://github.com/MickLesk) ([#12356](https://github.com/community-scripts/ProxmoxVE/pull/12356))\n\n## 2026-02-25\n\n### 🆕 New Scripts\n\n  - Zerobyte ([#12321](https://github.com/community-scripts/ProxmoxVE/pull/12321))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: overseer migration [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12340](https://github.com/community-scripts/ProxmoxVE/pull/12340))\n    - add: vikunja: daemon reload [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12323](https://github.com/community-scripts/ProxmoxVE/pull/12323))\n    - opnsense-VM: Use ip link to verify bridge existence [@MickLesk](https://github.com/MickLesk) ([#12329](https://github.com/community-scripts/ProxmoxVE/pull/12329))\n    - wger: Use $http_host for proxy Host header [@MickLesk](https://github.com/MickLesk) ([#12327](https://github.com/community-scripts/ProxmoxVE/pull/12327))\n    - Passbolt: Update Nginx config `client_max_body_size` [@tremor021](https://github.com/tremor021) ([#12313](https://github.com/community-scripts/ProxmoxVE/pull/12313))\n    - Zammad: configure Elasticsearch before zammad start [@MickLesk](https://github.com/MickLesk) ([#12308](https://github.com/community-scripts/ProxmoxVE/pull/12308))\n\n  - #### 🔧 Refactor\n\n    - OpenProject: Various fixes [@tremor021](https://github.com/tremor021) ([#12246](https://github.com/community-scripts/ProxmoxVE/pull/12246))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - Fix detection of ssh keys [@1-tempest](https://github.com/1-tempest) ([#12230](https://github.com/community-scripts/ProxmoxVE/pull/12230))\n\n  - #### ✨ New Features\n\n    - tools.func: Improve GitHub/Codeberg API error handling and error output [@MickLesk](https://github.com/MickLesk) ([#12330](https://github.com/community-scripts/ProxmoxVE/pull/12330))\n\n  - #### 🔧 Refactor\n\n    - core: remove duplicate traps, consolidate error handling and harden signal traps [@MickLesk](https://github.com/MickLesk) ([#12316](https://github.com/community-scripts/ProxmoxVE/pull/12316))\n\n### 📂 Github\n\n  - github: improvements for node drift wf [@MickLesk](https://github.com/MickLesk) ([#12309](https://github.com/community-scripts/ProxmoxVE/pull/12309))\n\n## 2026-02-24\n\n### 🚀 Updated Scripts\n\n  - several scripts: add additional github link in source [@MickLesk](https://github.com/MickLesk) ([#12282](https://github.com/community-scripts/ProxmoxVE/pull/12282))\n- adds further documentation during the installation script. [@d12rio](https://github.com/d12rio) ([#12248](https://github.com/community-scripts/ProxmoxVE/pull/12248))\n\n  - #### 🐞 Bug Fixes\n\n    - [Fix] PatchMon: remove VITE_API_URL from frontend env [@vhsdream](https://github.com/vhsdream) ([#12294](https://github.com/community-scripts/ProxmoxVE/pull/12294))\n    - fix(searxng): remove orphaned fi causing syntax error [@mark-jeffrey](https://github.com/mark-jeffrey) ([#12283](https://github.com/community-scripts/ProxmoxVE/pull/12283))\n    - Refactor n8n [@MickLesk](https://github.com/MickLesk) ([#12264](https://github.com/community-scripts/ProxmoxVE/pull/12264))\n    - Firefly: PHP bump [@tremor021](https://github.com/tremor021) ([#12247](https://github.com/community-scripts/ProxmoxVE/pull/12247))\n\n  - #### ✨ New Features\n\n    - Databasus: add mariadb path for mysql/mariadb backups | add mongodb database tools [@MickLesk](https://github.com/MickLesk) ([#12259](https://github.com/community-scripts/ProxmoxVE/pull/12259))\n    - make searxng updateable [@shtefko](https://github.com/shtefko) ([#12207](https://github.com/community-scripts/ProxmoxVE/pull/12207))\n\n  - #### 💥 Breaking Changes\n\n    - fix: wealthfolio for v3 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11765](https://github.com/community-scripts/ProxmoxVE/pull/11765))\n\n  - #### 🔧 Refactor\n\n    - bump various scripts from Node 22 to 24 [@MickLesk](https://github.com/MickLesk) ([#12265](https://github.com/community-scripts/ProxmoxVE/pull/12265))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: fix broken \"command not found\" after err_trap [@MickLesk](https://github.com/MickLesk) ([#12280](https://github.com/community-scripts/ProxmoxVE/pull/12280))\n\n  - #### ✨ New Features\n\n    - tools.func: add get_latest_gh_tag helper function [@MickLesk](https://github.com/MickLesk) ([#12261](https://github.com/community-scripts/ProxmoxVE/pull/12261))\n\n### 🧰 Tools\n\n  - Arcane ([#12263](https://github.com/community-scripts/ProxmoxVE/pull/12263))\n\n### 📂 Github\n\n  - github: add weekly Node.js version drift check workflow [@MickLesk](https://github.com/MickLesk) ([#12267](https://github.com/community-scripts/ProxmoxVE/pull/12267))\n- add: workflow to close stale PRs [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12243](https://github.com/community-scripts/ProxmoxVE/pull/12243))\n\n## 2026-02-23\n\n### 🆕 New Scripts\n\n  - SeaweedFS ([#12220](https://github.com/community-scripts/ProxmoxVE/pull/12220))\n- Sonobarr ([#12221](https://github.com/community-scripts/ProxmoxVE/pull/12221))\n- SparkyFitness ([#12185](https://github.com/community-scripts/ProxmoxVE/pull/12185))\n- Frigate v16.4 [@MickLesk](https://github.com/MickLesk) ([#11887](https://github.com/community-scripts/ProxmoxVE/pull/11887))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - memos: unpin version due new release artifacts [@MickLesk](https://github.com/MickLesk) ([#12224](https://github.com/community-scripts/ProxmoxVE/pull/12224))\n    - core: Enhance signal handling, reported \"status\" and logs [@MickLesk](https://github.com/MickLesk) ([#12216](https://github.com/community-scripts/ProxmoxVE/pull/12216))\n\n  - #### 🔧 Refactor\n\n    - booklore v2: embed frontend, bump Java to 25, remove nginx [@MickLesk](https://github.com/MickLesk) ([#12223](https://github.com/community-scripts/ProxmoxVE/pull/12223))\n\n### 🗑️ Deleted Scripts\n\n  - Remove: Huntarr (deprecated & Security) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12226](https://github.com/community-scripts/ProxmoxVE/pull/12226))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: Improve error handling and logging for LXC builds [@MickLesk](https://github.com/MickLesk) ([#12208](https://github.com/community-scripts/ProxmoxVE/pull/12208))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - calibre-web: update default credentials [@LaevaertK](https://github.com/LaevaertK) ([#12201](https://github.com/community-scripts/ProxmoxVE/pull/12201))\n\n  - #### 📝 Script Information\n\n    - chore: update Frigate documentation and website URLs [@JohnICB](https://github.com/JohnICB) ([#12218](https://github.com/community-scripts/ProxmoxVE/pull/12218))\n\n## 2026-02-22\n\n### 🆕 New Scripts\n\n  - Gramps-Web ([#12157](https://github.com/community-scripts/ProxmoxVE/pull/12157))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: Apache Guacamole - bump to Temurin JDK 17 to resolve Debian 13 (Trixie) install failure [@Copilot](https://github.com/Copilot) ([#12161](https://github.com/community-scripts/ProxmoxVE/pull/12161))\n    - Docker-VM: add error handling for virt-customize finalization [@MickLesk](https://github.com/MickLesk) ([#12127](https://github.com/community-scripts/ProxmoxVE/pull/12127))\n    - [Fix] Sure: add Sidekiq service [@vhsdream](https://github.com/vhsdream) ([#12186](https://github.com/community-scripts/ProxmoxVE/pull/12186))\n\n  - #### ✨ New Features\n\n    - Refactor & Bump to v2: Plex [@MickLesk](https://github.com/MickLesk) ([#12179](https://github.com/community-scripts/ProxmoxVE/pull/12179))\n\n  - #### 🔧 Refactor\n\n    - karakeep: bump to node 24 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12183](https://github.com/community-scripts/ProxmoxVE/pull/12183))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func: add GitHub API rate-limit detection and GITHUB_TOKEN support [@MickLesk](https://github.com/MickLesk) ([#12176](https://github.com/community-scripts/ProxmoxVE/pull/12176))\n\n### 🧰 Tools\n\n  - CR*NMASTER ([#12065](https://github.com/community-scripts/ProxmoxVE/pull/12065))\n\n  - #### 🔧 Refactor\n\n    - Update package management commands in clean-lxcs.sh [@heinemannj](https://github.com/heinemannj) ([#12166](https://github.com/community-scripts/ProxmoxVE/pull/12166))\n\n### ❔ Uncategorized\n\n  - calibre-web: Update logo URL [@MickLesk](https://github.com/MickLesk) ([#12178](https://github.com/community-scripts/ProxmoxVE/pull/12178))\n\n## 2026-02-21\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pangolin: restore config before db migration, use drizzle-kit push [@MickLesk](https://github.com/MickLesk) ([#12130](https://github.com/community-scripts/ProxmoxVE/pull/12130))\n    - PLANKA: fix msg's [@danielalanbates](https://github.com/danielalanbates) ([#12143](https://github.com/community-scripts/ProxmoxVE/pull/12143))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - MediaManager: Update documentation URL [@tremor021](https://github.com/tremor021) ([#12154](https://github.com/community-scripts/ProxmoxVE/pull/12154))\n\n## 2026-02-20\n\n### 🆕 New Scripts\n\n  - Sure ([#12114](https://github.com/community-scripts/ProxmoxVE/pull/12114))\n- Calibre-Web ([#12115](https://github.com/community-scripts/ProxmoxVE/pull/12115))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Zammad: fix Elasticsearch JVM config and add daemon-reload [@MickLesk](https://github.com/MickLesk) ([#12125](https://github.com/community-scripts/ProxmoxVE/pull/12125))\n    - Huntarr: add build-essential for native pip dependencies [@MickLesk](https://github.com/MickLesk) ([#12126](https://github.com/community-scripts/ProxmoxVE/pull/12126))\n    - Dokploy: fix update function [@vhsdream](https://github.com/vhsdream) ([#12116](https://github.com/community-scripts/ProxmoxVE/pull/12116))\n\n  - #### 💥 Breaking Changes\n\n    - recyclarr: adjust paths for v8.0 breaking changes [@MickLesk](https://github.com/MickLesk) ([#12129](https://github.com/community-scripts/ProxmoxVE/pull/12129))\n\n  - #### 🔧 Refactor\n\n    - Planka: migrate data paths to new v2 directory structure [@MickLesk](https://github.com/MickLesk) ([#12128](https://github.com/community-scripts/ProxmoxVE/pull/12128))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - fixen broken link to dawarich documentation [@RiX012](https://github.com/RiX012) ([#12103](https://github.com/community-scripts/ProxmoxVE/pull/12103))\n\n## 2026-02-19\n\n### 🆕 New Scripts\n\n  - TrueNAS-VM ([#12059](https://github.com/community-scripts/ProxmoxVE/pull/12059))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - add: patchmon breaking change msg [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12075](https://github.com/community-scripts/ProxmoxVE/pull/12075))\n    - LibreNMS: Various fixes [@tremor021](https://github.com/tremor021) ([#12089](https://github.com/community-scripts/ProxmoxVE/pull/12089))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - truenas-vm: slug fix for source code link [@juronja](https://github.com/juronja) ([#12088](https://github.com/community-scripts/ProxmoxVE/pull/12088))\n\n## 2026-02-18\n\n### 🚀 Updated Scripts\n\n  - #### 💥 Breaking Changes\n\n    - [Fix] PatchMon: use `SERVER_PORT` in Nginx config if set in env [@vhsdream](https://github.com/vhsdream) ([#12053](https://github.com/community-scripts/ProxmoxVE/pull/12053))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: Execution ID & Telemetry Improvements [@MickLesk](https://github.com/MickLesk) ([#12041](https://github.com/community-scripts/ProxmoxVE/pull/12041))\n\n## 2026-02-17\n\n### 🆕 New Scripts\n\n  - Databasus ([#12018](https://github.com/community-scripts/ProxmoxVE/pull/12018))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [Hotfix] Cleanuparr: backup config before update [@vhsdream](https://github.com/vhsdream) ([#12039](https://github.com/community-scripts/ProxmoxVE/pull/12039))\n    - fix: pterodactyl-panel add symlink [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11997](https://github.com/community-scripts/ProxmoxVE/pull/11997))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: call get_lxc_ip in start() before updates [@MickLesk](https://github.com/MickLesk) ([#12015](https://github.com/community-scripts/ProxmoxVE/pull/12015))\n\n  - #### ✨ New Features\n\n    - tools/pve: add data analytics / formatting / linting [@MickLesk](https://github.com/MickLesk) ([#12034](https://github.com/community-scripts/ProxmoxVE/pull/12034))\n    - core: smart recovery for failed installs | extend exit_codes  [@MickLesk](https://github.com/MickLesk) ([#11221](https://github.com/community-scripts/ProxmoxVE/pull/11221))\n\n  - #### 🔧 Refactor\n\n    - core: error-handler improvements | better exit_code handling | better tools.func source check [@MickLesk](https://github.com/MickLesk) ([#12019](https://github.com/community-scripts/ProxmoxVE/pull/12019))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - Immich Public Proxy: centralize and fix systemd service creation [@MickLesk](https://github.com/MickLesk) ([#12025](https://github.com/community-scripts/ProxmoxVE/pull/12025))\n\n### 📚 Documentation\n\n  - fix contribution/setup-fork [@andreasabeck](https://github.com/andreasabeck) ([#12047](https://github.com/community-scripts/ProxmoxVE/pull/12047))\n\n## 2026-02-16\n\n### 🆕 New Scripts\n\n  - RomM ([#11987](https://github.com/community-scripts/ProxmoxVE/pull/11987))\n- LinkDing ([#11976](https://github.com/community-scripts/ProxmoxVE/pull/11976))\n\n### 🚀 Updated Scripts\n\n  - Opencloud: Pin version to 5.1.0 [@vhsdream](https://github.com/vhsdream) ([#12004](https://github.com/community-scripts/ProxmoxVE/pull/12004))\n\n  - #### 🐞 Bug Fixes\n\n    - Tududi: Fix sed command for DB_FILE configuration [@tremor021](https://github.com/tremor021) ([#11988](https://github.com/community-scripts/ProxmoxVE/pull/11988))\n    - slskd: fix exit position [@MickLesk](https://github.com/MickLesk) ([#11963](https://github.com/community-scripts/ProxmoxVE/pull/11963))\n    - cryptpad: restore config earlier and run onlyoffice upgrade [@MickLesk](https://github.com/MickLesk) ([#11964](https://github.com/community-scripts/ProxmoxVE/pull/11964))\n    - jellyseerr/overseerr: Migrate update script to Seerr; prompt rerun [@MickLesk](https://github.com/MickLesk) ([#11965](https://github.com/community-scripts/ProxmoxVE/pull/11965))\n\n  - #### 🔧 Refactor\n\n    - core/vm's: ensure script state is sent on script exit  [@MickLesk](https://github.com/MickLesk) ([#11991](https://github.com/community-scripts/ProxmoxVE/pull/11991))\n    - Vaultwarden: export VW_VERSION as version number [@MickLesk](https://github.com/MickLesk) ([#11966](https://github.com/community-scripts/ProxmoxVE/pull/11966))\n    - Zabbix: Improve zabbix-agent service detection [@MickLesk](https://github.com/MickLesk) ([#11968](https://github.com/community-scripts/ProxmoxVE/pull/11968))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func: ensure /usr/local/bin PATH persists for pct enter sessions [@MickLesk](https://github.com/MickLesk) ([#11970](https://github.com/community-scripts/ProxmoxVE/pull/11970))\n\n  - #### 🔧 Refactor\n\n    - core: remove duplicate error handler from alpine-install.func [@MickLesk](https://github.com/MickLesk) ([#11971](https://github.com/community-scripts/ProxmoxVE/pull/11971))\n\n### 📂 Github\n\n  - github: add \"website\" label if \"json\" changed [@MickLesk](https://github.com/MickLesk) ([#11975](https://github.com/community-scripts/ProxmoxVE/pull/11975))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update Wishlist LXC webpage to include reverse proxy info [@summoningpixels](https://github.com/summoningpixels) ([#11973](https://github.com/community-scripts/ProxmoxVE/pull/11973))\n    - Update OpenCloud LXC webpage to include services ports [@summoningpixels](https://github.com/summoningpixels) ([#11969](https://github.com/community-scripts/ProxmoxVE/pull/11969))\n\n## 2026-02-15\n\n### 🆕 New Scripts\n\n  - ebusd ([#11942](https://github.com/community-scripts/ProxmoxVE/pull/11942))\n- add: seer script and migrations [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11930](https://github.com/community-scripts/ProxmoxVE/pull/11930))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix seerr URL in jellyseerr script [@lucacome](https://github.com/lucacome) ([#11951](https://github.com/community-scripts/ProxmoxVE/pull/11951))\n    - Fix jellyseer and overseer script replacement [@lucacome](https://github.com/lucacome) ([#11949](https://github.com/community-scripts/ProxmoxVE/pull/11949))\n    - Tautulli: Add setuptools < 81 [@tremor021](https://github.com/tremor021) ([#11943](https://github.com/community-scripts/ProxmoxVE/pull/11943))\n\n  - #### 💥 Breaking Changes\n\n    - Refactor: Patchmon [@vhsdream](https://github.com/vhsdream) ([#11888](https://github.com/community-scripts/ProxmoxVE/pull/11888))\n\n## 2026-02-14\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Increase disk allocation for OpenWebUI and Ollama to prevent installation failures [@Copilot](https://github.com/Copilot) ([#11920](https://github.com/community-scripts/ProxmoxVE/pull/11920))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: handle missing RAM speed in nested VMs [@MickLesk](https://github.com/MickLesk) ([#11913](https://github.com/community-scripts/ProxmoxVE/pull/11913))\n\n  - #### ✨ New Features\n\n    - core: overwriteable app version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11753](https://github.com/community-scripts/ProxmoxVE/pull/11753))\n    - core: validate container IDs cluster-wide across all nodes [@MickLesk](https://github.com/MickLesk) ([#11906](https://github.com/community-scripts/ProxmoxVE/pull/11906))\n    - core: improve error reporting with structured error strings and better categorization + output formatting [@MickLesk](https://github.com/MickLesk) ([#11907](https://github.com/community-scripts/ProxmoxVE/pull/11907))\n    - core: unified logging system with combined logs [@MickLesk](https://github.com/MickLesk) ([#11761](https://github.com/community-scripts/ProxmoxVE/pull/11761))\n\n### 🧰 Tools\n\n  - lxc-updater: add patchmon aware [@failure101](https://github.com/failure101) ([#11905](https://github.com/community-scripts/ProxmoxVE/pull/11905))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Disable UniFi script - APT packages no longer available [@Copilot](https://github.com/Copilot) ([#11898](https://github.com/community-scripts/ProxmoxVE/pull/11898))\n\n## 2026-02-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - OpenWebUI: pin numba constraint [@MickLesk](https://github.com/MickLesk) ([#11874](https://github.com/community-scripts/ProxmoxVE/pull/11874))\n    - Planka: add migrate step to update function [@ZimmermannLeon](https://github.com/ZimmermannLeon) ([#11877](https://github.com/community-scripts/ProxmoxVE/pull/11877))\n    - Pangolin: switch sqlite-specific back to generic [@MickLesk](https://github.com/MickLesk) ([#11868](https://github.com/community-scripts/ProxmoxVE/pull/11868))\n    - [Hotfix] Jotty: Copy contents of config backup into /opt/jotty/config [@vhsdream](https://github.com/vhsdream) ([#11864](https://github.com/community-scripts/ProxmoxVE/pull/11864))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Radicale [@vhsdream](https://github.com/vhsdream) ([#11850](https://github.com/community-scripts/ProxmoxVE/pull/11850))\n    - chore(donetick): add config entry for v0.1.73 [@tomfrenzel](https://github.com/tomfrenzel) ([#11872](https://github.com/community-scripts/ProxmoxVE/pull/11872))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: retry reporting with fallback payloads [@MickLesk](https://github.com/MickLesk) ([#11885](https://github.com/community-scripts/ProxmoxVE/pull/11885))\n\n### 📡 API\n\n  - #### ✨ New Features\n\n    - error-handler: Implement json_escape and enhance error handling [@MickLesk](https://github.com/MickLesk) ([#11875](https://github.com/community-scripts/ProxmoxVE/pull/11875))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - SQLServer-2025: add PVE9/Kernel 6.x incompatibility warning [@MickLesk](https://github.com/MickLesk) ([#11829](https://github.com/community-scripts/ProxmoxVE/pull/11829))\n\n## 2026-02-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - EMQX: increase disk to 6GB and add optional MQ disable prompt [@MickLesk](https://github.com/MickLesk) ([#11844](https://github.com/community-scripts/ProxmoxVE/pull/11844))\n    - Increased the Grafana container default disk size. [@shtefko](https://github.com/shtefko) ([#11840](https://github.com/community-scripts/ProxmoxVE/pull/11840))\n    - Pangolin: Update database generation command in install script [@tremor021](https://github.com/tremor021) ([#11825](https://github.com/community-scripts/ProxmoxVE/pull/11825))\n    - Deluge: add python3-setuptools as dep [@MickLesk](https://github.com/MickLesk) ([#11833](https://github.com/community-scripts/ProxmoxVE/pull/11833))\n    - Dispatcharr: migrate to uv sync [@MickLesk](https://github.com/MickLesk) ([#11831](https://github.com/community-scripts/ProxmoxVE/pull/11831))\n\n  - #### ✨ New Features\n\n    - Archlinux-VM: fix LVM/LVM-thin storage and improve error reporting | VM's add correct exit_code for analytics [@MickLesk](https://github.com/MickLesk) ([#11842](https://github.com/community-scripts/ProxmoxVE/pull/11842))\n    - Debian13-VM: Optimize First Boot & add noCloud/Cloud Selection [@MickLesk](https://github.com/MickLesk) ([#11810](https://github.com/community-scripts/ProxmoxVE/pull/11810))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func: auto-detect binary vs armored GPG keys in setup_deb822_repo [@MickLesk](https://github.com/MickLesk) ([#11841](https://github.com/community-scripts/ProxmoxVE/pull/11841))\n    - core: remove old Go API and extend misc/api.func with new backend [@MickLesk](https://github.com/MickLesk) ([#11822](https://github.com/community-scripts/ProxmoxVE/pull/11822))\n\n  - #### 🔧 Refactor\n\n    - error_handler: prevent stuck 'installing' status [@MickLesk](https://github.com/MickLesk) ([#11845](https://github.com/community-scripts/ProxmoxVE/pull/11845))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - Tailscale: fix DNS check and keyrings directory issues [@MickLesk](https://github.com/MickLesk) ([#11837](https://github.com/community-scripts/ProxmoxVE/pull/11837))\n\n## 2026-02-11\n\n### 🆕 New Scripts\n\n  - Draw.io ([#11788](https://github.com/community-scripts/ProxmoxVE/pull/11788))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - dispatcharr: include port 9191 in success-message [@MickLesk](https://github.com/MickLesk) ([#11808](https://github.com/community-scripts/ProxmoxVE/pull/11808))\n    - fix: make donetick 0.1.71 compatible [@tomfrenzel](https://github.com/tomfrenzel) ([#11804](https://github.com/community-scripts/ProxmoxVE/pull/11804))\n    - Kasm: Support new version URL format without hash suffix [@MickLesk](https://github.com/MickLesk) ([#11787](https://github.com/community-scripts/ProxmoxVE/pull/11787))\n    - LibreTranslate: Remove Torch [@tremor021](https://github.com/tremor021) ([#11783](https://github.com/community-scripts/ProxmoxVE/pull/11783))\n    - Snowshare: fix update script [@TuroYT](https://github.com/TuroYT) ([#11726](https://github.com/community-scripts/ProxmoxVE/pull/11726))\n\n  - #### ✨ New Features\n\n    - [Feature] OpenCloud: support PosixFS Collaborative Mode [@vhsdream](https://github.com/vhsdream) ([#11806](https://github.com/community-scripts/ProxmoxVE/pull/11806))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: respect EDITOR variable for config editing [@ls-root](https://github.com/ls-root) ([#11693](https://github.com/community-scripts/ProxmoxVE/pull/11693))\n\n### 📚 Documentation\n\n  - Fix formatting in kutt.json notes section [@tiagodenoronha](https://github.com/tiagodenoronha) ([#11774](https://github.com/community-scripts/ProxmoxVE/pull/11774))\n\n## 2026-02-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Pin version to 2.5.6 [@vhsdream](https://github.com/vhsdream) ([#11775](https://github.com/community-scripts/ProxmoxVE/pull/11775))\n    - Libretranslate: Fix setuptools [@tremor021](https://github.com/tremor021) ([#11772](https://github.com/community-scripts/ProxmoxVE/pull/11772))\n    - Element Synapse: prevent systemd invoke failure during apt install [@MickLesk](https://github.com/MickLesk) ([#11758](https://github.com/community-scripts/ProxmoxVE/pull/11758))\n\n  - #### ✨ New Features\n\n    - Refactor: Slskd & Soularr [@vhsdream](https://github.com/vhsdream) ([#11674](https://github.com/community-scripts/ProxmoxVE/pull/11674))\n\n### 🗑️ Deleted Scripts\n\n  - move paperless-exporter from LXC to addon ([#11737](https://github.com/community-scripts/ProxmoxVE/pull/11737))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - feat: improve storage parsing & add guestname [@carlosmaroot](https://github.com/carlosmaroot) ([#11752](https://github.com/community-scripts/ProxmoxVE/pull/11752))\n\n### 📂 Github\n\n  - Github-Version Workflow: include addon scripts in extraction [@MickLesk](https://github.com/MickLesk) ([#11757](https://github.com/community-scripts/ProxmoxVE/pull/11757))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Snowshare: fix typo in config file path on website [@BirdMakingStuff](https://github.com/BirdMakingStuff) ([#11754](https://github.com/community-scripts/ProxmoxVE/pull/11754))\n\n## 2026-02-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - several scripts: add --clear to uv venv calls for uv 0.10 compatibility [@MickLesk](https://github.com/MickLesk) ([#11723](https://github.com/community-scripts/ProxmoxVE/pull/11723))\n    - Koillection: ensure setup_composer is in update script [@MickLesk](https://github.com/MickLesk) ([#11734](https://github.com/community-scripts/ProxmoxVE/pull/11734))\n    - PeaNUT: symlink server.js after update [@vhsdream](https://github.com/vhsdream) ([#11696](https://github.com/community-scripts/ProxmoxVE/pull/11696))\n    - Umlautadaptarr: use release appsettings.json instead of hardcoded copy [@MickLesk](https://github.com/MickLesk) ([#11725](https://github.com/community-scripts/ProxmoxVE/pull/11725))\n    - tracearr: prepare for next stable release [@durzo](https://github.com/durzo) ([#11673](https://github.com/community-scripts/ProxmoxVE/pull/11673))\n\n  - #### ✨ New Features\n\n    - remove whiptail from update scripts for unattended update support [@MickLesk](https://github.com/MickLesk) ([#11712](https://github.com/community-scripts/ProxmoxVE/pull/11712))\n\n  - #### 🔧 Refactor\n\n    - Refactor: FileFlows [@tremor021](https://github.com/tremor021) ([#11108](https://github.com/community-scripts/ProxmoxVE/pull/11108))\n    - Refactor: wger [@MickLesk](https://github.com/MickLesk) ([#11722](https://github.com/community-scripts/ProxmoxVE/pull/11722))\n    - Nginx-UI: better User Handling | ACME [@MickLesk](https://github.com/MickLesk) ([#11715](https://github.com/community-scripts/ProxmoxVE/pull/11715))\n    - NginxProxymanager: use better-sqlite3 [@MickLesk](https://github.com/MickLesk) ([#11708](https://github.com/community-scripts/ProxmoxVE/pull/11708))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - hwaccel: add libmfx-gen1.2 to Intel Arc setup for QSV support [@MickLesk](https://github.com/MickLesk) ([#11707](https://github.com/community-scripts/ProxmoxVE/pull/11707))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - addons: ensure curl is installed before use [@MickLesk](https://github.com/MickLesk) ([#11718](https://github.com/community-scripts/ProxmoxVE/pull/11718))\n    - Netbird (addon): add systemd ordering to start after Docker [@MickLesk](https://github.com/MickLesk) ([#11716](https://github.com/community-scripts/ProxmoxVE/pull/11716))\n\n### ❔ Uncategorized\n\n  - Bichon: Update website [@tremor021](https://github.com/tremor021) ([#11711](https://github.com/community-scripts/ProxmoxVE/pull/11711))\n\n## 2026-02-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - feat(healthchecks): add sendalerts service [@Mika56](https://github.com/Mika56) ([#11694](https://github.com/community-scripts/ProxmoxVE/pull/11694))\n    - ComfyUI: Dynamic Fetch PyTorch Versions [@MickLesk](https://github.com/MickLesk) ([#11657](https://github.com/community-scripts/ProxmoxVE/pull/11657))\n\n  - #### 💥 Breaking Changes\n\n    - Semaphore: switch from Debian  to Ubuntu 24.04 [@MickLesk](https://github.com/MickLesk) ([#11670](https://github.com/community-scripts/ProxmoxVE/pull/11670))\n\n## 2026-02-07\n\n### 🆕 New Scripts\n\n  - Checkmate ([#11672](https://github.com/community-scripts/ProxmoxVE/pull/11672))\n- Bichon ([#11671](https://github.com/community-scripts/ProxmoxVE/pull/11671))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - NocoDB: pin to v0.301.1 [@MickLesk](https://github.com/MickLesk) ([#11655](https://github.com/community-scripts/ProxmoxVE/pull/11655))\n    - Pin Memos to v0.25.3 - last version with release binaries [@MickLesk](https://github.com/MickLesk) ([#11658](https://github.com/community-scripts/ProxmoxVE/pull/11658))\n    - Downgrade: OpenProject | NginxProxyManager | Semaphore to Debian 12 due to persistent SHA1 issues  [@MickLesk](https://github.com/MickLesk) ([#11654](https://github.com/community-scripts/ProxmoxVE/pull/11654))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools: fallback to previous release when asset is missing [@MickLesk](https://github.com/MickLesk) ([#11660](https://github.com/community-scripts/ProxmoxVE/pull/11660))\n\n### 📚 Documentation\n\n  - fix(setup): correctly auto-detect username when using --full [@ls-root](https://github.com/ls-root) ([#11650](https://github.com/community-scripts/ProxmoxVE/pull/11650))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - feat(frontend): add JSON script import functionality [@ls-root](https://github.com/ls-root) ([#11563](https://github.com/community-scripts/ProxmoxVE/pull/11563))\n\n## 2026-02-06\n\n### 🆕 New Scripts\n\n  - Nightscout ([#11621](https://github.com/community-scripts/ProxmoxVE/pull/11621))\n- PVE LXC Apps Updater [@MickLesk](https://github.com/MickLesk) ([#11533](https://github.com/community-scripts/ProxmoxVE/pull/11533))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: supress startup messages for immich-admin [@vhsdream](https://github.com/vhsdream) ([#11635](https://github.com/community-scripts/ProxmoxVE/pull/11635))\n    - Semaphore: Change Ubuntu release from 'jammy' to 'noble' [@MickLesk](https://github.com/MickLesk) ([#11625](https://github.com/community-scripts/ProxmoxVE/pull/11625))\n    - Pangolin: replace build:sqlite with db:generate + build [@MickLesk](https://github.com/MickLesk) ([#11616](https://github.com/community-scripts/ProxmoxVE/pull/11616))\n    - [FIX] OpenCloud: path issues [@vhsdream](https://github.com/vhsdream) ([#11593](https://github.com/community-scripts/ProxmoxVE/pull/11593))\n    - [FIX] Homepage: preserve public/images & public/icons if they exist [@vhsdream](https://github.com/vhsdream) ([#11594](https://github.com/community-scripts/ProxmoxVE/pull/11594))\n\n  - #### ✨ New Features\n\n    - Shelfmark: remove Chromedriver dep, add URL_BASE env [@vhsdream](https://github.com/vhsdream) ([#11619](https://github.com/community-scripts/ProxmoxVE/pull/11619))\n    - Immich: pin to v2.5.5 [@vhsdream](https://github.com/vhsdream) ([#11598](https://github.com/community-scripts/ProxmoxVE/pull/11598))\n\n  - #### 🔧 Refactor\n\n    - refactor: homepage [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11605](https://github.com/community-scripts/ProxmoxVE/pull/11605))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - fix(core): spinner misalignment [@ls-root](https://github.com/ls-root) ([#11627](https://github.com/community-scripts/ProxmoxVE/pull/11627))\n\n  - #### 🔧 Refactor\n\n    - [Fix] build.func: QOL grammar adjustment for Creating LXC message [@vhsdream](https://github.com/vhsdream) ([#11633](https://github.com/community-scripts/ProxmoxVE/pull/11633))\n\n### 📚 Documentation\n\n  - [gh] Update to the New Script request template [@tremor021](https://github.com/tremor021) ([#11612](https://github.com/community-scripts/ProxmoxVE/pull/11612))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Update LXC App Updater JSON to reflect tag override option [@vhsdream](https://github.com/vhsdream) ([#11626](https://github.com/community-scripts/ProxmoxVE/pull/11626))\n\n### ❔ Uncategorized\n\n  - Opencloud: fix JSON [@vhsdream](https://github.com/vhsdream) ([#11617](https://github.com/community-scripts/ProxmoxVE/pull/11617))\n\n## 2026-02-05\n\n### 🆕 New Scripts\n\n  - OpenCloud ([#11538](https://github.com/community-scripts/ProxmoxVE/pull/11538))\n- Nginx-UI ([#11573](https://github.com/community-scripts/ProxmoxVE/pull/11573))\n- New: SQL-Server 2025 | Refactor SQL-Server 2022  [@MickLesk](https://github.com/MickLesk) ([#11546](https://github.com/community-scripts/ProxmoxVE/pull/11546))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - OpenCloud: pin version to 5.0.2; Collabora CSP fix [@vhsdream](https://github.com/vhsdream) ([#11585](https://github.com/community-scripts/ProxmoxVE/pull/11585))\n    - Wanderer: Fix repo [@tremor021](https://github.com/tremor021) ([#11567](https://github.com/community-scripts/ProxmoxVE/pull/11567))\n\n  - #### ✨ New Features\n\n    - Refactor: Docker-VM (Multi-OS / Cloud-Init / Stabilization)  [@MickLesk](https://github.com/MickLesk) ([#9047](https://github.com/community-scripts/ProxmoxVE/pull/9047))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - cloud-init: add interactive SSH key discovery and selection [@MickLesk](https://github.com/MickLesk) ([#11547](https://github.com/community-scripts/ProxmoxVE/pull/11547))\n\n### 📚 Documentation\n\n  - github: extend docs / contribution / templates [@MickLesk](https://github.com/MickLesk) ([#10921](https://github.com/community-scripts/ProxmoxVE/pull/10921))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - fix(frontend): theme respective syntax highlighting [@ls-root](https://github.com/ls-root) ([#11565](https://github.com/community-scripts/ProxmoxVE/pull/11565))\n\n## 2026-02-04\n\n### 🆕 New Scripts\n\n  - Wishlist ([#11527](https://github.com/community-scripts/ProxmoxVE/pull/11527))\n- WriteFreely ([#11524](https://github.com/community-scripts/ProxmoxVE/pull/11524))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: create vm-core.func from dev [@MickLesk](https://github.com/MickLesk) ([#11528](https://github.com/community-scripts/ProxmoxVE/pull/11528))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - fix(frontend): implement weighted search scoring for command menu [@ls-root](https://github.com/ls-root) ([#11534](https://github.com/community-scripts/ProxmoxVE/pull/11534))\n\n## 2026-02-03\n\n### 🆕 New Scripts\n\n  - Wealthfolio ([#11511](https://github.com/community-scripts/ProxmoxVE/pull/11511))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [FIX] Shelfmark: unpin Chromium version [@vhsdream](https://github.com/vhsdream) ([#11505](https://github.com/community-scripts/ProxmoxVE/pull/11505))\n\n  - #### ✨ New Features\n\n    - [FEAT] Scanopy: automatically update integrated daemon [@vhsdream](https://github.com/vhsdream) ([#11506](https://github.com/community-scripts/ProxmoxVE/pull/11506))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - [FIX] tools.func: trim spaces in app_lc when checking for gh release [@vhsdream](https://github.com/vhsdream) ([#11512](https://github.com/community-scripts/ProxmoxVE/pull/11512))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - fix(frontend): decouple table pagination from summary fetching [@ls-root](https://github.com/ls-root) ([#11495](https://github.com/community-scripts/ProxmoxVE/pull/11495))\n\n## 2026-02-02\n\n### 🆕 New Scripts\n\n  - rustypaste | Alpine-rustypaste ([#11457](https://github.com/community-scripts/ProxmoxVE/pull/11457))\n- KitchenOwl ([#11453](https://github.com/community-scripts/ProxmoxVE/pull/11453))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Grist: Update dependencies [@tremor021](https://github.com/tremor021) ([#11489](https://github.com/community-scripts/ProxmoxVE/pull/11489))\n    - Allow \"downgrade\" of libigdgmm12 [@vhsdream](https://github.com/vhsdream) ([#11478](https://github.com/community-scripts/ProxmoxVE/pull/11478))\n    - Disable NPM install and update due to OpenResty SHA-1 signature issues [@MickLesk](https://github.com/MickLesk) ([#11471](https://github.com/community-scripts/ProxmoxVE/pull/11471))\n\n  - #### ✨ New Features\n\n    - Refactor: Forgejo & readeck - migrate to codeberg functions [@MickLesk](https://github.com/MickLesk) ([#11460](https://github.com/community-scripts/ProxmoxVE/pull/11460))\n\n  - #### 💥 Breaking Changes\n\n    - [FIX] Scanopy: remove daemon build [@vhsdream](https://github.com/vhsdream) ([#11444](https://github.com/community-scripts/ProxmoxVE/pull/11444))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Vaultwarden [@MickLesk](https://github.com/MickLesk) ([#11445](https://github.com/community-scripts/ProxmoxVE/pull/11445))\n    - various scripts: use ensure_dependencies instead of apt [@MickLesk](https://github.com/MickLesk) ([#11463](https://github.com/community-scripts/ProxmoxVE/pull/11463))\n\n### 🌐 Website\n\n  - cleanup(frontend): remove unused /category-view route [@ls-root](https://github.com/ls-root) ([#11461](https://github.com/community-scripts/ProxmoxVE/pull/11461))\n\n  - #### ✨ New Features\n\n    - feat(frontend): preview tab [@ls-root](https://github.com/ls-root) ([#11475](https://github.com/community-scripts/ProxmoxVE/pull/11475))\n\n## 2026-02-01\n\n### 🚀 Updated Scripts\n\n  - fix headers [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11422](https://github.com/community-scripts/ProxmoxVE/pull/11422))\n\n  - #### 🐞 Bug Fixes\n\n    - 2fauth: export PHP_VERSION for nginx config [@MickLesk](https://github.com/MickLesk) ([#11441](https://github.com/community-scripts/ProxmoxVE/pull/11441))\n    - Prometheus Paperless NGX Exporter: Set correct binary path in systemd unit file [@andygrunwald](https://github.com/andygrunwald) ([#11438](https://github.com/community-scripts/ProxmoxVE/pull/11438))\n    - tracearr: install/update new prestart script from upstream [@durzo](https://github.com/durzo) ([#11433](https://github.com/community-scripts/ProxmoxVE/pull/11433))\n    - n8n: Fix dependencies [@tremor021](https://github.com/tremor021) ([#11429](https://github.com/community-scripts/ProxmoxVE/pull/11429))\n    - [Hotfix] Bunkerweb update [@vhsdream](https://github.com/vhsdream) ([#11402](https://github.com/community-scripts/ProxmoxVE/pull/11402))\n    - [Hotfix] Immich: revert healthcheck feature [@vhsdream](https://github.com/vhsdream) ([#11427](https://github.com/community-scripts/ProxmoxVE/pull/11427))\n\n  - #### ✨ New Features\n\n    - tools.func: add codeberg functions & autocaliweb: migrate from GitHub to Codeberg [@MickLesk](https://github.com/MickLesk) ([#11440](https://github.com/community-scripts/ProxmoxVE/pull/11440))\n    - Immich Refactor #2 [@vhsdream](https://github.com/vhsdream) ([#11375](https://github.com/community-scripts/ProxmoxVE/pull/11375))\n\n  - #### 🔧 Refactor\n\n    - WordPress: Refactor [@tremor021](https://github.com/tremor021) ([#11408](https://github.com/community-scripts/ProxmoxVE/pull/11408))\n    - Refactor: Whisparr [@tremor021](https://github.com/tremor021) ([#11411](https://github.com/community-scripts/ProxmoxVE/pull/11411))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - [tools]: Update `fetch_and_deply_from_url()` [@tremor021](https://github.com/tremor021) ([#11410](https://github.com/community-scripts/ProxmoxVE/pull/11410))\n\n### 🌐 Website\n\n  - feat(frontend): implement UX refinements and syntax highlighting [@ls-root](https://github.com/ls-root) ([#11423](https://github.com/community-scripts/ProxmoxVE/pull/11423))\n\n  - #### ✨ New Features\n\n    - feat(frontend): add contribution CTA to empty search state [@ls-root](https://github.com/ls-root) ([#11412](https://github.com/community-scripts/ProxmoxVE/pull/11412))\n"
  },
  {
    "path": ".github/changelogs/2026/03.md",
    "content": "## 2026-03-31\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Graylog: set vm.max_map_count on host for OpenSearch [@MickLesk](https://github.com/MickLesk) ([#13441](https://github.com/community-scripts/ProxmoxVE/pull/13441))\n    - Koillection: ensure newline before appending to .env.local [@MickLesk](https://github.com/MickLesk) ([#13440](https://github.com/community-scripts/ProxmoxVE/pull/13440))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: skip empty gateway value in network config [@MickLesk](https://github.com/MickLesk) ([#13442](https://github.com/community-scripts/ProxmoxVE/pull/13442))\n\n## 2026-03-30\n\n### 🆕 New Scripts\n\n  - Bambuddy ([#13411](https://github.com/community-scripts/ProxmoxVE/pull/13411))\n\n### 🚀 Updated Scripts\n\n  - #### 💥 Breaking Changes\n\n    - Rename: BirdNET > BirdNET-Go [@MickLesk](https://github.com/MickLesk) ([#13410](https://github.com/community-scripts/ProxmoxVE/pull/13410))\n\n## 2026-03-29\n\n### 🆕 New Scripts\n\n  - YOURLS ([#13379](https://github.com/community-scripts/ProxmoxVE/pull/13379))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix(victoriametrics): use jq to filter releases [@Joery-M](https://github.com/Joery-M) ([#13393](https://github.com/community-scripts/ProxmoxVE/pull/13393))\n    - Ollama: add error handling for Intel GPG key imports [@MickLesk](https://github.com/MickLesk) ([#13397](https://github.com/community-scripts/ProxmoxVE/pull/13397))\n    - Immich: ignore Redis connection error on maintenance mode disable [@MickLesk](https://github.com/MickLesk) ([#13398](https://github.com/community-scripts/ProxmoxVE/pull/13398))\n    - NPM: unmask openresty after migration from package [@MickLesk](https://github.com/MickLesk) ([#13399](https://github.com/community-scripts/ProxmoxVE/pull/13399))\n\n## 2026-03-28\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Fix: Update gokapi binary name for v2.2.4+ and add migration step [@krazos](https://github.com/krazos) ([#13377](https://github.com/community-scripts/ProxmoxVE/pull/13377))\n    - Fix: update gokapi asset matching for v2.2.4+ naming convention [@krazos](https://github.com/krazos) ([#13369](https://github.com/community-scripts/ProxmoxVE/pull/13369))\n    - Tandoor Recipes: Add missing env variable [@tremor021](https://github.com/tremor021) ([#13365](https://github.com/community-scripts/ProxmoxVE/pull/13365))\n\n  - #### ✨ New Features\n\n    - FileFlows: add option to install Node [@tremor021](https://github.com/tremor021) ([#13368](https://github.com/community-scripts/ProxmoxVE/pull/13368))\n\n## 2026-03-27\n\n### 🆕 New Scripts\n\n  - Matter-Server ([#13355](https://github.com/community-scripts/ProxmoxVE/pull/13355))\n- GeoPulse ([#13320](https://github.com/community-scripts/ProxmoxVE/pull/13320))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - RevealJS: Switch from gulp to vite [@tremor021](https://github.com/tremor021) ([#13336](https://github.com/community-scripts/ProxmoxVE/pull/13336))\n\n  - #### ✨ New Features\n\n    - Dispatcharr add custom Postgres port support for upgrade [@MickLesk](https://github.com/MickLesk) ([#13347](https://github.com/community-scripts/ProxmoxVE/pull/13347))\n    - Immich: bump to v2.6.3 [@MickLesk](https://github.com/MickLesk) ([#13324](https://github.com/community-scripts/ProxmoxVE/pull/13324))\n\n### 🧰 Tools\n\n  - #### ✨ New Features\n\n    - Refactor/Feature-Bump/Security: Update-Cron-LXCs (Now Local Mode!) [@MickLesk](https://github.com/MickLesk) ([#13339](https://github.com/community-scripts/ProxmoxVE/pull/13339))\n\n## 2026-03-26\n\n### 🆕 New Scripts\n\n  - BirdNET ([#13313](https://github.com/community-scripts/ProxmoxVE/pull/13313))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Bump to 2.6.2 | use start.sh in service, ensure DB_HOSTNAME in .env | Fix Rights Issue with ZFS Shares [@MickLesk](https://github.com/MickLesk) ([#13199](https://github.com/community-scripts/ProxmoxVE/pull/13199))\n\n  - #### ✨ New Features\n\n    - SparkyFitness: add garmin microservice as addon [@tomfrenzel](https://github.com/tomfrenzel) ([#12642](https://github.com/community-scripts/ProxmoxVE/pull/12642))\n    - Frigate: bump to v0.17.1 & change build order [@MickLesk](https://github.com/MickLesk) ([#13304](https://github.com/community-scripts/ProxmoxVE/pull/13304))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: pin npm to 11.11.0 to work around Node.js 22.22.2 regression [@MickLesk](https://github.com/MickLesk) ([#13296](https://github.com/community-scripts/ProxmoxVE/pull/13296))\n\n  - #### ✨ New Features\n\n    - core: APT/APK Mirror Fallback for CDN Failures [@MickLesk](https://github.com/MickLesk) ([#13316](https://github.com/community-scripts/ProxmoxVE/pull/13316))\n    - core/tools: replace generic return 1 exit_codes with more specific exit_codes [@MickLesk](https://github.com/MickLesk) ([#13311](https://github.com/community-scripts/ProxmoxVE/pull/13311))\n\n  - #### 🔧 Refactor\n\n    - core: use /usr/bin/install to prevent function shadowing [@MickLesk](https://github.com/MickLesk) ([#13299](https://github.com/community-scripts/ProxmoxVE/pull/13299))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - SparkyFitness-Garmin: fix app name [@tomfrenzel](https://github.com/tomfrenzel) ([#13325](https://github.com/community-scripts/ProxmoxVE/pull/13325))\n\n## 2026-03-25\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Komodo v2: migrate env vars to v2 and update source [@MickLesk](https://github.com/MickLesk) ([#13262](https://github.com/community-scripts/ProxmoxVE/pull/13262))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: make shell command substitutions safe with || true [@MickLesk](https://github.com/MickLesk) ([#13279](https://github.com/community-scripts/ProxmoxVE/pull/13279))\n\n## 2026-03-24\n\n### 🆕 New Scripts\n\n  - Homebrew (Addon) ([#13249](https://github.com/community-scripts/ProxmoxVE/pull/13249))\n- NextExplorer ([#13252](https://github.com/community-scripts/ProxmoxVE/pull/13252))\n\n### 🚀 Updated Scripts\n\n  - #### ✨ New Features\n\n    - Turnkey: modernize turnkey.sh with shared libraries  [@MickLesk](https://github.com/MickLesk) ([#13242](https://github.com/community-scripts/ProxmoxVE/pull/13242))\n\n  - #### 🔧 Refactor\n\n    - chore: replace helper-scripts.com with community-scripts.com [@MickLesk](https://github.com/MickLesk) ([#13244](https://github.com/community-scripts/ProxmoxVE/pull/13244))\n\n### 🗑️ Deleted Scripts\n\n  - Remove: Booklore [@MickLesk](https://github.com/MickLesk) ([#13265](https://github.com/community-scripts/ProxmoxVE/pull/13265))\n\n## 2026-03-23\n\n### 🚀 Updated Scripts\n\n  - #### 🔧 Refactor\n\n    - core: harden shell scripts against injection and insecure permissions [@MickLesk](https://github.com/MickLesk) ([#13239](https://github.com/community-scripts/ProxmoxVE/pull/13239))\n\n## 2026-03-22\n\n### 🆕 New Scripts\n\n  - versitygw ([#13180](https://github.com/community-scripts/ProxmoxVE/pull/13180))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Adventurelog: pin DRF <3.15 to fix coreapi module removal [@MickLesk](https://github.com/MickLesk) ([#13194](https://github.com/community-scripts/ProxmoxVE/pull/13194))\n\n  - #### ✨ New Features\n\n    - ConvertX: add libreoffice-writer for ODT/document conversions [@MickLesk](https://github.com/MickLesk) ([#13196](https://github.com/community-scripts/ProxmoxVE/pull/13196))\n\n  - #### 🔧 Refactor\n\n    - iSponsorblockTV: add AVX CPU check before installation [@MickLesk](https://github.com/MickLesk) ([#13197](https://github.com/community-scripts/ProxmoxVE/pull/13197))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: guard against empty IPv6 address in static mode [@MickLesk](https://github.com/MickLesk) ([#13195](https://github.com/community-scripts/ProxmoxVE/pull/13195))\n\n## 2026-03-21\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Anytype-server: wait for MongoDB readiness before rs.initiate() [@MickLesk](https://github.com/MickLesk) ([#13165](https://github.com/community-scripts/ProxmoxVE/pull/13165))\n    - Frigate: use correct CPU model fallback path [@MickLesk](https://github.com/MickLesk) ([#13164](https://github.com/community-scripts/ProxmoxVE/pull/13164))\n    - iSponsorBlockTV: Fix release fetching  [@tremor021](https://github.com/tremor021) ([#13157](https://github.com/community-scripts/ProxmoxVE/pull/13157))\n    - Isponsorblocktv: use quoted heredoc to prevent unbound variable error during CLI wrapper creation [@Copilot](https://github.com/Copilot) ([#13146](https://github.com/community-scripts/ProxmoxVE/pull/13146))\n\n  - #### ✨ New Features\n\n    - Headscale: Enable TUN [@tremor021](https://github.com/tremor021) ([#13158](https://github.com/community-scripts/ProxmoxVE/pull/13158))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: add missing -searchdomain/-nameserver prefix in base_settings [@MickLesk](https://github.com/MickLesk) ([#13166](https://github.com/community-scripts/ProxmoxVE/pull/13166))\n\n## 2026-03-20\n\n### 🆕 New Scripts\n\n  - iSponsorBlockTV ([#13123](https://github.com/community-scripts/ProxmoxVE/pull/13123))\n- Alpine-Wakapi ([#13119](https://github.com/community-scripts/ProxmoxVE/pull/13119))\n- teleport ([#13086](https://github.com/community-scripts/ProxmoxVE/pull/13086))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Reactive-Resume: add git dependency for v5.0.13+ [@MickLesk](https://github.com/MickLesk) ([#13133](https://github.com/community-scripts/ProxmoxVE/pull/13133))\n    - Scanopy: increase default CPU, RAM, and HDD to prevent OOM during Rust build [@Copilot](https://github.com/Copilot) ([#13130](https://github.com/community-scripts/ProxmoxVE/pull/13130))\n\n  - #### ✨ New Features\n\n    - Immich: v2.6.1 [@vhsdream](https://github.com/vhsdream) ([#13111](https://github.com/community-scripts/ProxmoxVE/pull/13111))\n    - VM's: add input validation and hostname sanitization to all VM scripts [@MickLesk](https://github.com/MickLesk) ([#12973](https://github.com/community-scripts/ProxmoxVE/pull/12973))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - Harden code-server addon install script [@MickLesk](https://github.com/MickLesk) ([#13116](https://github.com/community-scripts/ProxmoxVE/pull/13116))\n\n## 2026-03-19\n\n### 🚀 Updated Scripts\n\n  - Owncast: increase default disk size from 2GB to 10GB [@Copilot](https://github.com/Copilot) ([#13079](https://github.com/community-scripts/ProxmoxVE/pull/13079))\n\n  - #### 🐞 Bug Fixes\n\n    - fix: remove extra backslash to match single quoted here-doc [@Zelnes](https://github.com/Zelnes) ([#13108](https://github.com/community-scripts/ProxmoxVE/pull/13108))\n    - Reactive-Resume: Upgrade Node to 24 and enable Corepack [@MickLesk](https://github.com/MickLesk) ([#13093](https://github.com/community-scripts/ProxmoxVE/pull/13093))\n    - Increase Tracearr RAM; derive APP_VERSION [@MickLesk](https://github.com/MickLesk) ([#13087](https://github.com/community-scripts/ProxmoxVE/pull/13087))\n    - ProjectSend: Update application access URL [@tremor021](https://github.com/tremor021) ([#13078](https://github.com/community-scripts/ProxmoxVE/pull/13078))\n    - Dispatcharr: use npm install --no-audit --progress=false [@MickLesk](https://github.com/MickLesk) ([#13074](https://github.com/community-scripts/ProxmoxVE/pull/13074))\n    - core: reorder hwaccel setup and adjust GPU group usermod [@MickLesk](https://github.com/MickLesk) ([#13072](https://github.com/community-scripts/ProxmoxVE/pull/13072))\n\n  - #### ✨ New Features\n\n    - tools.func: display pin reason in release-check messages [@MickLesk](https://github.com/MickLesk) ([#13095](https://github.com/community-scripts/ProxmoxVE/pull/13095))\n    - NocoDB: Unpin Version to latest [@MickLesk](https://github.com/MickLesk) ([#13094](https://github.com/community-scripts/ProxmoxVE/pull/13094))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: use dpkg-query for reliable JDK version detection [@MickLesk](https://github.com/MickLesk) ([#13101](https://github.com/community-scripts/ProxmoxVE/pull/13101))\n\n### 📚 Documentation\n\n  - Update link from helper-scripts.com to community-scripts.org [@adnanvaldes](https://github.com/adnanvaldes) ([#13098](https://github.com/community-scripts/ProxmoxVE/pull/13098))\n- github: add PocketBase bot workflow [@MickLesk](https://github.com/MickLesk) ([#13075](https://github.com/community-scripts/ProxmoxVE/pull/13075))\n\n## 2026-03-18\n\n### 🆕 New Scripts\n\n  - Alpine-Ntfy [@MickLesk](https://github.com/MickLesk) ([#13048](https://github.com/community-scripts/ProxmoxVE/pull/13048))\n- Split-Pro ([#12975](https://github.com/community-scripts/ProxmoxVE/pull/12975))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Tdarr: use curl_with_retry and correct exit code [@MickLesk](https://github.com/MickLesk) ([#13060](https://github.com/community-scripts/ProxmoxVE/pull/13060))\n    - reitti: fix: v4 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13039](https://github.com/community-scripts/ProxmoxVE/pull/13039))\n    - Paperless-NGX: increase default RAM to 3GB [@MickLesk](https://github.com/MickLesk) ([#13018](https://github.com/community-scripts/ProxmoxVE/pull/13018))\n    - Plex: restart service after update to apply new version [@MickLesk](https://github.com/MickLesk) ([#13017](https://github.com/community-scripts/ProxmoxVE/pull/13017))\n\n  - #### ✨ New Features\n\n    - tools: centralize GPU group setup via setup_hwaccel [@MickLesk](https://github.com/MickLesk) ([#13044](https://github.com/community-scripts/ProxmoxVE/pull/13044))\n    - Termix: add guacd build and systemd integration [@MickLesk](https://github.com/MickLesk) ([#12999](https://github.com/community-scripts/ProxmoxVE/pull/12999))\n\n  - #### 🔧 Refactor\n\n    - Podman: replace deprecated commands with Quadlets [@MickLesk](https://github.com/MickLesk) ([#13052](https://github.com/community-scripts/ProxmoxVE/pull/13052))\n    - Refactor: Jellyfin repo, ffmpeg package and symlinks [@MickLesk](https://github.com/MickLesk) ([#13045](https://github.com/community-scripts/ProxmoxVE/pull/13045))\n    - pve-scripts-local: Increase default disk size from 4GB to 10GB [@MickLesk](https://github.com/MickLesk) ([#13009](https://github.com/community-scripts/ProxmoxVE/pull/13009))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func Implement pg_cron setup for setup_postgresql [@MickLesk](https://github.com/MickLesk) ([#13053](https://github.com/community-scripts/ProxmoxVE/pull/13053))\n    - tools.func: Implement check_for_gh_tag function [@MickLesk](https://github.com/MickLesk) ([#12998](https://github.com/community-scripts/ProxmoxVE/pull/12998))\n    - tools.func: Implement fetch_and_deploy_gh_tag function [@MickLesk](https://github.com/MickLesk) ([#13000](https://github.com/community-scripts/ProxmoxVE/pull/13000))\n\n## 2026-03-17\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Gluetun: add OpenVPN process user and cleanup stale config [@MickLesk](https://github.com/MickLesk) ([#13016](https://github.com/community-scripts/ProxmoxVE/pull/13016))\n    - Frigate: check OpenVino model files exist before configuring detector and use curl_with_retry instead of default wget [@MickLesk](https://github.com/MickLesk) ([#13019](https://github.com/community-scripts/ProxmoxVE/pull/13019))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - tools.func: Update `create_self_signed_cert()` [@tremor021](https://github.com/tremor021) ([#13008](https://github.com/community-scripts/ProxmoxVE/pull/13008))\n\n## 2026-03-16\n\n### 🆕 New Scripts\n\n  - Gluetun ([#12976](https://github.com/community-scripts/ProxmoxVE/pull/12976))\n- Anytype-Server ([#12974](https://github.com/community-scripts/ProxmoxVE/pull/12974))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: use gcc-13 for compilation & add uv python pre-install with retry logic [@MickLesk](https://github.com/MickLesk) ([#12935](https://github.com/community-scripts/ProxmoxVE/pull/12935))\n    - Tautulli: add setuptools<81 constraint to update script [@MickLesk](https://github.com/MickLesk) ([#12959](https://github.com/community-scripts/ProxmoxVE/pull/12959))\n    - Seerr: add missing build deps [@MickLesk](https://github.com/MickLesk) ([#12960](https://github.com/community-scripts/ProxmoxVE/pull/12960))\n    - fix: yubal update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12961](https://github.com/community-scripts/ProxmoxVE/pull/12961))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - hwaccel: remove ROCm install from AMD APU setup [@MickLesk](https://github.com/MickLesk) ([#12958](https://github.com/community-scripts/ProxmoxVE/pull/12958))\n\n## 2026-03-15\n\n### 🆕 New Scripts\n\n  - Yamtrack ([#12936](https://github.com/community-scripts/ProxmoxVE/pull/12936))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Wishlist: use --frozen-lockfile for pnpm install [@MickLesk](https://github.com/MickLesk) ([#12892](https://github.com/community-scripts/ProxmoxVE/pull/12892))\n    - SparkyFitness: use --legacy-peer-deps for npm install [@MickLesk](https://github.com/MickLesk) ([#12888](https://github.com/community-scripts/ProxmoxVE/pull/12888))\n    - Frigate: add fallback for OpenVino labelmap file [@MickLesk](https://github.com/MickLesk) ([#12889](https://github.com/community-scripts/ProxmoxVE/pull/12889))\n\n  - #### 🔧 Refactor\n\n    - Refactor: ITSM-NG [@MickLesk](https://github.com/MickLesk) ([#12918](https://github.com/community-scripts/ProxmoxVE/pull/12918))\n    - core: unify RELEASE variable for check_for_gh_release and fetch_and_deploy [@MickLesk](https://github.com/MickLesk) ([#12917](https://github.com/community-scripts/ProxmoxVE/pull/12917))\n    - Standardize NSAPP names across VM scripts [@MickLesk](https://github.com/MickLesk) ([#12924](https://github.com/community-scripts/ProxmoxVE/pull/12924))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: retry downloads with exponential backoff [@MickLesk](https://github.com/MickLesk) ([#12896](https://github.com/community-scripts/ProxmoxVE/pull/12896))\n\n### ❔ Uncategorized\n\n  - [go2rtc] Add ffmpeg dependency to install script [@Copilot](https://github.com/Copilot) ([#12944](https://github.com/community-scripts/ProxmoxVE/pull/12944))\n\n## 2026-03-14\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Patchmon: remove v prefix from pinned version  [@MickLesk](https://github.com/MickLesk) ([#12891](https://github.com/community-scripts/ProxmoxVE/pull/12891))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: don't abort on AMD repo apt update failure [@MickLesk](https://github.com/MickLesk) ([#12890](https://github.com/community-scripts/ProxmoxVE/pull/12890))\n\n## 2026-03-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Hotfix: Removed clean install usage from original script. [@nickheyer](https://github.com/nickheyer) ([#12870](https://github.com/community-scripts/ProxmoxVE/pull/12870))\n\n  - #### 🔧 Refactor\n\n    - Discopanel: V2 Support + Script rewrite [@nickheyer](https://github.com/nickheyer) ([#12763](https://github.com/community-scripts/ProxmoxVE/pull/12763))\n\n### 🧰 Tools\n\n  - update-apps: fix restore path, add PBS support and improve restore messages [@omertahaoztop](https://github.com/omertahaoztop) ([#12528](https://github.com/community-scripts/ProxmoxVE/pull/12528))\n\n  - #### 🐞 Bug Fixes\n\n    - fix(pve-privilege-converter): handle already stopped container in manage_states [@liuqitoday](https://github.com/liuqitoday) ([#12765](https://github.com/community-scripts/ProxmoxVE/pull/12765))\n\n### 📚 Documentation\n\n  - Update: Docs/website metadata workflow [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12858](https://github.com/community-scripts/ProxmoxVE/pull/12858))\n\n## 2026-03-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - manyfold: fix incorrect port in upstream requests by forwarding original host [@anlopo](https://github.com/anlopo) ([#12812](https://github.com/community-scripts/ProxmoxVE/pull/12812))\n    - SparkyFitness: install pnpm dependencies from workspace root [@MickLesk](https://github.com/MickLesk) ([#12792](https://github.com/community-scripts/ProxmoxVE/pull/12792))\n    - n8n: add build-essential to update dependencies [@MickLesk](https://github.com/MickLesk) ([#12795](https://github.com/community-scripts/ProxmoxVE/pull/12795))\n    - Frigate openvino labelmap patch [@semtex1987](https://github.com/semtex1987) ([#12751](https://github.com/community-scripts/ProxmoxVE/pull/12751))\n\n  - #### 🔧 Refactor\n\n    - Pin Patchmon to 1.4.2 [@vhsdream](https://github.com/vhsdream) ([#12789](https://github.com/community-scripts/ProxmoxVE/pull/12789))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: correct PATH escaping in ROCm profile script [@MickLesk](https://github.com/MickLesk) ([#12793](https://github.com/community-scripts/ProxmoxVE/pull/12793))\n\n  - #### ✨ New Features\n\n    - core: add mode=generated for unattended frontend installs [@MickLesk](https://github.com/MickLesk) ([#12807](https://github.com/community-scripts/ProxmoxVE/pull/12807))\n    - core: validate storage availability when loading defaults [@MickLesk](https://github.com/MickLesk) ([#12794](https://github.com/community-scripts/ProxmoxVE/pull/12794))\n\n  - #### 🔧 Refactor\n\n    - tools.func: support older NVIDIA driver versions with 2 segments (xxx.xxx) [@MickLesk](https://github.com/MickLesk) ([#12796](https://github.com/community-scripts/ProxmoxVE/pull/12796))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - Fix PBS microcode naming [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12834](https://github.com/community-scripts/ProxmoxVE/pull/12834))\n\n### 📂 Github\n\n  - Cleanup: remove old workflow files [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12818](https://github.com/community-scripts/ProxmoxVE/pull/12818))\n- Cleanup: remove frontend, move JSONs to json/ top-level [@MickLesk](https://github.com/MickLesk) ([#12813](https://github.com/community-scripts/ProxmoxVE/pull/12813))\n\n### ❔ Uncategorized\n\n  - Remove json files [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12830](https://github.com/community-scripts/ProxmoxVE/pull/12830))\n\n## 2026-03-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: Init telemetry in addon scripts [@MickLesk](https://github.com/MickLesk) ([#12777](https://github.com/community-scripts/ProxmoxVE/pull/12777))\n    - Tracearr:  Increase default disk variable from 5 to 10 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12762](https://github.com/community-scripts/ProxmoxVE/pull/12762))\n    - Fix Wireguard Dashboard update [@odin568](https://github.com/odin568) ([#12767](https://github.com/community-scripts/ProxmoxVE/pull/12767))\n\n### 🧰 Tools\n\n  - #### ✨ New Features\n\n    - Coder-Code-Server: Check if config file exists [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12758](https://github.com/community-scripts/ProxmoxVE/pull/12758))\n\n## 2026-03-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [Fix] Immich: Pin libvips to 8.17.3 [@vhsdream](https://github.com/vhsdream) ([#12744](https://github.com/community-scripts/ProxmoxVE/pull/12744))\n\n## 2026-03-09\n\n### 🚀 Updated Scripts\n\n  - Pin Opencloud to 5.2.0 [@vhsdream](https://github.com/vhsdream) ([#12721](https://github.com/community-scripts/ProxmoxVE/pull/12721))\n\n  - #### 🐞 Bug Fixes\n\n    - [Hotfix] qBittorrent: Disable UPnP port forwarding by default [@vhsdream](https://github.com/vhsdream) ([#12728](https://github.com/community-scripts/ProxmoxVE/pull/12728))\n    - [Quickfix] Opencloud: ensure correct case for binary [@vhsdream](https://github.com/vhsdream) ([#12729](https://github.com/community-scripts/ProxmoxVE/pull/12729))\n    - Omada: Bump libssl [@MickLesk](https://github.com/MickLesk) ([#12724](https://github.com/community-scripts/ProxmoxVE/pull/12724))\n    - openwebui: Ensure required dependencies [@MickLesk](https://github.com/MickLesk) ([#12717](https://github.com/community-scripts/ProxmoxVE/pull/12717))\n    - Frigate: try an OpenVino model build fallback [@MickLesk](https://github.com/MickLesk) ([#12704](https://github.com/community-scripts/ProxmoxVE/pull/12704))\n    - Change cronjob setup to use www-data user [@opastorello](https://github.com/opastorello) ([#12695](https://github.com/community-scripts/ProxmoxVE/pull/12695))\n    - RustDesk Server: Fix check_for_gh_release function call [@tremor021](https://github.com/tremor021) ([#12694](https://github.com/community-scripts/ProxmoxVE/pull/12694))\n\n  - #### ✨ New Features\n\n    - feat: improve zigbee2mqtt backup handler [@MickLesk](https://github.com/MickLesk) ([#12714](https://github.com/community-scripts/ProxmoxVE/pull/12714))\n\n  - #### 💥 Breaking Changes\n\n    - Reactive Resume: rewrite for v5 using original repo amruthpilla/reactive-resume [@MickLesk](https://github.com/MickLesk) ([#12705](https://github.com/community-scripts/ProxmoxVE/pull/12705))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools: add Alpine (apk) support to ensure_dependencies and is_package_installed [@MickLesk](https://github.com/MickLesk) ([#12703](https://github.com/community-scripts/ProxmoxVE/pull/12703))\n    - tools.func: extend hwaccel with ROCm  [@MickLesk](https://github.com/MickLesk) ([#12707](https://github.com/community-scripts/ProxmoxVE/pull/12707))\n\n### 🌐 Website\n\n  - #### ✨ New Features\n\n    - feat: add CopycatWarningToast component for user warnings [@BramSuurdje](https://github.com/BramSuurdje) ([#12733](https://github.com/community-scripts/ProxmoxVE/pull/12733))\n\n## 2026-03-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [Fix] Immich: chown install dir before machine-learning update [@vhsdream](https://github.com/vhsdream) ([#12684](https://github.com/community-scripts/ProxmoxVE/pull/12684))\n    - [Fix] Scanopy: Build generate-fixtures [@vhsdream](https://github.com/vhsdream) ([#12686](https://github.com/community-scripts/ProxmoxVE/pull/12686))\n    - fix: rustdeskserver: use correct repo string [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12682](https://github.com/community-scripts/ProxmoxVE/pull/12682))\n    - NZBGet: Fixes for RAR5 handling [@tremor021](https://github.com/tremor021) ([#12675](https://github.com/community-scripts/ProxmoxVE/pull/12675))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - LXC-Execute: Fix slug [@tremor021](https://github.com/tremor021) ([#12681](https://github.com/community-scripts/ProxmoxVE/pull/12681))\n\n## 2026-03-07\n\n### 🆕 New Scripts\n\n  - ImmichFrame ([#12653](https://github.com/community-scripts/ProxmoxVE/pull/12653))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Grocy: bump PHP version from 8.3 to 8.5 [@MickLesk](https://github.com/MickLesk) ([#12651](https://github.com/community-scripts/ProxmoxVE/pull/12651))\n    - Check for influxdb3 installation in update_script [@odin568](https://github.com/odin568) ([#12648](https://github.com/community-scripts/ProxmoxVE/pull/12648))\n    - Update Rdtclient to dotnet 10.0 [@asylumexp](https://github.com/asylumexp) ([#12638](https://github.com/community-scripts/ProxmoxVE/pull/12638))\n    - fix(immich): fix update script failing to add Debian testing repo when preferences file already exists [@Copilot](https://github.com/Copilot) ([#12631](https://github.com/community-scripts/ProxmoxVE/pull/12631))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools: add interactive GitHub PAT prompt on rate limit / auth failure [@MickLesk](https://github.com/MickLesk) ([#12652](https://github.com/community-scripts/ProxmoxVE/pull/12652))\n\n### 🌐 Website\n\n  - #### 📝 Script Information\n\n    - Papra: update repository URL to papra-hq/papra [@MickLesk](https://github.com/MickLesk) ([#12650](https://github.com/community-scripts/ProxmoxVE/pull/12650))\n\n## 2026-03-06\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - RustDesk Server: Fix update script [@tremor021](https://github.com/tremor021) ([#12625](https://github.com/community-scripts/ProxmoxVE/pull/12625))\n    - [Node-RED] Restart service after update [@Aurelien30000](https://github.com/Aurelien30000) ([#12621](https://github.com/community-scripts/ProxmoxVE/pull/12621))\n    - wealthfolio: update cors [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12617](https://github.com/community-scripts/ProxmoxVE/pull/12617))\n    - CryptPad: Better update handling [@tremor021](https://github.com/tremor021) ([#12611](https://github.com/community-scripts/ProxmoxVE/pull/12611))\n\n  - #### ✨ New Features\n\n    - RustDesk Server: Switch to updated repository [@tremor021](https://github.com/tremor021) ([#12083](https://github.com/community-scripts/ProxmoxVE/pull/12083))\n\n  - #### 💥 Breaking Changes\n\n    - Semaphore: Move from BoltDB to SQLite [@tremor021](https://github.com/tremor021) ([#12624](https://github.com/community-scripts/ProxmoxVE/pull/12624))\n\n## 2026-03-05\n\n### 🆕 New Scripts\n\n  - ddclient ([#12587](https://github.com/community-scripts/ProxmoxVE/pull/12587))\n- Netbird ([#12585](https://github.com/community-scripts/ProxmoxVE/pull/12585))\n- Papra ([#12577](https://github.com/community-scripts/ProxmoxVE/pull/12577))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fluid-calendar: add build-essential to install and update dependencies [@Copilot](https://github.com/Copilot) ([#12602](https://github.com/community-scripts/ProxmoxVE/pull/12602))\n    - Refactor: BentoPDF [@vhsdream](https://github.com/vhsdream) ([#12597](https://github.com/community-scripts/ProxmoxVE/pull/12597))\n    - Tianji: Fix the bug introduced by the refactor [@tremor021](https://github.com/tremor021) ([#12564](https://github.com/community-scripts/ProxmoxVE/pull/12564))\n    - PowerDNS: use 'launch=' instead of 'launch+=' for gsqlite3 backend [@MickLesk](https://github.com/MickLesk) ([#12579](https://github.com/community-scripts/ProxmoxVE/pull/12579))\n\n### 🗑️ Deleted Scripts\n\n  - Suwayomi-Server: remove due to inactivity and very low usage [@MickLesk](https://github.com/MickLesk) ([#12596](https://github.com/community-scripts/ProxmoxVE/pull/12596))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: add var_os / var_version to whitelist for app.vars [@MickLesk](https://github.com/MickLesk) ([#12576](https://github.com/community-scripts/ProxmoxVE/pull/12576))\n\n## 2026-03-04\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: gitea-mirror [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12549](https://github.com/community-scripts/ProxmoxVE/pull/12549))\n    - fix(immich): correct LibRaw clone URL to official upstream [@DenislavDenev](https://github.com/DenislavDenev) ([#12526](https://github.com/community-scripts/ProxmoxVE/pull/12526))\n    - update: stirling-pdf: java 25 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12552](https://github.com/community-scripts/ProxmoxVE/pull/12552))\n    - Docmost: register NoopAuditService globally when EE submodule is missing [@MickLesk](https://github.com/MickLesk) ([#12551](https://github.com/community-scripts/ProxmoxVE/pull/12551))\n    - jellyseer/overseer migration corrupting /usr/bin/update [@MickLesk](https://github.com/MickLesk) ([#12539](https://github.com/community-scripts/ProxmoxVE/pull/12539))\n    - PowerDNS: use gsqlite3 backend instead of BIND [@MickLesk](https://github.com/MickLesk) ([#12538](https://github.com/community-scripts/ProxmoxVE/pull/12538))\n    - addon migrations: /usr/bin/update replacement to prevent syntax error [@MickLesk](https://github.com/MickLesk) ([#12540](https://github.com/community-scripts/ProxmoxVE/pull/12540))\n\n  - #### 🔧 Refactor\n\n    - Fluid-Calendar: NodeJS bump [@tremor021](https://github.com/tremor021) ([#12558](https://github.com/community-scripts/ProxmoxVE/pull/12558))\n    - Refactor: LiteLLM [@tremor021](https://github.com/tremor021) ([#12550](https://github.com/community-scripts/ProxmoxVE/pull/12550))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools: fall back to distro packages for psql [@MickLesk](https://github.com/MickLesk) ([#12542](https://github.com/community-scripts/ProxmoxVE/pull/12542))\n    - fix: whitelist var_searchdomain and fix the handling of var_ns and va… [@tommoyer](https://github.com/tommoyer) ([#12521](https://github.com/community-scripts/ProxmoxVE/pull/12521))\n\n## 2026-03-03\n\n### 🆕 New Scripts\n\n  - Tinyauth: v5 Support & add Debian Version [@MickLesk](https://github.com/MickLesk) ([#12501](https://github.com/community-scripts/ProxmoxVE/pull/12501))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - cross-seed: install build-essential to resolve missing `make` error [@Copilot](https://github.com/Copilot) ([#12522](https://github.com/community-scripts/ProxmoxVE/pull/12522))\n    - meshcentral: increased disk space to 4GB [@MickLesk](https://github.com/MickLesk) ([#12509](https://github.com/community-scripts/ProxmoxVE/pull/12509))\n\n  - #### 🔧 Refactor\n\n    - opnsense-vm: harden temp dir, bridge detection and network selection [@MickLesk](https://github.com/MickLesk) ([#12513](https://github.com/community-scripts/ProxmoxVE/pull/12513))\n\n### 🗑️ Deleted Scripts\n\n  - Remove Unifi Network Server scripts (dead APT repo) [@Copilot](https://github.com/Copilot) ([#12500](https://github.com/community-scripts/ProxmoxVE/pull/12500))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: recovery - add ENOSPC disk-full detection with auto-retry using * 2 hdd [@MickLesk](https://github.com/MickLesk) ([#12511](https://github.com/community-scripts/ProxmoxVE/pull/12511))\n\n### 📚 Documentation\n\n  - Fix config_path casing in reactive-resume.json [@ScubyG](https://github.com/ScubyG) ([#12525](https://github.com/community-scripts/ProxmoxVE/pull/12525))\n\n### 🌐 Website\n\n  - #### 🐞 Bug Fixes\n\n    - Revert #11534 PR that messed up search [@BramSuurdje](https://github.com/BramSuurdje) ([#12492](https://github.com/community-scripts/ProxmoxVE/pull/12492))\n\n## 2026-03-02\n\n### 🆕 New Scripts\n\n  - PowerDNS ([#12481](https://github.com/community-scripts/ProxmoxVE/pull/12481))\n- Profilarr ([#12441](https://github.com/community-scripts/ProxmoxVE/pull/12441))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Tracearr: prepare for imminent v1.4.19 release [@durzo](https://github.com/durzo) ([#12413](https://github.com/community-scripts/ProxmoxVE/pull/12413))\n\n  - #### ✨ New Features\n\n    - Frigate: Bump to v0.17 [@MickLesk](https://github.com/MickLesk) ([#12474](https://github.com/community-scripts/ProxmoxVE/pull/12474))\n\n  - #### 💥 Breaking Changes\n\n    - Migrate: DokPloy, Komodo, Coolify, Dockge, Runtipi to Addons [@MickLesk](https://github.com/MickLesk) ([#12275](https://github.com/community-scripts/ProxmoxVE/pull/12275))\n\n  - #### 🔧 Refactor\n\n    - ref: replace generic exit 1 with specific exit codes in ct & install [@MickLesk](https://github.com/MickLesk) ([#12475](https://github.com/community-scripts/ProxmoxVE/pull/12475))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func: Improve stability with retry logic, caching, and debug mode [@MickLesk](https://github.com/MickLesk) ([#10351](https://github.com/community-scripts/ProxmoxVE/pull/10351))\n\n  - #### 🔧 Refactor\n\n    - core: standardize exit codes and add mappings [@MickLesk](https://github.com/MickLesk) ([#12467](https://github.com/community-scripts/ProxmoxVE/pull/12467))\n\n### 🌐 Website\n\n  - frontend: improve detail view badges, addon texts, and HTML title [@MickLesk](https://github.com/MickLesk) ([#12461](https://github.com/community-scripts/ProxmoxVE/pull/12461))\n\n## 2026-03-01\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Sparkyfitness: use pnpm [@tomfrenzel](https://github.com/tomfrenzel) ([#12445](https://github.com/community-scripts/ProxmoxVE/pull/12445))\n    - OpenArchiver: Fix installation [@tremor021](https://github.com/tremor021) ([#12447](https://github.com/community-scripts/ProxmoxVE/pull/12447))\n"
  },
  {
    "path": ".github/changelogs/2026/04.md",
    "content": "## 2026-04-30\n\n### 🆕 New Scripts\n\n  - Nagios ([#14126](https://github.com/community-scripts/ProxmoxVE/pull/14126))\n- Neko ([#14121](https://github.com/community-scripts/ProxmoxVE/pull/14121))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - alpine-docker: install openssl as core dependency | alpine-komodo: check & install openssl if missing [@MickLesk](https://github.com/MickLesk) ([#14134](https://github.com/community-scripts/ProxmoxVE/pull/14134))\n    - endurain: update source references to Codeberg [@MickLesk](https://github.com/MickLesk) ([#14128](https://github.com/community-scripts/ProxmoxVE/pull/14128))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - tools.func: Manage minor versions for MongoDB 8.x [@tremor021](https://github.com/tremor021) ([#14131](https://github.com/community-scripts/ProxmoxVE/pull/14131))\n\n## 2026-04-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GrayLog: MongoDB update to 8.2.x [@tremor021](https://github.com/tremor021) ([#14114](https://github.com/community-scripts/ProxmoxVE/pull/14114))\n    - Graylog: Better information in the log file [@tremor021](https://github.com/tremor021) ([#14110](https://github.com/community-scripts/ProxmoxVE/pull/14110))\n\n  - #### 🔧 Refactor\n\n    - Refactor: checkMK [@MickLesk](https://github.com/MickLesk) ([#14105](https://github.com/community-scripts/ProxmoxVE/pull/14105))\n    - PatchMon: Unpin release [@tremor021](https://github.com/tremor021) ([#14097](https://github.com/community-scripts/ProxmoxVE/pull/14097))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: add guidance when storage lacks rootdir support [@MickLesk](https://github.com/MickLesk) ([#14108](https://github.com/community-scripts/ProxmoxVE/pull/14108))\n\n## 2026-04-28\n\n### 🆕 New Scripts\n\n  - StoryBook ([#14081](https://github.com/community-scripts/ProxmoxVE/pull/14081))\n- CoreDNS ([#14082](https://github.com/community-scripts/ProxmoxVE/pull/14082))\n\n### 🚀 Updated Scripts\n\n  - Fix Dawarich Install/Update [@Jerry1098](https://github.com/Jerry1098) ([#14078](https://github.com/community-scripts/ProxmoxVE/pull/14078))\n\n  - #### ✨ New Features\n\n    - PatchMon Version 2.0.2 Script update [@9technologygroup](https://github.com/9technologygroup) ([#14095](https://github.com/community-scripts/ProxmoxVE/pull/14095))\n\n## 2026-04-27\n\n### 🚀 Updated Scripts\n\n  - Add pamUsername column to userOrgs table [@JVKeller](https://github.com/JVKeller) ([#14075](https://github.com/community-scripts/ProxmoxVE/pull/14075))\n\n  - #### 🐞 Bug Fixes\n\n    - Dawarich: run db:migrate before assets:precompile [@MickLesk](https://github.com/MickLesk) ([#14051](https://github.com/community-scripts/ProxmoxVE/pull/14051))\n    - TechnitiumDNS: always install .NET 10 if not already present [@MickLesk](https://github.com/MickLesk) ([#14049](https://github.com/community-scripts/ProxmoxVE/pull/14049))\n\n  - #### 💥 Breaking Changes\n\n    - PatchMon: v2.0.0 migration [@vhsdream](https://github.com/vhsdream) ([#14015](https://github.com/community-scripts/ProxmoxVE/pull/14015))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - Update build.func - fixed spelling mistake [@m1ckywill](https://github.com/m1ckywill) ([#14047](https://github.com/community-scripts/ProxmoxVE/pull/14047))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - update-lxcs/apps: avoid pct exec on containers mid-shutdown [@MickLesk](https://github.com/MickLesk) ([#14050](https://github.com/community-scripts/ProxmoxVE/pull/14050))\n\n  - #### ✨ New Features\n\n    - Add patchmon-agent report execution in update script [@heinemannj](https://github.com/heinemannj) ([#14054](https://github.com/community-scripts/ProxmoxVE/pull/14054))\n\n## 2026-04-26\n\n### 🆕 New Scripts\n\n  - TREK ([#14017](https://github.com/community-scripts/ProxmoxVE/pull/14017))\n\n### 🚀 Updated Scripts\n\n  - fix(2fauth): handle stale backup directory on update [@omertahaoztop](https://github.com/omertahaoztop) ([#14018](https://github.com/community-scripts/ProxmoxVE/pull/14018))\n\n  - #### 🐞 Bug Fixes\n\n    -  Increase Frigate default CPU cores from 4 to 8 [@MickLesk](https://github.com/MickLesk) ([#14039](https://github.com/community-scripts/ProxmoxVE/pull/14039))\n    - Technitium DNS: Ensure directories exist before running service [@tremor021](https://github.com/tremor021) ([#14030](https://github.com/community-scripts/ProxmoxVE/pull/14030))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: Correct deb822 repository flat path detection [@MickLesk](https://github.com/MickLesk) ([#14037](https://github.com/community-scripts/ProxmoxVE/pull/14037))\n\n## 2026-04-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - VictoriaMetrics: Stop vmagent/vmalert before update [@irishpadres](https://github.com/irishpadres) ([#14016](https://github.com/community-scripts/ProxmoxVE/pull/14016))\n    - Domain-Monitor: start apache2 after stop instead of reload [@omertahaoztop](https://github.com/omertahaoztop) ([#14019](https://github.com/community-scripts/ProxmoxVE/pull/14019))\n    - Transmute: Fix ffmpeg detection [@tremor021](https://github.com/tremor021) ([#14008](https://github.com/community-scripts/ProxmoxVE/pull/14008))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Technitium DNS [@tremor021](https://github.com/tremor021) ([#14013](https://github.com/community-scripts/ProxmoxVE/pull/14013))\n\n## 2026-04-24\n\n### 🆕 New Scripts\n\n  - Apprise-API ([#13934](https://github.com/community-scripts/ProxmoxVE/pull/13934))\n- fireshare ([#13995](https://github.com/community-scripts/ProxmoxVE/pull/13995))\n- Transmute ([#13935](https://github.com/community-scripts/ProxmoxVE/pull/13935))\n- Jitsi-Meet ([#13897](https://github.com/community-scripts/ProxmoxVE/pull/13897))\n\n### 🚀 Updated Scripts\n\n  - Update wger.sh [@Soppster1029](https://github.com/Soppster1029) ([#13977](https://github.com/community-scripts/ProxmoxVE/pull/13977))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Ghostfolio [@MickLesk](https://github.com/MickLesk) ([#13990](https://github.com/community-scripts/ProxmoxVE/pull/13990))\n\n## 2026-04-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - mealie: start.sh missing after failed update [@MickLesk](https://github.com/MickLesk) ([#13958](https://github.com/community-scripts/ProxmoxVE/pull/13958))\n    - twingate-connector: perform real apt upgrade during update flow [@MickLesk](https://github.com/MickLesk) ([#13959](https://github.com/community-scripts/ProxmoxVE/pull/13959))\n\n  - #### ✨ New Features\n\n    - core: auto-size NODE_OPTIONS heap [@MickLesk](https://github.com/MickLesk) ([#13960](https://github.com/community-scripts/ProxmoxVE/pull/13960))\n\n  - #### 🔧 Refactor\n\n    - Update scripts to match standard [@tremor021](https://github.com/tremor021) ([#13956](https://github.com/community-scripts/ProxmoxVE/pull/13956))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: upgrade Node.js minor/patch on same major version [@MickLesk](https://github.com/MickLesk) ([#13957](https://github.com/community-scripts/ProxmoxVE/pull/13957))\n    - core: hotfix - prefer silent mode on PHS env conflict [@MickLesk](https://github.com/MickLesk) ([#13951](https://github.com/community-scripts/ProxmoxVE/pull/13951))\n\n  - #### 🔧 Refactor\n\n    - core: improve system update information / lxc stack upgrade [@MickLesk](https://github.com/MickLesk) ([#13970](https://github.com/community-scripts/ProxmoxVE/pull/13970))\n\n## 2026-04-22\n\n### 🆕 New Scripts\n\n  - Dashy ([#13817](https://github.com/community-scripts/ProxmoxVE/pull/13817))\n- Mini-QR ([#13902](https://github.com/community-scripts/ProxmoxVE/pull/13902))\n- ownfoil ([#13904](https://github.com/community-scripts/ProxmoxVE/pull/13904))\n- ERPNext ([#13921](https://github.com/community-scripts/ProxmoxVE/pull/13921))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - add --clear to uv venv in update_script() to prevent interactive prompt [@MickLesk](https://github.com/MickLesk) ([#13926](https://github.com/community-scripts/ProxmoxVE/pull/13926))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: Add PHS_VERBOSE env var to skip verbose mode prompts [@gormanity](https://github.com/gormanity) ([#13797](https://github.com/community-scripts/ProxmoxVE/pull/13797))\n\n## 2026-04-21\n\n### 🆕 New Scripts\n\n  - gogs ([#13896](https://github.com/community-scripts/ProxmoxVE/pull/13896))\n- anchor ([#13895](https://github.com/community-scripts/ProxmoxVE/pull/13895))\n- minthcm ([#13903](https://github.com/community-scripts/ProxmoxVE/pull/13903))\n- foldergram ([#13900](https://github.com/community-scripts/ProxmoxVE/pull/13900))\n\n### 🚀 Updated Scripts\n\n  - OpenCloud: Pin version to 6.1.0 [@vhsdream](https://github.com/vhsdream) ([#13890](https://github.com/community-scripts/ProxmoxVE/pull/13890))\n\n  - #### 🐞 Bug Fixes\n\n    - Domain-Locker: Update dependencies [@tremor021](https://github.com/tremor021) ([#13901](https://github.com/community-scripts/ProxmoxVE/pull/13901))\n    - homelable: fix install failure by correcting password-reset chmod target [@Copilot](https://github.com/Copilot) ([#13894](https://github.com/community-scripts/ProxmoxVE/pull/13894))\n\n  - #### ✨ New Features\n\n    - FileFlows: Update dependencies [@tremor021](https://github.com/tremor021) ([#13917](https://github.com/community-scripts/ProxmoxVE/pull/13917))\n\n## 2026-04-20\n\n### 🆕 New Scripts\n\n  - WhoDB ([#13880](https://github.com/community-scripts/ProxmoxVE/pull/13880))\n\n### 🚀 Updated Scripts\n\n  - pangolin: create migration tables before data transfer to prevent role loss [@MickLesk](https://github.com/MickLesk) ([#13874](https://github.com/community-scripts/ProxmoxVE/pull/13874))\n\n  - #### 🐞 Bug Fixes\n\n    - Pangolin: pre-apply schema migrations to prevent data loss [@MickLesk](https://github.com/MickLesk) ([#13861](https://github.com/community-scripts/ProxmoxVE/pull/13861))\n    - ActualBudget: change migration messages to warnings [@MickLesk](https://github.com/MickLesk) ([#13860](https://github.com/community-scripts/ProxmoxVE/pull/13860))\n    - slskd: migrate config keys for 0.25.0 breaking change [@MickLesk](https://github.com/MickLesk) ([#13862](https://github.com/community-scripts/ProxmoxVE/pull/13862))\n\n  - #### ✨ New Features\n\n    - Wanderer: add pocketbase CLI wrapper with env [@MickLesk](https://github.com/MickLesk) ([#13863](https://github.com/community-scripts/ProxmoxVE/pull/13863))\n    - feat(homelable): add password reset utility script [@davidsoncabista](https://github.com/davidsoncabista) ([#13798](https://github.com/community-scripts/ProxmoxVE/pull/13798))\n\n  - #### 🔧 Refactor\n\n    - Several Scripts: Bump NodeJS to align Node.js versions with upstream for 5 scripts [@MickLesk](https://github.com/MickLesk) ([#13875](https://github.com/community-scripts/ProxmoxVE/pull/13875))\n    - Refactor: PMG Post Install [@MickLesk](https://github.com/MickLesk) ([#13693](https://github.com/community-scripts/ProxmoxVE/pull/13693))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: detect Perl breakage after LXC stack upgrade and improve storage validation [@MickLesk](https://github.com/MickLesk) ([#13879](https://github.com/community-scripts/ProxmoxVE/pull/13879))\n\n## 2026-04-19\n\n### 🆕 New Scripts\n\n  - nametag ([#13849](https://github.com/community-scripts/ProxmoxVE/pull/13849))\n\n## 2026-04-18\n\n### 🆕 New Scripts\n\n  - Dagu ([#13830](https://github.com/community-scripts/ProxmoxVE/pull/13830))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - BabyBuddy: set DJANGO_SETTINGS_MODULE before migrate in update [@MickLesk](https://github.com/MickLesk) ([#13836](https://github.com/community-scripts/ProxmoxVE/pull/13836))\n    - litellm: add prisma generate and use venv binary directly [@MickLesk](https://github.com/MickLesk) ([#13835](https://github.com/community-scripts/ProxmoxVE/pull/13835))\n    - yamtrack: add missing nginx.conf sed edits to update script [@MickLesk](https://github.com/MickLesk) ([#13834](https://github.com/community-scripts/ProxmoxVE/pull/13834))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - SparkyFitness Garmin Microservice: fix update function [@tomfrenzel](https://github.com/tomfrenzel) ([#13824](https://github.com/community-scripts/ProxmoxVE/pull/13824))\n\n  - #### 🔧 Refactor\n\n    - Clean-Orphan-LVM: check all cluster nodes for VM/CT configs [@MickLesk](https://github.com/MickLesk) ([#13837](https://github.com/community-scripts/ProxmoxVE/pull/13837))\n\n## 2026-04-17\n\n### 🆕 New Scripts\n\n  - step-ca ([#13775](https://github.com/community-scripts/ProxmoxVE/pull/13775))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - core: pin IGC version to compute-runtime compatible tag (Intel GPU) [@MickLesk](https://github.com/MickLesk) ([#13814](https://github.com/community-scripts/ProxmoxVE/pull/13814))\n    - Fix for bambuddy community script update [@abbasegbeyemi](https://github.com/abbasegbeyemi) ([#13816](https://github.com/community-scripts/ProxmoxVE/pull/13816))\n    - Umami: Fix update procedure [@tremor021](https://github.com/tremor021) ([#13807](https://github.com/community-scripts/ProxmoxVE/pull/13807))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: sanitize mount_fs input — strip spaces and trailing commas [@MickLesk](https://github.com/MickLesk) ([#13806](https://github.com/community-scripts/ProxmoxVE/pull/13806))\n\n  - #### 🔧 Refactor\n\n    - core: fix some pct create issues (telemetry) + cleanup [@MickLesk](https://github.com/MickLesk) ([#13810](https://github.com/community-scripts/ProxmoxVE/pull/13810))\n\n## 2026-04-16\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Add pnpm as a dependency to ghost-cli install [@YourFavoriteKyle](https://github.com/YourFavoriteKyle) ([#13789](https://github.com/community-scripts/ProxmoxVE/pull/13789))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: wire ENABLE_MKNOD and ALLOW_MOUNT_FS into LXC features [@MickLesk](https://github.com/MickLesk) ([#13796](https://github.com/community-scripts/ProxmoxVE/pull/13796))\n\n## 2026-04-15\n\n### 🆕 New Scripts\n\n  - iGotify ([#13773](https://github.com/community-scripts/ProxmoxVE/pull/13773))\n- GitHub-Runner ([#13709](https://github.com/community-scripts/ProxmoxVE/pull/13709))\n- Revert \"Remove low-install-count CT scripts and installers (#13570)\" [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13752](https://github.com/community-scripts/ProxmoxVE/pull/13752))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [alpine-nextcloud] Update Nginx MIME types to support .mjs files [@GuiltyFox](https://github.com/GuiltyFox) ([#13771](https://github.com/community-scripts/ProxmoxVE/pull/13771))\n    - Domain Monitor: Fix file ownership after update [@tremor021](https://github.com/tremor021) ([#13759](https://github.com/community-scripts/ProxmoxVE/pull/13759))\n\n  - #### 💥 Breaking Changes\n\n    - Reitti: refactor scripts for v4 - remove RabbitMQ and Photon [@MickLesk](https://github.com/MickLesk) ([#13728](https://github.com/community-scripts/ProxmoxVE/pull/13728))\n\n  - #### 🔧 Refactor\n\n    - Semaphore: add BoltDB to SQLite migration [@tremor021](https://github.com/tremor021) ([#13779](https://github.com/community-scripts/ProxmoxVE/pull/13779))\n\n### 📚 Documentation\n\n  - cleanup: remove docs/, update README & CONTRIBUTING, fix repo config [@MickLesk](https://github.com/MickLesk) ([#13770](https://github.com/community-scripts/ProxmoxVE/pull/13770))\n\n## 2026-04-14\n\n### 🚀 Updated Scripts\n\n  - Immich: Pin photo-processing library revisions [@vhsdream](https://github.com/vhsdream) ([#13748](https://github.com/community-scripts/ProxmoxVE/pull/13748))\n\n  - #### 🐞 Bug Fixes\n\n    - BentoPDF: Nginx fixes [@tremor021](https://github.com/tremor021) ([#13741](https://github.com/community-scripts/ProxmoxVE/pull/13741))\n    - Zerobyte: add git to dependencies to fix bun install failure [@Copilot](https://github.com/Copilot) ([#13721](https://github.com/community-scripts/ProxmoxVE/pull/13721))\n    - alpine-nextcloud-install: do not use deprecated nginx config [@AlexanderStein](https://github.com/AlexanderStein) ([#13726](https://github.com/community-scripts/ProxmoxVE/pull/13726))\n\n  - #### ✨ New Features\n\n    - Mealie: support v3.15+ Nuxt 4 migration [@MickLesk](https://github.com/MickLesk) ([#13731](https://github.com/community-scripts/ProxmoxVE/pull/13731))\n\n  - #### 🔧 Refactor\n\n    - Lyrion: correct service name and version file in update script [@MickLesk](https://github.com/MickLesk) ([#13734](https://github.com/community-scripts/ProxmoxVE/pull/13734))\n    - Changedetection: move env vars from service file to .env [@tremor021](https://github.com/tremor021) ([#13732](https://github.com/community-scripts/ProxmoxVE/pull/13732))\n\n## 2026-04-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Slskd: Remove stale Soularr lock file on startup and redirect logs to stderr [@MickLesk](https://github.com/MickLesk) ([#13669](https://github.com/community-scripts/ProxmoxVE/pull/13669))\n    - Bambuddy: preserve database and archive on update [@Copilot](https://github.com/Copilot) ([#13706](https://github.com/community-scripts/ProxmoxVE/pull/13706))\n\n  - #### ✨ New Features\n\n    - Immich: Pin version to 2.7.5 [@vhsdream](https://github.com/vhsdream) ([#13715](https://github.com/community-scripts/ProxmoxVE/pull/13715))\n    - Bytestash: auto backup/restore data on update [@MickLesk](https://github.com/MickLesk) ([#13707](https://github.com/community-scripts/ProxmoxVE/pull/13707))\n    - OpenCloud: pin version to 6.0.0 [@vhsdream](https://github.com/vhsdream) ([#13691](https://github.com/community-scripts/ProxmoxVE/pull/13691))\n\n  - #### 💥 Breaking Changes\n\n    - Mealie: pin version to v3.14.0 in install and update scripts [@Copilot](https://github.com/Copilot) ([#13724](https://github.com/community-scripts/ProxmoxVE/pull/13724))\n\n  - #### 🔧 Refactor\n\n    - core: remove unused TEMP_DIR mktemp leak in build_container / clean sonarqube [@MickLesk](https://github.com/MickLesk) ([#13708](https://github.com/community-scripts/ProxmoxVE/pull/13708))\n\n## 2026-04-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-Wakapi: Remove container checks in update_script function [@MickLesk](https://github.com/MickLesk) ([#13694](https://github.com/community-scripts/ProxmoxVE/pull/13694))\n\n  - #### 🔧 Refactor\n\n    - IronClaw: Install keychain dependencies and launch in a DBus session [@MickLesk](https://github.com/MickLesk) ([#13692](https://github.com/community-scripts/ProxmoxVE/pull/13692))\n    - MeTube: Allow pnpm build scripts to fix ERR_PNPM_IGNORED_BUILDS [@MickLesk](https://github.com/MickLesk) ([#13668](https://github.com/community-scripts/ProxmoxVE/pull/13668))\n\n## 2026-04-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Ensure newline before appending IMMICH_HELMET_FILE to .env [@MickLesk](https://github.com/MickLesk) ([#13667](https://github.com/community-scripts/ProxmoxVE/pull/13667))\n\n  - #### ✨ New Features\n\n    - BentoPDF: replace http-server with nginx to fix WASM initialization timeout [@MickLesk](https://github.com/MickLesk) ([#13625](https://github.com/community-scripts/ProxmoxVE/pull/13625))\n    - Element Synapse: Add MatrixRTC configuration for Element Call support [@MickLesk](https://github.com/MickLesk) ([#13665](https://github.com/community-scripts/ProxmoxVE/pull/13665))\n    - RomM: Use ROMM_BASE_PATH from .env for symlinks and nginx config [@MickLesk](https://github.com/MickLesk) ([#13666](https://github.com/community-scripts/ProxmoxVE/pull/13666))\n    - Immich: Pin version to 2.7.4 [@vhsdream](https://github.com/vhsdream) ([#13661](https://github.com/community-scripts/ProxmoxVE/pull/13661))\n\n  - #### 🔧 Refactor\n\n    - Crafty Controller: Wait for credentials file instead of fixed sleep [@MickLesk](https://github.com/MickLesk) ([#13670](https://github.com/community-scripts/ProxmoxVE/pull/13670))\n    - Refactor: Alpine-Wakapi [@tremor021](https://github.com/tremor021) ([#13656](https://github.com/community-scripts/ProxmoxVE/pull/13656))\n\n## 2026-04-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: ensure trailing newline in redis.conf before appending bind directive [@Copilot](https://github.com/Copilot) ([#13647](https://github.com/community-scripts/ProxmoxVE/pull/13647))\n\n  - #### ✨ New Features\n\n    - Immich: Pin version to 2.7.3 [@vhsdream](https://github.com/vhsdream) ([#13631](https://github.com/community-scripts/ProxmoxVE/pull/13631))\n    - Homarr: bind Redis to localhost only [@MickLesk](https://github.com/MickLesk) ([#13552](https://github.com/community-scripts/ProxmoxVE/pull/13552))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: prevent script crash when entering GitHub token after rate limit [@MickLesk](https://github.com/MickLesk) ([#13638](https://github.com/community-scripts/ProxmoxVE/pull/13638))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - addons: Filebrowser & Filebrowser-Quantum get warning if host install [@MickLesk](https://github.com/MickLesk) ([#13639](https://github.com/community-scripts/ProxmoxVE/pull/13639))\n\n## 2026-04-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - boostack: add: git [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13620](https://github.com/community-scripts/ProxmoxVE/pull/13620))\n\n  - #### ✨ New Features\n\n    - Update OPNsense version from 25.7 to 26.1 [@tdn131](https://github.com/tdn131) ([#13626](https://github.com/community-scripts/ProxmoxVE/pull/13626))\n    - CheckMK: Bump Default OS to 13 (trixie) + dynamic codename + fix RELEASE-Tag Fetching [@MickLesk](https://github.com/MickLesk) ([#13610](https://github.com/community-scripts/ProxmoxVE/pull/13610))\n\n## 2026-04-08\n\n### 🆕 New Scripts\n\n  - IronClaw | Alpine-IronClaw ([#13591](https://github.com/community-scripts/ProxmoxVE/pull/13591))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - immich: disable upgrade-insecure-requests CSP directive [@MickLesk](https://github.com/MickLesk) ([#13600](https://github.com/community-scripts/ProxmoxVE/pull/13600))\n    - Immich: v2.7.2 [@vhsdream](https://github.com/vhsdream) ([#13579](https://github.com/community-scripts/ProxmoxVE/pull/13579))\n    - Update flaresolverr-install.sh [@maztheman](https://github.com/maztheman) ([#13584](https://github.com/community-scripts/ProxmoxVE/pull/13584))\n\n  - #### ✨ New Features\n\n    - bambuddy: add mkdir before data restore & add ffmpeg dependency [@MickLesk](https://github.com/MickLesk) ([#13601](https://github.com/community-scripts/ProxmoxVE/pull/13601))\n\n  - #### 🔧 Refactor\n\n    - feat: update UHF Server script to use setup_ffmpeg [@zackwithak13](https://github.com/zackwithak13) ([#13564](https://github.com/community-scripts/ProxmoxVE/pull/13564))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: add script page badges to descriptions | change donate URL [@MickLesk](https://github.com/MickLesk) ([#13596](https://github.com/community-scripts/ProxmoxVE/pull/13596))\n\n## 2026-04-07\n\n### 🗑️ Deleted Scripts\n\n  - Remove low-install-count CT scripts and installers [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#13570](https://github.com/community-scripts/ProxmoxVE/pull/13570))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: improve resilience for top Proxmox error codes (209, 215, 118, 206) [@MickLesk](https://github.com/MickLesk) ([#13575](https://github.com/community-scripts/ProxmoxVE/pull/13575))\n\n## 2026-04-06\n\n### 🆕 New Scripts\n\n  - OpenThread Border Router ([#13536](https://github.com/community-scripts/ProxmoxVE/pull/13536))\n- Homelable ([#13539](https://github.com/community-scripts/ProxmoxVE/pull/13539))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Papra: check env before copy [@MickLesk](https://github.com/MickLesk) ([#13553](https://github.com/community-scripts/ProxmoxVE/pull/13553))\n    - changedetection: fix: typing_extensions error [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13548](https://github.com/community-scripts/ProxmoxVE/pull/13548))\n    - kasm: fix: fetch latest version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13547](https://github.com/community-scripts/ProxmoxVE/pull/13547))\n\n## 2026-04-05\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Grist: remove install:ee step (private repo, not needed for grist-core) [@MickLesk](https://github.com/MickLesk) ([#13526](https://github.com/community-scripts/ProxmoxVE/pull/13526))\n    - Nginx Proxy Manager: ensure /tmp/nginx/body exists via openresty service  [@MickLesk](https://github.com/MickLesk) ([#13528](https://github.com/community-scripts/ProxmoxVE/pull/13528))\n    - MotionEye: run as root to enable SMB share support [@MickLesk](https://github.com/MickLesk) ([#13527](https://github.com/community-scripts/ProxmoxVE/pull/13527))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: silent() function - use return instead of exit to allow || true error handling [@MickLesk](https://github.com/MickLesk) ([#13529](https://github.com/community-scripts/ProxmoxVE/pull/13529))\n\n## 2026-04-04\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - komodo: set `PERIPHERY_CORE_PUBLIC_KEYS` to default value if absent [@4ndv](https://github.com/4ndv) ([#13519](https://github.com/community-scripts/ProxmoxVE/pull/13519))\n\n## 2026-04-03\n\n### 🆕 New Scripts\n\n  - netboot.xyz ([#13480](https://github.com/community-scripts/ProxmoxVE/pull/13480))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - OpenWRT-VM: use poweroff instead of halt to properly stop VM [@MickLesk](https://github.com/MickLesk) ([#13504](https://github.com/community-scripts/ProxmoxVE/pull/13504))\n    - NginxProxyManager: fix openresty restart by setting user root before reload [@MickLesk](https://github.com/MickLesk) ([#13500](https://github.com/community-scripts/ProxmoxVE/pull/13500))\n\n  - #### ✨ New Features\n\n    - Crafty Controller: add Java 25 for Minecraft 1.26.1+ [@MickLesk](https://github.com/MickLesk) ([#13502](https://github.com/community-scripts/ProxmoxVE/pull/13502))\n    - Wealthfolio: update to v3.2.1 and Node.js 24 [@afadil](https://github.com/afadil) ([#13486](https://github.com/community-scripts/ProxmoxVE/pull/13486))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core.func: prevent profile.d scripts from aborting on non-zero exit [@MickLesk](https://github.com/MickLesk) ([#13503](https://github.com/community-scripts/ProxmoxVE/pull/13503))\n\n  - #### ✨ New Features\n\n    - APT Proxy: Support full URLs (http/https with custom ports) [@MickLesk](https://github.com/MickLesk) ([#13474](https://github.com/community-scripts/ProxmoxVE/pull/13474))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - PVE LXC-Updater: pipe apt list through cat to prevent pager hang [@MickLesk](https://github.com/MickLesk) ([#13501](https://github.com/community-scripts/ProxmoxVE/pull/13501))\n\n## 2026-04-02\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Grist: Guard backup restore for empty docs/db files [@MickLesk](https://github.com/MickLesk) ([#13472](https://github.com/community-scripts/ProxmoxVE/pull/13472))\n    - fix(zigbee2mqtt): suppress grep error when pnpm-workspace.yaml is absent on update [@Copilot](https://github.com/Copilot) ([#13476](https://github.com/community-scripts/ProxmoxVE/pull/13476))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - Cron LXC Updater: Add full PATH for cron environment [@MickLesk](https://github.com/MickLesk) ([#13473](https://github.com/community-scripts/ProxmoxVE/pull/13473))\n\n## 2026-04-01\n\n### 🆕 New Scripts\n\n  - DrawDB ([#13454](https://github.com/community-scripts/ProxmoxVE/pull/13454))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - Filebrowser: make noauth setup use correct database [@MickLesk](https://github.com/MickLesk) ([#13457](https://github.com/community-scripts/ProxmoxVE/pull/13457))\n"
  },
  {
    "path": ".github/changelogs/2026/05.md",
    "content": "## 2026-05-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - FlowiseAI: Migrate to pnpm [@MickLesk](https://github.com/MickLesk) ([#14344](https://github.com/community-scripts/ProxmoxVE/pull/14344))\n    - Purge openresty [@lucacome](https://github.com/lucacome) ([#14353](https://github.com/community-scripts/ProxmoxVE/pull/14353))\n    - Check for release for Sonarr [@lucacome](https://github.com/lucacome) ([#14354](https://github.com/community-scripts/ProxmoxVE/pull/14354))\n    - fix(termix-install.sh): add tmpfiles.d persistence and systemd PIDFile path [@runnylogan](https://github.com/runnylogan) ([#14350](https://github.com/community-scripts/ProxmoxVE/pull/14350))\n    - ERPNext: start bench Redis services before bench new-site [@MickLesk](https://github.com/MickLesk) ([#14343](https://github.com/community-scripts/ProxmoxVE/pull/14343))\n    - [Hotfix]Jotty: use absolute path when creating data dir [@vhsdream](https://github.com/vhsdream) ([#14355](https://github.com/community-scripts/ProxmoxVE/pull/14355))\n\n## 2026-05-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - wishlist: pin pnpm to v10 to match engine requirements [@MickLesk](https://github.com/MickLesk) ([#14342](https://github.com/community-scripts/ProxmoxVE/pull/14342))\n    - [pelican] fix env copy regression [@LetterN](https://github.com/LetterN) ([#14328](https://github.com/community-scripts/ProxmoxVE/pull/14328))\n    - fix(homepage): fix ERR_PNPM_IGNORED_BUILDS error [@Sergih28](https://github.com/Sergih28) ([#14315](https://github.com/community-scripts/ProxmoxVE/pull/14315))\n\n  - #### ✨ New Features\n\n    - tools.func: add setup_nltk as new function [@MickLesk](https://github.com/MickLesk) ([#14314](https://github.com/community-scripts/ProxmoxVE/pull/14314))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: fix meilisearch import-dump background process handling [@MickLesk](https://github.com/MickLesk) ([#14341](https://github.com/community-scripts/ProxmoxVE/pull/14341))\n\n## 2026-05-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - termix: create /tmp/nginx before nginx -t [@MickLesk](https://github.com/MickLesk) ([#14312](https://github.com/community-scripts/ProxmoxVE/pull/14312))\n    - The Lounge: Fix service not starting automaticaly [@tremor021](https://github.com/tremor021) ([#14311](https://github.com/community-scripts/ProxmoxVE/pull/14311))\n    - netbird-lxc: fix installation check [@MickLesk](https://github.com/MickLesk) ([#14309](https://github.com/community-scripts/ProxmoxVE/pull/14309))\n    - databasus: Backup and secure configuration file [@MickLesk](https://github.com/MickLesk) ([#14308](https://github.com/community-scripts/ProxmoxVE/pull/14308))\n    - vm: update disk image URL for Ubuntu 25.04 [@MickLesk](https://github.com/MickLesk) ([#14290](https://github.com/community-scripts/ProxmoxVE/pull/14290))\n\n  - #### ✨ New Features\n\n    - pangolin: bump version to 1.18.3 [@MickLesk](https://github.com/MickLesk) ([#14297](https://github.com/community-scripts/ProxmoxVE/pull/14297))\n\n### 🗑️ Deleted Scripts\n\n  - Remove: LiteLLM [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14294](https://github.com/community-scripts/ProxmoxVE/pull/14294))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - update-apps: some  improvements [@MickLesk](https://github.com/MickLesk) ([#14275](https://github.com/community-scripts/ProxmoxVE/pull/14275))\n\n## 2026-05-06\n\n### 🆕 New Scripts\n\n  - Hoodik ([#14279](https://github.com/community-scripts/ProxmoxVE/pull/14279))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pelican-Panel: create backup subdirectory before copying storage [@MickLesk](https://github.com/MickLesk) ([#14274](https://github.com/community-scripts/ProxmoxVE/pull/14274))\n    - Rustdeskserver: remove redundant else with undefined RELEASE var [@MickLesk](https://github.com/MickLesk) ([#14272](https://github.com/community-scripts/ProxmoxVE/pull/14272))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - AdguardHome-Sync replace ifconfig with hostname -I for IP detection [@MickLesk](https://github.com/MickLesk) ([#14273](https://github.com/community-scripts/ProxmoxVE/pull/14273))\n\n## 2026-05-05\n\n### 🆕 New Scripts\n\n  - LibreChat ([#14247](https://github.com/community-scripts/ProxmoxVE/pull/14247))\n- Matomo ([#14248](https://github.com/community-scripts/ProxmoxVE/pull/14248))\n- Storyteller ([#14122](https://github.com/community-scripts/ProxmoxVE/pull/14122))\n\n### 🧰 Tools\n\n  - Fix container count message in update-apps.sh [@Quotacious](https://github.com/Quotacious) ([#14265](https://github.com/community-scripts/ProxmoxVE/pull/14265))\n\n## 2026-05-04\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Databasus: move .env to filesystem root so service starts correctly [@Copilot](https://github.com/Copilot) ([#14252](https://github.com/community-scripts/ProxmoxVE/pull/14252))\n    - Databasus: update mongo-tools fallback to 100.16.1 and use now pnpm instead of npm ci [@MickLesk](https://github.com/MickLesk) ([#14240](https://github.com/community-scripts/ProxmoxVE/pull/14240))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func get_latest_gh_tag - add pagination to find prefixed tags beyond first 50 [@MickLesk](https://github.com/MickLesk) ([#14241](https://github.com/community-scripts/ProxmoxVE/pull/14241))\n    - tools.func: add GitLab release check/fetch/deploy helpers [@MickLesk](https://github.com/MickLesk) ([#14242](https://github.com/community-scripts/ProxmoxVE/pull/14242))\n\n## 2026-05-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Hortusfox: fix update issues [@tomfrenzel](https://github.com/tomfrenzel) ([#14214](https://github.com/community-scripts/ProxmoxVE/pull/14214))\n\n  - #### ✨ New Features\n\n    - Refactor: PeaNUT for v6 [@MickLesk](https://github.com/MickLesk) ([#14224](https://github.com/community-scripts/ProxmoxVE/pull/14224))\n    - pangolin: pin version, drop manual SQL, use upstream migrator [@MickLesk](https://github.com/MickLesk) ([#14223](https://github.com/community-scripts/ProxmoxVE/pull/14223))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: fix validate_bridge function [@MichaelOultram](https://github.com/MichaelOultram) ([#14206](https://github.com/community-scripts/ProxmoxVE/pull/14206))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - pve/pbs scripts: guard sed against missing /etc/apt/sources.list [@MickLesk](https://github.com/MickLesk) ([#14222](https://github.com/community-scripts/ProxmoxVE/pull/14222))\n\n## 2026-05-02\n\n### 🆕 New Scripts\n\n  - protonmail-bridge ([#14136](https://github.com/community-scripts/ProxmoxVE/pull/14136))\n- Tube Archivist ([#14123](https://github.com/community-scripts/ProxmoxVE/pull/14123))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Nagios: Ping fix [@tremor021](https://github.com/tremor021) ([#14186](https://github.com/community-scripts/ProxmoxVE/pull/14186))\n    - opnsense-vm: retry pvesm alloc on transient zfs 'got timeout' errors [@MickLesk](https://github.com/MickLesk) ([#14157](https://github.com/community-scripts/ProxmoxVE/pull/14157))\n    - ImmichFrame: fix update by reinstalling dotnet-sdk before publish [@MickLesk](https://github.com/MickLesk) ([#14158](https://github.com/community-scripts/ProxmoxVE/pull/14158))\n    - [FIX]ShelfMark: Use UV sync for shelfmark backend build; update to Python 3.14 [@vhsdream](https://github.com/vhsdream) ([#14170](https://github.com/community-scripts/ProxmoxVE/pull/14170))\n    - alpine: remove deb/ubuntu-only resource & storage checks from update-script [@MickLesk](https://github.com/MickLesk) ([#14166](https://github.com/community-scripts/ProxmoxVE/pull/14166))\n    - Threadfin: use 'threadfin-app' as app name to avoid version-file clash  [@MickLesk](https://github.com/MickLesk) ([#14159](https://github.com/community-scripts/ProxmoxVE/pull/14159))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: prompt to also run installed addon update scripts (…/bin/update_*) after update_script [@MickLesk](https://github.com/MickLesk) ([#14162](https://github.com/community-scripts/ProxmoxVE/pull/14162))\n\n## 2026-05-01\n\n### 🆕 New Scripts\n\n  - SoulSync ([#14124](https://github.com/community-scripts/ProxmoxVE/pull/14124))\n- Teable ([#14125](https://github.com/community-scripts/ProxmoxVE/pull/14125))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Step ca update [@heinemannj](https://github.com/heinemannj) ([#14058](https://github.com/community-scripts/ProxmoxVE/pull/14058))\n    - paperless-ngx: refresh NLTK data on update [@kurtislanderson](https://github.com/kurtislanderson) ([#14144](https://github.com/community-scripts/ProxmoxVE/pull/14144))\n    - [Pelican Panel] stop deleting the public storage [@LetterN](https://github.com/LetterN) ([#14145](https://github.com/community-scripts/ProxmoxVE/pull/14145))\n\n  - #### 🔧 Refactor\n\n    - Mail-Archiver: update dependencies [@tremor021](https://github.com/tremor021) ([#14152](https://github.com/community-scripts/ProxmoxVE/pull/14152))\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## ✍️ Description  \n<!-- Provide a clear and concise description of your changes. -->  \n\n## 🔗 Related PR / Discussion / Issue  \n\nLink: #\n\n## ✅ Prerequisites  \n\nBefore this PR can be reviewed, the following must be completed:  \n\n- [] **Self-review performed** – Code follows established patterns and conventions.  \n- [] **Testing performed** – Changes have been thoroughly tested and verified.  \n\n## 📋 Additional Information (optional)  \n<!-- Provide extra context, screenshots, or references if needed. -->  \n\n- [ ] **Self-review completed** – Code follows project standards.\n- [ ] **Tested thoroughly** – Changes work as expected.\n- [ ] **No security risks** – No hardcoded secrets, unnecessary privilege escalations, or permission issues.\n\n---\n\n## 🛠️ Type of Change (**X** in brackets)\n\n- [ ] 🐞 **Bug fix** – Resolves an issue without breaking functionality.\n- [ ] ✨ **New feature** – Adds new, non-breaking functionality.\n- [ ] 💥 **Breaking change** – Alters existing functionality in a way that may require updates.\n- [ ] 🆕 **New script** – A fully functional and tested script or script set.\n- [ ] 🌍 **Website update** – Changes to script metadata (PocketBase/website data).\n- [ ] 🔧 **Refactoring / Code Cleanup** – Improves readability or maintainability without changing functionality.\n- [ ] 📝 **Documentation update** – Changes to `README`, `AppName.md`, `CONTRIBUTING.md`, or other docs.\n"
  },
  {
    "path": ".github/workflows/delete-pocketbase-entry-on-removal.yml",
    "content": "name: Set state to is_deleted in pocketbase\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"json/**\"\n      - \"vm/**\"\n      - \"tools/**\"\n      - \"turnkey/**\"\n      - \"ct/**\"\n      - \"install/**\"\n\njobs:\n  delete-pocketbase-entry:\n    runs-on: self-hosted\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Get slugs from deleted JSON and script files\n        id: slugs\n        run: |\n          BEFORE=\"${{ github.event.before }}\"\n          AFTER=\"${{ github.event.after }}\"\n          slugs=\"\"\n\n          # Deleted JSON files: get slug from previous commit\n          deleted_json=$(git diff --name-only --diff-filter=D \"$BEFORE\" \"$AFTER\" -- json/ | grep '\\.json$' || true)\n          for f in $deleted_json; do\n            [[ -z \"$f\" ]] && continue\n            s=$(git show \"$BEFORE:$f\" 2>/dev/null | jq -r '.slug // empty' 2>/dev/null || true)\n            [[ -n \"$s\" ]] && slugs=\"$slugs $s\"\n          done\n\n          # Deleted script files: derive slug from path\n          deleted_sh=$(git diff --name-only --diff-filter=D \"$BEFORE\" \"$AFTER\" -- ct/ install/ tools/ turnkey/ vm/ | grep '\\.sh$' || true)\n          for f in $deleted_sh; do\n            [[ -z \"$f\" ]] && continue\n            base=\"${f##*/}\"\n            base=\"${base%.sh}\"\n            if [[ \"$f\" == install/* && \"$base\" == *-install ]]; then\n              s=\"${base%-install}\"\n            else\n              s=\"$base\"\n            fi\n            [[ -n \"$s\" ]] && slugs=\"$slugs $s\"\n          done\n\n          slugs=$(echo $slugs | xargs -n1 | sort -u | tr '\\n' ' ')\n          if [[ -z \"$slugs\" ]]; then\n            echo \"No deleted JSON or script files to mark as deleted in PocketBase.\"\n            echo \"count=0\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n          echo \"$slugs\" > slugs_to_delete.txt\n          echo \"count=$(echo $slugs | wc -w)\" >> \"$GITHUB_OUTPUT\"\n          echo \"Slugs to mark as deleted: $slugs\"\n\n      - name: Mark as deleted in PocketBase\n        if: steps.slugs.outputs.count != '0'\n        env:\n          POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}\n          POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}\n          POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}\n          POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}\n        run: |\n          node << 'ENDSCRIPT'\n          (async function() {\n            const fs = require('fs');\n            const https = require('https');\n            const http = require('http');\n            const url = require('url');\n\n            function request(fullUrl, opts, redirectCount) {\n              redirectCount = redirectCount || 0;\n              return new Promise(function(resolve, reject) {\n                const u = url.parse(fullUrl);\n                const isHttps = u.protocol === 'https:';\n                const body = opts.body;\n                const options = {\n                  hostname: u.hostname,\n                  port: u.port || (isHttps ? 443 : 80),\n                  path: u.path,\n                  method: opts.method || 'GET',\n                  headers: opts.headers || {}\n                };\n                if (body) options.headers['Content-Length'] = Buffer.byteLength(body);\n                const lib = isHttps ? https : http;\n                const req = lib.request(options, function(res) {\n                  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {\n                    if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));\n                    const redirectUrl = url.resolve(fullUrl, res.headers.location);\n                    res.resume();\n                    resolve(request(redirectUrl, opts, redirectCount + 1));\n                    return;\n                  }\n                  let data = '';\n                  res.on('data', function(chunk) { data += chunk; });\n                  res.on('end', function() {\n                    resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });\n                  });\n                });\n                req.on('error', reject);\n                if (body) req.write(body);\n                req.end();\n              });\n            }\n\n            const raw = process.env.POCKETBASE_URL.replace(/\\/$/, '');\n            const apiBase = /\\/api$/i.test(raw) ? raw : raw + '/api';\n            const coll = process.env.POCKETBASE_COLLECTION;\n            const slugs = fs.readFileSync('slugs_to_delete.txt', 'utf8').trim().split(/\\s+/).filter(Boolean);\n\n            const authUrl = apiBase + '/collections/users/auth-with-password';\n            const authBody = JSON.stringify({\n              identity: process.env.POCKETBASE_ADMIN_EMAIL,\n              password: process.env.POCKETBASE_ADMIN_PASSWORD\n            });\n            const authRes = await request(authUrl, {\n              method: 'POST',\n              headers: { 'Content-Type': 'application/json' },\n              body: authBody\n            });\n            if (!authRes.ok) {\n              throw new Error('Auth failed. Response: ' + authRes.body);\n            }\n            const token = JSON.parse(authRes.body).token;\n            const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';\n\n            const patchBody = JSON.stringify({ is_deleted: true });\n\n            for (const slug of slugs) {\n              const filter = \"(slug='\" + slug + \"')\";\n              const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {\n                headers: { 'Authorization': token }\n              });\n              const list = JSON.parse(listRes.body);\n              const existingId = list.items && list.items[0] && list.items[0].id;\n              if (!existingId) {\n                console.log('No PocketBase record for slug \"' + slug + '\", skipping.');\n                continue;\n              }\n              const patchRes = await request(recordsUrl + '/' + existingId, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: patchBody\n              });\n              if (patchRes.ok) {\n                console.log('Set is_deleted=true for slug \"' + slug + '\" (id=' + existingId + ').');\n              } else {\n                console.warn('PATCH failed for slug \"' + slug + '\": ' + patchRes.statusCode + ' ' + patchRes.body);\n              }\n            }\n            console.log('Done.');\n          })().catch(e => { console.error(e); process.exit(1); });\n          ENDSCRIPT\n        shell: bash\n"
  },
  {
    "path": ".github/workflows/pocketbase-bot.yml",
    "content": "name: PocketBase Bot\n\non:\n  issue_comment:\n    types: [created]\n\npermissions:\n  issues: write\n  pull-requests: write\n  contents: write\n\njobs:\n  pocketbase-bot:\n    runs-on: self-hosted\n\n    # Only act on /pocketbase commands\n    if: startsWith(github.event.comment.body, '/pocketbase')\n\n    steps:\n      - name: Execute PocketBase bot command\n        env:\n          POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}\n          POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}\n          POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}\n          POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}\n          COMMENT_BODY: ${{ github.event.comment.body }}\n          COMMENT_ID: ${{ github.event.comment.id }}\n          ISSUE_NUMBER: ${{ github.event.issue.number }}\n          REPO_OWNER: ${{ github.repository_owner }}\n          REPO_NAME: ${{ github.event.repository.name }}\n          ACTOR: ${{ github.event.comment.user.login }}\n          ACTOR_ASSOCIATION: ${{ github.event.comment.author_association }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          FRONTEND_URL: ${{ secrets.FRONTEND_URL }}\n          REVALIDATE_SECRET: ${{ secrets.REVALIDATE_SECRET }}\n        run: |\n          node << 'ENDSCRIPT'\n          (async function () {\n            const https = require('https');\n            const http = require('http');\n            const url = require('url');\n\n            // ── HTTP helper with redirect following ────────────────────────────\n            function request(fullUrl, opts, redirectCount) {\n              redirectCount = redirectCount || 0;\n              return new Promise(function (resolve, reject) {\n                const u = url.parse(fullUrl);\n                const isHttps = u.protocol === 'https:';\n                const body = opts.body;\n                const options = {\n                  hostname: u.hostname,\n                  port: u.port || (isHttps ? 443 : 80),\n                  path: u.path,\n                  method: opts.method || 'GET',\n                  headers: opts.headers || {}\n                };\n                if (body) options.headers['Content-Length'] = Buffer.byteLength(body);\n                const lib = isHttps ? https : http;\n                const req = lib.request(options, function (res) {\n                  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {\n                    if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));\n                    const redirectUrl = url.resolve(fullUrl, res.headers.location);\n                    res.resume();\n                    resolve(request(redirectUrl, opts, redirectCount + 1));\n                    return;\n                  }\n                  let data = '';\n                  res.on('data', function (chunk) { data += chunk; });\n                  res.on('end', function () {\n                    resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });\n                  });\n                });\n                req.on('error', reject);\n                if (body) req.write(body);\n                req.end();\n              });\n            }\n\n            // ── GitHub API helpers ─────────────────────────────────────────────\n            const owner = process.env.REPO_OWNER;\n            const repo = process.env.REPO_NAME;\n            const issueNumber = parseInt(process.env.ISSUE_NUMBER, 10);\n            const commentId = parseInt(process.env.COMMENT_ID, 10);\n            const actor = process.env.ACTOR;\n\n            function ghRequest(path, method, body) {\n              const headers = {\n                'Authorization': 'Bearer ' + process.env.GITHUB_TOKEN,\n                'Accept': 'application/vnd.github+json',\n                'X-GitHub-Api-Version': '2022-11-28',\n                'User-Agent': 'PocketBase-Bot'\n              };\n              const bodyStr = body ? JSON.stringify(body) : undefined;\n              if (bodyStr) headers['Content-Type'] = 'application/json';\n              return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });\n            }\n\n            function encodeContentPath(filePath) {\n              return filePath.split('/').map(encodeURIComponent).join('/');\n            }\n\n            function decodeGitHubContent(content) {\n              return Buffer.from((content || '').replace(/\\n/g, ''), 'base64').toString('utf8');\n            }\n\n            function sanitizeBranchPart(value) {\n              return (value || '')\n                .toLowerCase()\n                .replace(/[^a-z0-9._/-]+/g, '-')\n                .replace(/\\/+/g, '/')\n                .replace(/^-+|-+$/g, '');\n            }\n\n            function applyCtDefaultChanges(scriptText, varChanges) {\n              let nextText = scriptText;\n              const updatedVars = [];\n              const unchangedVars = [];\n              for (const [varName, rawValue] of Object.entries(varChanges)) {\n                const newValue = String(rawValue);\n                const pattern = new RegExp('(^\\\\s*' + varName + '=\"\\\\$\\\\{' + varName + ':-)([^\"}]*)(\\\\}\"\\\\s*$)', 'm');\n                const match = nextText.match(pattern);\n                if (!match) continue;\n                if (match[2] === newValue) {\n                  unchangedVars.push(varName);\n                  continue;\n                }\n                nextText = nextText.replace(pattern, '$1' + newValue + '$3');\n                updatedVars.push(varName);\n              }\n              return { nextText, updatedVars, unchangedVars };\n            }\n\n            async function ensureBranch(defaultBranch, branchName) {\n              const branchRefRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/ref/heads/' + encodeURIComponent(branchName));\n              if (branchRefRes.ok) return;\n\n              const defaultRefRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/ref/heads/' + encodeURIComponent(defaultBranch));\n              if (!defaultRefRes.ok) {\n                throw new Error('Could not read default branch ref: ' + defaultRefRes.body);\n              }\n              const defaultRef = JSON.parse(defaultRefRes.body);\n              const createBranchRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/refs', 'POST', {\n                ref: 'refs/heads/' + branchName,\n                sha: defaultRef.object.sha\n              });\n              if (!createBranchRes.ok) {\n                throw new Error('Could not create branch: ' + createBranchRes.body);\n              }\n            }\n\n            async function upsertCtDefaultsPr(slugValue, varChanges) {\n              const wantedEntries = Object.entries(varChanges || {}).filter(function ([, v]) {\n                return v !== undefined && v !== null && String(v) !== '';\n              });\n              if (wantedEntries.length === 0) {\n                return { status: 'skipped', reason: 'No mapped CT defaults changed.' };\n              }\n\n              const repoRes = await ghRequest('/repos/' + owner + '/' + repo);\n              if (!repoRes.ok) {\n                throw new Error('Could not read repository metadata: ' + repoRes.body);\n              }\n              const repoInfo = JSON.parse(repoRes.body);\n              const defaultBranch = repoInfo.default_branch;\n\n              const ctPath = 'ct/' + slugValue + '.sh';\n              const encodedCtPath = encodeContentPath(ctPath);\n              const defaultFileRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath + '?ref=' + encodeURIComponent(defaultBranch));\n              if (defaultFileRes.statusCode === 404) {\n                return { status: 'skipped', reason: 'No matching CT file found at `' + ctPath + '`.' };\n              }\n              if (!defaultFileRes.ok) {\n                throw new Error('Could not read CT file from default branch: ' + defaultFileRes.body);\n              }\n\n              const branchName = 'pocketbase-sync/' + sanitizeBranchPart(slugValue || 'unknown');\n              await ensureBranch(defaultBranch, branchName);\n\n              const branchFileRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath + '?ref=' + encodeURIComponent(branchName));\n              if (!branchFileRes.ok) {\n                throw new Error('Could not read CT file from sync branch: ' + branchFileRes.body);\n              }\n              const branchFile = JSON.parse(branchFileRes.body);\n              const currentBranchText = decodeGitHubContent(branchFile.content);\n\n              const updateResult = applyCtDefaultChanges(currentBranchText, Object.fromEntries(wantedEntries));\n              if (updateResult.updatedVars.length === 0) {\n                return { status: 'skipped', reason: 'CT defaults already up to date.', unchangedVars: updateResult.unchangedVars };\n              }\n\n              const commitMessage = 'chore(ct): sync ' + slugValue + ' defaults from PocketBase';\n              const putRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath, 'PUT', {\n                message: commitMessage,\n                content: Buffer.from(updateResult.nextText, 'utf8').toString('base64'),\n                sha: branchFile.sha,\n                branch: branchName\n              });\n              if (!putRes.ok) {\n                throw new Error('Could not update CT file: ' + putRes.body);\n              }\n\n              const openPrRes = await ghRequest(\n                '/repos/' + owner + '/' + repo + '/pulls?state=open&head=' + encodeURIComponent(owner + ':' + branchName) + '&base=' + encodeURIComponent(defaultBranch)\n              );\n              if (!openPrRes.ok) {\n                throw new Error('Could not query existing PRs: ' + openPrRes.body);\n              }\n              const openPrs = JSON.parse(openPrRes.body);\n              if (openPrs.length > 0) {\n                return { status: 'updated', prUrl: openPrs[0].html_url, updatedVars: updateResult.updatedVars };\n              }\n\n              const prTitle = 'chore(ct): sync ' + slugValue + ' defaults with PocketBase';\n              const prBody =\n                '## Summary\\n' +\n                '- Sync default CT variables for `' + slugValue + '` after `/pocketbase` update.\\n' +\n                '- Updated vars: `' + updateResult.updatedVars.join('`, `') + '`.\\n\\n' +\n                '## Source\\n' +\n                '- Triggered by @' + actor + ' via PocketBase bot.\\n';\n              const createPrRes = await ghRequest('/repos/' + owner + '/' + repo + '/pulls', 'POST', {\n                title: prTitle,\n                body: prBody,\n                head: branchName,\n                base: defaultBranch\n              });\n              if (!createPrRes.ok) {\n                throw new Error('Could not create PR: ' + createPrRes.body);\n              }\n              const pr = JSON.parse(createPrRes.body);\n              return { status: 'created', prUrl: pr.html_url, updatedVars: updateResult.updatedVars };\n            }\n\n            function formatCtSyncResult(syncResult) {\n              if (!syncResult) return '';\n              if (syncResult.status === 'created') return '\\n\\n**CT sync PR:** ' + syncResult.prUrl;\n              if (syncResult.status === 'updated') return '\\n\\n**CT sync PR updated:** ' + syncResult.prUrl;\n              if (syncResult.status === 'skipped') return '\\n\\n**CT sync skipped:** ' + syncResult.reason;\n              return '';\n            }\n\n            async function addReaction(content) {\n              try {\n                await ghRequest(\n                  '/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions',\n                  'POST', { content }\n                );\n              } catch (e) {\n                console.warn('Could not add reaction:', e.message);\n              }\n            }\n\n            async function postComment(text) {\n              const res = await ghRequest(\n                '/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments',\n                'POST', { body: text }\n              );\n              if (!res.ok) console.warn('Could not post comment:', res.body);\n            }\n\n            // ── Permission check ───────────────────────────────────────────────\n            const association = process.env.ACTOR_ASSOCIATION;\n            if (association !== 'OWNER' && association !== 'MEMBER') {\n              await addReaction('-1');\n              await postComment(\n                '❌ **PocketBase Bot**: @' + actor + ' is not authorized to use this command.\\n' +\n                'Only org members (Contributors team) can use `/pocketbase`.'\n              );\n              process.exit(0);\n            }\n\n            // ── Acknowledge ────────────────────────────────────────────────────\n            await addReaction('eyes');\n\n            // ── Parse command ──────────────────────────────────────────────────\n            const commentBody = process.env.COMMENT_BODY || '';\n            const lines = commentBody.trim().split('\\n');\n            const firstLine = lines[0].trim();\n            const withoutCmd = firstLine.replace(/^\\/pocketbase\\s+/, '').trim();\n\n            function extractCodeBlock(body) {\n              const m = body.match(/```[^\\n]*\\n([\\s\\S]*?)```/);\n              return m ? m[1].trim() : null;\n            }\n            const codeBlockValue = extractCodeBlock(commentBody);\n\n            const HELP_TEXT =\n              '**Show current state:**\\n' +\n              '```\\n/pocketbase <slug> info\\n```\\n\\n' +\n              '**Field update (simple):** `/pocketbase <slug> field=value [field=value ...]`\\n\\n' +\n              '**Field update (HTML/multiline) — value from code block:**\\n' +\n              '````\\n' +\n              '/pocketbase <slug> set description\\n' +\n              '```html\\n' +\n              '<p>Your <b>HTML</b> or multi-line content here</p>\\n' +\n              '```\\n' +\n              '````\\n\\n' +\n              '**Note management:**\\n' +\n              '```\\n' +\n              '/pocketbase <slug> note list\\n' +\n              '/pocketbase <slug> note add <type> \"<text>\"\\n' +\n              '/pocketbase <slug> note edit <type> \"<old text>\" \"<new text>\"\\n' +\n              '/pocketbase <slug> note remove <type> \"<text>\"\\n' +\n              '```\\n\\n' +\n              '**Install method management:**\\n' +\n              '```\\n' +\n              '/pocketbase <slug> method list\\n' +\n              '/pocketbase <slug> method <type> cpu=4 ram=2048 hdd=20\\n' +\n              '/pocketbase <slug> method <type> config_path=\"/opt/app/.env\"\\n' +\n              '/pocketbase <slug> method <type> os=debian version=13\\n' +\n              '/pocketbase <slug> method add <type> cpu=2 ram=2048 hdd=8 os=debian version=13\\n' +\n              '/pocketbase <slug> method remove <type>\\n' +\n              '```\\n' +\n              'Method fields: `cpu` `ram` `hdd` `os` `version` `config_path` `script`\\n\\n' +\n              '**Editable fields:** `name` `description` `logo` `documentation` `website` `project_url` `github` ' +\n              '`config_path` `port` `default_user` `default_passwd` ' +\n              '`updateable` `privileged` `has_arm` `is_dev` ' +\n              '`is_disabled` `disable_message` `is_deleted` `deleted_message`';\n\n            if (!withoutCmd) {\n              await addReaction('-1');\n              await postComment('❌ **PocketBase Bot**: No slug or command specified.\\n\\n' + HELP_TEXT);\n              process.exit(0);\n            }\n\n            const spaceIdx = withoutCmd.indexOf(' ');\n            const slug = (spaceIdx === -1 ? withoutCmd : withoutCmd.substring(0, spaceIdx)).trim();\n            const rest = spaceIdx === -1 ? '' : withoutCmd.substring(spaceIdx + 1).trim();\n\n            if (!rest) {\n              await addReaction('-1');\n              await postComment('❌ **PocketBase Bot**: No command specified for slug `' + slug + '`.\\n\\n' + HELP_TEXT);\n              process.exit(0);\n            }\n\n            // ── PocketBase: authenticate ───────────────────────────────────────\n            const raw = process.env.POCKETBASE_URL.replace(/\\/$/, '');\n            const apiBase = /\\/api$/i.test(raw) ? raw : raw + '/api';\n            const coll = process.env.POCKETBASE_COLLECTION;\n\n            const authRes = await request(apiBase + '/collections/users/auth-with-password', {\n              method: 'POST',\n              headers: { 'Content-Type': 'application/json' },\n              body: JSON.stringify({\n                identity: process.env.POCKETBASE_ADMIN_EMAIL,\n                password: process.env.POCKETBASE_ADMIN_PASSWORD\n              })\n            });\n            if (!authRes.ok) {\n              await addReaction('-1');\n              await postComment('❌ **PocketBase Bot**: PocketBase authentication failed. CC @' + owner + '/maintainers');\n              process.exit(1);\n            }\n            const token = JSON.parse(authRes.body).token;\n\n            // ── PocketBase: find record by slug ────────────────────────────────\n            const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';\n            const filter = \"(slug='\" + slug.replace(/'/g, \"''\") + \"')\";\n            const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {\n              headers: { 'Authorization': token }\n            });\n            const list = JSON.parse(listRes.body);\n            const record = list.items && list.items[0];\n\n            if (!record) {\n              await addReaction('-1');\n              await postComment(\n                '❌ **PocketBase Bot**: No record found for slug `' + slug + '`.\\n\\n' +\n                'Make sure the script was already pushed to PocketBase (JSON must exist and have been synced).'\n              );\n              process.exit(0);\n            }\n\n            // ── Shared helpers ─────────────────────────────────────────────────\n\n            // Key=value parser: handles unquoted and \"quoted\" values\n            function parseKVPairs(str) {\n              const fields = {};\n              let pos = 0;\n              while (pos < str.length) {\n                while (pos < str.length && /\\s/.test(str[pos])) pos++;\n                if (pos >= str.length) break;\n                let keyStart = pos;\n                while (pos < str.length && str[pos] !== '=' && !/\\s/.test(str[pos])) pos++;\n                const key = str.substring(keyStart, pos).trim();\n                if (!key || pos >= str.length || str[pos] !== '=') { pos++; continue; }\n                pos++;\n                let value;\n                if (pos < str.length && str[pos] === '\"') {\n                  pos++;\n                  let valStart = pos;\n                  while (pos < str.length && str[pos] !== '\"') {\n                    if (str[pos] === '\\\\') pos++;\n                    pos++;\n                  }\n                  value = str.substring(valStart, pos).replace(/\\\\\"/g, '\"');\n                  if (pos < str.length) pos++;\n                } else {\n                  let valStart = pos;\n                  while (pos < str.length && !/\\s/.test(str[pos])) pos++;\n                  value = str.substring(valStart, pos);\n                }\n                fields[key] = value;\n              }\n              return fields;\n            }\n\n            // Token parser for note commands: unquoted-word OR \"quoted string\"\n            function parseTokens(str) {\n              const tokens = [];\n              let pos = 0;\n              while (pos < str.length) {\n                while (pos < str.length && /\\s/.test(str[pos])) pos++;\n                if (pos >= str.length) break;\n                if (str[pos] === '\"') {\n                  pos++;\n                  let start = pos;\n                  while (pos < str.length && str[pos] !== '\"') {\n                    if (str[pos] === '\\\\') pos++;\n                    pos++;\n                  }\n                  tokens.push(str.substring(start, pos).replace(/\\\\\"/g, '\"'));\n                  if (pos < str.length) pos++;\n                } else {\n                  let start = pos;\n                  while (pos < str.length && !/\\s/.test(str[pos])) pos++;\n                  tokens.push(str.substring(start, pos));\n                }\n              }\n              return tokens;\n            }\n\n            // Read JSON blob from record (handles parsed objects and strings)\n            function readJsonBlob(val) {\n              if (Array.isArray(val)) return val;\n              try { return JSON.parse(val || '[]'); } catch (e) { return []; }\n            }\n\n            // Frontend cache revalidation (silent, best-effort)\n            async function revalidate(s) {\n              const frontendUrl = process.env.FRONTEND_URL;\n              const secret = process.env.REVALIDATE_SECRET;\n              if (!frontendUrl || !secret) return;\n              try {\n                await request(frontendUrl.replace(/\\/$/, '') + '/api/revalidate', {\n                  method: 'POST',\n                  headers: { 'Authorization': 'Bearer ' + secret, 'Content-Type': 'application/json' },\n                  body: JSON.stringify({ tags: ['scripts', 'script-' + s] })\n                });\n              } catch (e) { console.warn('Revalidation skipped:', e.message); }\n            }\n\n            // Format notes list for display\n            function formatNotesList(arr) {\n              if (arr.length === 0) return '*None*';\n              return arr.map(function (n, i) {\n                return (i + 1) + '. **`' + (n.type || '?') + '`**: ' + (n.text || '');\n              }).join('\\n');\n            }\n\n            // Format install methods list for display\n            function formatMethodsList(arr) {\n              if (arr.length === 0) return '*None*';\n              return arr.map(function (im, i) {\n                const r = im.resources || {};\n                const parts = [\n                  (r.os || '?') + ' ' + (r.version || '?'),\n                  (r.cpu != null ? r.cpu : '?') + 'C / ' + (r.ram != null ? r.ram : '?') + ' MB / ' + (r.hdd != null ? r.hdd : '?') + ' GB'\n                ];\n                if (im.config_path) parts.push('config: `' + im.config_path + '`');\n                if (im.script) parts.push('script: `' + im.script + '`');\n                return (i + 1) + '. **`' + (im.type || '?') + '`** — ' + parts.join(', ');\n              }).join('\\n');\n            }\n\n            // ── Route: dispatch to subcommand handler ──────────────────────────\n            const infoMatch   = rest.match(/^info$/i);\n            const noteMatch   = rest.match(/^note\\s+(list|add|edit|remove)\\b/i);\n            const methodMatch = rest.match(/^method\\b/i);\n            const setMatch    = rest.match(/^set\\s+(\\S+)/i);\n\n            if (infoMatch) {\n              // ── INFO SUBCOMMAND ──────────────────────────────────────────────\n              const notesArr   = readJsonBlob(record.notes);\n              const methodsArr = readJsonBlob(record.install_methods);\n\n              const out = [];\n              out.push('ℹ️ **PocketBase Bot**: Info for **`' + slug + '`**\\n');\n\n              out.push('**Basic info:**');\n              out.push('- **Name:** ' + (record.name || '—'));\n              out.push('- **Slug:** `' + slug + '`');\n              out.push('- **Port:** ' + (record.port != null ? '`' + record.port + '`' : '—'));\n              out.push('- **Updateable:** ' + (record.updateable ? 'Yes' : 'No'));\n              out.push('- **Privileged:** ' + (record.privileged ? 'Yes' : 'No'));\n              out.push('- **ARM:** ' + (record.has_arm ? 'Yes' : 'No'));\n              if (record.is_dev) out.push('- **Dev:** Yes');\n              if (record.is_disabled) out.push('- **Disabled:** Yes' + (record.disable_message ? ' — ' + record.disable_message : ''));\n              if (record.is_deleted) out.push('- **Deleted:** Yes' + (record.deleted_message ? ' — ' + record.deleted_message : ''));\n              out.push('');\n\n              out.push('**Links:**');\n              out.push('- **Website:** ' + (record.website || '—'));\n              out.push('- **Docs:** ' + (record.documentation || '—'));\n              out.push('- **Logo:** ' + (record.logo ? '[link](' + record.logo + ')' : '—'));\n              out.push('- **GitHub:** ' + (record.github || '—'));\n              if (record.config_path) out.push('- **Config:** `' + record.config_path + '`');\n              out.push('');\n\n              out.push('**Credentials:**');\n              out.push('- **User:** ' + (record.default_user || '—'));\n              out.push('- **Password:** ' + (record.default_passwd ? '*(set)*' : '—'));\n              out.push('');\n\n              out.push('**Install methods** (' + methodsArr.length + '):');\n              out.push(formatMethodsList(methodsArr));\n              out.push('');\n\n              out.push('**Notes** (' + notesArr.length + '):');\n              out.push(formatNotesList(notesArr));\n\n              await addReaction('+1');\n              await postComment(out.join('\\n'));\n\n            } else if (noteMatch) {\n              // ── NOTE SUBCOMMAND ──────────────────────────────────────────────\n              const noteAction  = noteMatch[1].toLowerCase();\n              const noteArgsStr = rest.substring(noteMatch[0].length).trim();\n              let notesArr = readJsonBlob(record.notes);\n\n              async function patchNotes(arr) {\n                const res = await request(recordsUrl + '/' + record.id, {\n                  method: 'PATCH',\n                  headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                  body: JSON.stringify({ notes: arr })\n                });\n                if (!res.ok) {\n                  await addReaction('-1');\n                  await postComment('❌ **PocketBase Bot**: Failed to update notes:\\n```\\n' + res.body + '\\n```');\n                  process.exit(1);\n                }\n              }\n\n              if (noteAction === 'list') {\n                await addReaction('+1');\n                await postComment(\n                  'ℹ️ **PocketBase Bot**: Notes for **`' + slug + '`** (' + notesArr.length + ' total)\\n\\n' +\n                  formatNotesList(notesArr)\n                );\n\n              } else if (noteAction === 'add') {\n                const tokens = parseTokens(noteArgsStr);\n                if (tokens.length < 2) {\n                  await addReaction('-1');\n                  await postComment(\n                    '❌ **PocketBase Bot**: `note add` requires `<type>` and `\"<text>\"`.\\n\\n' +\n                    '**Usage:** `/pocketbase ' + slug + ' note add <type> \"<text>\"`'\n                  );\n                  process.exit(0);\n                }\n                const noteType = tokens[0].toLowerCase();\n                const noteText = tokens.slice(1).join(' ');\n                notesArr.push({ type: noteType, text: noteText });\n                await patchNotes(notesArr);\n                await revalidate(slug);\n                await addReaction('+1');\n                await postComment(\n                  '✅ **PocketBase Bot**: Added note to **`' + slug + '`**\\n\\n' +\n                  '- **Type:** `' + noteType + '`\\n' +\n                  '- **Text:** ' + noteText + '\\n\\n' +\n                  '*Executed by @' + actor + '*'\n                );\n\n              } else if (noteAction === 'edit') {\n                const tokens = parseTokens(noteArgsStr);\n                if (tokens.length < 3) {\n                  await addReaction('-1');\n                  await postComment(\n                    '❌ **PocketBase Bot**: `note edit` requires `<type>`, `\"<old text>\"`, and `\"<new text>\"`.\\n\\n' +\n                    '**Usage:** `/pocketbase ' + slug + ' note edit <type> \"<old text>\" \"<new text>\"`\\n\\n' +\n                    'Use `/pocketbase ' + slug + ' note list` to see current notes.'\n                  );\n                  process.exit(0);\n                }\n                const noteType = tokens[0].toLowerCase();\n                const oldText  = tokens[1];\n                const newText  = tokens[2];\n                const idx = notesArr.findIndex(function (n) {\n                  return n.type.toLowerCase() === noteType && n.text === oldText;\n                });\n                if (idx === -1) {\n                  await addReaction('-1');\n                  await postComment(\n                    '❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\\n\\n' +\n                    '**Current notes for `' + slug + '`:**\\n' + formatNotesList(notesArr)\n                  );\n                  process.exit(0);\n                }\n                notesArr[idx].text = newText;\n                await patchNotes(notesArr);\n                await revalidate(slug);\n                await addReaction('+1');\n                await postComment(\n                  '✅ **PocketBase Bot**: Edited note in **`' + slug + '`**\\n\\n' +\n                  '- **Type:** `' + noteType + '`\\n' +\n                  '- **Old:** ' + oldText + '\\n' +\n                  '- **New:** ' + newText + '\\n\\n' +\n                  '*Executed by @' + actor + '*'\n                );\n\n              } else if (noteAction === 'remove') {\n                const tokens = parseTokens(noteArgsStr);\n                if (tokens.length < 2) {\n                  await addReaction('-1');\n                  await postComment(\n                    '❌ **PocketBase Bot**: `note remove` requires `<type>` and `\"<text>\"`.\\n\\n' +\n                    '**Usage:** `/pocketbase ' + slug + ' note remove <type> \"<text>\"`\\n\\n' +\n                    'Use `/pocketbase ' + slug + ' note list` to see current notes.'\n                  );\n                  process.exit(0);\n                }\n                const noteType = tokens[0].toLowerCase();\n                const noteText = tokens[1];\n                const before   = notesArr.length;\n                notesArr = notesArr.filter(function (n) {\n                  return !(n.type.toLowerCase() === noteType && n.text === noteText);\n                });\n                if (notesArr.length === before) {\n                  await addReaction('-1');\n                  await postComment(\n                    '❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\\n\\n' +\n                    '**Current notes for `' + slug + '`:**\\n' + formatNotesList(notesArr)\n                  );\n                  process.exit(0);\n                }\n                await patchNotes(notesArr);\n                await revalidate(slug);\n                await addReaction('+1');\n                await postComment(\n                  '✅ **PocketBase Bot**: Removed note from **`' + slug + '`**\\n\\n' +\n                  '- **Type:** `' + noteType + '`\\n' +\n                  '- **Text:** ' + noteText + '\\n\\n' +\n                  '*Executed by @' + actor + '*'\n                );\n              }\n\n            } else if (methodMatch) {\n              // ── METHOD SUBCOMMAND ────────────────────────────────────────────\n              const methodArgs     = rest.replace(/^method\\s*/i, '').trim();\n              const methodListMode = !methodArgs || methodArgs.toLowerCase() === 'list';\n              let methodsArr = readJsonBlob(record.install_methods);\n\n              // Method field classification\n              const RESOURCE_KEYS = { cpu: 'number', ram: 'number', hdd: 'number', os: 'string', version: 'string' };\n              const METHOD_KEYS   = { config_path: 'string', script: 'string' };\n              const ALL_METHOD_KEYS = Object.assign({}, RESOURCE_KEYS, METHOD_KEYS);\n              const RESOURCE_TO_CT_VAR = { cpu: 'var_cpu', ram: 'var_ram', hdd: 'var_disk', os: 'var_os', version: 'var_version' };\n\n              function applyMethodChanges(method, parsed) {\n                if (!method.resources) method.resources = {};\n                for (const [k, v] of Object.entries(parsed)) {\n                  if (RESOURCE_KEYS[k]) {\n                    method.resources[k] = RESOURCE_KEYS[k] === 'number' ? parseInt(v, 10) : v;\n                  } else if (METHOD_KEYS[k]) {\n                    method[k] = v === '' ? null : v;\n                  }\n                }\n              }\n\n              async function patchMethods(arr) {\n                const res = await request(recordsUrl + '/' + record.id, {\n                  method: 'PATCH',\n                  headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                  body: JSON.stringify({ install_methods: arr })\n                });\n                if (!res.ok) {\n                  await addReaction('-1');\n                  await postComment('❌ **PocketBase Bot**: Failed to update install methods:\\n```\\n' + res.body + '\\n```');\n                  process.exit(1);\n                }\n              }\n\n              if (methodListMode) {\n                await addReaction('+1');\n                await postComment(\n                  'ℹ️ **PocketBase Bot**: Install methods for **`' + slug + '`** (' + methodsArr.length + ' total)\\n\\n' +\n                  formatMethodsList(methodsArr)\n                );\n\n              } else {\n                // Check for add / remove sub-actions\n                const addMatch    = methodArgs.match(/^add\\s+(\\S+)(?:\\s+(.+))?$/i);\n                const removeMatch = methodArgs.match(/^remove\\s+(\\S+)$/i);\n\n                if (addMatch) {\n                  // ── METHOD ADD ───────────────────────────────────────────────\n                  const newType = addMatch[1];\n                  const parsed = addMatch[2] ? parseKVPairs(addMatch[2]) : {};\n                  if (methodsArr.some(function (im) { return (im.type || '').toLowerCase() === newType.toLowerCase(); })) {\n                    await addReaction('-1');\n                    await postComment('❌ **PocketBase Bot**: Install method `' + newType + '` already exists for `' + slug + '`.\\n\\nUse `/pocketbase ' + slug + ' method list` to see all methods.');\n                    process.exit(0);\n                  }\n                  const newMethod = { type: newType, resources: { cpu: 1, ram: 512, hdd: 4, os: 'debian', version: '13' } };\n                  if (addMatch[2]) {\n                    const unknown = Object.keys(parsed).filter(function (k) { return !ALL_METHOD_KEYS[k]; });\n                    if (unknown.length > 0) {\n                      await addReaction('-1');\n                      await postComment('❌ **PocketBase Bot**: Unknown method field(s): `' + unknown.join('`, `') + '`\\n\\n**Allowed:** `' + Object.keys(ALL_METHOD_KEYS).join('`, `') + '`');\n                      process.exit(0);\n                    }\n                    applyMethodChanges(newMethod, parsed);\n                  }\n                  methodsArr.push(newMethod);\n                  await patchMethods(methodsArr);\n                  await revalidate(slug);\n                  const addCtChanges = {};\n                  for (const [k, v] of Object.entries(parsed)) {\n                    if (RESOURCE_TO_CT_VAR[k]) addCtChanges[RESOURCE_TO_CT_VAR[k]] = v;\n                  }\n                  let addCtSync = null;\n                  try {\n                    addCtSync = await upsertCtDefaultsPr(slug, addCtChanges);\n                  } catch (e) {\n                    addCtSync = { status: 'skipped', reason: 'CT sync failed: ' + e.message };\n                  }\n                  await addReaction('+1');\n                  await postComment(\n                    '✅ **PocketBase Bot**: Added install method **`' + newType + '`** to **`' + slug + '`**\\n\\n' +\n                    formatMethodsList([newMethod]) + '\\n\\n' +\n                    formatCtSyncResult(addCtSync) + '\\n\\n' +\n                    '*Executed by @' + actor + '*'\n                  );\n\n                } else if (removeMatch) {\n                  // ── METHOD REMOVE ────────────────────────────────────────────\n                  const removeType = removeMatch[1].toLowerCase();\n                  const removed = methodsArr.filter(function (im) { return (im.type || '').toLowerCase() === removeType; });\n                  if (removed.length === 0) {\n                    await addReaction('-1');\n                    const available = methodsArr.map(function (im) { return im.type || '?'; });\n                    await postComment('❌ **PocketBase Bot**: No install method `' + removeType + '` found.\\n\\n**Available:** `' + (available.length ? available.join('`, `') : '(none)') + '`');\n                    process.exit(0);\n                  }\n                  methodsArr = methodsArr.filter(function (im) { return (im.type || '').toLowerCase() !== removeType; });\n                  await patchMethods(methodsArr);\n                  await revalidate(slug);\n                  await addReaction('+1');\n                  await postComment(\n                    '✅ **PocketBase Bot**: Removed install method **`' + removed[0].type + '`** from **`' + slug + '`**\\n\\n' +\n                    '*Executed by @' + actor + '*'\n                  );\n\n                } else {\n                  // ── METHOD EDIT ──────────────────────────────────────────────\n                  const editParts = methodArgs.match(/^(\\S+)\\s+(.+)$/);\n                  if (!editParts) {\n                    await addReaction('-1');\n                    await postComment(\n                      '❌ **PocketBase Bot**: Invalid `method` syntax.\\n\\n' +\n                      '**Usage:**\\n```\\n/pocketbase ' + slug + ' method list\\n' +\n                      '/pocketbase ' + slug + ' method <type> cpu=4 ram=2048 hdd=20\\n' +\n                      '/pocketbase ' + slug + ' method <type> config_path=\"/opt/app/.env\"\\n' +\n                      '/pocketbase ' + slug + ' method add <type> cpu=2 ram=2048 hdd=8\\n' +\n                      '/pocketbase ' + slug + ' method remove <type>\\n```'\n                    );\n                    process.exit(0);\n                  }\n                  const targetType = editParts[1].toLowerCase();\n                  const parsed = parseKVPairs(editParts[2]);\n\n                  const unknown = Object.keys(parsed).filter(function (k) { return !ALL_METHOD_KEYS[k]; });\n                  if (unknown.length > 0) {\n                    await addReaction('-1');\n                    await postComment('❌ **PocketBase Bot**: Unknown method field(s): `' + unknown.join('`, `') + '`\\n\\n**Allowed:** `' + Object.keys(ALL_METHOD_KEYS).join('`, `') + '`');\n                    process.exit(0);\n                  }\n                  if (Object.keys(parsed).length === 0) {\n                    await addReaction('-1');\n                    await postComment('❌ **PocketBase Bot**: No valid `key=value` pairs found.\\n\\n**Allowed:** `' + Object.keys(ALL_METHOD_KEYS).join('`, `') + '`');\n                    process.exit(0);\n                  }\n\n                  const idx = methodsArr.findIndex(function (im) { return (im.type || '').toLowerCase() === targetType; });\n                  if (idx === -1) {\n                    await addReaction('-1');\n                    const available = methodsArr.map(function (im) { return im.type || '?'; });\n                    await postComment(\n                      '❌ **PocketBase Bot**: No install method `' + targetType + '` found for `' + slug + '`.\\n\\n' +\n                      '**Available:** `' + (available.length ? available.join('`, `') : '(none)') + '`\\n\\n' +\n                      'Use `/pocketbase ' + slug + ' method list` to see all methods.'\n                    );\n                    process.exit(0);\n                  }\n\n                  applyMethodChanges(methodsArr[idx], parsed);\n                  await patchMethods(methodsArr);\n                  await revalidate(slug);\n                  const editCtChanges = {};\n                  for (const [k, v] of Object.entries(parsed)) {\n                    if (RESOURCE_TO_CT_VAR[k]) editCtChanges[RESOURCE_TO_CT_VAR[k]] = v;\n                  }\n                  let editCtSync = null;\n                  try {\n                    editCtSync = await upsertCtDefaultsPr(slug, editCtChanges);\n                  } catch (e) {\n                    editCtSync = { status: 'skipped', reason: 'CT sync failed: ' + e.message };\n                  }\n\n                  const changesLines = Object.entries(parsed)\n                    .map(function ([k, v]) {\n                      const unit = k === 'ram' ? ' MB' : k === 'hdd' ? ' GB' : '';\n                      return '- `' + k + '` → `' + v + unit + '`';\n                    }).join('\\n');\n                  await addReaction('+1');\n                  await postComment(\n                    '✅ **PocketBase Bot**: Updated install method **`' + methodsArr[idx].type + '`** for **`' + slug + '`**\\n\\n' +\n                    '**Changes applied:**\\n' + changesLines + '\\n\\n' +\n                    formatCtSyncResult(editCtSync) + '\\n\\n' +\n                    '*Executed by @' + actor + '*'\n                  );\n                }\n              }\n\n            } else if (setMatch) {\n              // ── SET SUBCOMMAND (value from code block) ───────────────────────\n              const fieldName = setMatch[1].toLowerCase();\n              const SET_ALLOWED = {\n                name: 'string', description: 'string', logo: 'string',\n                documentation: 'string', website: 'string', project_url: 'string', github: 'string',\n                config_path: 'string', disable_message: 'string', deleted_message: 'string'\n              };\n              if (!SET_ALLOWED[fieldName]) {\n                await addReaction('-1');\n                await postComment(\n                  '❌ **PocketBase Bot**: `set` only supports text fields.\\n\\n' +\n                  '**Allowed:** `' + Object.keys(SET_ALLOWED).join('`, `') + '`\\n\\n' +\n                  'For boolean/number fields use `field=value` syntax instead.'\n                );\n                process.exit(0);\n              }\n              if (!codeBlockValue) {\n                await addReaction('-1');\n                await postComment(\n                  '❌ **PocketBase Bot**: `set` requires a code block with the value.\\n\\n' +\n                  '**Usage:**\\n````\\n/pocketbase ' + slug + ' set ' + fieldName + '\\n```\\nYour content here (HTML, multiline, special chars all fine)\\n```\\n````'\n                );\n                process.exit(0);\n              }\n              const setPayload = {};\n              setPayload[fieldName] = codeBlockValue;\n              const setPatchRes = await request(recordsUrl + '/' + record.id, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify(setPayload)\n              });\n              if (!setPatchRes.ok) {\n                await addReaction('-1');\n                await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\\n```\\n' + setPatchRes.body + '\\n```');\n                process.exit(1);\n              }\n              await revalidate(slug);\n              const preview = codeBlockValue.length > 300 ? codeBlockValue.substring(0, 300) + '…' : codeBlockValue;\n              await addReaction('+1');\n              await postComment(\n                '✅ **PocketBase Bot**: Set `' + fieldName + '` for **`' + slug + '`**\\n\\n' +\n                '**Value set:**\\n```\\n' + preview + '\\n```\\n\\n' +\n                '*Executed by @' + actor + '*'\n              );\n\n            } else {\n              // ── FIELD=VALUE PATH ─────────────────────────────────────────────\n              const ALLOWED_FIELDS = {\n                name:             'string',\n                description:      'string',\n                logo:             'string',\n                documentation:    'string',\n                website:          'string',\n                project_url:      'string',\n                github:           'string',\n                config_path:      'string',\n                tags:             'string',\n                port:             'number',\n                default_user:     'nullable_string',\n                default_passwd:   'nullable_string',\n                unprivileged:     'number',\n                updateable:       'boolean',\n                privileged:       'boolean',\n                has_arm:          'boolean',\n                is_dev:           'boolean',\n                is_disabled:      'boolean',\n                disable_message:  'string',\n                is_deleted:       'boolean',\n                deleted_message:  'string',\n              };\n\n              const parsedFields = parseKVPairs(rest);\n\n              const unknownFields = Object.keys(parsedFields).filter(function (f) { return !ALLOWED_FIELDS[f]; });\n              if (unknownFields.length > 0) {\n                await addReaction('-1');\n                await postComment(\n                  '❌ **PocketBase Bot**: Unknown field(s): `' + unknownFields.join('`, `') + '`\\n\\n' +\n                  '**Allowed fields:** `' + Object.keys(ALLOWED_FIELDS).join('`, `') + '`'\n                );\n                process.exit(0);\n              }\n\n              if (Object.keys(parsedFields).length === 0) {\n                await addReaction('-1');\n                await postComment('❌ **PocketBase Bot**: Could not parse any valid `field=value` pairs.\\n\\n' + HELP_TEXT);\n                process.exit(0);\n              }\n\n              // Cast values to correct types\n              const payload = {};\n              for (const [key, rawVal] of Object.entries(parsedFields)) {\n                const type = ALLOWED_FIELDS[key];\n                if (type === 'boolean') {\n                  if (rawVal === 'true') payload[key] = true;\n                  else if (rawVal === 'false') payload[key] = false;\n                  else {\n                    await addReaction('-1');\n                    await postComment('❌ **PocketBase Bot**: `' + key + '` must be `true` or `false`, got: `' + rawVal + '`');\n                    process.exit(0);\n                  }\n                } else if (type === 'number') {\n                  const n = parseInt(rawVal, 10);\n                  if (isNaN(n)) {\n                    await addReaction('-1');\n                    await postComment('❌ **PocketBase Bot**: `' + key + '` must be a number, got: `' + rawVal + '`');\n                    process.exit(0);\n                  }\n                  payload[key] = n;\n                } else if (type === 'nullable_string') {\n                  payload[key] = rawVal === '' ? null : rawVal;\n                } else {\n                  payload[key] = rawVal;\n                }\n              }\n\n              const patchRes = await request(recordsUrl + '/' + record.id, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify(payload)\n              });\n              if (!patchRes.ok) {\n                await addReaction('-1');\n                await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\\n```\\n' + patchRes.body + '\\n```');\n                process.exit(1);\n              }\n              await revalidate(slug);\n              const FIELD_TO_CT_VAR = { tags: 'var_tags', unprivileged: 'var_unprivileged' };\n              const fieldCtChanges = {};\n              for (const [k, v] of Object.entries(payload)) {\n                if (FIELD_TO_CT_VAR[k]) fieldCtChanges[FIELD_TO_CT_VAR[k]] = v;\n              }\n              let fieldCtSync = null;\n              try {\n                fieldCtSync = await upsertCtDefaultsPr(slug, fieldCtChanges);\n              } catch (e) {\n                fieldCtSync = { status: 'skipped', reason: 'CT sync failed: ' + e.message };\n              }\n              await addReaction('+1');\n              const changesLines = Object.entries(payload)\n                .map(function ([k, v]) { return '- `' + k + '` → `' + JSON.stringify(v) + '`'; })\n                .join('\\n');\n              await postComment(\n                '✅ **PocketBase Bot**: Updated **`' + slug + '`** successfully!\\n\\n' +\n                '**Changes applied:**\\n' + changesLines + '\\n\\n' +\n                formatCtSyncResult(fieldCtSync) + '\\n\\n' +\n                '*Executed by @' + actor + '*'\n              );\n            }\n\n            console.log('Done.');\n          })().catch(function (e) {\n            console.error('Fatal error:', e.message || e);\n            process.exit(1);\n          });\n          ENDSCRIPT\n        shell: bash\n"
  },
  {
    "path": ".github/workflows/push-json-to-pocketbase.yml",
    "content": "name: Push JSON changes to PocketBase\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"json/**\"\n\njobs:\n  push-json:\n    runs-on: self-hosted\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Get changed JSON files with slug\n        id: changed\n        run: |\n          changed=$(git diff --name-only \"${{ github.event.before }}\" \"${{ github.event.after }}\" -- json/ | grep '\\.json$' || true)\n          with_slug=\"\"\n          for f in $changed; do\n            [[ -f \"$f\" ]] || continue\n            jq -e '.slug' \"$f\" >/dev/null 2>&1 && with_slug=\"$with_slug $f\"\n          done\n          with_slug=$(echo $with_slug | xargs -n1)\n          if [[ -z \"$with_slug\" ]]; then\n            echo \"No app JSON files changed (or no files with slug).\"\n            echo \"count=0\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n          echo \"$with_slug\" > changed_app_jsons.txt\n          echo \"count=$(echo \"$with_slug\" | wc -w)\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Push to PocketBase\n        if: steps.changed.outputs.count != '0'\n        env:\n          POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}\n          POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}\n          POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}\n          POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}\n        run: |\n          node << 'ENDSCRIPT'\n          (async function() {\n          const fs = require('fs');\n          const https = require('https');\n          const http = require('http');\n          const url = require('url');\n          function request(fullUrl, opts, redirectCount) {\n            redirectCount = redirectCount || 0;\n            return new Promise(function(resolve, reject) {\n              const u = url.parse(fullUrl);\n              const isHttps = u.protocol === 'https:';\n              const body = opts.body;\n              const options = {\n                hostname: u.hostname,\n                port: u.port || (isHttps ? 443 : 80),\n                path: u.path,\n                method: opts.method || 'GET',\n                headers: opts.headers || {}\n              };\n              if (body) options.headers['Content-Length'] = Buffer.byteLength(body);\n              const lib = isHttps ? https : http;\n              const req = lib.request(options, function(res) {\n                if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {\n                  if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));\n                  const redirectUrl = url.resolve(fullUrl, res.headers.location);\n                  res.resume();\n                  resolve(request(redirectUrl, opts, redirectCount + 1));\n                  return;\n                }\n                let data = '';\n                res.on('data', function(chunk) { data += chunk; });\n                res.on('end', function() {\n                  resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });\n                });\n              });\n              req.on('error', reject);\n              if (body) req.write(body);\n              req.end();\n            });\n          }\n          const raw = process.env.POCKETBASE_URL.replace(/\\/$/, '');\n          const apiBase = /\\/api$/i.test(raw) ? raw : raw + '/api';\n          const coll = process.env.POCKETBASE_COLLECTION;\n          const files = fs.readFileSync('changed_app_jsons.txt', 'utf8').trim().split(/\\s+/).filter(Boolean);\n          const authUrl = apiBase + '/collections/users/auth-with-password';\n          console.log('Auth URL: ' + authUrl);\n          const authBody = JSON.stringify({\n            identity: process.env.POCKETBASE_ADMIN_EMAIL,\n            password: process.env.POCKETBASE_ADMIN_PASSWORD\n          });\n          const authRes = await request(authUrl, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: authBody\n          });\n          if (!authRes.ok) {\n            throw new Error('Auth failed. Tried: ' + authUrl + ' - Verify POST to that URL with body {\"identity\":\"...\",\"password\":\"...\"} works. Response: ' + authRes.body);\n          }\n          const token = JSON.parse(authRes.body).token;\n          const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';\n          let categoryIdToName = {};\n          try {\n            const metadata = JSON.parse(fs.readFileSync('json/metadata.json', 'utf8'));\n            (metadata.categories || []).forEach(function(cat) { categoryIdToName[cat.id] = cat.name; });\n          } catch (e) { console.warn('Could not load metadata.json:', e.message); }\n          let typeValueToId = {};\n          let categoryNameToPbId = {};\n          try {\n            const typesRes = await request(apiBase + '/collections/z_ref_script_types/records?perPage=500', { headers: { 'Authorization': token } });\n            if (typesRes.ok) {\n              const typesData = JSON.parse(typesRes.body);\n              (typesData.items || []).forEach(function(item) {\n                if (item.type != null) typeValueToId[item.type] = item.id;\n                if (item.name != null) typeValueToId[item.name] = item.id;\n                if (item.value != null) typeValueToId[item.value] = item.id;\n              });\n            }\n          } catch (e) { console.warn('Could not fetch z_ref_script_types:', e.message); }\n          try {\n            const catRes = await request(apiBase + '/collections/script_categories/records?perPage=500', { headers: { 'Authorization': token } });\n            if (catRes.ok) {\n              const catData = JSON.parse(catRes.body);\n              (catData.items || []).forEach(function(item) { if (item.name) categoryNameToPbId[item.name] = item.id; });\n            }\n          } catch (e) { console.warn('Could not fetch script_categories:', e.message); }\n          var noteTypeToId = {};\n          var installMethodTypeToId = {};\n          var osToId = {};\n          var osVersionToId = {};\n          try {\n            const res = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });\n            if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) { noteTypeToId[item.type] = item.id; noteTypeToId[item.type.toLowerCase()] = item.id; } });\n          } catch (e) { console.warn('z_ref_note_types:', e.message); }\n          try {\n            const res = await request(apiBase + '/collections/z_ref_install_method_types/records?perPage=500', { headers: { 'Authorization': token } });\n            if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) { installMethodTypeToId[item.type] = item.id; installMethodTypeToId[item.type.toLowerCase()] = item.id; } });\n          } catch (e) { console.warn('z_ref_install_method_types:', e.message); }\n          try {\n            const res = await request(apiBase + '/collections/z_ref_os/records?perPage=500', { headers: { 'Authorization': token } });\n            if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.os != null) { osToId[item.os] = item.id; osToId[item.os.toLowerCase()] = item.id; } });\n          } catch (e) { console.warn('z_ref_os:', e.message); }\n          try {\n            const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });\n            if (res.ok) {\n              (JSON.parse(res.body).items || []).forEach(function(item) {\n                var osName = item.expand && item.expand.os && item.expand.os.os != null ? item.expand.os.os : null;\n                if (osName != null && item.version != null) osVersionToId[osName + '|' + item.version] = item.id;\n              });\n            }\n          } catch (e) { console.warn('z_ref_os_version:', e.message); }\n          var notesCollUrl = apiBase + '/collections/script_notes/records';\n          var installMethodsCollUrl = apiBase + '/collections/script_install_methods/records';\n          for (const file of files) {\n            if (!fs.existsSync(file)) continue;\n            const data = JSON.parse(fs.readFileSync(file, 'utf8'));\n            if (!data.slug) { console.log('Skipping', file, '(no slug)'); continue; }\n            var payload = {\n              name: data.name,\n              slug: data.slug,\n              script_created: data.date_created || data.script_created,\n              script_updated: new Date().toISOString().split('T')[0],\n              updateable: data.updateable,\n              privileged: data.privileged,\n              port: data.interface_port != null ? data.interface_port : data.port,\n              documentation: data.documentation,\n              website: data.website,\n              logo: data.logo,\n              description: data.description,\n              default_user: (data.default_credentials && data.default_credentials.username) || data.default_user || null,\n              default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd || null,\n              is_dev: false\n            };\n            var resolvedType = typeValueToId[data.type];\n            if (resolvedType == null && data.type === 'ct') resolvedType = typeValueToId['lxc'];\n            if (resolvedType) payload.type = resolvedType;\n            var resolvedCats = (data.categories || []).map(function(n) { return categoryNameToPbId[categoryIdToName[n]]; }).filter(Boolean);\n            if (resolvedCats.length) payload.categories = resolvedCats;\n            if (data.version !== undefined) payload.version = data.version;\n            if (data.changelog !== undefined) payload.changelog = data.changelog;\n            if (data.screenshots !== undefined) payload.screenshots = data.screenshots;\n            const filter = \"(slug='\" + data.slug + \"')\";\n            const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {\n              headers: { 'Authorization': token }\n            });\n            const list = JSON.parse(listRes.body);\n            const existingId = list.items && list.items[0] && list.items[0].id;\n            async function resolveNotesAndInstallMethods(scriptId) {\n              var noteIds = [];\n              for (var i = 0; i < (data.notes || []).length; i++) {\n                var note = data.notes[i];\n                var typeId = noteTypeToId[note.type];\n                if (typeId == null) continue;\n                var postRes = await request(notesCollUrl, {\n                  method: 'POST',\n                  headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                  body: JSON.stringify({ text: note.text || '', type: typeId, script: scriptId })\n                });\n                if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);\n              }\n              var installMethodIds = [];\n              for (var j = 0; j < (data.install_methods || []).length; j++) {\n                var im = data.install_methods[j];\n                var typeId = installMethodTypeToId[im.type];\n                var res = im.resources || {};\n                var osId = osToId[res.os];\n                var osVersionKey = (res.os != null && res.version != null) ? res.os + '|' + res.version : null;\n                var osVersionId = osVersionKey ? osVersionToId[osVersionKey] : null;\n                var imBody = {\n                  script: scriptId,\n                  resources_cpu: res.cpu != null ? res.cpu : 0,\n                  resources_ram: res.ram != null ? res.ram : 0,\n                  resources_hdd: res.hdd != null ? res.hdd : 0\n                };\n                if (typeId) imBody.type = typeId;\n                if (osId) imBody.os = osId;\n                if (osVersionId) imBody.os_version = osVersionId;\n                var imPostRes = await request(installMethodsCollUrl, {\n                  method: 'POST',\n                  headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                  body: JSON.stringify(imBody)\n                });\n                if (imPostRes.ok) installMethodIds.push(JSON.parse(imPostRes.body).id);\n              }\n              return { noteIds: noteIds, installMethodIds: installMethodIds };\n            }\n            if (existingId) {\n              var resolved = await resolveNotesAndInstallMethods(existingId);\n              payload.notes = resolved.noteIds;\n              payload.install_methods = resolved.installMethodIds;\n              console.log('Updating', file, '(slug=' + data.slug + ')');\n              const r = await request(recordsUrl + '/' + existingId, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify(payload)\n              });\n              if (!r.ok) throw new Error('PATCH failed: ' + r.body);\n            } else {\n              console.log('Creating', file, '(slug=' + data.slug + ')');\n              const r = await request(recordsUrl, {\n                method: 'POST',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify(payload)\n              });\n              if (!r.ok) throw new Error('POST failed: ' + r.body);\n              var scriptId = JSON.parse(r.body).id;\n              var resolved = await resolveNotesAndInstallMethods(scriptId);\n              var patchRes = await request(recordsUrl + '/' + scriptId, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify({ install_methods: resolved.installMethodIds, notes: resolved.noteIds })\n              });\n              if (!patchRes.ok) throw new Error('PATCH relations failed: ' + patchRes.body);\n            }\n          }\n          console.log('Done.');\n          })().catch(e => { console.error(e); process.exit(1); });\n          ENDSCRIPT\n        shell: bash\n"
  },
  {
    "path": ".github/workflows/trigger_github_pages_redirect.yml",
    "content": "name: Pages Redirect\n\non:\n  workflow_dispatch:\n\npermissions:\n  pages: write\n  id-token: write\n  contents: read\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Create redirect page\n        run: |\n          mkdir site\n          cat <<EOF > site/index.html\n          <!DOCTYPE html>\n          <html>\n          <head>\n            <meta charset=\"utf-8\">\n            <meta http-equiv=\"refresh\" content=\"0; url=https://community-scripts.org/\">\n            <link rel=\"canonical\" href=\"https://community-scripts.org/\">\n            <title>Redirecting...</title>\n          </head>\n          <body>\n            Redirecting...\n          </body>\n          </html>\n          EOF\n\n      - uses: actions/upload-pages-artifact@v3\n        with:\n          path: site\n\n      - name: Deploy\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/update-script-timestamp-on-sh-change.yml",
    "content": "name: Update script timestamp on .sh changes\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"ct/**/*.sh\"\n      - \"install/**/*.sh\"\n      - \"tools/**/*.sh\"\n      - \"turnkey/**/*.sh\"\n      - \"vm/**/*.sh\"\n\njobs:\n  update-script-timestamp:\n    runs-on: self-hosted\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Get changed .sh files and derive slugs\n        id: slugs\n        run: |\n          changed=$(git diff --name-only \"${{ github.event.before }}\" \"${{ github.event.after }}\" -- ct/ install/ tools/ turnkey/ vm/ | grep '\\.sh$' || true)\n          if [[ -z \"$changed\" ]]; then\n            echo \"No .sh files changed in ct/, install/, tools/, turnkey/, or vm/.\"\n            echo \"count=0\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n          declare -A seen\n          slugs=\"\"\n          for f in $changed; do\n            [[ -f \"$f\" ]] || continue\n            base=\"${f##*/}\"\n            base=\"${base%.sh}\"\n            if [[ \"$f\" == install/* && \"$base\" == *-install ]]; then\n              slug=\"${base%-install}\"\n            else\n              slug=\"$base\"\n            fi\n            if [[ -z \"${seen[$slug]:-}\" ]]; then\n              seen[$slug]=1\n              slugs=\"$slugs $slug\"\n            fi\n          done\n          slugs=$(echo $slugs | xargs -n1 | sort -u)\n          if [[ -z \"$slugs\" ]]; then\n            echo \"No slugs to update.\"\n            echo \"count=0\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n          echo \"$slugs\" > changed_slugs.txt\n          echo \"count=$(echo \"$slugs\" | wc -w)\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Parse PR number from merge commit\n        id: pr\n        run: |\n          re='#([0-9]+)'\n          if [[ \"$COMMIT_MSG\" =~ $re ]]; then\n            echo \"number=${BASH_REMATCH[1]}\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"number=\" >> \"$GITHUB_OUTPUT\"\n          fi\n        env:\n          COMMIT_MSG: ${{ github.event.head_commit.message }}\n\n      - name: Update script timestamps in PocketBase\n        if: steps.slugs.outputs.count != '0'\n        env:\n          POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}\n          POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}\n          POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}\n          POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}\n          COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}\n          PR_URL: ${{ steps.pr.outputs.number != '' && format('{0}/{1}/pull/{2}', github.server_url, github.repository, steps.pr.outputs.number) || '' }}\n        run: |\n          node << 'ENDSCRIPT'\n          (async function() {\n            const fs = require('fs');\n            const https = require('https');\n            const http = require('http');\n            const url = require('url');\n\n            function request(fullUrl, opts, redirectCount) {\n              redirectCount = redirectCount || 0;\n              return new Promise(function(resolve, reject) {\n                const u = url.parse(fullUrl);\n                const isHttps = u.protocol === 'https:';\n                const body = opts.body;\n                const options = {\n                  hostname: u.hostname,\n                  port: u.port || (isHttps ? 443 : 80),\n                  path: u.path,\n                  method: opts.method || 'GET',\n                  headers: opts.headers || {}\n                };\n                if (body) options.headers['Content-Length'] = Buffer.byteLength(body);\n                const lib = isHttps ? https : http;\n                const req = lib.request(options, function(res) {\n                  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {\n                    if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));\n                    const redirectUrl = url.resolve(fullUrl, res.headers.location);\n                    res.resume();\n                    resolve(request(redirectUrl, opts, redirectCount + 1));\n                    return;\n                  }\n                  let data = '';\n                  res.on('data', function(chunk) { data += chunk; });\n                  res.on('end', function() {\n                    resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });\n                  });\n                });\n                req.on('error', reject);\n                if (body) req.write(body);\n                req.end();\n              });\n            }\n\n            const raw = process.env.POCKETBASE_URL.replace(/\\/$/, '');\n            const apiBase = /\\/api$/i.test(raw) ? raw : raw + '/api';\n            const coll = process.env.POCKETBASE_COLLECTION;\n            const slugsText = fs.readFileSync('changed_slugs.txt', 'utf8').trim();\n            const slugs = slugsText ? slugsText.split(/\\s+/).filter(Boolean) : [];\n            if (slugs.length === 0) {\n              console.log('No slugs to update.');\n              return;\n            }\n\n            const authUrl = apiBase + '/collections/users/auth-with-password';\n            const authBody = JSON.stringify({\n              identity: process.env.POCKETBASE_ADMIN_EMAIL,\n              password: process.env.POCKETBASE_ADMIN_PASSWORD\n            });\n            const authRes = await request(authUrl, {\n              method: 'POST',\n              headers: { 'Content-Type': 'application/json' },\n              body: authBody\n            });\n            if (!authRes.ok) {\n              throw new Error('Auth failed: ' + authRes.body);\n            }\n            const token = JSON.parse(authRes.body).token;\n            const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';\n\n            for (const slug of slugs) {\n              const filter = \"(slug='\" + slug.replace(/'/g, \"''\") + \"')\";\n              const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {\n                headers: { 'Authorization': token }\n              });\n              const list = JSON.parse(listRes.body);\n              const record = list.items && list.items[0];\n              if (!record) {\n                console.log('Slug not in DB, skipping: ' + slug);\n                continue;\n              }\n              const today = new Date().toISOString().split('T')[0];\n              const patchBody = {\n                script_updated: today,\n                last_update_commit: process.env.PR_URL || process.env.COMMIT_URL || ''\n              };\n              // When a dev script is merged into main, promote it to production\n              if (record.is_dev === true) {\n                patchBody.is_dev = false;\n                patchBody.script_created = today;\n                console.log('Promoting dev script to production: ' + slug);\n              }\n              const patchRes = await request(recordsUrl + '/' + record.id, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify(patchBody)\n              });\n              if (!patchRes.ok) {\n                console.warn('PATCH failed for slug ' + slug + ': ' + patchRes.body);\n                continue;\n              }\n              console.log('Updated timestamp for slug: ' + slug);\n            }\n            console.log('Done.');\n          })().catch(e => { console.error(e); process.exit(1); });\n          ENDSCRIPT\n        shell: bash\n"
  },
  {
    "path": ".gitignore",
    "content": "script*.py\n# General OS files\n.DS_Store\nThumbs.db\ntest-app/\n\n# Editor & IDE files\n!.vscode/\n.vscode/*.workspace\n.vscode/*.tmp\n\n# Log files\nlogs/\n*.log\n\n# Install scripts and temporary files\ninstall/tmp/\ninstall/*.bak\n\n# VM and Container-specific exclusions\nvm/tmp/\nvm/*.qcow2\nvm/*.img\nvm/*.vmdk\nvm/*.iso\nvm/*.bak\n\n# Miscellaneous temporary files\n*.bak\n*.swp\n*.swo\n*.swn\n*.tmp\n*.backup\n\n# JSON temporary files\njson/\njson/*.bak\njson/*.tmp\n"
  },
  {
    "path": ".shellcheckrc",
    "content": "disable=SC2034,SC1091,SC2155,SC2086,SC2317,SC2181,SC2164\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"bmalehorn.shell-syntax\",\n    \"timonwong.shellcheck\",\n    \"foxundermoon.shell-format\",\n    \"editorconfig.editorconfig\"\n  ],\n  \"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"files.associations\": {\n        \"*.func\": \"shellscript\"\n    },\n    \"files.eol\": \"\\n\",\n    \"files.insertFinalNewline\": true,\n    \"editor.formatOnSave\": true,\n    \"editor.codeActionsOnSave\": {\n        \"source.fixAll\": \"explicit\"\n    }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "﻿<div align=\"center\">\n  <a href=\"#\">\n    <img src=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo.png\" height=\"100px\" />\n </a>\n</div>\n<h1 align=\"center\">Changelog</h1>\n\n<h3 align=\"center\">All notable changes to this project will be documented in this file.</h3>\n\n> [!CAUTION]\nExercise vigilance regarding copycat or coat-tailing sites that seek to exploit the project's popularity for potentially malicious purposes.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<details>\n<summary><h2>📜 History</h2></summary>\n\n\n<details>\n<summary><h3>2026</h3></summary>\n\n\n<details>\n<summary><h4>May (9 entries)</h4></summary>\n\n[View May 2026 Changelog](.github/changelogs/2026/05.md)\n\n</details>\n\n<details>\n<summary><h4>April (30 entries)</h4></summary>\n\n[View April 2026 Changelog](.github/changelogs/2026/04.md)\n\n</details>\n\n<details>\n<summary><h4>March (31 entries)</h4></summary>\n\n[View March 2026 Changelog](.github/changelogs/2026/03.md)\n\n</details>\n\n<details>\n<summary><h4>February (28 entries)</h4></summary>\n\n[View February 2026 Changelog](.github/changelogs/2026/02.md)\n\n</details>\n\n<details>\n<summary><h4>January (31 entries)</h4></summary>\n\n[View January 2026 Changelog](.github/changelogs/2026/01.md)\n\n</details>\n\n</details>\n\n<details>\n<summary><h3>2025</h3></summary>\n\n\n<details>\n<summary><h4>December (31 entries)</h4></summary>\n\n[View December 2025 Changelog](.github/changelogs/2025/12.md)\n\n</details>\n\n<details>\n<summary><h4>November (29 entries)</h4></summary>\n\n[View November 2025 Changelog](.github/changelogs/2025/11.md)\n\n</details>\n\n<details>\n<summary><h4>October (30 entries)</h4></summary>\n\n[View October 2025 Changelog](.github/changelogs/2025/10.md)\n\n</details>\n\n<details>\n<summary><h4>September (29 entries)</h4></summary>\n\n[View September 2025 Changelog](.github/changelogs/2025/09.md)\n\n</details>\n\n<details>\n<summary><h4>August (30 entries)</h4></summary>\n\n[View August 2025 Changelog](.github/changelogs/2025/08.md)\n\n</details>\n\n<details>\n<summary><h4>July (29 entries)</h4></summary>\n\n[View July 2025 Changelog](.github/changelogs/2025/07.md)\n\n</details>\n\n<details>\n<summary><h4>June (29 entries)</h4></summary>\n\n[View June 2025 Changelog](.github/changelogs/2025/06.md)\n\n</details>\n\n<details>\n<summary><h4>May (30 entries)</h4></summary>\n\n[View May 2025 Changelog](.github/changelogs/2025/05.md)\n\n</details>\n\n<details>\n<summary><h4>April (25 entries)</h4></summary>\n\n[View April 2025 Changelog](.github/changelogs/2025/04.md)\n\n</details>\n\n<details>\n<summary><h4>March (30 entries)</h4></summary>\n\n[View March 2025 Changelog](.github/changelogs/2025/03.md)\n\n</details>\n\n<details>\n<summary><h4>February (26 entries)</h4></summary>\n\n[View February 2025 Changelog](.github/changelogs/2025/02.md)\n\n</details>\n\n<details>\n<summary><h4>January (27 entries)</h4></summary>\n\n[View January 2025 Changelog](.github/changelogs/2025/01.md)\n\n</details>\n\n</details>\n\n<details>\n<summary><h3>2024</h3></summary>\n\n\n<details>\n<summary><h4>December (22 entries)</h4></summary>\n\n[View December 2024 Changelog](.github/changelogs/2024/12.md)\n\n</details>\n\n<details>\n<summary><h4>November (15 entries)</h4></summary>\n\n[View November 2024 Changelog](.github/changelogs/2024/11.md)\n\n</details>\n\n<details>\n<summary><h4>October (9 entries)</h4></summary>\n\n[View October 2024 Changelog](.github/changelogs/2024/10.md)\n\n</details>\n\n<details>\n<summary><h4>September (1 entries)</h4></summary>\n\n[View September 2024 Changelog](.github/changelogs/2024/09.md)\n\n</details>\n\n<details>\n<summary><h4>August (2 entries)</h4></summary>\n\n[View August 2024 Changelog](.github/changelogs/2024/08.md)\n\n</details>\n\n<details>\n<summary><h4>July (0 entries)</h4></summary>\n\n[View July 2024 Changelog](.github/changelogs/2024/07.md)\n\n</details>\n\n<details>\n<summary><h4>June (8 entries)</h4></summary>\n\n[View June 2024 Changelog](.github/changelogs/2024/06.md)\n\n</details>\n\n<details>\n<summary><h4>May (16 entries)</h4></summary>\n\n[View May 2024 Changelog](.github/changelogs/2024/05.md)\n\n</details>\n\n<details>\n<summary><h4>April (14 entries)</h4></summary>\n\n[View April 2024 Changelog](.github/changelogs/2024/04.md)\n\n</details>\n\n<details>\n<summary><h4>March (5 entries)</h4></summary>\n\n[View March 2024 Changelog](.github/changelogs/2024/03.md)\n\n</details>\n\n<details>\n<summary><h4>February (9 entries)</h4></summary>\n\n[View February 2024 Changelog](.github/changelogs/2024/02.md)\n\n</details>\n\n<details>\n<summary><h4>January (9 entries)</h4></summary>\n\n[View January 2024 Changelog](.github/changelogs/2024/01.md)\n\n</details>\n\n</details>\n\n<details>\n<summary><h3>2023</h3></summary>\n\n\n<details>\n<summary><h4>December (3 entries)</h4></summary>\n\n[View December 2023 Changelog](.github/changelogs/2023/12.md)\n\n</details>\n\n<details>\n<summary><h4>November (3 entries)</h4></summary>\n\n[View November 2023 Changelog](.github/changelogs/2023/11.md)\n\n</details>\n\n<details>\n<summary><h4>October (7 entries)</h4></summary>\n\n[View October 2023 Changelog](.github/changelogs/2023/10.md)\n\n</details>\n\n<details>\n<summary><h4>September (10 entries)</h4></summary>\n\n[View September 2023 Changelog](.github/changelogs/2023/09.md)\n\n</details>\n\n<details>\n<summary><h4>August (7 entries)</h4></summary>\n\n[View August 2023 Changelog](.github/changelogs/2023/08.md)\n\n</details>\n\n<details>\n<summary><h4>July (5 entries)</h4></summary>\n\n[View July 2023 Changelog](.github/changelogs/2023/07.md)\n\n</details>\n\n<details>\n<summary><h4>June (5 entries)</h4></summary>\n\n[View June 2023 Changelog](.github/changelogs/2023/06.md)\n\n</details>\n\n<details>\n<summary><h4>May (8 entries)</h4></summary>\n\n[View May 2023 Changelog](.github/changelogs/2023/05.md)\n\n</details>\n\n<details>\n<summary><h4>April (8 entries)</h4></summary>\n\n[View April 2023 Changelog](.github/changelogs/2023/04.md)\n\n</details>\n\n<details>\n<summary><h4>March (8 entries)</h4></summary>\n\n[View March 2023 Changelog](.github/changelogs/2023/03.md)\n\n</details>\n\n<details>\n<summary><h4>February (6 entries)</h4></summary>\n\n[View February 2023 Changelog](.github/changelogs/2023/02.md)\n\n</details>\n\n<details>\n<summary><h4>January (15 entries)</h4></summary>\n\n[View January 2023 Changelog](.github/changelogs/2023/01.md)\n\n</details>\n\n</details>\n\n<details>\n<summary><h3>2022</h3></summary>\n\n\n<details>\n<summary><h4>December (7 entries)</h4></summary>\n\n[View December 2022 Changelog](.github/changelogs/2022/12.md)\n\n</details>\n\n<details>\n<summary><h4>November (7 entries)</h4></summary>\n\n[View November 2022 Changelog](.github/changelogs/2022/11.md)\n\n</details>\n\n<details>\n<summary><h4>October (2 entries)</h4></summary>\n\n[View October 2022 Changelog](.github/changelogs/2022/10.md)\n\n</details>\n\n<details>\n<summary><h4>September (9 entries)</h4></summary>\n\n[View September 2022 Changelog](.github/changelogs/2022/09.md)\n\n</details>\n\n<details>\n<summary><h4>August (7 entries)</h4></summary>\n\n[View August 2022 Changelog](.github/changelogs/2022/08.md)\n\n</details>\n\n<details>\n<summary><h4>July (10 entries)</h4></summary>\n\n[View July 2022 Changelog](.github/changelogs/2022/07.md)\n\n</details>\n\n<details>\n<summary><h4>June (1 entries)</h4></summary>\n\n[View June 2022 Changelog](.github/changelogs/2022/06.md)\n\n</details>\n\n<details>\n<summary><h4>May (8 entries)</h4></summary>\n\n[View May 2022 Changelog](.github/changelogs/2022/05.md)\n\n</details>\n\n<details>\n<summary><h4>April (13 entries)</h4></summary>\n\n[View April 2022 Changelog](.github/changelogs/2022/04.md)\n\n</details>\n\n<details>\n<summary><h4>March (20 entries)</h4></summary>\n\n[View March 2022 Changelog](.github/changelogs/2022/03.md)\n\n</details>\n\n<details>\n<summary><h4>February (15 entries)</h4></summary>\n\n[View February 2022 Changelog](.github/changelogs/2022/02.md)\n\n</details>\n\n<details>\n<summary><h4>January (3 entries)</h4></summary>\n\n[View January 2022 Changelog](.github/changelogs/2022/01.md)\n\n</details>\n\n</details>\n\n</details>\n\n## 2026-05-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Save Omada version [@lucacome](https://github.com/lucacome) ([#14433](https://github.com/community-scripts/ProxmoxVE/pull/14433))\n\n## 2026-05-11\n\n### 🆕 New Scripts\n\n  - Lychee ([#14424](https://github.com/community-scripts/ProxmoxVE/pull/14424))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Termix: fix nginx pid path and log paths on update (#) [@MickLesk](https://github.com/MickLesk) ([#14419](https://github.com/community-scripts/ProxmoxVE/pull/14419))\n    - Nginxproxymanager: restore NPM nginx.conf after OpenResty rebuid [@MickLesk](https://github.com/MickLesk) ([#14421](https://github.com/community-scripts/ProxmoxVE/pull/14421))\n\n  - #### 🔧 Refactor\n\n    - InvestBrain: add commented reverse proxy config hints to .env [@MickLesk](https://github.com/MickLesk) ([#14422](https://github.com/community-scripts/ProxmoxVE/pull/14422))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - Cronmaster: fix unexpected EOF in update_cronmaster script [@MickLesk](https://github.com/MickLesk) ([#14420](https://github.com/community-scripts/ProxmoxVE/pull/14420))\n\n## 2026-05-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Save Beszel version [@lucacome](https://github.com/lucacome) ([#14389](https://github.com/community-scripts/ProxmoxVE/pull/14389))\n    - karakeep: Fix SERVER_VERSION update [@MickLesk](https://github.com/MickLesk) ([#14378](https://github.com/community-scripts/ProxmoxVE/pull/14378))\n    - inspIRCd: Fix service not autostarting [@tremor021](https://github.com/tremor021) ([#14368](https://github.com/community-scripts/ProxmoxVE/pull/14368))\n\n  - #### 🔧 Refactor\n\n    - refactor: webcheck [@CrazyWolf13](https://github.com/CrazyWolf13) ([#14391](https://github.com/community-scripts/ProxmoxVE/pull/14391))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - [tools.func]: Pin `pnpm` version [@tremor021](https://github.com/tremor021) ([#14386](https://github.com/community-scripts/ProxmoxVE/pull/14386))\n\n## 2026-05-09\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - FlowiseAI: Migrate to pnpm [@MickLesk](https://github.com/MickLesk) ([#14344](https://github.com/community-scripts/ProxmoxVE/pull/14344))\n    - Purge openresty [@lucacome](https://github.com/lucacome) ([#14353](https://github.com/community-scripts/ProxmoxVE/pull/14353))\n    - Check for release for Sonarr [@lucacome](https://github.com/lucacome) ([#14354](https://github.com/community-scripts/ProxmoxVE/pull/14354))\n    - fix(termix-install.sh): add tmpfiles.d persistence and systemd PIDFile path [@runnylogan](https://github.com/runnylogan) ([#14350](https://github.com/community-scripts/ProxmoxVE/pull/14350))\n    - ERPNext: start bench Redis services before bench new-site [@MickLesk](https://github.com/MickLesk) ([#14343](https://github.com/community-scripts/ProxmoxVE/pull/14343))\n    - [Hotfix]Jotty: use absolute path when creating data dir [@vhsdream](https://github.com/vhsdream) ([#14355](https://github.com/community-scripts/ProxmoxVE/pull/14355))\n\n## 2026-05-08\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - wishlist: pin pnpm to v10 to match engine requirements [@MickLesk](https://github.com/MickLesk) ([#14342](https://github.com/community-scripts/ProxmoxVE/pull/14342))\n    - [pelican] fix env copy regression [@LetterN](https://github.com/LetterN) ([#14328](https://github.com/community-scripts/ProxmoxVE/pull/14328))\n    - fix(homepage): fix ERR_PNPM_IGNORED_BUILDS error [@Sergih28](https://github.com/Sergih28) ([#14315](https://github.com/community-scripts/ProxmoxVE/pull/14315))\n\n  - #### ✨ New Features\n\n    - tools.func: add setup_nltk as new function [@MickLesk](https://github.com/MickLesk) ([#14314](https://github.com/community-scripts/ProxmoxVE/pull/14314))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: fix meilisearch import-dump background process handling [@MickLesk](https://github.com/MickLesk) ([#14341](https://github.com/community-scripts/ProxmoxVE/pull/14341))\n\n## 2026-05-07\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - termix: create /tmp/nginx before nginx -t [@MickLesk](https://github.com/MickLesk) ([#14312](https://github.com/community-scripts/ProxmoxVE/pull/14312))\n    - The Lounge: Fix service not starting automaticaly [@tremor021](https://github.com/tremor021) ([#14311](https://github.com/community-scripts/ProxmoxVE/pull/14311))\n    - netbird-lxc: fix installation check [@MickLesk](https://github.com/MickLesk) ([#14309](https://github.com/community-scripts/ProxmoxVE/pull/14309))\n    - databasus: Backup and secure configuration file [@MickLesk](https://github.com/MickLesk) ([#14308](https://github.com/community-scripts/ProxmoxVE/pull/14308))\n    - vm: update disk image URL for Ubuntu 25.04 [@MickLesk](https://github.com/MickLesk) ([#14290](https://github.com/community-scripts/ProxmoxVE/pull/14290))\n\n  - #### ✨ New Features\n\n    - pangolin: bump version to 1.18.3 [@MickLesk](https://github.com/MickLesk) ([#14297](https://github.com/community-scripts/ProxmoxVE/pull/14297))\n\n### 🗑️ Deleted Scripts\n\n  - Remove: LiteLLM [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14294](https://github.com/community-scripts/ProxmoxVE/pull/14294))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - update-apps: some  improvements [@MickLesk](https://github.com/MickLesk) ([#14275](https://github.com/community-scripts/ProxmoxVE/pull/14275))\n\n## 2026-05-06\n\n### 🆕 New Scripts\n\n  - Hoodik ([#14279](https://github.com/community-scripts/ProxmoxVE/pull/14279))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Pelican-Panel: create backup subdirectory before copying storage [@MickLesk](https://github.com/MickLesk) ([#14274](https://github.com/community-scripts/ProxmoxVE/pull/14274))\n    - Rustdeskserver: remove redundant else with undefined RELEASE var [@MickLesk](https://github.com/MickLesk) ([#14272](https://github.com/community-scripts/ProxmoxVE/pull/14272))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - AdguardHome-Sync replace ifconfig with hostname -I for IP detection [@MickLesk](https://github.com/MickLesk) ([#14273](https://github.com/community-scripts/ProxmoxVE/pull/14273))\n\n## 2026-05-05\n\n### 🆕 New Scripts\n\n  - LibreChat ([#14247](https://github.com/community-scripts/ProxmoxVE/pull/14247))\n- Matomo ([#14248](https://github.com/community-scripts/ProxmoxVE/pull/14248))\n- Storyteller ([#14122](https://github.com/community-scripts/ProxmoxVE/pull/14122))\n\n### 🧰 Tools\n\n  - Fix container count message in update-apps.sh [@Quotacious](https://github.com/Quotacious) ([#14265](https://github.com/community-scripts/ProxmoxVE/pull/14265))\n\n## 2026-05-04\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Databasus: move .env to filesystem root so service starts correctly [@Copilot](https://github.com/Copilot) ([#14252](https://github.com/community-scripts/ProxmoxVE/pull/14252))\n    - Databasus: update mongo-tools fallback to 100.16.1 and use now pnpm instead of npm ci [@MickLesk](https://github.com/MickLesk) ([#14240](https://github.com/community-scripts/ProxmoxVE/pull/14240))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - tools.func get_latest_gh_tag - add pagination to find prefixed tags beyond first 50 [@MickLesk](https://github.com/MickLesk) ([#14241](https://github.com/community-scripts/ProxmoxVE/pull/14241))\n    - tools.func: add GitLab release check/fetch/deploy helpers [@MickLesk](https://github.com/MickLesk) ([#14242](https://github.com/community-scripts/ProxmoxVE/pull/14242))\n\n## 2026-05-03\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Hortusfox: fix update issues [@tomfrenzel](https://github.com/tomfrenzel) ([#14214](https://github.com/community-scripts/ProxmoxVE/pull/14214))\n\n  - #### ✨ New Features\n\n    - Refactor: PeaNUT for v6 [@MickLesk](https://github.com/MickLesk) ([#14224](https://github.com/community-scripts/ProxmoxVE/pull/14224))\n    - pangolin: pin version, drop manual SQL, use upstream migrator [@MickLesk](https://github.com/MickLesk) ([#14223](https://github.com/community-scripts/ProxmoxVE/pull/14223))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: fix validate_bridge function [@MichaelOultram](https://github.com/MichaelOultram) ([#14206](https://github.com/community-scripts/ProxmoxVE/pull/14206))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - pve/pbs scripts: guard sed against missing /etc/apt/sources.list [@MickLesk](https://github.com/MickLesk) ([#14222](https://github.com/community-scripts/ProxmoxVE/pull/14222))\n\n## 2026-05-02\n\n### 🆕 New Scripts\n\n  - protonmail-bridge ([#14136](https://github.com/community-scripts/ProxmoxVE/pull/14136))\n- Tube Archivist ([#14123](https://github.com/community-scripts/ProxmoxVE/pull/14123))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Nagios: Ping fix [@tremor021](https://github.com/tremor021) ([#14186](https://github.com/community-scripts/ProxmoxVE/pull/14186))\n    - opnsense-vm: retry pvesm alloc on transient zfs 'got timeout' errors [@MickLesk](https://github.com/MickLesk) ([#14157](https://github.com/community-scripts/ProxmoxVE/pull/14157))\n    - ImmichFrame: fix update by reinstalling dotnet-sdk before publish [@MickLesk](https://github.com/MickLesk) ([#14158](https://github.com/community-scripts/ProxmoxVE/pull/14158))\n    - [FIX]ShelfMark: Use UV sync for shelfmark backend build; update to Python 3.14 [@vhsdream](https://github.com/vhsdream) ([#14170](https://github.com/community-scripts/ProxmoxVE/pull/14170))\n    - alpine: remove deb/ubuntu-only resource & storage checks from update-script [@MickLesk](https://github.com/MickLesk) ([#14166](https://github.com/community-scripts/ProxmoxVE/pull/14166))\n    - Threadfin: use 'threadfin-app' as app name to avoid version-file clash  [@MickLesk](https://github.com/MickLesk) ([#14159](https://github.com/community-scripts/ProxmoxVE/pull/14159))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: prompt to also run installed addon update scripts (…/bin/update_*) after update_script [@MickLesk](https://github.com/MickLesk) ([#14162](https://github.com/community-scripts/ProxmoxVE/pull/14162))\n\n## 2026-05-01\n\n### 🆕 New Scripts\n\n  - SoulSync ([#14124](https://github.com/community-scripts/ProxmoxVE/pull/14124))\n- Teable ([#14125](https://github.com/community-scripts/ProxmoxVE/pull/14125))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Step ca update [@heinemannj](https://github.com/heinemannj) ([#14058](https://github.com/community-scripts/ProxmoxVE/pull/14058))\n    - paperless-ngx: refresh NLTK data on update [@kurtislanderson](https://github.com/kurtislanderson) ([#14144](https://github.com/community-scripts/ProxmoxVE/pull/14144))\n    - [Pelican Panel] stop deleting the public storage [@LetterN](https://github.com/LetterN) ([#14145](https://github.com/community-scripts/ProxmoxVE/pull/14145))\n\n  - #### 🔧 Refactor\n\n    - Mail-Archiver: update dependencies [@tremor021](https://github.com/tremor021) ([#14152](https://github.com/community-scripts/ProxmoxVE/pull/14152))\n\n## 2026-04-30\n\n### 🆕 New Scripts\n\n  - Nagios ([#14126](https://github.com/community-scripts/ProxmoxVE/pull/14126))\n- Neko ([#14121](https://github.com/community-scripts/ProxmoxVE/pull/14121))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - alpine-docker: install openssl as core dependency | alpine-komodo: check & install openssl if missing [@MickLesk](https://github.com/MickLesk) ([#14134](https://github.com/community-scripts/ProxmoxVE/pull/14134))\n    - endurain: update source references to Codeberg [@MickLesk](https://github.com/MickLesk) ([#14128](https://github.com/community-scripts/ProxmoxVE/pull/14128))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - tools.func: Manage minor versions for MongoDB 8.x [@tremor021](https://github.com/tremor021) ([#14131](https://github.com/community-scripts/ProxmoxVE/pull/14131))\n\n## 2026-04-29\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - GrayLog: MongoDB update to 8.2.x [@tremor021](https://github.com/tremor021) ([#14114](https://github.com/community-scripts/ProxmoxVE/pull/14114))\n    - Graylog: Better information in the log file [@tremor021](https://github.com/tremor021) ([#14110](https://github.com/community-scripts/ProxmoxVE/pull/14110))\n\n  - #### 🔧 Refactor\n\n    - Refactor: checkMK [@MickLesk](https://github.com/MickLesk) ([#14105](https://github.com/community-scripts/ProxmoxVE/pull/14105))\n    - PatchMon: Unpin release [@tremor021](https://github.com/tremor021) ([#14097](https://github.com/community-scripts/ProxmoxVE/pull/14097))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - core: add guidance when storage lacks rootdir support [@MickLesk](https://github.com/MickLesk) ([#14108](https://github.com/community-scripts/ProxmoxVE/pull/14108))\n\n## 2026-04-28\n\n### 🆕 New Scripts\n\n  - StoryBook ([#14081](https://github.com/community-scripts/ProxmoxVE/pull/14081))\n- CoreDNS ([#14082](https://github.com/community-scripts/ProxmoxVE/pull/14082))\n\n### 🚀 Updated Scripts\n\n  - Fix Dawarich Install/Update [@Jerry1098](https://github.com/Jerry1098) ([#14078](https://github.com/community-scripts/ProxmoxVE/pull/14078))\n\n  - #### ✨ New Features\n\n    - PatchMon Version 2.0.2 Script update [@9technologygroup](https://github.com/9technologygroup) ([#14095](https://github.com/community-scripts/ProxmoxVE/pull/14095))\n\n## 2026-04-27\n\n### 🚀 Updated Scripts\n\n  - Add pamUsername column to userOrgs table [@JVKeller](https://github.com/JVKeller) ([#14075](https://github.com/community-scripts/ProxmoxVE/pull/14075))\n\n  - #### 🐞 Bug Fixes\n\n    - Dawarich: run db:migrate before assets:precompile [@MickLesk](https://github.com/MickLesk) ([#14051](https://github.com/community-scripts/ProxmoxVE/pull/14051))\n    - TechnitiumDNS: always install .NET 10 if not already present [@MickLesk](https://github.com/MickLesk) ([#14049](https://github.com/community-scripts/ProxmoxVE/pull/14049))\n\n  - #### 💥 Breaking Changes\n\n    - PatchMon: v2.0.0 migration [@vhsdream](https://github.com/vhsdream) ([#14015](https://github.com/community-scripts/ProxmoxVE/pull/14015))\n\n### 💾 Core\n\n  - #### 🔧 Refactor\n\n    - Update build.func - fixed spelling mistake [@m1ckywill](https://github.com/m1ckywill) ([#14047](https://github.com/community-scripts/ProxmoxVE/pull/14047))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - update-lxcs/apps: avoid pct exec on containers mid-shutdown [@MickLesk](https://github.com/MickLesk) ([#14050](https://github.com/community-scripts/ProxmoxVE/pull/14050))\n\n  - #### ✨ New Features\n\n    - Add patchmon-agent report execution in update script [@heinemannj](https://github.com/heinemannj) ([#14054](https://github.com/community-scripts/ProxmoxVE/pull/14054))\n\n## 2026-04-26\n\n### 🆕 New Scripts\n\n  - TREK ([#14017](https://github.com/community-scripts/ProxmoxVE/pull/14017))\n\n### 🚀 Updated Scripts\n\n  - fix(2fauth): handle stale backup directory on update [@omertahaoztop](https://github.com/omertahaoztop) ([#14018](https://github.com/community-scripts/ProxmoxVE/pull/14018))\n\n  - #### 🐞 Bug Fixes\n\n    -  Increase Frigate default CPU cores from 4 to 8 [@MickLesk](https://github.com/MickLesk) ([#14039](https://github.com/community-scripts/ProxmoxVE/pull/14039))\n    - Technitium DNS: Ensure directories exist before running service [@tremor021](https://github.com/tremor021) ([#14030](https://github.com/community-scripts/ProxmoxVE/pull/14030))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: Correct deb822 repository flat path detection [@MickLesk](https://github.com/MickLesk) ([#14037](https://github.com/community-scripts/ProxmoxVE/pull/14037))\n\n## 2026-04-25\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - VictoriaMetrics: Stop vmagent/vmalert before update [@irishpadres](https://github.com/irishpadres) ([#14016](https://github.com/community-scripts/ProxmoxVE/pull/14016))\n    - Domain-Monitor: start apache2 after stop instead of reload [@omertahaoztop](https://github.com/omertahaoztop) ([#14019](https://github.com/community-scripts/ProxmoxVE/pull/14019))\n    - Transmute: Fix ffmpeg detection [@tremor021](https://github.com/tremor021) ([#14008](https://github.com/community-scripts/ProxmoxVE/pull/14008))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Technitium DNS [@tremor021](https://github.com/tremor021) ([#14013](https://github.com/community-scripts/ProxmoxVE/pull/14013))\n\n## 2026-04-24\n\n### 🆕 New Scripts\n\n  - Apprise-API ([#13934](https://github.com/community-scripts/ProxmoxVE/pull/13934))\n- fireshare ([#13995](https://github.com/community-scripts/ProxmoxVE/pull/13995))\n- Transmute ([#13935](https://github.com/community-scripts/ProxmoxVE/pull/13935))\n- Jitsi-Meet ([#13897](https://github.com/community-scripts/ProxmoxVE/pull/13897))\n\n### 🚀 Updated Scripts\n\n  - Update wger.sh [@Soppster1029](https://github.com/Soppster1029) ([#13977](https://github.com/community-scripts/ProxmoxVE/pull/13977))\n\n  - #### 🔧 Refactor\n\n    - Refactor: Ghostfolio [@MickLesk](https://github.com/MickLesk) ([#13990](https://github.com/community-scripts/ProxmoxVE/pull/13990))\n\n## 2026-04-23\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - mealie: start.sh missing after failed update [@MickLesk](https://github.com/MickLesk) ([#13958](https://github.com/community-scripts/ProxmoxVE/pull/13958))\n    - twingate-connector: perform real apt upgrade during update flow [@MickLesk](https://github.com/MickLesk) ([#13959](https://github.com/community-scripts/ProxmoxVE/pull/13959))\n\n  - #### ✨ New Features\n\n    - core: auto-size NODE_OPTIONS heap [@MickLesk](https://github.com/MickLesk) ([#13960](https://github.com/community-scripts/ProxmoxVE/pull/13960))\n\n  - #### 🔧 Refactor\n\n    - Update scripts to match standard [@tremor021](https://github.com/tremor021) ([#13956](https://github.com/community-scripts/ProxmoxVE/pull/13956))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: upgrade Node.js minor/patch on same major version [@MickLesk](https://github.com/MickLesk) ([#13957](https://github.com/community-scripts/ProxmoxVE/pull/13957))\n    - core: hotfix - prefer silent mode on PHS env conflict [@MickLesk](https://github.com/MickLesk) ([#13951](https://github.com/community-scripts/ProxmoxVE/pull/13951))\n\n  - #### 🔧 Refactor\n\n    - core: improve system update information / lxc stack upgrade [@MickLesk](https://github.com/MickLesk) ([#13970](https://github.com/community-scripts/ProxmoxVE/pull/13970))\n\n## 2026-04-22\n\n### 🆕 New Scripts\n\n  - Dashy ([#13817](https://github.com/community-scripts/ProxmoxVE/pull/13817))\n- Mini-QR ([#13902](https://github.com/community-scripts/ProxmoxVE/pull/13902))\n- ownfoil ([#13904](https://github.com/community-scripts/ProxmoxVE/pull/13904))\n- ERPNext ([#13921](https://github.com/community-scripts/ProxmoxVE/pull/13921))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - add --clear to uv venv in update_script() to prevent interactive prompt [@MickLesk](https://github.com/MickLesk) ([#13926](https://github.com/community-scripts/ProxmoxVE/pull/13926))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: Add PHS_VERBOSE env var to skip verbose mode prompts [@gormanity](https://github.com/gormanity) ([#13797](https://github.com/community-scripts/ProxmoxVE/pull/13797))\n\n## 2026-04-21\n\n### 🆕 New Scripts\n\n  - gogs ([#13896](https://github.com/community-scripts/ProxmoxVE/pull/13896))\n- anchor ([#13895](https://github.com/community-scripts/ProxmoxVE/pull/13895))\n- minthcm ([#13903](https://github.com/community-scripts/ProxmoxVE/pull/13903))\n- foldergram ([#13900](https://github.com/community-scripts/ProxmoxVE/pull/13900))\n\n### 🚀 Updated Scripts\n\n  - OpenCloud: Pin version to 6.1.0 [@vhsdream](https://github.com/vhsdream) ([#13890](https://github.com/community-scripts/ProxmoxVE/pull/13890))\n\n  - #### 🐞 Bug Fixes\n\n    - Domain-Locker: Update dependencies [@tremor021](https://github.com/tremor021) ([#13901](https://github.com/community-scripts/ProxmoxVE/pull/13901))\n    - homelable: fix install failure by correcting password-reset chmod target [@Copilot](https://github.com/Copilot) ([#13894](https://github.com/community-scripts/ProxmoxVE/pull/13894))\n\n  - #### ✨ New Features\n\n    - FileFlows: Update dependencies [@tremor021](https://github.com/tremor021) ([#13917](https://github.com/community-scripts/ProxmoxVE/pull/13917))\n\n## 2026-04-20\n\n### 🆕 New Scripts\n\n  - WhoDB ([#13880](https://github.com/community-scripts/ProxmoxVE/pull/13880))\n\n### 🚀 Updated Scripts\n\n  - pangolin: create migration tables before data transfer to prevent role loss [@MickLesk](https://github.com/MickLesk) ([#13874](https://github.com/community-scripts/ProxmoxVE/pull/13874))\n\n  - #### 🐞 Bug Fixes\n\n    - Pangolin: pre-apply schema migrations to prevent data loss [@MickLesk](https://github.com/MickLesk) ([#13861](https://github.com/community-scripts/ProxmoxVE/pull/13861))\n    - ActualBudget: change migration messages to warnings [@MickLesk](https://github.com/MickLesk) ([#13860](https://github.com/community-scripts/ProxmoxVE/pull/13860))\n    - slskd: migrate config keys for 0.25.0 breaking change [@MickLesk](https://github.com/MickLesk) ([#13862](https://github.com/community-scripts/ProxmoxVE/pull/13862))\n\n  - #### ✨ New Features\n\n    - Wanderer: add pocketbase CLI wrapper with env [@MickLesk](https://github.com/MickLesk) ([#13863](https://github.com/community-scripts/ProxmoxVE/pull/13863))\n    - feat(homelable): add password reset utility script [@davidsoncabista](https://github.com/davidsoncabista) ([#13798](https://github.com/community-scripts/ProxmoxVE/pull/13798))\n\n  - #### 🔧 Refactor\n\n    - Several Scripts: Bump NodeJS to align Node.js versions with upstream for 5 scripts [@MickLesk](https://github.com/MickLesk) ([#13875](https://github.com/community-scripts/ProxmoxVE/pull/13875))\n    - Refactor: PMG Post Install [@MickLesk](https://github.com/MickLesk) ([#13693](https://github.com/community-scripts/ProxmoxVE/pull/13693))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: detect Perl breakage after LXC stack upgrade and improve storage validation [@MickLesk](https://github.com/MickLesk) ([#13879](https://github.com/community-scripts/ProxmoxVE/pull/13879))\n\n## 2026-04-19\n\n### 🆕 New Scripts\n\n  - nametag ([#13849](https://github.com/community-scripts/ProxmoxVE/pull/13849))\n\n## 2026-04-18\n\n### 🆕 New Scripts\n\n  - Dagu ([#13830](https://github.com/community-scripts/ProxmoxVE/pull/13830))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - BabyBuddy: set DJANGO_SETTINGS_MODULE before migrate in update [@MickLesk](https://github.com/MickLesk) ([#13836](https://github.com/community-scripts/ProxmoxVE/pull/13836))\n    - litellm: add prisma generate and use venv binary directly [@MickLesk](https://github.com/MickLesk) ([#13835](https://github.com/community-scripts/ProxmoxVE/pull/13835))\n    - yamtrack: add missing nginx.conf sed edits to update script [@MickLesk](https://github.com/MickLesk) ([#13834](https://github.com/community-scripts/ProxmoxVE/pull/13834))\n\n### 🧰 Tools\n\n  - #### 🐞 Bug Fixes\n\n    - SparkyFitness Garmin Microservice: fix update function [@tomfrenzel](https://github.com/tomfrenzel) ([#13824](https://github.com/community-scripts/ProxmoxVE/pull/13824))\n\n  - #### 🔧 Refactor\n\n    - Clean-Orphan-LVM: check all cluster nodes for VM/CT configs [@MickLesk](https://github.com/MickLesk) ([#13837](https://github.com/community-scripts/ProxmoxVE/pull/13837))\n\n## 2026-04-17\n\n### 🆕 New Scripts\n\n  - step-ca ([#13775](https://github.com/community-scripts/ProxmoxVE/pull/13775))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - core: pin IGC version to compute-runtime compatible tag (Intel GPU) [@MickLesk](https://github.com/MickLesk) ([#13814](https://github.com/community-scripts/ProxmoxVE/pull/13814))\n    - Fix for bambuddy community script update [@abbasegbeyemi](https://github.com/abbasegbeyemi) ([#13816](https://github.com/community-scripts/ProxmoxVE/pull/13816))\n    - Umami: Fix update procedure [@tremor021](https://github.com/tremor021) ([#13807](https://github.com/community-scripts/ProxmoxVE/pull/13807))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - core: sanitize mount_fs input — strip spaces and trailing commas [@MickLesk](https://github.com/MickLesk) ([#13806](https://github.com/community-scripts/ProxmoxVE/pull/13806))\n\n  - #### 🔧 Refactor\n\n    - core: fix some pct create issues (telemetry) + cleanup [@MickLesk](https://github.com/MickLesk) ([#13810](https://github.com/community-scripts/ProxmoxVE/pull/13810))\n\n## 2026-04-16\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Add pnpm as a dependency to ghost-cli install [@YourFavoriteKyle](https://github.com/YourFavoriteKyle) ([#13789](https://github.com/community-scripts/ProxmoxVE/pull/13789))\n\n### 💾 Core\n\n  - #### ✨ New Features\n\n    - core: wire ENABLE_MKNOD and ALLOW_MOUNT_FS into LXC features [@MickLesk](https://github.com/MickLesk) ([#13796](https://github.com/community-scripts/ProxmoxVE/pull/13796))\n\n## 2026-04-15\n\n### 🆕 New Scripts\n\n  - iGotify ([#13773](https://github.com/community-scripts/ProxmoxVE/pull/13773))\n- GitHub-Runner ([#13709](https://github.com/community-scripts/ProxmoxVE/pull/13709))\n- Revert \"Remove low-install-count CT scripts and installers (#13570)\" [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13752](https://github.com/community-scripts/ProxmoxVE/pull/13752))\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - [alpine-nextcloud] Update Nginx MIME types to support .mjs files [@GuiltyFox](https://github.com/GuiltyFox) ([#13771](https://github.com/community-scripts/ProxmoxVE/pull/13771))\n    - Domain Monitor: Fix file ownership after update [@tremor021](https://github.com/tremor021) ([#13759](https://github.com/community-scripts/ProxmoxVE/pull/13759))\n\n  - #### 💥 Breaking Changes\n\n    - Reitti: refactor scripts for v4 - remove RabbitMQ and Photon [@MickLesk](https://github.com/MickLesk) ([#13728](https://github.com/community-scripts/ProxmoxVE/pull/13728))\n\n  - #### 🔧 Refactor\n\n    - Semaphore: add BoltDB to SQLite migration [@tremor021](https://github.com/tremor021) ([#13779](https://github.com/community-scripts/ProxmoxVE/pull/13779))\n\n### 📚 Documentation\n\n  - cleanup: remove docs/, update README & CONTRIBUTING, fix repo config [@MickLesk](https://github.com/MickLesk) ([#13770](https://github.com/community-scripts/ProxmoxVE/pull/13770))\n\n## 2026-04-14\n\n### 🚀 Updated Scripts\n\n  - Immich: Pin photo-processing library revisions [@vhsdream](https://github.com/vhsdream) ([#13748](https://github.com/community-scripts/ProxmoxVE/pull/13748))\n\n  - #### 🐞 Bug Fixes\n\n    - BentoPDF: Nginx fixes [@tremor021](https://github.com/tremor021) ([#13741](https://github.com/community-scripts/ProxmoxVE/pull/13741))\n    - Zerobyte: add git to dependencies to fix bun install failure [@Copilot](https://github.com/Copilot) ([#13721](https://github.com/community-scripts/ProxmoxVE/pull/13721))\n    - alpine-nextcloud-install: do not use deprecated nginx config [@AlexanderStein](https://github.com/AlexanderStein) ([#13726](https://github.com/community-scripts/ProxmoxVE/pull/13726))\n\n  - #### ✨ New Features\n\n    - Mealie: support v3.15+ Nuxt 4 migration [@MickLesk](https://github.com/MickLesk) ([#13731](https://github.com/community-scripts/ProxmoxVE/pull/13731))\n\n  - #### 🔧 Refactor\n\n    - Lyrion: correct service name and version file in update script [@MickLesk](https://github.com/MickLesk) ([#13734](https://github.com/community-scripts/ProxmoxVE/pull/13734))\n    - Changedetection: move env vars from service file to .env [@tremor021](https://github.com/tremor021) ([#13732](https://github.com/community-scripts/ProxmoxVE/pull/13732))\n\n## 2026-04-13\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Slskd: Remove stale Soularr lock file on startup and redirect logs to stderr [@MickLesk](https://github.com/MickLesk) ([#13669](https://github.com/community-scripts/ProxmoxVE/pull/13669))\n    - Bambuddy: preserve database and archive on update [@Copilot](https://github.com/Copilot) ([#13706](https://github.com/community-scripts/ProxmoxVE/pull/13706))\n\n  - #### ✨ New Features\n\n    - Immich: Pin version to 2.7.5 [@vhsdream](https://github.com/vhsdream) ([#13715](https://github.com/community-scripts/ProxmoxVE/pull/13715))\n    - Bytestash: auto backup/restore data on update [@MickLesk](https://github.com/MickLesk) ([#13707](https://github.com/community-scripts/ProxmoxVE/pull/13707))\n    - OpenCloud: pin version to 6.0.0 [@vhsdream](https://github.com/vhsdream) ([#13691](https://github.com/community-scripts/ProxmoxVE/pull/13691))\n\n  - #### 💥 Breaking Changes\n\n    - Mealie: pin version to v3.14.0 in install and update scripts [@Copilot](https://github.com/Copilot) ([#13724](https://github.com/community-scripts/ProxmoxVE/pull/13724))\n\n  - #### 🔧 Refactor\n\n    - core: remove unused TEMP_DIR mktemp leak in build_container / clean sonarqube [@MickLesk](https://github.com/MickLesk) ([#13708](https://github.com/community-scripts/ProxmoxVE/pull/13708))\n\n## 2026-04-12\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Alpine-Wakapi: Remove container checks in update_script function [@MickLesk](https://github.com/MickLesk) ([#13694](https://github.com/community-scripts/ProxmoxVE/pull/13694))\n\n  - #### 🔧 Refactor\n\n    - IronClaw: Install keychain dependencies and launch in a DBus session [@MickLesk](https://github.com/MickLesk) ([#13692](https://github.com/community-scripts/ProxmoxVE/pull/13692))\n    - MeTube: Allow pnpm build scripts to fix ERR_PNPM_IGNORED_BUILDS [@MickLesk](https://github.com/MickLesk) ([#13668](https://github.com/community-scripts/ProxmoxVE/pull/13668))\n\n## 2026-04-11\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - Immich: Ensure newline before appending IMMICH_HELMET_FILE to .env [@MickLesk](https://github.com/MickLesk) ([#13667](https://github.com/community-scripts/ProxmoxVE/pull/13667))\n\n  - #### ✨ New Features\n\n    - BentoPDF: replace http-server with nginx to fix WASM initialization timeout [@MickLesk](https://github.com/MickLesk) ([#13625](https://github.com/community-scripts/ProxmoxVE/pull/13625))\n    - Element Synapse: Add MatrixRTC configuration for Element Call support [@MickLesk](https://github.com/MickLesk) ([#13665](https://github.com/community-scripts/ProxmoxVE/pull/13665))\n    - RomM: Use ROMM_BASE_PATH from .env for symlinks and nginx config [@MickLesk](https://github.com/MickLesk) ([#13666](https://github.com/community-scripts/ProxmoxVE/pull/13666))\n    - Immich: Pin version to 2.7.4 [@vhsdream](https://github.com/vhsdream) ([#13661](https://github.com/community-scripts/ProxmoxVE/pull/13661))\n\n  - #### 🔧 Refactor\n\n    - Crafty Controller: Wait for credentials file instead of fixed sleep [@MickLesk](https://github.com/MickLesk) ([#13670](https://github.com/community-scripts/ProxmoxVE/pull/13670))\n    - Refactor: Alpine-Wakapi [@tremor021](https://github.com/tremor021) ([#13656](https://github.com/community-scripts/ProxmoxVE/pull/13656))\n\n## 2026-04-10\n\n### 🚀 Updated Scripts\n\n  - #### 🐞 Bug Fixes\n\n    - fix: ensure trailing newline in redis.conf before appending bind directive [@Copilot](https://github.com/Copilot) ([#13647](https://github.com/community-scripts/ProxmoxVE/pull/13647))\n\n  - #### ✨ New Features\n\n    - Immich: Pin version to 2.7.3 [@vhsdream](https://github.com/vhsdream) ([#13631](https://github.com/community-scripts/ProxmoxVE/pull/13631))\n    - Homarr: bind Redis to localhost only [@MickLesk](https://github.com/MickLesk) ([#13552](https://github.com/community-scripts/ProxmoxVE/pull/13552))\n\n### 💾 Core\n\n  - #### 🐞 Bug Fixes\n\n    - tools.func: prevent script crash when entering GitHub token after rate limit [@MickLesk](https://github.com/MickLesk) ([#13638](https://github.com/community-scripts/ProxmoxVE/pull/13638))\n\n### 🧰 Tools\n\n  - #### 🔧 Refactor\n\n    - addons: Filebrowser & Filebrowser-Quantum get warning if host install [@MickLesk](https://github.com/MickLesk) ([#13639](https://github.com/community-scripts/ProxmoxVE/pull/13639))"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Proxmox VE Helper-Scripts\n\nWelcome! We're glad you want to contribute. This guide covers everything you need to add new scripts, improve existing ones, or help in other ways.\n\nFor detailed coding standards and full documentation, visit **[community-scripts.org/docs](https://community-scripts.org/docs)**.\n\n---\n\n## How Can I Help?\n\n> [!IMPORTANT]\n> **New scripts** must always be submitted to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) first — not to this repository.\n> PRs with new scripts opened directly against ProxmoxVE **will be closed without review**.\n> **Bug fixes, improvements, and features for existing scripts** go here (ProxmoxVE).\n\n| I want to…                                  | Where to go                                                                                  |\n| :------------------------------------------ | :------------------------------------------------------------------------------------------- |\n| **Add a brand-new script**                  | [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) — testing repo for new scripts |\n| **Fix a bug or improve an existing script** | This repo (ProxmoxVE) — open a PR here                                                       |\n| **Add a feature to an existing script**     | This repo (ProxmoxVE) — open a PR here                                                       |\n| Report a bug or broken script               | [Open an Issue](https://github.com/community-scripts/ProxmoxVE/issues)                       |\n| Request a new script or feature             | [Start a Discussion](https://github.com/community-scripts/ProxmoxVE/discussions)             |\n| Report a security vulnerability             | [Security Policy](SECURITY.md)                                                               |\n| Chat with contributors                      | [Discord](https://discord.gg/3AnUqsXnmK)                                                     |\n\n---\n\n## Prerequisites\n\nBefore writing scripts, we recommend setting up:\n\n- **Visual Studio Code** with these extensions:\n  - [Shell Syntax](https://marketplace.visualstudio.com/items?itemName=bmalehorn.shell-syntax)\n  - [ShellCheck](https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck)\n  - [Shell Format](https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format)\n\n---\n\n## Script Structure\n\nEvery script consists of two files:\n\n| File                         | Purpose                                                 |\n| :--------------------------- | :------------------------------------------------------ |\n| `ct/AppName.sh`              | Container creation, variable setup, and update handling |\n| `install/AppName-install.sh` | Application installation logic                          |\n\nUse existing scripts in [`ct/`](ct/) and [`install/`](install/) as reference. Full coding standards and annotated templates are at **[community-scripts.org/docs/contribution](https://community-scripts.org/docs/contribution)**.\n\n---\n\n## Contribution Process\n\n### Adding a new script\n\nNew scripts are **not accepted directly in this repository**. The workflow is:\n\n1. Fork [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) and clone it\n2. Create a branch: `git switch -c feat/myapp`\n3. Write your two script files:\n   - `ct/myapp.sh`\n   - `install/myapp-install.sh`\n4. Test thoroughly in ProxmoxVED — run the script against a real Proxmox instance\n5. Open a PR in **ProxmoxVED** for review and testing\n6. Once accepted and verified there, the script will be promoted to ProxmoxVE by maintainers\n\nFollow the coding standards at [community-scripts.org/docs/contribution](https://community-scripts.org/docs/contribution).\n\n---\n\n### Fixing a bug or improving an existing script\n\nChanges to scripts that already exist in ProxmoxVE go directly here:\n\n1. Fork **this repository** (ProxmoxVE) and clone it:\n\n   ```bash\n   git clone https://github.com/YOUR_USERNAME/ProxmoxVE\n   cd ProxmoxVE\n   ```\n\n2. Create a branch:\n\n   ```bash\n   git switch -c fix/myapp-description\n   ```\n\n3. Make your changes to the relevant files in `ct/` and/or `install/`\n\n4. Open a PR from your fork to `community-scripts/ProxmoxVE/main`\n\nYour PR should only contain the files you changed. Do not include unrelated modifications.\n\n---\n\n## Code Standards\n\nKey rules at a glance:\n\n- One script per service — keep them focused\n- Naming convention: lowercase, hyphen-separated (`my-app.sh`)\n- Shebang: `#!/usr/bin/env bash`\n- Quote all variables: `\"$VAR\"` not `$VAR`\n- Use lowercase variable names\n- Do not hardcode credentials or sensitive values\n\nFull standards and examples: **[community-scripts.org/docs/contribution](https://community-scripts.org/docs/contribution)**\n\n---\n\n## Developer Mode & Debugging\n\nSet the `dev_mode` variable to enable debugging features when testing. Flags can be combined (comma-separated):\n\n```bash\ndev_mode=\"trace,keep\" bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/myapp.sh)\"\n```\n\n| Flag         | Description                                                  |\n| :----------- | :----------------------------------------------------------- |\n| `trace`      | Enables `set -x` for maximum verbosity during execution      |\n| `keep`       | Prevents the container from being deleted if the build fails |\n| `pause`      | Pauses execution at key points before customization          |\n| `breakpoint` | Drops to a shell at hardcoded `breakpoint` calls in scripts  |\n| `logs`       | Saves detailed build logs to `/var/log/community-scripts/`   |\n| `dryrun`     | Bypasses actual container creation (limited support)         |\n| `motd`       | Forces an update of the Message of the Day                   |\n\n---\n\n## Notes\n\n- **Website metadata** (name, description, logo, tags) is managed via the website — use the \"Report Issue\" link on any script page to request changes. Do not submit metadata changes via repo files.\n- **JSON files** in `json/` define script properties used by the website. See existing files for structure reference.\n- Keep PRs small and focused. One fix or feature per PR is ideal.\n- PRs with **new scripts** opened against ProxmoxVE will be closed — submit them to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) instead.\n- PRs that fail CI checks will not be merged.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-2026 asylumexp\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <p align=\"center\">\n    <a href=\"#\">\n      <img src=\"https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/images/logo.png\" height=\"100px\" />\n    </a>\n  </p>\n</div>\n<h1 align=\"center\">Proxmox arm64 Install Scripts</h1>\n<h3 align=\"center\">a port of the Promox VE Helper-Scripts to arm64!</h3>\n\n<p align=\"center\">\n  <a href=\"https://pimox-scripts.com\">Website</a> | \n  <a href=\"https://github.com/asylumexp/Proxmox/blob/main/.github/CONTRIBUTING.md\">Contribute</a> |\n  <a href=\"https://github.com/asylumexp/Proxmox/blob/main/USER_SUBMITTED_GUIDES.md\">Guides</a> |\n  <a href=\"https://github.com/asylumexp/Proxmox/blob/main/CHANGELOG.md\">Changelog</a> |\n  <a href=\"https://ko-fi.com/D1D7EP4GF\">Support (tteck)</a>\n</p>\n\n<p align=\"center\">\n<img alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars/asylumexp/proxmox?style=flat\">\n\n## </p>\n\n### About\n\nPorts the [Proxmox VE Helper Scripts](https://github.com/community-scripts/proxmoxve) project to ARM64.\n\n## Support the Project\n\nThis project is maintained by volunteers. All infrastructure costs come out of pocket, and the work is done in people's spare time.\n\n**30% of all donations are forwarded directly to cancer research and hospice care** — a cause that was important to tteck.\n\n<div align=\"center\">\n  <a href=\"https://ko-fi.com/community_scripts\">\n    <img src=\"https://img.shields.io/badge/Support_on_Ko--fi-FF5F5F?style=for-the-badge&logo=ko-fi&logoColor=white\" alt=\"Support on Ko-fi\" />\n  </a>\n  &nbsp;\n  <a href=\"https://community-scripts.org/donate\">\n    <img src=\"https://img.shields.io/badge/Donate-community--scripts.org%2Fdonate-4c9b3f?style=for-the-badge\" alt=\"Donate via community-scripts.org\" />\n  </a>\n</div>\n\n---\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE) — free to use, modify, and redistribute for personal and commercial purposes.\n\nSee the full license text in [LICENSE](LICENSE).\n\nFind the scripts in the website linked above.\n\nAny issues with the scripts, please put an issue within this repository rather than upstream, as it is likely caused by my modifications.\n\n### Support\n\nIf you would like to offer support, I would appreciate a star on the repository, or for you to support the creator of the Proxmox scripts [tteck on Ko-Fi](https://ko-fi.com/D1D7EP4GF)!\n\n## Compatibility Guide\n\n[View Compatibility Guide here](https://pimox-scripts.com)\n\n<div align=\"center\">\n  <sub>Built on the foundation of <a href=\"https://github.com/tteck\">tteck</a>'s original work · <a href=\"https://github.com/tteck/Proxmox\">Original Repository</a></sub><br/>\n  <sub>Maintained and expanded by the community · In memory of tteck</sub><br/>\n  <sub><i>Proxmox® is a registered trademark of <a href=\"https://www.proxmox.com/en/about/company\">Proxmox Server Solutions GmbH</a></i></sub>\n</div>\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nThis project currently supports the following versions of Proxmox VE (PVE):\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 9.1.x   | :white_check_mark: |\n| 9.0.x   | :white_check_mark: |\n| 8.4.x   | :white_check_mark: |\n| 8.3.x   | Limited support* ❕ |\n| 8.2.x   | Limited support* ❕ |\n| 8.1.x   | Limited support* ❕ |\n| 8.0.x   | Limited support* ❕ |\n| < 8.0   | :x:                |\n\n*Version 8.0.x  - 8.3.x has limited support. Security updates may not be provided for all issues affecting this version. \n\n*Debian 13 Containers may fail to install. You can write var_version=12 before the bash call. \n\n---\n\n## Reporting a Vulnerability\n\nSecurity vulnerabilities must not be reported publicly to avoid potential exploitation.  \nInstead, please report them privately via one of the following channels:\n\n- **Discord**: Join our [Discord server](https://discord.gg/jsYVk5JBxq) and send a direct message to a maintainer.  \n- **Email**: Write to us at **contact@community-scripts.org** with the subject line:  \n  `Vulnerability Report - <Project/Script Name>`.\n\nWhen reporting a vulnerability, please provide:\n\n- A clear description of the issue  \n- Steps to reproduce the vulnerability  \n- Affected versions or environments  \n- (Optional) Suggested fixes or workarounds  \n\n---\n\n## Response Process\n\n1. **Acknowledgment**  \n   - We will review and acknowledge your report within **7 business days**.\n\n2. **Assessment**  \n   - The maintainers will verify the issue and classify its severity.  \n   - Depending on impact, a patch may be released immediately or scheduled for the next update.\n\n3. **Resolution**  \n   - Critical security fixes will be prioritized.  \n   - Non-critical issues may be deferred or declined with an explanation.\n\n---\n\n## Disclaimer\n\nNot all reported issues will be treated as vulnerabilities.  \nReports may be declined if they are deemed:  \n- Low-risk  \n- Out of project scope  \n- Conflicting with intended design or architecture  \n\n---\n\nIf you have any questions or concerns about this security policy, please reach out to the maintainers through the contact options above.\n"
  },
  {
    "path": "ct/2fauth.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jkrgr0\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.2fauth.app/\n\nAPP=\"2FAuth\"\nvar_tags=\"${var_tags:-2fa;authenticator}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/2fauth ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"2fauth\" \"Bubka/2FAuth\"; then\n    $STD apt update\n    $STD apt -y upgrade\n\n    msg_info \"Creating Backup\"\n    rm -rf /opt/2fauth-backup\n    mv /opt/2fauth /opt/2fauth-backup\n    if ! dpkg -l | grep -q 'php8.4'; then\n      cp /etc/nginx/conf.d/2fauth.conf /etc/nginx/conf.d/2fauth.conf.bak\n    fi\n    msg_ok \"Backup Created\"\n\n    if ! dpkg -l | grep -q 'php8.4'; then\n      PHP_VERSION=\"8.4\" PHP_FPM=\"YES\" setup_php\n      sed -i 's/php8\\.[0-9]/php8.4/g' /etc/nginx/conf.d/2fauth.conf\n    fi\n    fetch_and_deploy_gh_release \"2fauth\" \"Bubka/2FAuth\" \"tarball\"\n    setup_composer\n    cp /opt/2fauth-backup/.env /opt/2fauth/.env\n    cp -r /opt/2fauth-backup/storage /opt/2fauth/storage\n    cd /opt/2fauth || return\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-dev --prefer-dist\n    php artisan 2fauth:install\n    chown -R www-data: /opt/2fauth\n    chmod -R 755 /opt/2fauth\n    $STD systemctl restart php8.4-fpm\n    $STD systemctl restart nginx\n    rm -rf /opt/2fauth-backup\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/5etools.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: TheRealVira\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://5e.tools/\n\n# App Default Values\nAPP=\"5etools\"\nvar_tags=\"wiki\"\nvar_cpu=\"1\"\nvar_ram=\"512\"\nvar_disk=\"13\"\nvar_os=\"debian\"\nvar_version=\"12\"\nvar_unprivileged=\"1\"\n\n# App Output & Base Settings\nheader_info \"$APP\"\nbase_settings\n\n# Core\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n\n    # Check if installation is present | -f for file, -d for folder\n    if [[ ! -d \"/opt/${APP}\" ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    RELEASE=$(curl -s https://api.github.com/repos/5etools-mirror-3/5etools-src/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\n    if [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]] || [[ ! -f \"/opt/${APP}_version.txt\" ]]; then\n        # Crawling the new version and checking whether an update is required\n        msg_info \"Updating System\"\n        apt-get update &>/dev/null\n        apt-get -y upgrade &>/dev/null\n        msg_ok \"Updated System\"\n\n        # Execute Update\n        msg_info \"Updating base 5etools\"\n        cd /opt\n        wget -q \"https://github.com/5etools-mirror-3/5etools-src/archive/refs/tags/${RELEASE}.zip\"\n        unzip -q \"${RELEASE}.zip\"\n        mv \"/opt/${APP}/img\" \"/opt/img-backup\"\n        rm -rf \"/opt/${APP}\"\n        mv \"${APP}-src-${RELEASE:1}\" \"/opt/${APP}\"\n        mv \"/opt/img-backup\" \"/opt/${APP}/img\"\n        cd /opt/5etools\n        $STD npm install\n        $STD npm run build\n        cd ~\n        echo \"${RELEASE}\" >\"/opt/${APP}_version.txt\"\n        chown -R www-data: \"/opt/${APP}\"\n        chmod -R 755 \"/opt/${APP}\"\n        msg_ok \"Updated base 5etools\"\n        # Cleaning up\n        msg_info \"Cleaning Up\"\n        rm -rf /opt/${RELEASE}.zip\n        $STD apt-get -y autoremove\n        $STD apt-get -y autoclean\n        msg_ok \"Cleanup Completed\"\n    else\n        msg_ok \"No update required. Base ${APP} is already at ${RELEASE}\"\n    fi\n\n    IMG_RELEASE=$(curl -s https://api.github.com/repos/5etools-mirror-2/5etools-img/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\n    if [[ \"${IMG_RELEASE}\" != \"$(cat /opt/${APP}_IMG_version.txt)\" ]] || [[ ! -f \"/opt/${APP}_IMG_version.txt\" ]]; then\n        # Crawling the new version and checking whether an update is required\n        msg_info \"Updating System\"\n        apt-get update &>/dev/null\n        apt-get -y upgrade &>/dev/null\n        msg_ok \"Updated System\"\n\n        # Execute Update\n        msg_info \"Updating 5etools images\"\n        curl -sSL \"https://github.com/5etools-mirror-2/5etools-img/archive/refs/tags/${IMG_RELEASE}.zip\" > \"${IMG_RELEASE}.zip\"\n        unzip -q \"${IMG_RELEASE}.zip\"\n        rm -rf \"/opt/${APP}/img\"\n        mv \"${APP}-img-${IMG_RELEASE:1}\" \"/opt/${APP}/img\"\n        echo \"${IMG_RELEASE}\" >\"/opt/${APP}_IMG_version.txt\"\n        chown -R www-data: \"/opt/${APP}\"\n        chmod -R 755 \"/opt/${APP}\"\n\n        msg_ok \"Updating 5etools images\"\n\n        # Cleaning up\n        msg_info \"Cleaning Up\"\n        rm -rf /opt/${RELEASE}.zip\n        rm -rf ${IMG_RELEASE}.zip\n        $STD apt-get -y autoremove\n        $STD apt-get -y autoclean\n        msg_ok \"Cleanup Completed\"\n    else\n        msg_ok \"No update required. ${APP} images are already at ${IMG_RELEASE}\"\n    fi\n\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/actualbudget.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://actualbudget.org/\n\nAPP=\"Actual Budget\"\nvar_tags=\"${var_tags:-finance}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f ~/.actualbudget && ! -f /opt/actualbudget_version.txt ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" setup_nodejs\n  RELEASE=$(get_latest_github_release \"actualbudget/actual\")\n  if [[ -f /opt/actualbudget-data/config.json ]]; then\n    if check_for_gh_release \"actualbudget\" \"actualbudget/actual\"; then\n      msg_info \"Stopping Service\"\n      systemctl stop actualbudget\n      msg_ok \"Stopped Service\"\n\n      msg_info \"Updating Actual Budget to ${RELEASE}\"\n      $STD npm update -g @actual-app/sync-server\n      echo \"${RELEASE}\" >~/.actualbudget\n      msg_ok \"Updated Actual Budget to ${RELEASE}\"\n\n      msg_info \"Starting Service\"\n      systemctl start actualbudget\n      msg_ok \"Started Service\"\n      msg_ok \"Updated successfully!\"\n    fi\n  else\n    msg_warn \"Old Installation Found, you need to migrate your data and recreate to a new container\"\n    msg_warn \"Please follow the instructions on the Actual Budget website to migrate your data\"\n    msg_warn \"https://actualbudget.org/docs/backup-restore/backup\"\n    exit\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:5006${CL}\"\n"
  },
  {
    "path": "ct/adguard.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://adguard.com/\n\nAPP=\"Adguard\"\nvar_tags=\"${var_tags:-adblock}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/AdGuardHome ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_error \"Adguard Home can only be updated via the user interface.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/adventurelog.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://adventurelog.app/\n\nAPP=\"AdventureLog\"\nvar_tags=\"${var_tags:-traveling}\"\nvar_disk=\"${var_disk:-7}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/adventurelog ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  ensure_dependencies memcached libmemcached-tools\n  if check_for_gh_release \"adventurelog\" \"seanmorley15/adventurelog\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop adventurelog-backend\n    systemctl stop adventurelog-frontend\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Backup Old Installation\"\n    cp -r /opt/adventurelog /opt/adventurelog-backup\n    rm -rf /opt/adventurelog\n    msg_ok \"Backup done\"\n\n    fetch_and_deploy_gh_release \"adventurelog\" \"seanmorley15/adventurelog\" \"tarball\"\n    PYTHON_VERSION=\"3.13\" setup_uv\n\n    msg_info \"Ensuring PostgreSQL Extensions\"\n    $STD sudo -u postgres psql -d adventurelog_db -c \"CREATE EXTENSION IF NOT EXISTS postgis;\"\n    msg_ok \"PostgreSQL Extensions Ready\"\n\n    msg_info \"Updating ${APP}\"\n    cp /opt/adventurelog-backup/backend/server/.env /opt/adventurelog/backend/server/.env\n    cp -r /opt/adventurelog-backup/backend/server/media /opt/adventurelog/backend/server/media\n    cd /opt/adventurelog/backend/server\n    if [[ ! -x .venv/bin/python ]]; then\n      $STD uv venv --clear .venv\n      $STD .venv/bin/python -m ensurepip --upgrade\n    fi\n    $STD .venv/bin/python -m pip install --upgrade pip\n    $STD .venv/bin/python -m pip install -r requirements.txt\n    $STD .venv/bin/python -m pip install 'djangorestframework<3.15'\n    $STD .venv/bin/python -m manage collectstatic --noinput\n    $STD .venv/bin/python -m manage migrate\n\n    cp /opt/adventurelog-backup/frontend/.env /opt/adventurelog/frontend/.env\n    cd /opt/adventurelog/frontend\n    $STD pnpm i\n    $STD pnpm build\n    rm -rf /opt/adventurelog-backup\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Services\"\n    systemctl daemon-reexec\n    systemctl start adventurelog-backend\n    systemctl start adventurelog-frontend\n    msg_ok \"Services Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/agentdvr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.ispyconnect.com/\n\nAPP=\"AgentDVR\"\nvar_tags=\"${var_tags:-dvr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/agentdvr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -fsSL \"https://www.ispyconnect.com/api/Agent/DownloadLocation4?platform=LinuxARM64&fromVersion=0\" | tr -d '\\r' | grep -Eo 'https://[^\"[:space:]]+\\.zip' | head -n1)\n  if [[ \"${RELEASE}\" != \"$(cat ~/.agentdvr 2>/dev/null)\" ]] || [[ ! -f ~/.agentdvr ]]; then\n    msg_info \"Stopping service\"\n    systemctl stop AgentDVR\n    msg_ok \"Service stopped\"\n\n    msg_info \"Updating AgentDVR\"\n    cd /opt/agentdvr/agent\n    curl -fsSL \"$RELEASE\" -o $(basename \"$RELEASE\")\n    $STD unzip -o Agent_Linux64*.zip\n    chmod +x ./Agent\n    echo $RELEASE >~/.agentdvr\n    rm -rf Agent_Linux64*.zip\n    msg_ok \"Updated AgentDVR\"\n\n    msg_info \"Starting service\"\n    systemctl start AgentDVR\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/alpine-adguard.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://adguardhome.com/\n\nAPP=\"Alpine-AdGuard\"\nvar_tags=\"${var_tags:-alpine;adblock}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating AdGuard Home\"\n  $STD /opt/AdGuardHome/AdGuardHome --update\n  msg_ok \"Updated AdGuard Home\"\n\n  msg_info \"Restarting AdGuard Home\"\n  $STD rc-service adguardhome restart\n  msg_ok \"Restarted AdGuard Home\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/alpine-bitmagnet.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bitmagnet-io/bitmagnet\n\nAPP=\"Alpine-bitmagnet\"\nvar_tags=\"${var_tags:-alpine;torrent}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [[ ! -d /opt/bitmagnet ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  RELEASE=$(curl -fsSL https://api.github.com/repos/bitmagnet-io/bitmagnet/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  if [ \"${RELEASE}\" != \"$(cat /opt/bitmagnet_version.txt)\" ] || [ ! -f /opt/bitmagnet_version.txt ]; then\n    msg_info \"Backing up database\"\n    rm -f /tmp/backup.sql\n    $STD sudo -u postgres pg_dump \\\n      --column-inserts \\\n      --data-only \\\n      --on-conflict-do-nothing \\\n      --rows-per-insert=1000 \\\n      --table=metadata_sources \\\n      --table=content \\\n      --table=content_attributes \\\n      --table=content_collections \\\n      --table=content_collections_content \\\n      --table=torrent_sources \\\n      --table=torrents \\\n      --table=torrent_files \\\n      --table=torrent_hints \\\n      --table=torrent_contents \\\n      --table=torrent_tags \\\n      --table=torrents_torrent_sources \\\n      --table=key_values \\\n      bitmagnet \\\n      >/tmp/backup.sql\n    mv /tmp/backup.sql /opt/\n    msg_ok \"Database backed up\"\n\n    msg_info \"Updating ${APP} from $(cat /opt/bitmagnet_version.txt) to ${RELEASE}\"\n    $STD apk -U upgrade\n    $STD service bitmagnet stop\n    [ -f /opt/bitmagnet/.env ] && cp /opt/bitmagnet/.env /opt/\n    [ -f /opt/bitmagnet/config.yml ] && cp /opt/bitmagnet/config.yml /opt/\n    rm -rf /opt/bitmagnet/*\n    temp_file=$(mktemp)\n    curl -fsSL \"https://github.com/bitmagnet-io/bitmagnet/archive/refs/tags/v${RELEASE}.tar.gz\" -o \"$temp_file\"\n    tar zxf \"$temp_file\" --strip-components=1 -C /opt/bitmagnet\n    cd /opt/bitmagnet\n    VREL=v$RELEASE\n    $STD go build -ldflags \"-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=$VREL\"\n    chmod +x bitmagnet\n    [ -f \"/opt/.env\" ] && cp \"/opt/.env\" /opt/bitmagnet/\n    [ -f \"/opt/config.yml\" ] && cp \"/opt/config.yml\" /opt/bitmagnet/\n    rm -f \"$temp_file\"\n    echo \"${RELEASE}\" >/opt/bitmagnet_version.txt\n    $STD service bitmagnet start\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3333${CL}\"\n"
  },
  {
    "path": "ct/alpine-borgbackup-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Sander Koenders (sanderkoenders)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.borgbackup.org/\n\nAPP=\"Alpine-BorgBackup-Server\"\nvar_tags=\"${var_tags:-alpine;backup}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [[ ! -f /usr/bin/borg ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  CHOICE=$(msg_menu \"BorgBackup Server Update Options\" \\\n    \"1\" \"Update BorgBackup Server\" \\\n    \"2\" \"Reset SSH Access\" \\\n    \"3\" \"Enable password authentication for backup user (not recommended, use SSH key instead)\" \\\n    \"4\" \"Disable password authentication for backup user (recommended for security, use SSH key)\")\n\n  case $CHOICE in\n  1)\n    msg_info \"Updating $APP LXC\"\n    $STD apk -U upgrade\n    msg_ok \"Updated $APP LXC successfully!\"\n    ;;\n  2)\n    if [[ \"${PHS_SILENT:-0}\" == \"1\" ]]; then\n      msg_warn \"Reset SSH Public key requires interactive mode, skipping.\"\n      exit\n    fi\n\n    msg_info \"Setting up SSH Public Key for backup user\"\n\n    msg_info \"Please paste your SSH public key (e.g., ssh-rsa AAAAB3... user@host): \\n\"\n    read -p \"Key: \" SSH_PUBLIC_KEY\n    echo\n\n    if [[ -z \"$SSH_PUBLIC_KEY\" ]]; then\n      msg_error \"No SSH public key provided!\"\n      exit 1\n    fi\n\n    if [[ ! \"$SSH_PUBLIC_KEY\" =~ ^(ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-) ]]; then\n      msg_error \"Invalid SSH public key format!\"\n      exit 1\n    fi\n\n    msg_info \"Setting up SSH access\"\n    mkdir -p /home/backup/.ssh\n    echo \"$SSH_PUBLIC_KEY\" >/home/backup/.ssh/authorized_keys\n\n    chown -R backup:backup /home/backup/.ssh\n    chmod 700 /home/backup/.ssh\n    chmod 600 /home/backup/.ssh/authorized_keys\n\n    msg_ok \"SSH access configured for backup user\"\n    ;;\n  3)\n    if [[ \"${PHS_SILENT:-0}\" == \"1\" ]]; then\n      msg_warn \"Enabling password authentication requires interactive mode, skipping.\"\n      exit\n    fi\n\n    msg_info \"Enabling password authentication for backup user\"\n    msg_warn \"Password authentication is less secure than using SSH keys. Consider using SSH keys instead.\"\n    passwd backup\n    sed -i 's/^#*\\s*PasswordAuthentication\\s\\+\\(yes\\|no\\)/PasswordAuthentication yes/' /etc/ssh/sshd_config\n    rc-service sshd restart\n    msg_ok \"Password authentication enabled for backup user\"\n    ;;\n  4)\n    msg_info \"Disabling password authentication for backup user\"\n    sed -i 's/^#*\\s*PasswordAuthentication\\s\\+\\(yes\\|no\\)/PasswordAuthentication no/' /etc/ssh/sshd_config\n    rc-service sshd restart\n    msg_ok \"Password authentication disabled for backup user\"\n    ;;\n  esac\n\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW}Connection information:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}ssh backup@${IP}${CL}\"\necho -e \"${TAB}${VERIFYPW}${YW}To set SSH key, run this script with the 'update' option and select option 2${CL}\"\n"
  },
  {
    "path": "ct/alpine-caddy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: cobalt (cobaltgit)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://caddyserver.com/\n\nAPP=\"Alpine-Caddy\"\nvar_tags=\"${var_tags:-webserver}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [[ ! -d /etc/caddy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apk -U upgrade\n  msg_ok \"Updated $APP LXC\"\n\n  msg_info \"Restarting Caddy\"\n  rc-service caddy restart\n  msg_ok \"Restarted Caddy\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/alpine-docker.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.docker.com/\n\nAPP=\"Alpine-Docker\"\nvar_tags=\"${var_tags:-docker;alpine}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  $STD apk -U upgrade\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "ct/alpine-forgejo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Johann3s-H (An!ma)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://forgejo.org/\n\nAPP=\"Alpine-Forgejo\"\nvar_tags=\"${var_tags:-alpine;git}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating Forgejo\"\n  $STD apk upgrade forgejo\n  msg_ok \"Updated Forgejo\"\n\n  msg_info \"Restarting Forgejo\"\n  $STD rc-service forgejo restart\n  msg_ok \"Restarted Forgejo\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/alpine-garage.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://garagehq.deuxfleurs.fr/\n\nAPP=\"Alpine-Garage\"\nvar_tags=\"${var_tags:-alpine;object-storage}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [[ ! -f /usr/local/bin/garage ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  GITEA_RELEASE=$(curl -fsSL https://api.github.com/repos/deuxfleurs-org/garage/tags | jq -r '.[0].name')\n  if [[ \"${GITEA_RELEASE}\" != \"$(cat ~/.garage 2>/dev/null)\" ]] || [[ ! -f ~/.garage ]]; then\n    msg_info \"Stopping Service\"\n    rc-service garage stop || true\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing Up Data\"\n    cp /usr/local/bin/garage /usr/local/bin/garage.old 2>/dev/null || true\n    cp /etc/garage.toml /etc/garage.toml.bak 2>/dev/null || true\n    msg_ok \"Backed Up Data\"\n\n    msg_info \"Updating Garage\"\n    curl -fsSL \"https://garagehq.deuxfleurs.fr/_releases/${GITEA_RELEASE}/aarch64-unknown-linux-musl/garage\" -o /usr/local/bin/garage\n    chmod +x /usr/local/bin/garage\n    echo \"${GITEA_RELEASE}\" >~/.garage\n    msg_ok \"Updated Garage\"\n\n    msg_info \"Starting Service\"\n    rc-service garage start || rc-service garage restart\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. Garage is already at ${GITEA_RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/alpine-gatus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TwiN/gatus\n\nAPP=\"Alpine-gatus\"\nvar_tags=\"${var_tags:-alpine;monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [[ ! -d /opt/gatus ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  RELEASE=$(curl -s https://api.github.com/repos/TwiN/gatus/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  if [ \"${RELEASE}\" != \"$(cat /opt/gatus_version.txt)\" ] || [ ! -f /opt/gatus_version.txt ]; then\n    msg_info \"Updating ${APP} LXC\"\n    $STD apk -U upgrade\n    $STD service gatus stop\n    mv /opt/gatus/config/config.yaml /opt\n    rm -rf /opt/gatus/*\n    temp_file=$(mktemp)\n    curl -fsSL \"https://github.com/TwiN/gatus/archive/refs/tags/v${RELEASE}.tar.gz\" -o \"$temp_file\"\n    tar zxf \"$temp_file\" --strip-components=1 -C /opt/gatus\n    cd /opt/gatus\n    $STD go mod tidy\n    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus .\n    setcap CAP_NET_RAW+ep gatus\n    mv /opt/config.yaml config\n    rm -f \"$temp_file\"\n    echo \"${RELEASE}\" >/opt/gatus_version.txt\n    $STD service gatus start\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/alpine-gitea.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gitea.io\n\nAPP=\"Alpine-Gitea\"\nvar_tags=\"${var_tags:-alpine;git}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating Gitea\"\n  apk upgrade gitea\n  msg_ok \"Updated Gitea\"\n\n  msg_info \"Restarting Gitea\"\n  rc-service gitea restart\n  msg_ok \"Restarted Gitea\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/alpine-grafana.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://grafana.com/\n\nAPP=\"Alpine-Grafana\"\nvar_tags=\"${var_tags:-alpine;monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  LXCIP=$(ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n\n  CHOICE=$(msg_menu \"Grafana Update Options\" \\\n    \"1\" \"Check for Grafana Updates\" \\\n    \"2\" \"Allow 0.0.0.0 for listening\" \\\n    \"3\" \"Allow only ${LXCIP} for listening\")\n\n  case $CHOICE in\n  1)\n    $STD apk -U upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n    ;;\n  2)\n    sed -i -e \"s/cfg:server.http_addr=.*/cfg:server.http_addr=0.0.0.0/g\" /etc/conf.d/grafana\n    service grafana restart\n    msg_ok \"Allowed listening on all interfaces!\"\n    exit\n    ;;\n  3)\n    sed -i -e \"s/cfg:server.http_addr=.*/cfg:server.http_addr=$LXCIP/g\" /etc/conf.d/grafana\n    service grafana restart\n    msg_ok \"Allowed listening only on ${LXCIP}!\"\n    exit\n    ;;\n  esac\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${APP} should be reachable by going to the following URL.\n         ${BL}http://${IP}:3000${CL} \\n\"\n"
  },
  {
    "path": "ct/alpine-ironclaw.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/nearai/ironclaw\n\nAPP=\"Alpine-IronClaw\"\nvar_tags=\"${var_tags:-ai;agent;alpine}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [[ ! -f /usr/local/bin/ironclaw ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"ironclaw-bin\" \"nearai/ironclaw\"; then\n    msg_info \"Stopping Service\"\n    rc-service ironclaw stop 2>/dev/null || true\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /root/.ironclaw/.env /root/ironclaw.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    fetch_and_deploy_gh_release \"ironclaw-bin\" \"nearai/ironclaw\" \"prebuild\" \"latest\" \"/usr/local/bin\" \\\n      \"ironclaw-$(uname -m)-unknown-linux-musl.tar.gz\"\n    chmod +x /usr/local/bin/ironclaw\n\n    msg_info \"Restoring Configuration\"\n    cp /root/ironclaw.env.bak /root/.ironclaw/.env\n    rm -f /root/ironclaw.env.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    rc-service ironclaw start\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Complete setup by running:${CL}\"\necho -e \"${TAB}${BGN}ironclaw onboard${CL}\"\necho -e \"${INFO}${YW} Then start the service:${CL}\"\necho -e \"${TAB}${BGN}rc-service ironclaw start${CL}\"\necho -e \"${INFO}${YW} Access the Web UI at:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\necho -e \"${INFO}${YW} Auth token and database credentials:${CL}\"\necho -e \"${TAB}${BGN}cat /root/.ironclaw/.env${CL}\"\n"
  },
  {
    "path": "ct/alpine-it-tools.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: nicedevil007 (NiceDevil)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://it-tools.tech/\n\nAPP=\"Alpine-IT-Tools\"\nvar_tags=\"${var_tags:-alpine;development}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [ ! -d /usr/share/nginx/html ]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -fsSL https://api.github.com/repos/sharevb/it-tools/releases/latest | grep '\"tag_name\":' | cut -d '\"' -f4)\n  if [ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ] || [ ! -f /opt/${APP}_version.txt ]; then\n    msg_info \"Updating ${APP} LXC\"\n    curl -fsSL \"https://github.com/sharevb/it-tools/releases/download/${RELEASE}/it-tools-${RELEASE#v}.zip\" -o it-tools.zip\n    mkdir -p /usr/share/nginx/html\n    rm -rf /usr/share/nginx/html/*\n    $STD unzip it-tools.zip -d /tmp\n    cp -r /tmp/dist/* /usr/share/nginx/html\n    rm -rf /tmp/dist\n    rm -f it-tools.zip\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/alpine-komodo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://komo.do\n\nAPP=\"Alpine-Komodo\"\nvar_tags=\"${var_tags:-docker;alpine}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nADDON_SCRIPT=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/komodo.sh\"\n\nfunction update_script() {\n  if [[ ! -d /opt/komodo ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_warn \"⚠️  ${APP} has been migrated to an addon script.\"\n  echo \"\"\n  msg_info \"This is a one-time migration. After this, you can update ${APP} anytime with:\"\n  echo -e \"${TAB}${TAB}${GN}update_komodo${CL}  or  ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}\"\n  echo \"\"\n  read -r -p \"${TAB}Migrate update function now? [y/N]: \" CONFIRM\n  if [[ ! \"${CONFIRM,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Migration skipped. The old update will continue to work for now.\"\n    msg_warn \"⚠️  Komodo v2 uses :2 image tags. The :latest tag is deprecated and will not receive v2 updates.\"\n    msg_warn \"Please migrate to the addon script to receive Komodo v2.\"\n    msg_info \"Updating ${APP} (legacy)\"\n    COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)\n    if [[ -z \"$COMPOSE_FILE\" ]]; then\n      msg_error \"No valid compose file found in /opt/komodo!\"\n      exit 252\n    fi\n    $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file /opt/komodo/compose.env pull\n    $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file /opt/komodo/compose.env up -d\n    msg_ok \"Updated ${APP}\"\n    exit\n  fi\n\n  msg_info \"Migrating update function\"\n  TMP_UPDATE=$(mktemp)\n  cat <<'MIGRATION_EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/komodo.sh)\"\nMIGRATION_EOF\n  mv \"$TMP_UPDATE\" /usr/bin/update\n  chmod +x /usr/bin/update\n\n  ln -sf /usr/bin/update /usr/bin/update_komodo 2>/dev/null || true\n  msg_ok \"Migration complete\"\n\n  msg_info \"Running addon update\"\n  type=update bash <(curl -fsSL \"${ADDON_SCRIPT}\")\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9120${CL}\"\n"
  },
  {
    "path": "ct/alpine-loki.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: hoholms\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/grafana/loki\n\nAPP=\"Alpine-Loki\"\nvar_tags=\"${var_tags:-alpine;monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  LXCIP=$(ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n\n  CHOICE=$(msg_menu \"Loki Update Options\" \\\n    \"1\" \"Check for Loki Updates\" \\\n    \"2\" \"Allow 0.0.0.0 for listening\" \\\n    \"3\" \"Allow only ${LXCIP} for listening\")\n\n  case $CHOICE in\n  1)\n    $STD apk -U upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n    ;;\n  2)\n    sed -i -e \"s/cfg:server.http_addr=.*/cfg:server.http_addr=0.0.0.0/g\" /etc/conf.d/loki\n    service loki restart\n    msg_ok \"Allowed listening on all interfaces!\"\n    exit\n    ;;\n  3)\n    sed -i -e \"s/cfg:server.http_addr=.*/cfg:server.http_addr=$LXCIP/g\" /etc/conf.d/loki\n    service loki restart\n    msg_ok \"Allowed listening only on ${LXCIP}!\"\n    exit\n    ;;\n  esac\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${APP} should be reachable by going to the following URL.\n         ${BL}http://${IP}:3100${CL} \\n\"\necho -e \"Promtail should be reachable by going to the following URL.\n         ${BL}http://${IP}:9080${CL} \\n\"\n"
  },
  {
    "path": "ct/alpine-mariadb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mariadb.org\n\nAPP=\"Alpine-MariaDB\"\nvar_tags=\"${var_tags:-alpine;database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating MariaDB\"\n  $STD apk upgrade mariadb mariadb-client\n  msg_ok \"Updated MariaDB\"\n\n  msg_info \"Restarting MariaDB\"\n  $STD rc-service mariadb restart\n  msg_ok \"Restarted MariaDB\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:3306${CL}\"\n"
  },
  {
    "path": "ct/alpine-nextcloud.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nextcloud.com/\n\nAPP=\"Alpine-Nextcloud\"\nvar_tags=\"${var_tags:-alpine;cloud}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  if [[ ! -d /usr/share/webapps/nextcloud ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  CHOICE=$(msg_menu \"Nextcloud Options\" \\\n    \"1\" \"Update Alpine Packages\" \\\n    \"2\" \"Nextcloud Login Credentials\" \\\n    \"3\" \"Renew Self-signed Certificate\")\n\n  case $CHOICE in\n  1)\n    msg_info \"Updating Alpine Packages\"\n    $STD apk -U upgrade\n    msg_ok \"Updated Alpine Packages\"\n    msg_ok \"Updated successfully!\"\n    exit\n    ;;\n  2)\n    cat nextcloud.creds\n    exit\n    ;;\n  3)\n    openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout /etc/ssl/private/nextcloud-selfsigned.key -out /etc/ssl/certs/nextcloud-selfsigned.crt -subj \"/C=US/O=Nextcloud/OU=Domain Control Validated/CN=nextcloud.local\" >/dev/null 2>&1\n    rc-service nginx restart\n    msg_ok \"Renewed self-signed certificate\"\n    exit\n    ;;\n  esac\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${APP} should be reachable by going to the following URL.\n         ${BL}https://${IP}${CL} \\n\"\n"
  },
  {
    "path": "ct/alpine-node-red.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nodered.org\n\nAPP=\"Alpine-Node-RED\"\nvar_tags=\"${var_tags:-alpine;automation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating Node.js and npm\"\n  $STD apk upgrade nodejs npm\n  msg_ok \"Updated Node.js and npm\"\n\n  msg_info \"Updating Node-RED\"\n  $STD npm install -g --unsafe-perm node-red\n  msg_ok \"Updated Node-RED\"\n\n  msg_info \"Restarting Node-RED\"\n  $STD rc-service nodered restart\n  msg_ok \"Restarted Node-RED\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1880${CL}\"\n"
  },
  {
    "path": "ct/alpine-ntfy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: cobalt (cobaltgit)\n# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE\n# Source: https://ntfy.sh/\n\nAPP=\"Alpine-ntfy\"\nvar_tags=\"${var_tags:-notification}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [[ ! -d /etc/ntfy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ntfy LXC\"\n  $STD apk -U upgrade\n  setcap 'cap_net_bind_service=+ep' /usr/bin/ntfy\n  msg_ok \"Updated ntfy LXC\"\n\n  msg_info \"Restarting ntfy\"\n  rc-service ntfy restart\n  msg_ok \"Restarted ntfy\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/alpine-postgresql.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://postgresql.org/\n\nAPP=\"Alpine-PostgreSQL\"\nvar_tags=\"${var_tags:-alpine;database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating PostgreSQL\"\n  $STD apk upgrade postgresql postgresql-contrib\n  msg_ok \"Updated PostgreSQL\"\n\n  msg_info \"Restarting PostgreSQL\"\n  $STD rc-service postgresql restart\n  msg_ok \"Restarted PostgreSQL\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:5432${CL}\"\n"
  },
  {
    "path": "ct/alpine-prometheus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prometheus.io/\n\nAPP=\"Alpine-Prometheus\"\nvar_tags=\"${var_tags:-alpine;monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating Prometheus\"\n  $STD apk upgrade prometheus\n  msg_ok \"Updated Prometheus\"\n\n  msg_info \"Restarting Prometheus\"\n  $STD rc-service prometheus restart\n  msg_ok \"Restarted Prometheus\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9090${CL}\"\n"
  },
  {
    "path": "ct/alpine-rclone.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rclone/rclone\n\nAPP=\"Alpine-rclone\"\nvar_tags=\"${var_tags:-alpine;backup}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_fuse=\"${var_fuse:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [ ! -d /opt/rclone ]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  RELEASE=$(curl -s https://api.github.com/repos/rclone/rclone/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  if [ \"${RELEASE}\" != \"$(cat /opt/rclone_version.txt)\" ] || [ ! -f /opt/rclone_version.txt ]; then\n    msg_info \"Updating ${APP} LXC\"\n    temp_file=$(mktemp)\n    curl -fsSL \"https://github.com/rclone/rclone/releases/download/v${RELEASE}/rclone-v${RELEASE}-linux-arm64.zip\" -o \"$temp_file\"\n    $STD unzip -o \"$temp_file\" '*/**' -d /opt/rclone\n    rm -f \"$temp_file\"\n    echo \"${RELEASE}\" >/opt/rclone_version.txt\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/alpine-redis.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://redis.io/\n\nAPP=\"Alpine-Redis\"\nvar_tags=\"${var_tags:-alpine;database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  LXCIP=$(ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n\n  CHOICE=$(msg_menu \"Redis Management\" \\\n    \"1\" \"Update Redis\" \\\n    \"2\" \"Allow 0.0.0.0 for listening\" \\\n    \"3\" \"Allow only ${LXCIP} for listening\")\n\n  case $CHOICE in\n  1)\n    msg_info \"Updating Redis\"\n    apk update && apk upgrade redis\n    rc-service redis restart\n    msg_ok \"Updated successfully!\"\n    exit\n    ;;\n  2)\n    msg_info \"Setting Redis to listen on all interfaces\"\n    sed -i 's/^bind .*/bind 0.0.0.0/' /etc/redis.conf\n    rc-service redis restart\n    msg_ok \"Redis now listens on all interfaces!\"\n    exit\n    ;;\n  3)\n    msg_info \"Setting Redis to listen only on ${LXCIP}\"\n    sed -i \"s/^bind .*/bind ${LXCIP}/\" /etc/redis.conf\n    rc-service redis restart\n    msg_ok \"Redis now listens only on ${LXCIP}!\"\n    exit\n    ;;\n  esac\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${APP} should be reachable on port 6379.\n         ${BL}redis-cli -h ${IP} -p 6379${CL} \\n\"\n"
  },
  {
    "path": "ct/alpine-redlib.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: andrej-kocijan (Andrej Kocijan)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/redlib-org/redlib\n\nAPP=\"Alpine-Redlib\"\nvar_tags=\"${var_tags:-alpine;frontend}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [[ ! -d /opt/redlib ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Stopping Service\"\n  $STD rc-service redlib stop\n  msg_ok \"Stopped Service\"\n\n  fetch_and_deploy_gh_release \"redlib\" \"redlib-org/redlib\" \"prebuild\" \"latest\" \"/opt/redlib\" \"redlib-aarch64-unknown-linux-musl.tar.gz\"\n\n  msg_info \"Starting Service\"\n  $STD rc-service redlib start\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5252${CL}\"\n"
  },
  {
    "path": "ct/alpine-rustdeskserver.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rustdesk/rustdesk-server\n\nAPP=\"Alpine-RustDeskServer\"\nvar_tags=\"${var_tags:-alpine;monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [[ ! -d /opt/rustdesk-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  APIRELEASE=$(curl -s https://api.github.com/repos/lejianwen/rustdesk-api/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  RELEASE=$(curl -s https://api.github.com/repos/lejianwen/rustdesk-server/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\n  if [ \"${RELEASE}\" != \"$(cat ~/.rustdesk-server 2>/dev/null)\" ] || [ ! -f ~/.rustdesk-server ]; then\n    msg_info \"Updating RustDesk Server to v${RELEASE}\"\n    $STD apk -U upgrade\n    $STD service rustdesk-server-hbbs stop\n    $STD service rustdesk-server-hbbr stop\n    temp_file1=$(mktemp)\n    curl -fsSL \"https://github.com/rustdesk/rustdesk-server/releases/download/${RELEASE}/rustdesk-server-linux-arm64.zip\" -o \"$temp_file1\"\n    $STD unzip \"$temp_file1\"\n    cp -r arm64/* /opt/rustdesk-server/\n    echo \"${RELEASE}\" >~/.rustdesk-server\n    $STD service rustdesk-server-hbbs start\n    $STD service rustdesk-server-hbbr start\n    rm -rf arm64\n    rm -f $temp_file1\n    msg_ok \"Updated RustDesk Server\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  if [ \"${APIRELEASE}\" != \"$(cat ~/.rustdesk-api)\" ] || [ ! -f ~/.rustdesk-api ]; then\n    msg_info \"Updating RustDesk API to v${APIRELEASE}\"\n    $STD service rustdesk-api stop\n    temp_file2=$(mktemp)\n    curl -fsSL \"https://github.com/lejianwen/rustdesk-api/releases/download/v${APIRELEASE}/linux-arm64.tar.gz\" -o \"$temp_file2\"\n    $STD tar zxvf \"$temp_file2\"\n    cp -r release/* /opt/rustdesk-api\n    echo \"${APIRELEASE}\" >~/.rustdesk-api\n    $STD service rustdesk-api start\n    rm -rf release\n    rm -f \"$temp_file2\"\n    msg_ok \"Updated RustDesk API\"\n  else\n    msg_ok \"No update required. RustDesk API is already at v${APIRELEASE}\"\n  fi\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:21114${CL}\"\n"
  },
  {
    "path": "ct/alpine-rustypaste.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/orhun/rustypaste\n\nAPP=\"Alpine-RustyPaste\"\nvar_tags=\"${var_tags:-alpine;pastebin;storage}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if ! apk info -e rustypaste >/dev/null 2>&1; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating RustyPaste\"\n  $STD apk update\n  $STD apk upgrade rustypaste --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community\n  msg_ok \"Updated RustyPaste\"\n\n  msg_info \"Restarting Services\"\n  $STD rc-service rustypaste restart\n  msg_ok \"Restarted Services\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/alpine-syncthing.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://syncthing.net/\n\nAPP=\"Alpine-Syncthing\"\nvar_tags=\"${var_tags:-alpine;networking}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating Syncthing\"\n  $STD apk upgrade syncthing\n  msg_ok \"Updated Syncthing\"\n\n  msg_info \"Restarting Syncthing\"\n  $STD rc-service syncthing restart\n  msg_ok \"Restarted Syncthing\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8384${CL}\"\n"
  },
  {
    "path": "ct/alpine-teamspeak-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021 (Slaviša Arežina)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://teamspeak.com/en/\n\nAPP=\"Alpine-TeamSpeak-Server\"\nvar_tags=\"${var_tags:-alpine;communication}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n\n  if [[ ! -d /opt/teamspeak-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -fsSL https://teamspeak.com/en/downloads/#server | sed -n 's/.*teamspeak3-server_linux_amd64-\\([0-9.]*[0-9]\\).*/\\1/p' | awk 'NR==1')\n\n  if [ \"${RELEASE}\" != \"$(cat ~/.teamspeak-server)\" ] || [ ! -f ~/.teamspeak-server ]; then\n    msg_info \"Updating ${APP} LXC\"\n    $STD apk -U upgrade\n    $STD service teamspeak stop\n    curl -fsSL \"https://files.teamspeak-services.com/releases/server/${RELEASE}/teamspeak3-server_linux_arm64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\n    tar -xf ./ts3server.tar.bz2\n    cp -ru teamspeak3-server_linux_arm64/* /opt/teamspeak-server/\n    rm -f ~/ts3server.tar.bz*\n    rm -rf teamspeak3-server_linux_arm64\n    echo \"${RELEASE}\" >~/.teamspeak-server\n    $STD service teamspeak start\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:9987${CL}\"\n"
  },
  {
    "path": "ct/alpine-tinyauth.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021) | Co-Author: Stavros (steveiliop56)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/steveiliop56/tinyauth\n\nAPP=\"Alpine-Tinyauth\"\nvar_tags=\"${var_tags:-alpine;auth}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  if [[ ! -d /opt/tinyauth ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated packages\"\n\n  RELEASE=$(curl -s https://api.github.com/repos/steveiliop56/tinyauth/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  if [ \"${RELEASE}\" != \"$(cat ~/.tinyauth 2>/dev/null)\" ] || [ ! -f ~/.tinyauth ]; then\n    msg_info \"Stopping Service\"\n    $STD service tinyauth stop\n    msg_ok \"Service Stopped\"\n\n    if [[ -f /opt/tinyauth/.env ]] && ! grep -q \"^TINYAUTH_\" /opt/tinyauth/.env; then\n      msg_info \"Migrating .env to v5 format\"\n      sed -i \\\n        -e 's/^DATABASE_PATH=/TINYAUTH_DATABASE_PATH=/' \\\n        -e 's/^USERS=/TINYAUTH_AUTH_USERS=/' \\\n        -e \"s/^USERS='/TINYAUTH_AUTH_USERS='/\" \\\n        -e 's/^APP_URL=/TINYAUTH_APPURL=/' \\\n        -e 's/^SECRET=/TINYAUTH_AUTH_SECRET=/' \\\n        -e 's/^PORT=/TINYAUTH_SERVER_PORT=/' \\\n        -e 's/^ADDRESS=/TINYAUTH_SERVER_ADDRESS=/' \\\n        /opt/tinyauth/.env\n      msg_ok \"Migrated .env to v5 format\"\n    fi\n\n    msg_info \"Updating Tinyauth\"\n    rm -f /opt/tinyauth/tinyauth\n    curl -fsSL \"https://github.com/steveiliop56/tinyauth/releases/download/v${RELEASE}/tinyauth-arm64\" -o /opt/tinyauth/tinyauth\n    chmod +x /opt/tinyauth/tinyauth\n    echo \"${RELEASE}\" >~/.tinyauth\n    msg_ok \"Updated Tinyauth\"\n\n    msg_info \"Restarting Tinyauth\"\n    $STD service tinyauth start\n    msg_ok \"Restarted Tinyauth\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/alpine-traefik.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://alpinelinux.org/\n\nAPP=\"Alpine-Traefik\"\nvar_tags=\"${var_tags:-os;alpine}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating traefik from edge\"\n  $STD apk add traefik --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community\n  msg_ok \"Updated traefik\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} WebUI Access (if configured) - using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/dashboard${CL}\"\n"
  },
  {
    "path": "ct/alpine-transmission.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://transmissionbt.com/\n\nAPP=\"Alpine-Transmission\"\nvar_tags=\"${var_tags:-alpine;torrent}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"Updating Transmission\"\n  $STD apk upgrade transmission-daemon\n  msg_ok \"Updated Transmission\"\n\n  msg_info \"Restarting Transmission\"\n  $STD rc-service transmission-daemon restart\n  msg_ok \"Restarted Transmission\"\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9091${CL}\"\n"
  },
  {
    "path": "ct/alpine-valkey.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pshankinclarke (lazarillo)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://valkey.io/\n\nAPP=\"Alpine-Valkey\"\nvar_tags=\"${var_tags:-alpine;database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  LXCIP=$(ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n\n  CHOICE=$(msg_menu \"Valkey Management\" \\\n    \"1\" \"Update Valkey\" \\\n    \"2\" \"Allow 0.0.0.0 for listening\" \\\n    \"3\" \"Allow only ${LXCIP} for listening\")\n\n  case $CHOICE in\n  1)\n    msg_info \"Updating Valkey\"\n    apk update && apk upgrade valkey\n    rc-service valkey restart\n    msg_ok \"Updated Valkey\"\n    msg_ok \"Updated successfully!\"\n    exit\n    ;;\n  2)\n    msg_info \"Setting Valkey to listen on all interfaces\"\n    sed -i 's/^bind .*/bind 0.0.0.0/' /etc/valkey/valkey.conf\n    rc-service valkey restart\n    msg_ok \"Valkey now listens on all interfaces!\"\n    exit\n    ;;\n  3)\n    msg_info \"Setting Valkey to listen only on ${LXCIP}\"\n    sed -i \"s/^bind .*/bind ${LXCIP}/\" /etc/valkey/valkey.conf\n    rc-service valkey restart\n    msg_ok \"Valkey now listens only on ${LXCIP}!\"\n    exit\n    ;;\n  esac\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${APP} should be reachable on port 6379.\n         ${BL}valkey-cli -h ${IP} -p 6379${CL} \\n\"\n"
  },
  {
    "path": "ct/alpine-vaultwarden.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dani-garcia/vaultwarden\n\nAPP=\"Alpine-Vaultwarden\"\nvar_tags=\"${var_tags:-alpine;vault}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  CHOICE=$(msg_menu \"Vaultwarden Update Options\" \\\n    \"1\" \"Update Vaultwarden\" \\\n    \"2\" \"Reset ADMIN_TOKEN\")\n\n  case $CHOICE in\n  1)\n    $STD apk -U upgrade\n    rc-service vaultwarden restart -q\n    msg_ok \"Updated successfully!\"\n    exit\n    ;;\n  2)\n    if [[ \"${PHS_SILENT:-0}\" == \"1\" ]]; then\n      msg_warn \"Reset ADMIN_TOKEN requires interactive mode, skipping.\"\n      exit\n    fi\n    read -r -s -p \"Setup your ADMIN_TOKEN (make it strong): \" NEWTOKEN\n    echo \"\"\n    if [[ -n \"$NEWTOKEN\" ]]; then\n      if ! command -v argon2 >/dev/null 2>&1; then apk add argon2 &>/dev/null; fi\n      TOKEN=$(echo -n \"${NEWTOKEN}\" | argon2 \"$(openssl rand -base64 32)\" -e -id -k 19456 -t 2 -p 1)\n      if [[ ! -f /var/lib/vaultwarden/config.json ]]; then\n        sed -i \"s|export ADMIN_TOKEN=.*|export ADMIN_TOKEN='${TOKEN}'|\" /etc/conf.d/vaultwarden\n      else\n        sed -i \"s|\\\"admin_token\\\": .*|\\\"admin_token\\\": \\\"${TOKEN}\\\",|\" /var/lib/vaultwarden/config.json\n      fi\n      rc-service vaultwarden restart -q\n      msg_ok \"Admin token updated\"\n    fi\n    exit\n    ;;\n  esac\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${APP} should be reachable by going to the following URL.\n         ${BL}https://${IP}:8000${CL} \\n\"\n"
  },
  {
    "path": "ct/alpine-wakapi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wakapi.dev/ | https://github.com/muety/wakapi\n\nAPP=\"Alpine-Wakapi\"\nvar_tags=\"${var_tags:-code;time-tracking}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [[ ! -d /opt/wakapi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -s https://api.github.com/repos/muety/wakapi/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\n  if [ \"${RELEASE}\" != \"$(cat ~/.wakapi 2>/dev/null)\" ] || [ ! -f ~/.wakapi ]; then\n    msg_info \"Stopping Wakapi Service\"\n    $STD rc-service wakapi stop\n    msg_ok \"Stopped Wakapi Service\"\n\n    msg_info \"Updating Wakapi LXC\"\n    $STD apk -U upgrade\n    msg_ok \"Updated Wakapi LXC\"\n\n    msg_info \"Creating backup\"\n    mkdir -p /opt/wakapi-backup\n    cp /opt/wakapi/config.yml /opt/wakapi/wakapi_db.db /opt/wakapi-backup/\n    msg_ok \"Created backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wakapi\" \"muety/wakapi\" \"prebuild\" \"latest\" \"/opt/wakapi\" \"wakapi_linux_amd64.zip\"\n\n    msg_info \"Configuring Wakapi\"\n    cd /opt/wakapi\n    cp /opt/wakapi-backup/config.yml /opt/wakapi/\n    cp /opt/wakapi-backup/wakapi_db.db /opt/wakapi/\n    rm -rf /opt/wakapi-backup\n    msg_ok \"Configured Wakapi\"\n\n    msg_info \"Starting Service\"\n    $STD rc-service wakapi start\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/alpine-wireguard.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.wireguard.com/\n\nAPP=\"Alpine-Wireguard\"\nvar_tags=\"${var_tags:-alpine;vpn}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_tun=\"${var_tun:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  msg_info \"Updating Alpine Packages\"\n  $STD apk -U upgrade\n  msg_ok \"Updated Alpine Packages\"\n\n  msg_info \"update wireguard-tools\"\n  $STD apk add --no-cache --upgrade wireguard-tools\n  msg_ok \"wireguard-tools updated\"\n\n  if [[ -d /etc/wgdashboard/src ]]; then\n    msg_info \"update WGDashboard\"\n    cd /etc/wgdashboard/src\n    echo \"y\" | ./wgd.sh update >/dev/null 2>&1\n    $STD ./wgd.sh start\n    msg_ok \"WGDashboard updated\"\n  fi\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} WGDashboard Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:10086${CL}\"\n"
  },
  {
    "path": "ct/alpine-zigbee2mqtt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zigbee2mqtt.io/\n\nAPP=\"Alpine-Zigbee2MQTT\"\nvar_tags=\"${var_tags:-alpine;zigbee;mqtt;smarthome}\"\nvar_disk=\"${var_disk:-1}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  $STD apk -U upgrade\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "ct/alpine.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://alpinelinux.org/\n\nAPP=\"Alpine\"\nvar_tags=\"${var_tags:-os;alpine}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  $STD apk -U upgrade\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "ct/ampache.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ampache/ampache\n\nAPP=\"Ampache\"\nvar_tags=\"${var_tags:-music}\"\nvar_disk=\"${var_disk:-5}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/ampache ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"Ampache\" \"ampache/ampache\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/ampache/config/ampache.cfg.php /tmp/ampache.cfg.php.backup\n    cp /opt/ampache/public/rest/.htaccess /tmp/ampache_rest.htaccess.backup\n    cp /opt/ampache/public/play/.htaccess /tmp/ampache_play.htaccess.backup\n    rm -rf /opt/ampache_backup\n    mv /opt/ampache /opt/ampache_backup\n    msg_ok \"Created Backup\"\n\n    fetch_and_deploy_gh_release \"Ampache\" \"ampache/ampache\" \"prebuild\" \"latest\" \"/opt/ampache\" \"ampache-*_all_php8.4.zip\"\n\n    msg_info \"Restoring Backup\"\n    cp /tmp/ampache.cfg.php.backup /opt/ampache/config/ampache.cfg.php\n    cp /tmp/ampache_rest.htaccess.backup /opt/ampache/public/rest/.htaccess\n    cp /tmp/ampache_play.htaccess.backup /opt/ampache/public/play/.htaccess\n    chmod 664 /opt/ampache/public/rest/.htaccess /opt/ampache/public/play/.htaccess\n    chown -R www-data:www-data /opt/ampache\n    rm -f /tmp/ampache*.backup\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n    msg_custom \"⚠️\" \"${YW}\" \"Complete database update by visiting: http://${LOCAL_IP}/update.php\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/install.php${CL}\"\n"
  },
  {
    "path": "ct/anchor.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ZhFahim/anchor\n\nAPP=\"Anchor\"\nvar_tags=\"${var_tags:-notes;productivity;sync}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f ~/.anchor ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"anchor\" \"ZhFahim/anchor\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop anchor-web anchor-server\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Configuration\"\n    cp /opt/anchor/.env /opt/anchor.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"anchor\" \"ZhFahim/anchor\" \"tarball\"\n\n    msg_info \"Building Server\"\n    cd /opt/anchor/server\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm prisma generate\n    $STD pnpm build\n    [[ -d src/generated ]] && mkdir -p dist/src && cp -R src/generated dist/src/\n    msg_ok \"Built Server\"\n\n    msg_info \"Building Web Interface\"\n    cd /opt/anchor/web\n    $STD pnpm install --frozen-lockfile\n    SERVER_URL=http://127.0.0.1:3001 $STD pnpm build\n    cp -r .next/static .next/standalone/.next/static\n    cp -r public .next/standalone/public\n    msg_ok \"Built Web Interface\"\n\n    cp /opt/anchor.env.bak /opt/anchor/.env\n    rm -f /opt/anchor.env.bak\n\n    msg_info \"Running Database Migrations\"\n    cd /opt/anchor/server\n    set -a && source /opt/anchor/.env && set +a\n    $STD pnpm prisma migrate deploy\n    msg_ok \"Ran Database Migrations\"\n\n    msg_info \"Starting Services\"\n    systemctl start anchor-server anchor-web\n    msg_ok \"Started Services\"\n    msg_ok \"Updated ${APP}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/anytype-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://anytype.io\n\nAPP=\"Anytype-Server\"\nvar_tags=\"${var_tags:-notes;productivity;sync}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/anytype/any-sync-bundle ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"anytype\" \"grishy/any-sync-bundle\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop anytype\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/anytype/data /opt/anytype_data_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"anytype\" \"grishy/any-sync-bundle\" \"prebuild\" \"latest\" \"/opt/anytype\" \"any-sync-bundle_*_linux_amd64.tar.gz\"\n    chmod +x /opt/anytype/any-sync-bundle\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/anytype_data_backup/. /opt/anytype/data\n    rm -rf /opt/anytype_data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start anytype\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:33010${CL}\"\necho -e \"${INFO}${YW} Client config file:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}/opt/anytype/data/client-config.yml${CL}\"\n"
  },
  {
    "path": "ct/apache-cassandra.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cassandra.apache.org/_/index.html\n\nAPP=\"Apache-Cassandra\"\nvar_tags=\"${var_tags:-database;NoSQL}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/init.d/cassandra ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Apache Cassandra\"\n  $STD apt update\n  $STD apt install -y --only-upgrade cassandra cassandra-tools\n  msg_ok \"Updated Apache Cassandra\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/apache-couchdb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://couchdb.apache.org/\n\nAPP=\"Apache-CouchDB\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/lib/systemd/system/couchdb.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Apache CouchDB\"\n  $STD apt-get update\n  $STD apt-get install -y --only-upgrade couchdb\n  msg_ok \"Updated Apache CouchDB\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5984/_utils/${CL}\"\n"
  },
  {
    "path": "ct/apache-guacamole.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://guacamole.apache.org/\n\nAPP=\"Apache-Guacamole\"\nvar_tags=\"${var_tags:-webserver;remote}\"\nvar_disk=\"${var_disk:-4}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/apache-guacamole ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Fetch latest versions\n  LATEST_TOMCAT=$(curl -fsSL https://dlcdn.apache.org/tomcat/tomcat-9/ | grep -oP '(?<=href=\")v[^\"/]+(?=/\")' | sed 's/^v//' | sort -V | tail -n1)\n  LATEST_SERVER=$(curl -fsSL https://api.github.com/repos/apache/guacamole-server/tags | jq -r '.[].name' | grep -v -- '-RC' | head -n 1)\n  LATEST_CLIENT=$(curl -fsSL https://api.github.com/repos/apache/guacamole-client/tags | jq -r '.[].name' | grep -v -- '-RC' | head -n 1)\n  LATEST_MYSQL_CONNECTOR=$(curl -fsSL \"https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/maven-metadata.xml\" | grep -oP '<latest>\\K[^<]+')\n\n  # Read current versions from ~/.guacamole_*\n  CURRENT_TOMCAT=$(cat ~/.guacamole_tomcat 2>/dev/null || echo \"unknown\")\n  CURRENT_SERVER=$(cat ~/.guacamole_server 2>/dev/null || echo \"unknown\")\n  CURRENT_CLIENT=$(cat ~/.guacamole_client 2>/dev/null || echo \"unknown\")\n  CURRENT_MYSQL_CONNECTOR=$(cat ~/.guacamole_mysql_connector 2>/dev/null || echo \"unknown\")\n\n  UPDATE_NEEDED=false\n  [[ \"$CURRENT_TOMCAT\" != \"$LATEST_TOMCAT\" ]] && UPDATE_NEEDED=true\n  [[ \"$CURRENT_SERVER\" != \"$LATEST_SERVER\" ]] && UPDATE_NEEDED=true\n  [[ \"$CURRENT_CLIENT\" != \"$LATEST_CLIENT\" ]] && UPDATE_NEEDED=true\n  [[ \"$CURRENT_MYSQL_CONNECTOR\" != \"$LATEST_MYSQL_CONNECTOR\" ]] && UPDATE_NEEDED=true\n\n  if [[ \"$UPDATE_NEEDED\" == \"false\" ]]; then\n    msg_ok \"All components are up to date\"\n    exit\n  fi\n\n  JAVA_VERSION=\"17\" setup_java\n\n  msg_info \"Stopping Services\"\n  systemctl stop guacd tomcat\n  msg_ok \"Stopped Services\"\n\n  # Update Tomcat\n  if [[ \"$CURRENT_TOMCAT\" != \"$LATEST_TOMCAT\" ]]; then\n    msg_info \"Updating Tomcat (${CURRENT_TOMCAT} → ${LATEST_TOMCAT})\"\n    cp -a /opt/apache-guacamole/tomcat9/conf /tmp/tomcat-conf-backup\n    curl -fsSL \"https://dlcdn.apache.org/tomcat/tomcat-9/v${LATEST_TOMCAT}/bin/apache-tomcat-${LATEST_TOMCAT}.tar.gz\" | tar -xz -C /opt/apache-guacamole/tomcat9 --strip-components=1 --exclude='conf/*'\n    cp -a /tmp/tomcat-conf-backup/* /opt/apache-guacamole/tomcat9/conf/\n    rm -rf /tmp/tomcat-conf-backup\n    chown -R tomcat: /opt/apache-guacamole/tomcat9\n    echo \"${LATEST_TOMCAT}\" >~/.guacamole_tomcat\n    msg_ok \"Updated Tomcat\"\n  else\n    msg_ok \"Tomcat already up to date (${CURRENT_TOMCAT})\"\n  fi\n\n  # Update Guacamole Server\n  if [[ \"$CURRENT_SERVER\" != \"$LATEST_SERVER\" ]]; then\n    msg_info \"Updating Guacamole Server (${CURRENT_SERVER} → ${LATEST_SERVER})\"\n    rm -rf /opt/apache-guacamole/server/*\n    curl -fsSL \"https://api.github.com/repos/apache/guacamole-server/tarball/refs/tags/${LATEST_SERVER}\" | tar -xz --strip-components=1 -C /opt/apache-guacamole/server\n    cd /opt/apache-guacamole/server\n    export CPPFLAGS=\"-Wno-error=deprecated-declarations\"\n    $STD autoreconf -fi\n    $STD ./configure --with-init-dir=/etc/init.d --enable-allow-freerdp-snapshots\n    $STD make\n    $STD make install\n    $STD ldconfig\n    echo \"${LATEST_SERVER}\" >~/.guacamole_server\n    msg_ok \"Updated Guacamole Server\"\n\n    # Auth JDBC follows server version\n    msg_info \"Updating Guacamole Auth JDBC\"\n    rm -f /etc/guacamole/extensions/guacamole-auth-jdbc-mysql-*.jar\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-jdbc-${LATEST_SERVER}.tar.gz\" -o \"/tmp/guacamole-auth-jdbc.tar.gz\"\n    $STD tar -xf /tmp/guacamole-auth-jdbc.tar.gz -C /tmp\n    mv /tmp/guacamole-auth-jdbc-\"${LATEST_SERVER}\"/mysql/guacamole-auth-jdbc-mysql-\"${LATEST_SERVER}\".jar /etc/guacamole/extensions/\n    echo \"${LATEST_SERVER}\" >~/.guacamole_auth_jdbc\n    msg_ok \"Updated Guacamole Auth JDBC\"\n  else\n    msg_ok \"Guacamole Server already up to date (${CURRENT_SERVER})\"\n  fi\n\n  # Update Guacamole Client\n  if [[ \"$CURRENT_CLIENT\" != \"$LATEST_CLIENT\" ]]; then\n    msg_info \"Updating Guacamole Client (${CURRENT_CLIENT} → ${LATEST_CLIENT})\"\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_CLIENT}/binary/guacamole-${LATEST_CLIENT}.war\" -o \"/opt/apache-guacamole/tomcat9/webapps/guacamole.war\"\n    echo \"${LATEST_CLIENT}\" >~/.guacamole_client\n    msg_ok \"Updated Guacamole Client\"\n  else\n    msg_ok \"Guacamole Client already up to date (${CURRENT_CLIENT})\"\n  fi\n\n  # Update MySQL Connector\n  if [[ \"$CURRENT_MYSQL_CONNECTOR\" != \"$LATEST_MYSQL_CONNECTOR\" ]]; then\n    msg_info \"Updating MySQL Connector (${CURRENT_MYSQL_CONNECTOR} → ${LATEST_MYSQL_CONNECTOR})\"\n    curl -fsSL \"https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/${LATEST_MYSQL_CONNECTOR}/mysql-connector-j-${LATEST_MYSQL_CONNECTOR}.jar\" -o \"/etc/guacamole/lib/mysql-connector-j.jar\"\n    echo \"${LATEST_MYSQL_CONNECTOR}\" >~/.guacamole_mysql_connector\n    msg_ok \"Updated MySQL Connector\"\n  else\n    msg_ok \"MySQL Connector already up to date (${CURRENT_MYSQL_CONNECTOR})\"\n  fi\n\n  # Apply SQL Schema Upgrades (CRITICAL!)\n  if [[ \"$CURRENT_SERVER\" != \"$LATEST_SERVER\" ]]; then\n    msg_info \"Applying MySQL Schema Upgrades\"\n    cd /tmp/guacamole-auth-jdbc-\"${LATEST_SERVER}\"/mysql/schema/upgrade/\n    UPGRADE_FILES=($(ls -1 upgrade-pre-*.sql 2>/dev/null | sort -V))\n\n    if [[ ${#UPGRADE_FILES[@]} -gt 0 ]]; then\n      for SQL_FILE in \"${UPGRADE_FILES[@]}\"; do\n        FILE_VERSION=$(echo ${SQL_FILE} | grep -oP 'upgrade-pre-\\K[0-9\\.]+(?=\\.)')\n        # Apply upgrade if file version is newer than current but older/equal to target\n        if [[ $(echo -e \"${FILE_VERSION}\\n${CURRENT_SERVER}\" | sort -V | head -n1) == \"${CURRENT_SERVER}\" && \"${FILE_VERSION}\" != \"${CURRENT_SERVER}\" ]]; then\n          msg_info \"Applying schema patch: ${SQL_FILE}\"\n          mysql -u root guacamole_db <\"${SQL_FILE}\" 2>/dev/null\n          if [[ $? -eq 0 ]]; then\n            msg_ok \"Applied ${SQL_FILE}\"\n          else\n            msg_warn \"Failed to apply ${SQL_FILE} (may already be applied)\"\n          fi\n        fi\n      done\n    fi\n    rm -rf /tmp/guacamole-auth-jdbc*\n    msg_ok \"MySQL Schema updated\"\n  fi\n\n  # Check and upgrade optional extensions\n  # TOTP Extension\n  if [[ -f /etc/guacamole/extensions/guacamole-auth-totp-*.jar ]]; then\n    msg_info \"Updating TOTP Extension\"\n    rm -f /etc/guacamole/extensions/guacamole-auth-totp-*.jar\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-totp-${LATEST_SERVER}.tar.gz\" -o \"/tmp/guacamole-auth-totp.tar.gz\"\n    $STD tar -xf /tmp/guacamole-auth-totp.tar.gz -C /tmp\n    mv /tmp/guacamole-auth-totp-\"${LATEST_SERVER}\"/guacamole-auth-totp-\"${LATEST_SERVER}\".jar /etc/guacamole/extensions/\n    chmod 664 /etc/guacamole/extensions/guacamole-auth-totp-\"${LATEST_SERVER}\".jar\n    rm -rf /tmp/guacamole-auth-totp*\n    msg_ok \"Updated TOTP Extension\"\n  fi\n\n  # DUO Extension\n  if [[ -f /etc/guacamole/extensions/guacamole-auth-duo-*.jar ]]; then\n    msg_info \"Updating DUO Extension\"\n    rm -f /etc/guacamole/extensions/guacamole-auth-duo-*.jar\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-duo-${LATEST_SERVER}.tar.gz\" -o \"/tmp/guacamole-auth-duo.tar.gz\"\n    $STD tar -xf /tmp/guacamole-auth-duo.tar.gz -C /tmp\n    mv /tmp/guacamole-auth-duo-\"${LATEST_SERVER}\"/guacamole-auth-duo-\"${LATEST_SERVER}\".jar /etc/guacamole/extensions/\n    chmod 664 /etc/guacamole/extensions/guacamole-auth-duo-\"${LATEST_SERVER}\".jar\n    rm -rf /tmp/guacamole-auth-duo*\n    msg_ok \"Updated DUO Extension\"\n  fi\n\n  # LDAP Extension\n  if [[ -f /etc/guacamole/extensions/guacamole-auth-ldap-*.jar ]]; then\n    msg_info \"Updating LDAP Extension\"\n    rm -f /etc/guacamole/extensions/guacamole-auth-ldap-*.jar\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-ldap-${LATEST_SERVER}.tar.gz\" -o \"/tmp/guacamole-auth-ldap.tar.gz\"\n    $STD tar -xf /tmp/guacamole-auth-ldap.tar.gz -C /tmp\n    mv /tmp/guacamole-auth-ldap-\"${LATEST_SERVER}\"/guacamole-auth-ldap-\"${LATEST_SERVER}\".jar /etc/guacamole/extensions/\n    chmod 664 /etc/guacamole/extensions/guacamole-auth-ldap-\"${LATEST_SERVER}\".jar\n    rm -rf /tmp/guacamole-auth-ldap*\n    msg_ok \"Updated LDAP Extension\"\n  fi\n\n  # Quick Connect Extension\n  if [[ -f /etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar ]]; then\n    msg_info \"Updating Quick Connect Extension\"\n    rm -f /etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-quickconnect-${LATEST_SERVER}.tar.gz\" -o \"/tmp/guacamole-auth-quickconnect.tar.gz\"\n    $STD tar -xf /tmp/guacamole-auth-quickconnect.tar.gz -C /tmp\n    mv /tmp/guacamole-auth-quickconnect-\"${LATEST_SERVER}\"/guacamole-auth-quickconnect-\"${LATEST_SERVER}\".jar /etc/guacamole/extensions/\n    chmod 664 /etc/guacamole/extensions/guacamole-auth-quickconnect-\"${LATEST_SERVER}\".jar\n    rm -rf /tmp/guacamole-auth-quickconnect*\n    msg_ok \"Updated Quick Connect Extension\"\n  fi\n\n  # History Recording Storage Extension\n  if [[ -f /etc/guacamole/extensions/guacamole-history-recording-storage-*.jar ]]; then\n    msg_info \"Updating History Recording Storage Extension\"\n    rm -f /etc/guacamole/extensions/guacamole-history-recording-storage-*.jar\n    curl -fsSL \"https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-history-recording-storage-${LATEST_SERVER}.tar.gz\" -o \"/tmp/guacamole-history-recording-storage.tar.gz\"\n    $STD tar -xf /tmp/guacamole-history-recording-storage.tar.gz -C /tmp\n    mv /tmp/guacamole-history-recording-storage-\"${LATEST_SERVER}\"/guacamole-history-recording-storage-\"${LATEST_SERVER}\".jar /etc/guacamole/extensions/\n    chmod 664 /etc/guacamole/extensions/guacamole-history-recording-storage-\"${LATEST_SERVER}\".jar\n    rm -rf /tmp/guacamole-history-recording-storage*\n    msg_ok \"Updated History Recording Storage Extension\"\n  fi\n\n  # Reset permissions and prepare for service start\n  msg_info \"Resetting permissions\"\n  mkdir -p /var/guacamole\n  chown daemon:daemon /var/guacamole\n  mkdir -p /home/daemon/.config/freerdp\n  chown daemon:daemon /home/daemon/.config/freerdp\n  msg_ok \"Permissions reset\"\n\n  msg_info \"Starting Services\"\n  systemctl daemon-reload\n  systemctl start tomcat guacd\n  msg_ok \"Started Services\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/guacamole${CL}\"\n"
  },
  {
    "path": "ct/apache-tika.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/apache/tika/\n\nAPP=\"Apache-Tika\"\nvar_tags=\"${var_tags:-document}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/apache-tika.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  RELEASE=\"$(curl -fsSL https://dlcdn.apache.org/tika/ | grep -oP '(?<=href=\")[0-9]+\\.[0-9]+\\.[0-9]+(?=/\")' | sort -V | tail -n1)\"\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache-tika\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP} to v${RELEASE}\"\n    cd /opt/apache-tika\n    curl -fsSL -o tika-server-standard-${RELEASE}.jar \"https://dlcdn.apache.org/tika/${RELEASE}/tika-server-standard-${RELEASE}.jar\"\n    mv --force tika-server-standard.jar tika-server-standard-prev-version.jar\n    mv tika-server-standard-${RELEASE}.jar tika-server-standard.jar\n    rm -rf /opt/apache-tika/tika-server-standard-prev-version.jar\n    echo \"${RELEASE}\" >/opt/${APP}_version.txt\n    msg_ok \"Updated ${APP} to v${RELEASE}\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache-tika\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9998${CL}\"\n"
  },
  {
    "path": "ct/apache-tomcat.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tomcat.apache.org/\n\nAPP=\"Apache-Tomcat\"\nvar_tags=\"${var_tags:-webserver}\"\nvar_disk=\"${var_disk:-5}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  TOMCAT_DIR=$(ls -d /opt/tomcat-* 2>/dev/null | head -n1)\n  if [[ -z \"$TOMCAT_DIR\" || ! -d \"$TOMCAT_DIR\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Detect major version and current version from install path (e.g., /opt/tomcat-11 -> 11)\n  TOMCAT_MAJOR=$(basename \"$TOMCAT_DIR\" | grep -oP 'tomcat-\\K[0-9]+')\n  if [[ -z \"$TOMCAT_MAJOR\" ]]; then\n    msg_error \"Cannot determine Tomcat major version from path: $TOMCAT_DIR\"\n    exit\n  fi\n  CURRENT_VERSION=$(grep -oP 'Apache Tomcat Version \\K[0-9.]+' \"$TOMCAT_DIR/RELEASE-NOTES\" 2>/dev/null || echo \"unknown\")\n  LATEST_VERSION=$(curl -fsSL \"https://dlcdn.apache.org/tomcat/tomcat-${TOMCAT_MAJOR}/\" | grep -oP 'v[0-9]+\\.[0-9]+\\.[0-9]+(-M[0-9]+)?/' | sort -V | tail -n1 | sed 's/\\/$//; s/v//')\n\n  if [[ -z \"$LATEST_VERSION\" ]]; then\n    msg_error \"Failed to fetch latest version for Tomcat ${TOMCAT_MAJOR}\"\n    exit\n  fi\n\n  if [[ \"$CURRENT_VERSION\" == \"$LATEST_VERSION\" ]]; then\n    msg_ok \"${APP} ${CURRENT_VERSION} is already up to date\"\n    exit\n  fi\n\n  msg_info \"Stopping Tomcat service\"\n  systemctl stop tomcat\n  msg_ok \"Stopped Tomcat service\"\n\n  msg_info \"Backing up configuration and applications\"\n  BACKUP_DIR=\"/tmp/tomcat-backup-$$\"\n  mkdir -p \"$BACKUP_DIR\"\n  cp -a \"$TOMCAT_DIR/conf\" \"$BACKUP_DIR/conf\"\n  cp -a \"$TOMCAT_DIR/webapps\" \"$BACKUP_DIR/webapps\"\n  [[ -d \"$TOMCAT_DIR/lib\" ]] && cp -a \"$TOMCAT_DIR/lib\" \"$BACKUP_DIR/lib\"\n  msg_ok \"Backed up configuration and applications\"\n\n  msg_info \"Downloading Tomcat ${LATEST_VERSION}\"\n  TOMCAT_URL=\"https://dlcdn.apache.org/tomcat/tomcat-${TOMCAT_MAJOR}/v${LATEST_VERSION}/bin/apache-tomcat-${LATEST_VERSION}.tar.gz\"\n  curl -fsSL \"$TOMCAT_URL\" -o /tmp/tomcat-update.tar.gz\n  msg_ok \"Downloaded Tomcat ${LATEST_VERSION}\"\n\n  msg_info \"Installing update\"\n  rm -rf \"${TOMCAT_DIR:?}\"/*\n  tar --strip-components=1 -xzf /tmp/tomcat-update.tar.gz -C \"$TOMCAT_DIR\"\n  rm -f /tmp/tomcat-update.tar.gz\n  msg_ok \"Installed update\"\n\n  msg_info \"Restoring configuration and applications\"\n  cp -a \"$BACKUP_DIR/conf\"/* \"$TOMCAT_DIR/conf/\"\n  cp -a \"$BACKUP_DIR/webapps\"/* \"$TOMCAT_DIR/webapps/\" 2>/dev/null || true\n  if [[ -d \"$BACKUP_DIR/lib\" ]]; then\n    for jar in \"$BACKUP_DIR/lib\"/*.jar; do\n      [[ -f \"$jar\" ]] || continue\n      jar_name=$(basename \"$jar\")\n      if [[ ! -f \"$TOMCAT_DIR/lib/$jar_name\" ]]; then\n        cp \"$jar\" \"$TOMCAT_DIR/lib/\"\n      fi\n    done\n  fi\n  rm -rf \"$BACKUP_DIR\"\n  chown -R root:root \"$TOMCAT_DIR\"\n  msg_ok \"Restored configuration and applications\"\n\n  msg_info \"Starting Tomcat service\"\n  systemctl start tomcat\n  msg_ok \"Started Tomcat service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/apprise-api.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: SystemIdleProcess\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/caronc/apprise-api\n\nAPP=\"Apprise-API\"\nvar_tags=\"${var_tags:-notification}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/apprise\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"apprise\" \"caronc/apprise-api\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apprise-api\n    msg_ok \"Stopped Service\"\n\n    PYTHON_VERSION=\"3.12\" setup_uv\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"apprise\" \"caronc/apprise-api\" \"tarball\"\n\n    msg_info \"Updating Apprise-API\"\n    cd /opt/apprise\n    cp ./requirements.txt /etc/requirements.txt\n    $STD apt install -y nginx git\n    $STD uv pip install -r requirements.txt gunicorn supervisor --system\n    cp -fr apprise_api/static /usr/share/nginx/html/s/\n    mv apprise_api/ webapp\n    touch /etc/nginx/server-override.conf\n    touch /etc/nginx/location-override.conf\n    mkdir -p /config/store /attach /plugin /tmp/apprise /opt/apprise/logs\n    chmod 1777 /tmp/apprise && chmod 777 /config /config/store /attach /plugin /opt/apprise/logs\n    sed -i \\\n      -e '/[[]program:nginx]/,/^[[]/ s|stdout_logfile=/dev/stdout|stdout_logfile=/opt/apprise/logs/nginx.log|' \\\n      -e '/[[]program:nginx]/,/^[[]/ s|stderr_logfile=/dev/stderr|stderr_logfile=/opt/apprise/logs/nginx_error.log|' \\\n      -e '/[[]program:gunicorn]/,/^[[]/ s|stdout_logfile=/dev/stdout|stdout_logfile=/opt/apprise/logs/gunicorn.log|' \\\n      -e '/[[]program:gunicorn]/,/^[[]/ s|stderr_logfile=/dev/stderr|stderr_logfile=/opt/apprise/logs/gunicorn_error.log|' \\\n      -e '/[[]supervisord]/,/^[[]/ s|logfile=/dev/null|logfile=/opt/apprise/logs/supervisor.log|' \\\n      -e 's|_maxbytes=0|_maxbytes=10485760|g' \\\n      /opt/apprise/webapp/etc/supervisord.conf\n    msg_ok \"Updated Apprise-API\"\n\n    msg_info \"Starting Service\"\n    systemctl start apprise-api\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/apt-cacher-ng.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wiki.debian.org/AptCacherNg\n\nAPP=\"Apt-Cacher-NG\"\nvar_tags=\"${var_tags:-caching}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Apt-Cacher-NG\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated Apt-Cacher-NG\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3142/acng-report.html${CL}\"\n"
  },
  {
    "path": "ct/archivebox.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://archivebox.io/\n\nAPP=\"ArchiveBox\"\nvar_tags=\"${var_tags:-archive;bookmark}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/archivebox ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"@postlight/parser@latest,single-file-cli@latest\" setup_nodejs\n  PYTHON_VERSION=\"3.13\" setup_uv\n\n  ensure_dependencies chromium\n\n  msg_info \"Stopping Service\"\n  systemctl stop archivebox\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Upgrading Playwright\"\n  $STD uv pip install playwright --system\n  $STD playwright install-deps chromium\n  msg_ok \"Upgraded Playwright\"\n\n  msg_info \"Updating ArchiveBox\"\n  cd /opt/archivebox/data\n  $STD uv pip install --system --upgrade --no-reinstall archivebox\n  sudo -u archivebox archivebox init\n  msg_ok \"Updated ArchiveBox\"\n\n  msg_info \"Starting Service\"\n  systemctl start archivebox\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000/admin/login${CL}\"\n"
  },
  {
    "path": "ct/argus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://release-argus.io/\n\nAPP=\"Argus\"\nvar_tags=\"${var_tags:-watcher}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/argus ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"argus\" \"release-argus/Argus\"; then\n    msg_info \"Stopping service\"\n    systemctl stop argus\n    msg_ok \"Service stopped\"\n\n    fetch_and_deploy_gh_release \"Argus\" \"release-argus/Argus\" \"singlefile\" \"latest\" \"/opt/argus\" \"Argus*linux-arm64\"\n\n    msg_info \"Starting service\"\n    systemctl start argus\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/aria2.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://aria2.github.io/\n\nAPP=\"Aria2\"\nvar_tags=\"${var_tags:-download-utility}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Aria2\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated Aria2\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6880${CL}\"\n"
  },
  {
    "path": "ct/asterisk.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://asterisk.org/\n\nAPP=\"Asterisk\"\nvar_tags=\"${var_tags:-telephone;pbx}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    msg_error \"No Update function provided for ${APP} LXC\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/audiobookshelf.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.audiobookshelf.org/\n\nAPP=\"audiobookshelf\"\nvar_tags=\"${var_tags:-podcast;audiobook}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/default/audiobookshelf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating AudiobookShelf\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated AudiobookShelf\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:13378${CL}\"\n"
  },
  {
    "path": "ct/authelia.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.authelia.com/\n\nAPP=\"Authelia\"\nvar_tags=\"${var_tags:-authenticator}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nbase_settings\n\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/authelia/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"authelia\" \"authelia/authelia\"; then\n    $STD apt update\n    $STD apt -y upgrade\n    fetch_and_deploy_gh_release \"authelia\" \"authelia/authelia\" \"binary\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9091 or https://auth.YOURDOMAIN ${CL}\"\n"
  },
  {
    "path": "ct/autobrr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://autobrr.com/\n\nAPP=\"Autobrr\"\nvar_tags=\"${var_tags:-arr;}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /root/.config/autobrr/config.toml ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"autobrr\" \"autobrr/autobrr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop autobrr\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"autobrr\" \"autobrr/autobrr\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"autobrr_*_linux_arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start autobrr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7474${CL}\"\n"
  },
  {
    "path": "ct/autocaliweb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://codeberg.org/gelbphoenix/autocaliweb\n\nAPP=\"Autocaliweb\"\nvar_tags=\"${var_tags:-ebooks}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/autocaliweb ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_uv\n\n  RELEASE=$(get_latest_codeberg_release \"gelbphoenix/autocaliweb\")\n  if check_for_codeberg_release \"autocaliweb\" \"gelbphoenix/autocaliweb\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop autocaliweb metadata-change-detector acw-ingest-service acw-auto-zipper\n    msg_ok \"Stopped Services\"\n\n    INSTALL_DIR=\"/opt/autocaliweb\"\n    export VIRTUAL_ENV=\"${INSTALL_DIR}/venv\"\n    $STD tar -cf ~/autocaliweb_bkp.tar \"$INSTALL_DIR\"/{metadata_change_logs,dirs.json,.env,scripts/ingest_watcher.sh,scripts/auto_zipper_wrapper.sh,scripts/metadata_change_detector_wrapper.sh}\n    fetch_and_deploy_codeberg_release \"autocaliweb\" \"gelbphoenix/autocaliweb\" \"tarball\" \"latest\" \"/opt/autocaliweb\"\n    \n    msg_info \"Updating Autocaliweb\"\n    cd \"$INSTALL_DIR\" \n    if [[ ! -d \"$VIRTUAL_ENV\" ]]; then\n      $STD uv venv --clear \"$VIRTUAL_ENV\"\n    fi\n    $STD uv sync --all-extras --active\n    cd \"$INSTALL_DIR\"/koreader/plugins \n    PLUGIN_DIGEST=\"$(find acwsync.koplugin -type f -name \"*.lua\" -o -name \"*.json\" | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)\"\n    echo \"Plugin files digest: $PLUGIN_DIGEST\" >acwsync.koplugin/\"${PLUGIN_DIGEST}\".digest\n    echo \"Build date: $(date)\" >>acwsync.koplugin/\"${PLUGIN_DIGEST}\".digest\n    echo \"Files included:\" >>acwsync.koplugin/\"${PLUGIN_DIGEST}\".digest\n    $STD zip -r koplugin.zip acwsync.koplugin/\n    cp -r koplugin.zip \"$INSTALL_DIR\"/cps/static\n    mkdir -p \"$INSTALL_DIR\"/metadata_temp\n    $STD tar -xf ~/autocaliweb_bkp.tar --directory /\n    KEPUB_VERSION=\"$(/usr/bin/kepubify --version)\"\n    CALIBRE_RELEASE=\"$(curl -s https://api.github.com/repos/kovidgoyal/calibre/releases/latest | grep -o '\"tag_name\": \"[^\"]*' | cut -d'\"' -f4)\"\n    echo \"${KEPUB_VERSION#v}\" >\"$INSTALL_DIR\"/KEPUBIFY_RELEASE\n    echo \"${CALIBRE_RELEASE#v}\" >/\"$INSTALL_DIR\"/CALIBRE_RELEASE\n    sed 's/^/v/' ~/.autocaliweb >\"$INSTALL_DIR\"/ACW_RELEASE\n    chown -R acw:acw \"$INSTALL_DIR\"\n    rm ~/autocaliweb_bkp.tar\n    msg_ok \"Updated Autocaliweb\"\n\n    msg_info \"Starting Services\"\n    systemctl start autocaliweb metadata-change-detector acw-ingest-service acw-auto-zipper\n    msg_ok \"Started Services\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8083${CL}\"\n"
  },
  {
    "path": "ct/babybuddy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/babybuddy/babybuddy\n\nAPP=\"Baby Buddy\"\nvar_tags=\"${var_tags:-baby}\"\nvar_disk=\"${var_disk:-5}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/babybuddy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"babybuddy\" \"babybuddy/babybuddy\"; then\n    setup_uv\n\n    msg_info \"Stopping Services\"\n    systemctl stop nginx\n    systemctl stop uwsgi\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Cleaning old files\"\n    cp /opt/babybuddy/babybuddy/settings/production.py /tmp/production.py.bak\n    find . -mindepth 1 -maxdepth 1 ! -name '.venv' -exec rm -rf {} +\n    msg_ok \"Cleaned old files\"\n\n    fetch_and_deploy_gh_release \"babybuddy\" \"babybuddy/babybuddy\" \"tarball\"\n\n    msg_info \"Updating ${APP}\"\n    cd /opt/babybuddy\n    mv /tmp/production.py.bak /opt/babybuddy/babybuddy/settings/production.py\n    source .venv/bin/activate\n    $STD uv pip install -r requirements.txt\n    export DJANGO_SETTINGS_MODULE=babybuddy.settings.production\n    $STD python manage.py migrate\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Fixing permissions\"\n    chown -R www-data:www-data /opt/data\n    chmod 640 /opt/data/db.sqlite3\n    chmod 750 /opt/data\n    msg_ok \"Permissions fixed\"\n\n    msg_info \"Starting Services\"\n    systemctl start uwsgi\n    systemctl start nginx\n    msg_ok \"Services Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/backrest.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: ksad (enirys31)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://garethgeorge.github.io/backrest/\n\nAPP=\"Backrest\"\nvar_tags=\"${var_tags:-backup}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/backrest ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"backrest\" \"garethgeorge/backrest\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop backrest\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"backrest\" \"garethgeorge/backrest\" \"prebuild\" \"latest\" \"/opt/backrest/bin\" \"backrest_Linux_arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start backrest\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9898${CL}\"\n"
  },
  {
    "path": "ct/baikal.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sabre.io/baikal/\n\nAPP=\"Baikal\"\nvar_tags=\"${var_tags:-Dav}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/baikal ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"baikal\" \"sabre-io/Baikal\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    mv /opt/baikal /opt/baikal-backup\n    msg_ok \"Backed up data\"\n\n    PHP_APACHE=\"YES\" PHP_VERSION=\"8.3\" setup_php\n    setup_composer\n    fetch_and_deploy_gh_release \"baikal\" \"sabre-io/Baikal\" \"tarball\"\n\n    msg_info \"Configuring Baikal\"\n    cp -r /opt/baikal-backup/config/baikal.yaml /opt/baikal/config/\n    cp -r /opt/baikal-backup/Specific/ /opt/baikal/\n    chown -R www-data:www-data /opt/baikal/\n    chmod -R 755 /opt/baikal/\n    cd /opt/baikal\n    $STD composer install\n    rm -rf /opt/baikal-backup\n    msg_ok \"Configured Baikal\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/bambuddy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Adrian-RDA\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/maziggy/bambuddy\n\nAPP=\"Bambuddy\"\nvar_tags=\"${var_tags:-media;3d-printing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/bambuddy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies ffmpeg\n\n  if check_for_gh_release \"bambuddy\" \"maziggy/bambuddy\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop bambuddy\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration and Data\"\n    cp /opt/bambuddy/.env /opt/bambuddy.env.bak\n    cp -r /opt/bambuddy/data /opt/bambuddy_data_bak\n    [[ -f /opt/bambuddy/bambuddy.db ]] && cp /opt/bambuddy/bambuddy.db /opt/bambuddy.db.bak\n    [[ -f /opt/bambuddy/bambutrack.db ]] && cp /opt/bambuddy/bambutrack.db /opt/bambutrack.db.bak\n    [[ -d /opt/bambuddy/archive ]] && cp -r /opt/bambuddy/archive /opt/bambuddy_archive_bak\n    msg_ok \"Backed up Configuration and Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"bambuddy\" \"maziggy/bambuddy\" \"tarball\" \"latest\" \"/opt/bambuddy\"\n\n    msg_info \"Updating Python Dependencies\"\n    cd /opt/bambuddy\n    $STD uv venv --clear\n    $STD uv pip install -r requirements.txt\n    msg_ok \"Updated Python Dependencies\"\n\n    msg_info \"Rebuilding Frontend\"\n    cd /opt/bambuddy/frontend\n    $STD npm install\n    $STD npm run build\n    msg_ok \"Rebuilt Frontend\"\n\n    msg_info \"Restoring Configuration and Data\"\n    mkdir -p /opt/bambuddy/data\n    cp /opt/bambuddy.env.bak /opt/bambuddy/.env\n    cp -r /opt/bambuddy_data_bak/. /opt/bambuddy/data/\n    [[ -f /opt/bambuddy.db.bak ]] && cp /opt/bambuddy.db.bak /opt/bambuddy/bambuddy.db\n    [[ -f /opt/bambutrack.db.bak ]] && cp /opt/bambutrack.db.bak /opt/bambuddy/bambutrack.db\n    if [[ -d /opt/bambuddy_archive_bak ]]; then\n      mkdir -p /opt/bambuddy/archive\n      cp -r /opt/bambuddy_archive_bak/. /opt/bambuddy/archive/\n    fi\n    rm -f /opt/bambuddy.env.bak /opt/bambuddy.db.bak /opt/bambutrack.db.bak\n    rm -rf /opt/bambuddy_data_bak /opt/bambuddy_archive_bak\n    msg_ok \"Restored Configuration and Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start bambuddy\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/bar-assistant.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01 | CanbiZ\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/karlomikus/bar-assistant\n# Source: https://github.com/karlomikus/vue-salt-rim\n# Source: https://www.meilisearch.com/\n\nAPP=\"Bar-Assistant\"\nvar_tags=\"${var_tags:-cocktails;drinks}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/bar-assistant ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"bar-assistant\" \"karlomikus/bar-assistant\"; then\n    msg_info \"Stopping nginx\"\n    systemctl stop nginx\n    msg_ok \"Stopped nginx\"\n\n    PHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"pdo-sqlite\" setup_php\n\n    msg_info \"Backing up Bar Assistant\"\n    mv /opt/bar-assistant /opt/bar-assistant-backup\n    msg_ok \"Backed up Bar Assistant\"\n\n    fetch_and_deploy_gh_release \"bar-assistant\" \"karlomikus/bar-assistant\" \"tarball\" \"latest\" \"/opt/bar-assistant\"\n    setup_composer\n\n    msg_info \"Updating Bar-Assistant\"\n    cp -r /opt/bar-assistant-backup/.env /opt/bar-assistant/.env\n    cp -r /opt/bar-assistant-backup/storage/bar-assistant /opt/bar-assistant/storage/bar-assistant\n    cd /opt/bar-assistant\n    $STD composer install --no-interaction\n    $STD php artisan migrate --force\n    $STD php artisan storage:link\n    $STD php artisan bar:setup-meilisearch\n    $STD php artisan scout:sync-index-settings\n    $STD php artisan config:cache\n    $STD php artisan route:cache\n    $STD php artisan event:cache\n    chown -R www-data:www-data /opt/bar-assistant\n    rm -rf /opt/bar-assistant-backup\n    msg_ok \"Updated Bar-Assistant\"\n\n    msg_info \"Starting nginx\"\n    systemctl start nginx\n    msg_ok \"Started nginx\"\n  fi\n\n  if check_for_gh_release \"vue-salt-rim\" \"karlomikus/vue-salt-rim\"; then\n    msg_info \"Backing up Vue Salt Rim\"\n    mv /opt/vue-salt-rim /opt/vue-salt-rim-backup\n    msg_ok \"Backed up Vue Salt Rim\"\n\n    msg_info \"Stopping nginx\"\n    systemctl stop nginx\n    msg_ok \"Stopped nginx\"\n\n    fetch_and_deploy_gh_release \"vue-salt-rim\" \"karlomikus/vue-salt-rim\" \"tarball\" \"latest\" \"/opt/vue-salt-rim\"\n\n    msg_info \"Updating Vue Salt Rim\"\n    cp /opt/vue-salt-rim-backup/public/config.js /opt/vue-salt-rim/public/config.js\n    cd /opt/vue-salt-rim\n    $STD npm install\n    $STD npm run build\n    rm -rf /opt/vue-salt-rim-backup\n    msg_ok \"Updated Vue Salt Rim\"\n\n    msg_info \"Starting nginx\"\n    systemctl start nginx\n    msg_ok \"Started nginx\"\n  fi\n\n  setup_meilisearch\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/bazarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.bazarr.media/\n\nAPP=\"Bazarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/bazarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"bazarr\" \"morpheus65535/bazarr\"; then\n    apt-get install -y libicu76 &>/dev/null\n    msg_info \"Stopping Service\"\n    systemctl stop bazarr\n    msg_ok \"Stopped Service\"\n\n    PYTHON_VERSION=\"3.12\" setup_uv\n    fetch_and_deploy_gh_release \"bazarr\" \"morpheus65535/bazarr\" \"prebuild\" \"latest\" \"/opt/bazarr\" \"bazarr.zip\"\n\n    msg_info \"Setup Bazarr\"\n    mkdir -p /var/lib/bazarr/\n    chmod 775 /opt/bazarr /var/lib/bazarr/\n    # Always ensure venv exists\n    if [[ ! -d /opt/bazarr/venv/ ]]; then\n      $STD uv venv --clear /opt/bazarr/venv --python 3.12\n    fi\n    \n    # Always check and fix service file if needed\n    if [[ -f /etc/systemd/system/bazarr.service ]] && grep -q \"ExecStart=/usr/bin/python3\" /etc/systemd/system/bazarr.service; then\n      sed -i \"s|ExecStart=/usr/bin/python3 /opt/bazarr/bazarr.py|ExecStart=/opt/bazarr/venv/bin/python3 /opt/bazarr/bazarr.py|g\" /etc/systemd/system/bazarr.service\n      systemctl daemon-reload\n    fi\n    sed -i.bak 's/--only-binary=Pillow//g' /opt/bazarr/requirements.txt\n    $STD uv pip install -r /opt/bazarr/requirements.txt --python /opt/bazarr/venv/bin/python3\n    msg_ok \"Setup Bazarr\"\n\n    msg_info \"Starting Service\"\n    systemctl start bazarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6767${CL}\"\n"
  },
  {
    "path": "ct/bentopdf.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alam00000/bentopdf\n\nAPP=\"BentoPDF\"\nvar_tags=\"${var_tags:-pdf-editor}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/bentopdf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"bentopdf\" \"alam00000/bentopdf\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop bentopdf\n    msg_ok \"Stopped Service\"\n\n    [[ -f /opt/bentopdf/.env.production ]] && cp /opt/bentopdf/.env.production /opt/production.env\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"bentopdf\" \"alam00000/bentopdf\" \"tarball\" \"latest\" \"/opt/bentopdf\"\n\n    msg_info \"Updating BentoPDF\"\n    cd /opt/bentopdf\n    $STD npm ci --no-audit --no-fund\n    if [[ -f /opt/production.env ]]; then\n      mv /opt/production.env ./.env.production\n    else\n      cp ./.env.example ./.env.production\n    fi\n    export NODE_OPTIONS=\"--max-old-space-size=3072\"\n    export SIMPLE_MODE=true\n    export VITE_USE_CDN=true\n    $STD npm run build:all\n    if [[ ! -f /opt/bentopdf/dist/config.json ]]; then\n      cat <<'EOF' >/opt/bentopdf/dist/config.json\n{}\nEOF\n    fi\n    msg_ok \"Updated BentoPDF\"\n\n    msg_info \"Starting Service\"\n    ensure_dependencies nginx openssl\n    if [[ ! -f /etc/ssl/private/bentopdf-selfsigned.key || ! -f /etc/ssl/certs/bentopdf-selfsigned.crt ]]; then\n      CERT_CN=\"$(hostname -I | awk '{print $1}')\"\n      $STD openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \\\n      -keyout /etc/ssl/private/bentopdf-selfsigned.key \\\n      -out /etc/ssl/certs/bentopdf-selfsigned.crt \\\n      -subj \"/CN=${CERT_CN}\"\n    fi\n    cat <<'EOF' >/etc/nginx/sites-available/bentopdf\nserver {\n    listen 8080;\n    server_name _;\n    return 301 https://$host:8443$request_uri;\n  }\n\n  server {\n    listen 8443 ssl;\n    server_name _;\n    ssl_certificate /etc/ssl/certs/bentopdf-selfsigned.crt;\n    ssl_certificate_key /etc/ssl/private/bentopdf-selfsigned.key;\n    root /opt/bentopdf/dist;\n    index index.html;\n\n    # Required for LibreOffice WASM (Word/Excel/PowerPoint to PDF via SharedArrayBuffer)\n    add_header Cross-Origin-Opener-Policy \"same-origin\" always;\n    add_header Cross-Origin-Embedder-Policy \"require-corp\" always;\n    add_header Cross-Origin-Resource-Policy \"cross-origin\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n\n    gzip_static on;\n\n    location ~* /libreoffice-wasm/soffice\\.wasm\\.gz$ {\n      gzip off;\n      types {} default_type application/wasm;\n      add_header Content-Encoding gzip;\n      add_header Vary \"Accept-Encoding\";\n      add_header Cache-Control \"public, immutable\";\n    }\n\n    location ~* /libreoffice-wasm/soffice\\.data\\.gz$ {\n      gzip off;\n      types {} default_type application/octet-stream;\n      add_header Content-Encoding gzip;\n      add_header Vary \"Accept-Encoding\";\n      add_header Cache-Control \"public, immutable\";\n    }\n\n    location ~* \\.wasm$ {\n      types {} default_type application/wasm;\n      expires 1y;\n      add_header Cache-Control \"public, immutable\";\n    }\n\n    location ~* \\.(wasm\\.gz|data\\.gz|data)$ {\n      expires 1y;\n      add_header Cache-Control \"public, immutable\";\n    }\n\n    location / {\n        try_files $uri $uri/ $uri.html =404;\n    }\n\n    error_page 404 /404.html;\n}\nEOF\n    rm -f /etc/nginx/sites-enabled/default\n    ln -sf /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/bentopdf\n    cat <<'EOF' >/etc/systemd/system/bentopdf.service\n[Unit]\nDescription=BentoPDF Service\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/sbin/nginx -g \"daemon off;\"\nExecReload=/bin/kill -HUP $MAINPID\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    systemctl start bentopdf\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}\"\n"
  },
  {
    "path": "ct/beszel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michelle Zitzerman (Sinofage)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://beszel.dev/\n\nAPP=\"Beszel\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/beszel ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"beszel\" \"henrygd/beszel\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop beszel-hub\n    msg_info \"Stopped Service\"\n\n    msg_info \"Updating Beszel\"\n    $STD /opt/beszel/beszel update\n    sleep 2 && chmod +x /opt/beszel/beszel\n    VERSION=$(/opt/beszel/beszel -v | awk '{print $3}')\n    echo \"${VERSION}\" >$HOME/.beszel\n    msg_ok \"Updated Beszel to ${VERSION}\"\n\n    msg_info \"Starting Service\"\n    systemctl start beszel-hub\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/bichon.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rustmailer/bichon\n\nAPP=\"Bichon\"\nvar_tags=\"${var_tags:-email;archive}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/bichon ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"bichon\" \"rustmailer/bichon\"; then\n    msg_info \"Stopping service\"\n    systemctl stop bichon\n    msg_ok \"Stopped service\"\n\n    cp /opt/bichon/bichon.env /tmp/bichon.env.backup\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"bichon\" \"rustmailer/bichon\" \"prebuild\" \"latest\" \"/opt/bichon\" \"bichon-*-aarch64-unknown-linux-gnu.tar.gz\"\n    cp /tmp/bichon.env.backup /opt/bichon/bichon.env\n\n    msg_info \"Starting service\"\n    systemctl start bichon\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:15630${CL}\"\n"
  },
  {
    "path": "ct/birdnet-go.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/tphakala/birdnet-go\n\nAPP=\"BirdNET-Go\"\nvar_tags=\"${var_tags:-monitoring;ai;nature}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-no}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/bin/birdnet-go ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"birdnet\" \"tphakala/birdnet-go\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop birdnet\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"birdnet\" \"tphakala/birdnet-go\" \"prebuild\" \"latest\" \"/opt/birdnet\" \"birdnet-go-linux-amd64.tar.gz\"\n\n    msg_info \"Deploying Binary\"\n    cp /opt/birdnet/birdnet-go /usr/local/bin/birdnet-go\n    chmod +x /usr/local/bin/birdnet-go\n    cp -r /opt/birdnet/libtensorflowlite_c.so /usr/local/lib/ || true\n    ldconfig\n    msg_ok \"Deployed Binary\"\n\n    msg_info \"Starting Service\"\n    systemctl start birdnet\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/bitmagnet.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bitmagnet/bitmagnet\n\nAPP=\"Bitmagnet\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/bitmagnet ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"bitmagnet\" \"bitmagnet-io/bitmagnet\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop bitmagnet-web\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    rm -f /tmp/backup.sql\n    $STD sudo -u postgres pg_dump \\\n      --column-inserts \\\n      --data-only \\\n      --on-conflict-do-nothing \\\n      --rows-per-insert=1000 \\\n      --table=metadata_sources \\\n      --table=content \\\n      --table=content_attributes \\\n      --table=content_collections \\\n      --table=content_collections_content \\\n      --table=torrent_sources \\\n      --table=torrents \\\n      --table=torrent_files \\\n      --table=torrent_hints \\\n      --table=torrent_contents \\\n      --table=torrent_tags \\\n      --table=torrents_torrent_sources \\\n      --table=key_values \\\n      bitmagnet \\\n      >/tmp/backup.sql\n    mv /tmp/backup.sql /opt/\n    [ -f /opt/bitmagnet/.env ] && cp /opt/bitmagnet/.env /opt/\n    [ -f /opt/bitmagnet/config.yml ] && cp /opt/bitmagnet/config.yml /opt/\n    msg_ok \"Data backed up\"\n\n    rm -rf /opt/bitmagnet\n    fetch_and_deploy_gh_release \"bitmagnet\" \"bitmagnet-io/bitmagnet\" \"tarball\"\n\n    msg_info \"Updating Bitmagnet\"\n    cd /opt/bitmagnet\n    VREL=v$(curl -fsSL https://api.github.com/repos/bitmagnet-io/bitmagnet/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n    $STD go build -ldflags \"-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=$VREL\"\n    chmod +x bitmagnet\n    [ -f \"/opt/.env\" ] && cp \"/opt/.env\" /opt/bitmagnet/\n    [ -f \"/opt/config.yml\" ] && cp \"/opt/config.yml\" /opt/bitmagnet/\n    msg_ok \"Updated Bitmagnet\"\n\n    msg_info \"Starting Service\"\n    systemctl start bitmagnet-web\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3333${CL}\"\n"
  },
  {
    "path": "ct/blocky.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://0xerr0r.github.io/blocky\n\nAPP=\"Blocky\"\nvar_tags=\"${var_tags:-adblock}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/blocky ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"blocky\" \"0xERR0R/blocky\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop blocky\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backup Config\"\n    mv /opt/blocky/config.yml /opt/config.yml\n    msg_ok \"Backed Up Config\"\n    \n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"blocky\" \"0xERR0R/blocky\" \"prebuild\" \"latest\" \"/opt/blocky\" \"blocky_*_Linux_aarch64.tar.gz\"\n\n    msg_info \"Restore Config\"\n    mv /opt/config.yml /opt/blocky/config.yml\n    msg_ok \"Restored Config\"\n\n    msg_info \"Starting Service\"\n    systemctl start blocky\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/bookstack.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/BookStackApp/BookStack\n\nAPP=\"Bookstack\"\nvar_tags=\"${var_tags:-organizer}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/bookstack ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  ensure_dependencies git\n  if check_for_gh_release \"bookstack\" \"BookStackApp/BookStack\"; then\n    msg_info \"Stopping Apache2\"\n    systemctl stop apache2\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Backing up data\"\n    mv /opt/bookstack /opt/bookstack-backup\n    msg_ok \"Backup finished\"\n\n    fetch_and_deploy_gh_release \"bookstack\" \"BookStackApp/BookStack\" \"tarball\"\n    PHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" PHP_MODULE=\"ldap,tidy,mysqli\" setup_php\n    setup_composer\n\n    msg_info \"Restoring backup\"\n    cp /opt/bookstack-backup/.env /opt/bookstack/.env\n    [[ -d /opt/bookstack-backup/public/uploads ]] && cp -a /opt/bookstack-backup/public/uploads/. /opt/bookstack/public/uploads/\n    [[ -d /opt/bookstack-backup/storage/uploads ]] && cp -a /opt/bookstack-backup/storage/uploads/. /opt/bookstack/storage/uploads/\n    [[ -d /opt/bookstack-backup/themes ]] && cp -a /opt/bookstack-backup/themes/. /opt/bookstack/themes/\n    msg_ok \"Backup restored\"\n\n    msg_info \"Configuring BookStack\"\n    cd /opt/bookstack\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD /usr/local/bin/composer install --no-dev\n    $STD php artisan migrate --force\n    chown www-data:www-data -R /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage\n    chmod -R 755 /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage\n    chmod -R 775 /opt/bookstack/storage /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads\n    chmod -R 640 /opt/bookstack/.env\n    rm -rf /opt/bookstack-backup\n    msg_ok \"Configured BookStack\"\n\n    msg_info \"Starting Apache2\"\n    systemctl start apache2\n    msg_ok \"Started Apache2\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/bunkerweb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.bunkerweb.io/\n\nAPP=\"BunkerWeb\"\nvar_tags=\"${var_tags:-webserver}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/bunkerweb ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"bunkerweb\" \"bunkerity/bunkerweb\"; then\n    msg_info \"Updating BunkerWeb\"\n    RELEASE=$(get_latest_github_release \"bunkerity/bunkerweb\")\n    cat <<EOF >/etc/apt/preferences.d/bunkerweb\nPackage: bunkerweb\nPin: version ${RELEASE}\nPin-Priority: 1001\nEOF\n    $STD apt update\n    $STD apt-mark unhold bunkerweb nginx\n    $STD apt install -y --allow-downgrades bunkerweb=\"${RELEASE}\"\n    msg_ok \"Updated BunkerWeb\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/setup${CL}\"\n"
  },
  {
    "path": "ct/byparr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ThePhaseless/Byparr\n\nAPP=\"Byparr\"\nvar_tags=\"${var_tags:-proxy}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/Byparr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Byparr\" \"ThePhaseless/Byparr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop byparr\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Byparr\" \"ThePhaseless/Byparr\" \"tarball\" \"latest\"\n\n    if ! dpkg -l | grep -q ffmpeg; then\n      msg_info \"Installing dependencies\"\n      $STD apt install -y --no-install-recommends \\\n        ffmpeg \\\n        libatk1.0-0 \\\n        libcairo-gobject2 \\\n        libcairo2 \\\n        libdbus-glib-1-2 \\\n        libfontconfig1 \\\n        libfreetype6 \\\n        libgdk-pixbuf-xlib-2.0-0 \\\n        libglib2.0-0 \\\n        libgtk-3-0 \\\n        libpango-1.0-0 \\\n        libpangocairo-1.0-0 \\\n        libpangoft2-1.0-0 \\\n        libx11-6 \\\n        libx11-xcb1 \\\n        libxcb-shm0 \\\n        libxcb1 \\\n        libxcomposite1 \\\n        libxcursor1 \\\n        libxdamage1 \\\n        libxext6 \\\n        libxfixes3 \\\n        libxi6 \\\n        libxrender1 \\\n        libxt6 \\\n        libxtst6 \\\n        xvfb \\\n        fonts-noto-color-emoji \\\n        fonts-unifont \\\n        xfonts-cyrillic \\\n        xfonts-scalable \\\n        fonts-liberation \\\n        fonts-ipafont-gothic \\\n        fonts-wqy-zenhei \\\n        fonts-tlwg-loma-otf\n      $STD apt autoremove -y chromium\n      msg_ok \"Installed dependencies\"\n    fi\n\n    msg_info \"Configuring Byparr\"\n    cd /opt/Byparr\n    $STD uv sync --link-mode copy\n    $STD uv run camoufox fetch\n    msg_ok \"Configured Byparr\"\n\n    msg_info \"Starting Service\"\n    systemctl start byparr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8191${CL}\"\n"
  },
  {
    "path": "ct/bytestash.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/jordan-dalby/ByteStash\n\nAPP=\"ByteStash\"\nvar_tags=\"${var_tags:-code}\"\nvar_disk=\"${var_disk:-4}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/bytestash ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"bytestash\" \"jordan-dalby/ByteStash\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop bytestash-backend bytestash-frontend\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Backing up data\"\n    tmp_dir=\"/opt/bytestash-data-backup\"\n    mkdir -p \"$tmp_dir\"\n    if [[ -d /opt/bytestash/data ]]; then\n      cp -r /opt/bytestash/data \"$tmp_dir\"/data\n    elif [[ -d /opt/data ]]; then\n      cp -r /opt/data \"$tmp_dir\"/data\n    fi\n    msg_ok \"Data backed up\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"bytestash\" \"jordan-dalby/ByteStash\" \"tarball\"\n\n    msg_info \"Restoring data\"\n    if [[ -d \"$tmp_dir\"/data ]]; then\n      mkdir -p /opt/bytestash/data\n      cp -r \"$tmp_dir\"/data/* /opt/bytestash/data/\n      rm -rf \"$tmp_dir\"\n    fi\n    msg_ok \"Data restored\"\n\n    msg_info \"Configuring ByteStash\"\n    cd /opt/bytestash/server\n    $STD npm install\n    cd /opt/bytestash/client\n    $STD npm install\n    msg_ok \"Updated ByteStash\"\n\n    msg_info \"Starting Services\"\n    systemctl start bytestash-backend bytestash-frontend\n    msg_ok \"Started Services\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/caddy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://caddyserver.com/\n\nAPP=\"Caddy\"\nvar_tags=\"${var_tags:-webserver}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/caddy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Caddy LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated Caddy LXC\"\n\n  if command -v xcaddy >/dev/null 2>&1; then\n    if check_for_gh_release \"xcaddy\" \"caddyserver/xcaddy\"; then\n      setup_go\n      fetch_and_deploy_gh_release \"xcaddy\" \"caddyserver/xcaddy\" \"binary\"\n\n      msg_info \"Updating xCaddy\"\n      $STD xcaddy build\n      msg_ok \"Updated xCaddy\"\n    fi\n  fi\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/calibre-web.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: mikolaj92\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/janeczku/calibre-web\n\nAPP=\"calibre-web\"\nvar_tags=\"${var_tags:-media;books}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/calibre-web ]]; then\n    msg_error \"No Calibre-Web Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Calibre-Web\" \"janeczku/calibre-web\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop calibre-web\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/calibre-web/app.db /opt/app.db_backup\n    cp -r /opt/calibre-web/data /opt/data_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Calibre-Web\" \"janeczku/calibre-web\" \"prebuild\" \"latest\" \"/opt/calibre-web\" \"calibre-web*.tar.gz\"\n    setup_uv\n\n    msg_info \"Installing Dependencies\"\n    cd /opt/calibre-web\n    $STD uv venv --clear /opt/calibre-web/.venv\n    $STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir --upgrade pip setuptools wheel\n    $STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir -r requirements.txt\n    msg_ok \"Installed Dependencies\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/app.db_backup /opt/calibre-web/app.db 2>/dev/null\n    cp -r /opt/data_backup /opt/calibre-web/data 2>/dev/null\n    rm -rf /opt/app.db_backup /opt/data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start calibre-web\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8083${CL}\"\n"
  },
  {
    "path": "ct/casaos.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://casaos.io/\n\nAPP=\"CasaOS\"\nvar_tags=\"${var_tags:-cloud}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt-get update\n  $STD apt-get -y upgrade\n  msg_ok \"Updated ${APP} LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/changedetection.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://changedetection.io/\n\nAPP=\"Change Detection\"\nvar_tags=\"${var_tags:-monitoring;crawler}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/changedetection.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies libjpeg-dev\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  msg_info \"Updating ${APP}\"\n  $STD pip3 install changedetection.io --upgrade --break-system-packages --ignore-installed typing_extensions\n  msg_ok \"Updated ${APP}\"\n\n  msg_info \"Updating Playwright\"\n  $STD pip3 install playwright --upgrade --break-system-packages\n  msg_ok \"Updated Playwright\"\n\n  if [[ -f /etc/systemd/system/browserless.service ]]; then\n    msg_info \"Updating Browserless (Patience)\"\n    $STD git -C /opt/browserless/ fetch --all\n    $STD git -C /opt/browserless/ reset --hard origin/main\n    $STD npm update --prefix /opt/browserless\n    $STD npm ci --include=optional --include=dev --prefix /opt/browserless\n    $STD /opt/browserless/node_modules/playwright-core/cli.js install --with-deps\n    # Update Chrome separately, as it has to be done with the force option. Otherwise the installation of other browsers will not be done if Chrome is already installed.\n    $STD /opt/browserless/node_modules/playwright-core/cli.js install --force chrome\n    $STD /opt/browserless/node_modules/playwright-core/cli.js install --force msedge\n    $STD /opt/browserless/node_modules/playwright-core/cli.js install chromium firefox webkit\n    $STD npm install --prefix /opt/browserless esbuild typescript ts-node @types/node --save-dev\n    $STD npm run build --prefix /opt/browserless\n    $STD npm run build:function --prefix /opt/browserless\n    $STD npm prune production --prefix /opt/browserless\n    systemctl restart browserless\n    msg_ok \"Updated Browserless\"\n  else\n    msg_error \"No Browserless Installation Found!\"\n  fi\n\n  systemctl restart changedetection\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/channels.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://getchannels.com/dvr-server/\n\nAPP=\"Channels\"\nvar_tags=\"${var_tags:-dvr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/channels-dvr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_error \"Currently we don't provide an update function for this ${APP}.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8089${CL}\"\n"
  },
  {
    "path": "ct/checkmate.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bluewave-labs/Checkmate\n\nAPP=\"Checkmate\"\nvar_tags=\"${var_tags:-monitoring;uptime}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/checkmate ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"checkmate\" \"bluewave-labs/Checkmate\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop checkmate-server checkmate-client nginx\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/checkmate/server/.env /opt/checkmate_server.env.bak\n    [ -f /opt/checkmate/client/.env.local ] && cp /opt/checkmate/client/.env.local /opt/checkmate_client.env.local.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"checkmate\" \"bluewave-labs/Checkmate\" \"tarball\"\n\n    msg_info \"Updating Checkmate Server\"\n    cd /opt/checkmate/server\n    $STD npm install\n    if [ -f package.json ]; then\n      grep -q '\"build\"' package.json && $STD npm run build || true\n    fi\n    msg_ok \"Updated Checkmate Server\"\n\n    msg_info \"Updating Checkmate Client\"\n    cd /opt/checkmate/client\n    $STD npm install\n    VITE_APP_API_BASE_URL=\"/api/v1\" UPTIME_APP_API_BASE_URL=\"/api/v1\" VITE_APP_LOG_LEVEL=\"warn\" $STD npm run build\n    msg_ok \"Updated Checkmate Client\"\n\n    msg_info \"Restoring Data\"\n    mv /opt/checkmate_server.env.bak /opt/checkmate/server/.env\n    [ -f /opt/checkmate_client.env.local.bak ] && mv /opt/checkmate_client.env.local.bak /opt/checkmate/client/.env.local\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Services\"\n    systemctl start checkmate-server checkmate-client nginx\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/checkmk.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://checkmk.com/\n\nAPP=\"checkmk\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if ! command -v omd &>/dev/null; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl_with_retry \"https://api.github.com/repos/checkmk/checkmk/tags\" \"-\" | grep \"name\" | awk '{print substr($2, 3, length($2)-4) }' | tr ' ' '\\n' | grep -Ev 'rc|b' | sort -V | tail -n 1)\n  RELEASE=\"${RELEASE%%+*}\"\n  msg_info \"Updating checkmk\"\n  $STD omd stop monitoring\n  $STD omd cp monitoring monitoringbackup\n  curl -fsSL \"https://download.checkmk.com/checkmk/${RELEASE}/check-mk-raw-${RELEASE}_0.bookworm_arm64.deb\" -o \"/opt/checkmk.deb\"\n  $STD apt-get install -y /opt/checkmk.deb\n  $STD omd --force -V ${RELEASE}.cre update --conflict=install monitoring\n  $STD omd start monitoring\n  $STD omd -f rm monitoringbackup\n  $STD omd cleanup\n  rm -rf /opt/checkmk.deb\n  msg_ok \"Updated checkmk\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/monitoring${CL}\"\n"
  },
  {
    "path": "ct/cleanuparr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Lucas Zampieri (zampierilucas) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Cleanuparr/Cleanuparr\n\nAPP=\"Cleanuparr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /opt/cleanuparr/Cleanuparr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"cleanuparr\" \"Cleanuparr/Cleanuparr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop cleanuparr\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up config\"\n    cp -r /opt/cleanuparr/config /opt/cleanuparr_config_backup\n    msg_ok \"Backed up config\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Cleanuparr\" \"Cleanuparr/Cleanuparr\" \"prebuild\" \"latest\" \"/opt/cleanuparr\" \"*linux-arm64.zip\"\n\n    msg_info \"Restoring config\"\n    [[ -d /opt/cleanuparr/config ]] && rm -rf /opt/cleanuparr/config\n    mv /opt/cleanuparr_config_backup /opt/cleanuparr/config\n    msg_ok \"Restored config\"\n\n    msg_info \"Starting Service\"\n    systemctl start cleanuparr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:11011${CL}\"\n"
  },
  {
    "path": "ct/cloudflare-ddns.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: edoardop13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/favonia/cloudflare-ddns\n\nAPP=\"Cloudflare-DDNS\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/cloudflare-ddns.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_error \"There is no update function for ${APP}.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "ct/cloudflared.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.cloudflare.com/\n\nAPP=\"Cloudflared\"\nvar_tags=\"${var_tags:-network;cloudflare}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/cloudflared.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/cloudreve.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cloudreve.org/\n\nAPP=\"Cloudreve\"\nvar_tags=\"${var_tags:-cloud}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/cloudreve ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"cloudreve\" \"cloudreve/cloudreve\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop cloudreve\n    msg_info \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"cloudreve\" \"cloudreve/cloudreve\" \"prebuild\" \"latest\" \"/opt/cloudreve\" \"*linux_arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start cloudreve\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5212${CL}\"\n"
  },
  {
    "path": "ct/cockpit.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cockpit-project.org/\n\nAPP=\"Cockpit\"\nvar_tags=\"${var_tags:-monitoring;network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/cockpit ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated ${APP} LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9090${CL}\"\n"
  },
  {
    "path": "ct/comfyui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jdacode\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/comfyanonymous/ComfyUI\n\nAPP=\"ComfyUI\"\nvar_tags=\"${var_tags:-ai}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-25}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/ComfyUI ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_error \"To update use the ComfyUI Manager.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8188${CL}\"\n"
  },
  {
    "path": "ct/commafeed.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.commafeed.com/#/welcome\n\nAPP=\"CommaFeed\"\nvar_tags=\"${var_tags:-rss-reader}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/commafeed ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  JAVA_VERSION=\"25\" setup_java\n  if check_for_gh_release \"commafeed\" \"Athou/commafeed\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop commafeed\n    msg_ok \"Stopped Service\"\n\n    ensure_dependencies rsync\n\n    if [ -d /opt/commafeed/data ] && [ \"$(ls -A /opt/commafeed/data)\" ]; then\n      msg_info \"Backing up existing data\"\n      mv /opt/commafeed/data /opt/data.bak\n      msg_ok \"Backed up existing data\"\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"commafeed\" \"Athou/commafeed\" \"prebuild\" \"latest\" \"/opt/commafeed\" \"commafeed-*-h2-jvm.zip\"\n\n    if [ -d /opt/data.bak ] && [ \"$(ls -A /opt/data.bak)\" ]; then\n      msg_info \"Restoring data\"\n      mv /opt/data.bak /opt/commafeed/data\n      msg_ok \"Restored data\"\n    fi\n\n    msg_info \"Starting Service\"\n    systemctl start commafeed\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8082${CL}\"\n"
  },
  {
    "path": "ct/configarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: finkerle\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/raydak-labs/configarr\n\nAPP=\"Configarr\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/configarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"configarr\" \"raydak-labs/configarr\"; then\n    apt-get install -y libicu76 &>/dev/null\n    msg_info \"Stopping Service\"\n    systemctl stop configarr-task.timer\n    msg_ok \"Stopped Service\"\n\n    mkdir -p /opt/backup/\n    mv /opt/configarr/{config.yml,secrets.yml,.env} /opt/backup/\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"configarr\" \"raydak-labs/configarr\" \"prebuild\" \"latest\" \"/opt/configarr\" \"configarr-linux-arm64.tar.xz\"\n    mv /opt/backup/{config.yml,secrets.yml,.env} /opt/configarr/\n    rm -rf /opt/backup\n\n    msg_info \"Starting Service\"\n    systemctl start configarr-task.timer\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL (no web-ui):${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8989${CL}\"\n"
  },
  {
    "path": "ct/convertx.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/C4illin/ConvertX\n\nAPP=\"ConvertX\"\nvar_tags=\"${var_tags:-converter}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/convertx ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"ConvertX\" \"C4illin/ConvertX\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop convertx\n    msg_info \"Stopped Service\"\n\n    ensure_dependencies libreoffice-writer\n\n    msg_info \"Move data-Folder\"\n    if [[ -d /opt/convertx/data ]]; then\n      mv /opt/convertx/data /opt/data\n    fi\n    msg_ok \"Moved data-Folder\"\n\n    fetch_and_deploy_gh_release \"ConvertX\" \"C4illin/ConvertX\" \"tarball\" \"latest\" \"/opt/convertx\"\n\n    msg_info \"Updating ConvertX\"\n    if [[ -d /opt/data ]]; then\n      mv /opt/data /opt/convertx/data\n    fi\n    cd /opt/convertx \n    $STD bun install\n    msg_ok \"Updated ConvertX\"\n\n    msg_info \"Starting Service\"\n    systemctl start convertx\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/coolify.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://coolify.io/\n\nAPP=\"Coolify\"\nvar_tags=\"${var_tags:-docker;paas}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-30}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nADDON_SCRIPT=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/coolify.sh\"\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /data/coolify ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_warn \"⚠️  ${APP} has been migrated to an addon script.\"\n  echo \"\"\n  msg_info \"This is a one-time migration. After this, you can update ${APP} anytime with:\"\n  echo -e \"${TAB}${TAB}${GN}update_coolify${CL}  or  ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}\"\n  echo \"\"\n  read -r -p \"${TAB}Migrate update function now? [y/N]: \" CONFIRM\n  if [[ ! \"${CONFIRM,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Migration skipped. The old update will continue to work for now.\"\n    msg_info \"Updating ${APP} (legacy)\"\n    $STD bash <(curl -fsSL https://cdn.coollabs.io/coolify/install.sh)\n    msg_ok \"Updated ${APP}\"\n    exit\n  fi\n\n  msg_info \"Migrating update function\"\n  TMP_UPDATE=$(mktemp)\n  cat <<'MIGRATION_EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/coolify.sh)\"\nMIGRATION_EOF\n  mv \"$TMP_UPDATE\" /usr/bin/update\n  chmod +x /usr/bin/update\n\n  ln -sf /usr/bin/update /usr/bin/update_coolify 2>/dev/null || true\n  msg_ok \"Migration complete\"\n\n  msg_info \"Running addon update\"\n  type=update bash <(curl -fsSL \"${ADDON_SCRIPT}\")\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/coredns.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/coredns/coredns\n\nAPP=\"CoreDNS\"\nvar_tags=\"${var_tags:-dns;network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-1}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/bin/coredns ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"coredns\" \"coredns/coredns\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop coredns\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"coredns\" \"coredns/coredns\" \"prebuild\" \"latest\" \"/usr/local/bin\" \\\n      \"coredns_*_linux_$(dpkg --print-architecture).tgz\"\n    chmod +x /usr/local/bin/coredns\n\n    msg_info \"Starting Service\"\n    systemctl start coredns\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} CoreDNS is listening on port 53 (DNS)${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}dns://${IP}${CL}\"\n"
  },
  {
    "path": "ct/cosmos.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://https://cosmos-cloud.io/\n\nAPP=\"Cosmos\"\nvar_tags=\"${var_tags:-cloud;docker}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_fuse=\"${var_fuse:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/cosmos ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_ok \"${APP} updates itself automatically!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/crafty-controller.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gitlab.com/crafty-controller/crafty-4\n\nAPP=\"Crafty-Controller\"\nvar_tags=\"${var_tags:-gaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/crafty-controller ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -fsSL \"https://gitlab.com/api/v4/projects/20430749/releases\" | grep -o '\"tag_name\":\"v[^\"]*\"' | head -n 1 | sed 's/\"tag_name\":\"v//;s/\"//')\n  if [[ ! -f /opt/crafty-controller_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/crafty-controller_version.txt)\" ]]; then\n\n    msg_info \"Stopping Crafty-Controller\"\n    systemctl stop crafty-controller\n    msg_ok \"Stopped Crafty-Controller\"\n\n    msg_info \"Creating Backup of config\"\n    cp -a /opt/crafty-controller/crafty/crafty-4/app/config/. /opt/crafty-controller/backup\n    rm /opt/crafty-controller/backup/version.json\n    rm /opt/crafty-controller/backup/credits.json\n    rm /opt/crafty-controller/backup/logging.json\n    rm /opt/crafty-controller/backup/default.json.example\n    rm /opt/crafty-controller/backup/motd_format.json\n    msg_ok \"Backup Created\"\n\n    msg_info \"Updating Crafty-Controller to v${RELEASE}\"\n    curl -fsSL \"https://gitlab.com/crafty-controller/crafty-4/-/archive/v${RELEASE}/crafty-4-v${RELEASE}.zip\" -o $(basename \"https://gitlab.com/crafty-controller/crafty-4/-/archive/v${RELEASE}/crafty-4-v${RELEASE}.zip\")\n    $STD unzip crafty-4-v\"${RELEASE}\".zip\n    cp -a crafty-4-v\"${RELEASE}\"/. /opt/crafty-controller/crafty/crafty-4/\n    rm -rf crafty-4-v\"${RELEASE}\"\n    cd /opt/crafty-controller/crafty/crafty-4\n    sudo -u crafty bash -c '\n        source /opt/crafty-controller/crafty/.venv/bin/activate\n        pip3 install --no-cache-dir -r requirements.txt\n      ' &>/dev/null\n    echo \"${RELEASE}\" >\"/opt/crafty-controller_version.txt\"\n    msg_ok \"Updated Crafty-Controller to v${RELEASE}\"\n\n    msg_info \"Restoring Backup of config\"\n    cp -a /opt/crafty-controller/backup/. /opt/crafty-controller/crafty/crafty-4/app/config\n    rm -rf /opt/crafty-controller/backup\n    chown -R crafty:crafty /opt/crafty-controller/\n    msg_ok \"Backup Restored\"\n\n    msg_info \"Starting Crafty-Controller\"\n    systemctl start crafty-controller\n    msg_ok \"Started Crafty-Controller\"\n\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}\"\n"
  },
  {
    "path": "ct/cronicle.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cronicle.net/\n\nAPP=\"Cronicle\"\nvar_tags=\"${var_tags:-task-scheduler}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  UPD=$(msg_menu \"Cronicle Update Options\" \\\n    \"1\" \"Update ${APP}\" \\\n    \"2\" \"Install ${APP} Worker\")\n\n  if [ \"$UPD\" == \"1\" ]; then\n    if [[ ! -d /opt/cronicle ]]; then\n      msg_error \"No ${APP} Installation Found!\"\n      exit\n    fi\n    NODE_VERSION=\"22\" setup_nodejs\n\n    msg_info \"Updating Cronicle\"\n    $STD /opt/cronicle/bin/control.sh upgrade\n    msg_ok \"Updated Cronicle\"\n    exit\n  fi\n  if [ \"$UPD\" == \"2\" ]; then\n    NODE_VERSION=\"22\" setup_nodejs\n    if check_for_gh_release \"cronicle\" \"jhuckaby/Cronicle\"; then\n      msg_info \"Installing Dependencies\"\n      ensure_dependencies git build-essential ca-certificates\n      msg_ok \"Installed Dependencies\"\n\n      NODE_VERSION=\"22\" setup_nodejs\n      fetch_and_deploy_gh_release \"cronicle\" \"jhuckaby/Cronicle\" \"tarball\"\n\n      msg_info \"Configuring Cronicle Worker\"\n      cd /opt/cronicle\n      $STD npm install\n      $STD node bin/build.js dist\n      sed -i \"s/localhost:3012/${LOCAL_IP}:3012/g\" /opt/cronicle/conf/config.json\n      $STD /opt/cronicle/bin/control.sh start\n      msg_ok \"Installed Cronicle Worker\"\n      echo -e \"\\n Add Masters secret key to /opt/cronicle/conf/config.json \\n\"\n      msg_ok \"Updated successfully!\"\n      exit\n    fi\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3012${CL}\"\n"
  },
  {
    "path": "ct/cross-seed.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Jakub Matraszek (jmatraszek)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.cross-seed.org\n\nAPP=\"cross-seed\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  NODE_VERSION=\"24\" setup_nodejs\n  ensure_dependencies build-essential\n\n  if command -v cross-seed &>/dev/null; then\n    current_version=$(cross-seed --version)\n    latest_version=$(npm show cross-seed version)\n    if [ \"$current_version\" != \"$latest_version\" ]; then\n      msg_info \"Updating cross-seed from version v${current_version} to v${latest_version}\"\n      $STD npm install -g cross-seed@latest\n      systemctl restart cross-seed\n      msg_ok \"Updated successfully!\"\n    else\n      msg_ok \"cross-seed is already at v${current_version}\"\n    fi\n  else\n    msg_error \"No cross-seed Installation Found!\"\n    exit\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access cross-seed API using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:2468${CL}\"\n"
  },
  {
    "path": "ct/cryptpad.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/cryptpad/cryptpad\n\nAPP=\"CryptPad\"\nvar_tags=\"${var_tags:-docs;office}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/cryptpad\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"cryptpad\" \"cryptpad/cryptpad\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop cryptpad\n    msg_info \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    [ -f /opt/cryptpad/config/config.js ] && mv /opt/cryptpad/config/config.js /opt/\n    for dir in blob block customize data datastore www/common/onlyoffice/dist onlyoffice-conf; do\n      [ -d \"/opt/cryptpad/${dir}\" ] && mv \"/opt/cryptpad/${dir}\" \"/tmp/cryptpad_${dir//\\//_}\"\n    done\n    msg_ok \"Created backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"cryptpad\" \"cryptpad/cryptpad\" \"tarball\"\n\n    msg_info \"Restoring backup\"\n    mv /opt/config.js /opt/cryptpad/config/\n    for dir in blob block customize data datastore www/common/onlyoffice/dist onlyoffice-conf; do\n      [ -d \"/tmp/cryptpad_${dir//\\//_}\" ] && mv \"/tmp/cryptpad_${dir//\\//_}\" \"/opt/cryptpad/${dir}\"\n    done\n    msg_ok \"Restored backup\"\n\n    msg_info \"Updating CryptPad\"\n    cd /opt/cryptpad\n    $STD npm ci\n    $STD npm run install:components\n    if [ -f \"/opt/cryptpad/install-onlyoffice.sh\" ]; then\n      $STD bash /opt/cryptpad/install-onlyoffice.sh --accept-license\n    fi\n    $STD npm run build\n    msg_ok \"Updated CryptaPad\"\n\n    msg_info \"Starting Service\"\n    systemctl start cryptpad\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/dagu.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://dagu.sh/\n\nAPP=\"Dagu\"\nvar_tags=\"${var_tags:-automation;workflow;scheduler}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/dagu/dagu ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"dagu\" \"dagucloud/dagu\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop dagu\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/dagu/data /opt/dagu_data_backup\n    msg_ok \"Backed up Data\"\n\n    fetch_and_deploy_gh_release \"dagu\" \"dagucloud/dagu\" \"prebuild\" \"latest\" \"/opt/dagu\" \"dagu_*_linux_amd64.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    mkdir -p /opt/dagu/data\n    cp -r /opt/dagu_data_backup/. /opt/dagu/data\n    rm -rf /opt/dagu_data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start dagu\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/dashy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://dashy.to/\n\nAPP=\"Dashy\"\nvar_tags=\"${var_tags:-dashboard}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/dashy/public/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"dashy\" \"Lissy93/dashy\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop dashy\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up conf.yml\"\n    if [[ -f /opt/dashy/public/conf.yml ]]; then\n      cp -R /opt/dashy/public/conf.yml /opt/dashy_conf_backup.yml\n    else\n      cp -R /opt/dashy/user-data/conf.yml /opt/dashy_conf_backup.yml\n    fi\n    msg_ok \"Backed up conf.yml\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"dashy\" \"Lissy93/dashy\" \"prebuild\" \"latest\" \"/opt/dashy\" \"dashy-*.tar.gz\"\n\n    msg_info \"Updating Dashy\"\n    cd /opt/dashy\n    $STD yarn install --ignore-engines --network-timeout 300000\n    msg_ok \"Updated Dashy\"\n\n    msg_info \"Restoring conf.yml\"\n    cp -R /opt/dashy_conf_backup.yml /opt/dashy/user-data\n    msg_ok \"Restored conf.yml\"\n\n    msg_info \"Cleaning\"\n    rm -rf /opt/dashy_conf_backup.yml /opt/dashy/public/conf.yml\n    msg_ok \"Cleaned\"\n\n    msg_info \"Starting Dashy\"\n    systemctl start dashy\n    msg_ok \"Started Dashy\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:4000${CL}\"\n"
  },
  {
    "path": "ct/databasus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/databasus/databasus\n\nAPP=\"Databasus\"\nvar_tags=\"${var_tags:-backup;postgresql;database}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/databasus/databasus ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"databasus\" \"databasus/databasus\"; then\n    msg_info \"Stopping Databasus\"\n    $STD systemctl stop databasus\n    msg_ok \"Stopped Databasus\"\n\n    msg_info \"Backing up Configuration\"\n    [[ ! -f /.env && -f /opt/databasus/.env ]] && cp /opt/databasus/.env /.env\n    chmod 600 /.env\n    cp /.env /opt/databasus.env.bak\n    chmod 600 /opt/databasus.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    msg_info \"Ensuring Database Clients\"\n    # Create PostgreSQL version symlinks for compatibility\n    for v in 12 13 14 15 16 18; do\n      ln -sf /usr/lib/postgresql/17 /usr/lib/postgresql/$v\n    done\n    # Install MongoDB Database Tools via direct .deb (no APT repo for Debian 13)\n    if ! command -v mongodump &>/dev/null; then\n      [[ \"$(get_os_info id)\" == \"ubuntu\" ]] && MONGO_DIST=\"ubuntu2204\" || MONGO_DIST=\"debian12\"\n      fetch_and_deploy_from_url \"https://fastdl.mongodb.org/tools/db/mongodb-database-tools-${MONGO_DIST}-x86_64-100.16.1.deb\"\n    fi\n    [[ -f /usr/bin/mongodump ]] && ln -sf /usr/bin/mongodump /usr/local/mongodb-database-tools/bin/mongodump\n    [[ -f /usr/bin/mongorestore ]] && ln -sf /usr/bin/mongorestore /usr/local/mongodb-database-tools/bin/mongorestore\n    # Create MariaDB and MySQL client symlinks for compatibility\n    ensure_dependencies mariadb-client\n    mkdir -p /usr/local/mariadb-{10.6,12.1}/bin /usr/local/mysql-{5.7,8.0,8.4,9}/bin /usr/local/mongodb-database-tools/bin\n    for dir in /usr/local/mariadb-{10.6,12.1}/bin; do\n      ln -sf /usr/bin/mariadb-dump \"$dir/mariadb-dump\"\n      ln -sf /usr/bin/mariadb \"$dir/mariadb\"\n    done\n    for dir in /usr/local/mysql-{5.7,8.0,8.4,9}/bin; do\n      ln -sf /usr/bin/mariadb-dump \"$dir/mysqldump\"\n      ln -sf /usr/bin/mariadb \"$dir/mysql\"\n    done\n    msg_ok \"Ensured Database Clients\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"databasus\" \"databasus/databasus\" \"tarball\" \"latest\" \"/opt/databasus\"\n\n    msg_info \"Updating Databasus\"\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    cd /opt/databasus/frontend\n    $STD corepack enable\n    $STD corepack prepare pnpm@latest --activate\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm run build\n    cd /opt/databasus/backend\n    $STD go mod download\n    $STD /root/go/bin/swag init -g cmd/main.go -o swagger\n    $STD env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o databasus ./cmd/main.go\n    mv /opt/databasus/backend/databasus /opt/databasus/databasus\n    mkdir -p /opt/databasus/ui/build\n    cp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/\n    cp -r /opt/databasus/backend/migrations /opt/databasus/\n    chown -R postgres:postgres /opt/databasus\n    msg_ok \"Updated Databasus\"\n\n    msg_info \"Restoring Configuration\"\n    cp /opt/databasus.env.bak /.env\n    rm -f /opt/databasus.env.bak\n    chmod 600 /.env\n    msg_ok \"Restored Configuration\"\n\n    if ! grep -q \"EnvironmentFile=/.env\" /etc/systemd/system/databasus.service; then\n      msg_info \"Updating Service\"\n      sed -i 's|EnvironmentFile=.*|EnvironmentFile=/.env|' /etc/systemd/system/databasus.service\n      $STD systemctl daemon-reload\n      msg_ok \"Updated Service\"\n    fi\n\n    msg_info \"Starting Databasus\"\n    $STD systemctl start databasus\n    msg_ok \"Started Databasus\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/dawarich.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Freika/dawarich\n\nAPP=\"Dawarich\"\nvar_tags=\"${var_tags:-location;tracking;gps}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-15}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/dawarich ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies libgeos++-dev libxml2-dev libxslt-dev libjemalloc-dev\n\n  if check_for_gh_release \"dawarich\" \"Freika/dawarich\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop dawarich-web dawarich-worker\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/dawarich/app/storage /opt/dawarich_storage_backup 2>/dev/null || true\n    cp /opt/dawarich/app/config/master.key /opt/dawarich_master.key 2>/dev/null || true\n    cp /opt/dawarich/app/config/credentials.yml.enc /opt/dawarich_credentials.yml.enc 2>/dev/null || true\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"dawarich\" \"Freika/dawarich\" \"tarball\" \"latest\" \"/opt/dawarich/app\"\n\n    RUBY_VERSION=$(cat /opt/dawarich/app/.ruby-version 2>/dev/null || echo \"3.4.6\")\n    RUBY_VERSION=${RUBY_VERSION} RUBY_INSTALL_RAILS=\"false\" setup_ruby\n\n    msg_info \"Running Migrations\"\n    cd /opt/dawarich/app\n    source /root/.profile\n    export PATH=\"/root/.rbenv/shims:/root/.rbenv/bin:$PATH\"\n    eval \"$(/root/.rbenv/bin/rbenv init - bash)\"\n\n    if ! grep -q \"OTP_ENCRYPTION_PRIMARY_KEY\" /opt/dawarich/.env; then\n      echo \"OTP_ENCRYPTION_PRIMARY_KEY=$(openssl rand -hex 64)\" >>/opt/dawarich/.env\n    fi\n\n    if ! grep -q \"OTP_ENCRYPTION_DETERMINISTIC_KEY\" /opt/dawarich/.env; then\n      echo \"OTP_ENCRYPTION_DETERMINISTIC_KEY=$(openssl rand -hex 64)\" >>/opt/dawarich/.env\n    fi\n\n    if ! grep -q \"OTP_ENCRYPTION_KEY_DERIVATION_SALT\" /opt/dawarich/.env; then\n      echo \"OTP_ENCRYPTION_KEY_DERIVATION_SALT=$(openssl rand -hex 64)\" >>/opt/dawarich/.env\n    fi\n\n    set -a && source /opt/dawarich/.env && set +a\n\n    $STD bundle config set --local deployment 'true'\n    $STD bundle config set --local without 'development test'\n    $STD bundle install\n\n    if [[ -f /opt/dawarich/package.json ]]; then\n      cd /opt/dawarich\n      $STD npm install\n      cd /opt/dawarich/app\n    elif [[ -f /opt/dawarich/app/package.json ]]; then\n      $STD npm install\n    fi\n\n    $STD bundle exec rails db:migrate\n    $STD bundle exec rake assets:precompile\n    $STD bundle exec rake data:migrate\n    msg_ok \"Ran Migrations\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/dawarich_storage_backup/. /opt/dawarich/app/storage/ 2>/dev/null || true\n    cp /opt/dawarich_master.key /opt/dawarich/app/config/master.key 2>/dev/null || true\n    cp /opt/dawarich_credentials.yml.enc /opt/dawarich/app/config/credentials.yml.enc 2>/dev/null || true\n    rm -rf /opt/dawarich_storage_backup /opt/dawarich_master.key /opt/dawarich_credentials.yml.enc\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Services\"\n    systemctl start dawarich-web dawarich-worker\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/ddclient.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: mitchscobell\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ddclient.net/\n\nAPP=\"ddclient\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/ddclient.conf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating ddclient\"\n  $STD apt update\n  $STD apt install --only-upgrade -y ddclient\n  $STD systemctl restart ddclient\n  msg_ok \"Updated ddclient\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/debian.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.debian.org/\n\nAPP=\"Debian\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/deconz.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.phoscon.de/en/conbee2/software#deconz\n\nAPP=\"deCONZ\"\nvar_tags=\"${var_tags:-zigbee}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apt/sources.list.d/deconz.list && ! -f /etc/apt/sources.list.d/deconz.sources ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating deCONZ\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated deCONZ\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/deluge.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.deluge-torrent.org/\n\nAPP=\"Deluge\"\nvar_tags=\"${var_tags:-torrent}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/deluged.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Deluge\"\n  ensure_dependencies python3-setuptools\n  $STD apt update\n  $STD pip3 install deluge[all] --upgrade\n  msg_ok \"Updated Deluge\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8112${CL}\"\n"
  },
  {
    "path": "ct/discopanel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: DragoQC\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://discopanel.app/\n\nAPP=\"DiscoPanel\"\nvar_tags=\"${var_tags:-gaming}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-15}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/discopanel\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_docker\n\n  if check_for_gh_release \"discopanel\" \"nickheyer/discopanel\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop discopanel\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /opt/discopanel_backup_temp\n    cp /opt/discopanel/data/discopanel.db /opt/discopanel_backup_temp/discopanel.db\n    msg_ok \"Created Backup\"\n\n    fetch_and_deploy_gh_release \"discopanel\" \"nickheyer/discopanel\" \"prebuild\" \"latest\" \"/opt/discopanel\" \"discopanel-linux-amd64.tar.gz\"\n    ln -sf /opt/discopanel/discopanel-linux-amd64 /opt/discopanel/discopanel\n\n    msg_info \"Restoring Data\"\n    mkdir -p /opt/discopanel/data\n    mv /opt/discopanel_backup_temp/discopanel.db /opt/discopanel/data/discopanel.db\n    rm -rf /opt/discopanel_backup_temp\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start discopanel\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/dispatcharr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: ekke85 | MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dispatcharr/Dispatcharr\n\nAPP=\"Dispatcharr\"\nvar_tags=\"${var_tags:-media;arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d \"/opt/dispatcharr\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_uv\n  NODE_VERSION=\"24\" setup_nodejs\n\n  # Fix for nginx not allowing large files\n  if ! grep -q \"client_max_body_size 100M;\" /etc/nginx/sites-available/dispatcharr.conf; then\n    sed -i '/server_name _;/a \\    client_max_body_size 100M;' /etc/nginx/sites-available/dispatcharr.conf\n    systemctl reload nginx\n  fi\n\n  ensure_dependencies vlc-bin vlc-plugin-base\n\n  if check_for_gh_release \"Dispatcharr\" \"Dispatcharr/Dispatcharr\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop dispatcharr-celery\n    systemctl stop dispatcharr-celerybeat\n    systemctl stop dispatcharr-daphne\n    systemctl stop dispatcharr\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Creating Backup\"\n    BACKUP_FILE=\"/opt/dispatcharr_backup_$(date +%F_%H-%M-%S).tar.gz\"\n    if [[ -f /opt/dispatcharr/.env ]]; then\n      cp /opt/dispatcharr/.env /tmp/dispatcharr.env.backup\n    fi\n    if [[ -f /opt/dispatcharr/start-gunicorn.sh ]]; then\n      cp /opt/dispatcharr/start-gunicorn.sh /tmp/start-gunicorn.sh.backup\n    fi\n    if [[ -f /opt/dispatcharr/start-celery.sh ]]; then\n      cp /opt/dispatcharr/start-celery.sh /tmp/start-celery.sh.backup\n    fi\n    if [[ -f /opt/dispatcharr/start-celerybeat.sh ]]; then\n      cp /opt/dispatcharr/start-celerybeat.sh /tmp/start-celerybeat.sh.backup\n    fi\n    if [[ -f /opt/dispatcharr/start-daphne.sh ]]; then\n      cp /opt/dispatcharr/start-daphne.sh /tmp/start-daphne.sh.backup\n    fi\n    if [[ -f /opt/dispatcharr/.env ]]; then\n      set -o allexport\n      source /opt/dispatcharr/.env\n      set +o allexport\n      if [[ -n \"$POSTGRES_DB\" ]] && [[ -n \"$POSTGRES_USER\" ]] && [[ -n \"$POSTGRES_PASSWORD\" ]]; then\n        PGPASSWORD=$POSTGRES_PASSWORD pg_dump -U \"$POSTGRES_USER\" -h \"${POSTGRES_HOST:-localhost}\" -p \"${POSTGRES_PORT:-5432}\" \"$POSTGRES_DB\" >/tmp/dispatcharr_db_$(date +%F).sql\n        msg_info \"Database backup created\"\n      fi\n    fi\n    $STD tar -czf \"$BACKUP_FILE\" -C /opt dispatcharr /tmp/dispatcharr_db_*.sql\n    msg_ok \"Backup created: $BACKUP_FILE\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"dispatcharr\" \"Dispatcharr/Dispatcharr\" \"tarball\"\n\n    msg_info \"Updating Dispatcharr Backend\"\n    if [[ -f /tmp/dispatcharr.env.backup ]]; then\n      mv /tmp/dispatcharr.env.backup /opt/dispatcharr/.env\n    fi\n    if [[ -f /tmp/start-gunicorn.sh.backup ]]; then\n      mv /tmp/start-gunicorn.sh.backup /opt/dispatcharr/start-gunicorn.sh\n    fi\n    if [[ -f /tmp/start-celery.sh.backup ]]; then\n      mv /tmp/start-celery.sh.backup /opt/dispatcharr/start-celery.sh\n    fi\n    if [[ -f /tmp/start-celerybeat.sh.backup ]]; then\n      mv /tmp/start-celerybeat.sh.backup /opt/dispatcharr/start-celerybeat.sh\n    fi\n    if [[ -f /tmp/start-daphne.sh.backup ]]; then\n      mv /tmp/start-daphne.sh.backup /opt/dispatcharr/start-daphne.sh\n    fi\n\n    if ! grep -q \"DJANGO_SECRET_KEY\" /opt/dispatcharr/.env; then\n      DJANGO_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | cut -c1-50)\n      echo \"DJANGO_SECRET_KEY=$DJANGO_SECRET\" >>/opt/dispatcharr/.env\n    fi\n\n    cd /opt/dispatcharr\n    rm -rf .venv\n    $STD uv venv --clear\n    $STD uv sync\n    $STD uv pip install gunicorn gevent celery redis daphne\n    msg_ok \"Updated Dispatcharr Backend\"\n\n    msg_info \"Building Frontend\"\n    cd /opt/dispatcharr/frontend\n    node -e \"const p=require('./package.json');p.overrides=p.overrides||{};p.overrides['webworkify-webpack']='2.1.3';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2));\"\n    rm -f package-lock.json\n    $STD npm install --no-audit --progress=false\n    $STD npm run build\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Running Django Migrations\"\n    cd /opt/dispatcharr\n    if [[ -f .env ]]; then\n      set -o allexport\n      source .env\n      set +o allexport\n    fi\n    $STD uv run python manage.py migrate --noinput\n    $STD uv run python manage.py collectstatic --noinput\n    rm -f /tmp/dispatcharr_db_*.sql\n    msg_ok \"Migrations Complete\"\n\n    msg_info \"Starting Services\"\n    systemctl start dispatcharr\n    systemctl start dispatcharr-celery\n    systemctl start dispatcharr-celerybeat\n    systemctl start dispatcharr-daphne\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9191${CL}\"\n"
  },
  {
    "path": "ct/docker.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.docker.com/\n\nAPP=\"Docker\"\nvar_tags=\"${var_tags:-docker}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  msg_info \"Updating base system\"\n  $STD apt update\n  $STD apt upgrade -y \n  msg_ok \"Base system updated\"\n\n  msg_info \"Updating Docker Engine\"\n  $STD apt install --only-upgrade -y docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-buildx-plugin\n  msg_ok \"Docker Engine updated\"\n\n  if docker ps -a --format '{{.Image}}' | grep -q '^portainer/portainer-ce:latest$'; then\n    msg_info \"Updating Portainer\"\n    $STD docker pull portainer/portainer-ce:latest\n    $STD docker stop portainer\n    $STD docker rm portainer\n    $STD docker volume create portainer_data >/dev/null 2>&1\n    $STD docker run -d \\\n      -p 8000:8000 \\\n      -p 9443:9443 \\\n      --name=portainer \\\n      --restart=always \\\n      -v /var/run/docker.sock:/var/run/docker.sock \\\n      -v portainer_data:/data \\\n      portainer/portainer-ce:latest\n    msg_ok \"Updated Portainer\"\n  fi\n\n  if docker ps -a --format '{{.Names}}' | grep -q '^portainer_agent$'; then\n    msg_info \"Updating Portainer Agent\"\n    $STD docker pull portainer/agent:latest\n    $STD docker stop portainer_agent\n    $STD docker rm portainer_agent\n    $STD docker run -d \\\n      -p 9001:9001 \\\n      --name=portainer_agent \\\n      --restart=always \\\n      -v /var/run/docker.sock:/var/run/docker.sock \\\n      -v /var/lib/docker/volumes:/var/lib/docker/volumes \\\n      portainer/agent\n    msg_ok \"Updated Portainer Agent\"\n  fi\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} If you installed Portainer, access it at the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:9443${CL}\"\n"
  },
  {
    "path": "ct/dockge.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://dockge.kuma.pet/\n\nAPP=\"Dockge\"\nvar_tags=\"${var_tags:-docker}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-18}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nADDON_SCRIPT=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/dockge.sh\"\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/dockge ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_warn \"⚠️  ${APP} has been migrated to an addon script.\"\n  echo \"\"\n  msg_info \"This is a one-time migration. After this, you can update ${APP} anytime with:\"\n  echo -e \"${TAB}${TAB}${GN}update_dockge${CL}  or  ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}\"\n  echo \"\"\n  read -r -p \"${TAB}Migrate update function now? [y/N]: \" CONFIRM\n  if [[ ! \"${CONFIRM,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Migration skipped. The old update will continue to work for now.\"\n    msg_info \"Updating ${APP} (legacy)\"\n    cd /opt/dockge\n    $STD docker compose pull\n    $STD docker compose up -d\n    msg_ok \"Updated ${APP}\"\n    exit\n  fi\n\n  msg_info \"Migrating update function\"\n  TMP_UPDATE=$(mktemp)\n  cat <<'MIGRATION_EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/dockge.sh)\"\nMIGRATION_EOF\n  mv \"$TMP_UPDATE\" /usr/bin/update\n  chmod +x /usr/bin/update\n\n  ln -sf /usr/bin/update /usr/bin/update_dockge 2>/dev/null || true\n  msg_ok \"Migration complete\"\n\n  msg_info \"Running addon update\"\n  type=update bash <(curl -fsSL \"${ADDON_SCRIPT}\")\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5001${CL}\"\n"
  },
  {
    "path": "ct/docmost.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docmost.com/\n\nAPP=\"Docmost\"\nvar_tags=\"${var_tags:-documents}\"\nvar_cpu=\"${var_cpu:-3}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/docmost ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if ! command -v node >/dev/null || [[ \"$(/usr/bin/env node -v | grep -oP '^v\\K[0-9]+')\" != \"22\" ]]; then\n    NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$(curl -s https://raw.githubusercontent.com/docmost/docmost/main/package.json | jq -r '.packageManager | split(\"@\")[1]')\" setup_nodejs\n  fi\n  export NODE_OPTIONS=\"--max_old_space_size=4096\"\n\n  if check_for_gh_release \"docmost\" \"docmost/docmost\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop docmost\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    cp /opt/docmost/.env /opt/\n    cp -r /opt/docmost/data /opt/\n    rm -rf /opt/docmost\n    msg_ok \"Data backed up\"\n\n    fetch_and_deploy_gh_release \"docmost\" \"docmost/docmost\" \"tarball\"\n\n    msg_info \"Updating ${APP}\"\n    cd /opt/docmost\n    mv /opt/.env /opt/docmost/.env\n    mv /opt/data /opt/docmost/data\n\n    # Fix: Docmost EE (audit logs etc.) lives in a git submodule that is NOT\n    # included in GitHub tarballs.  The community NoopAuditService exists but\n    # is only exported by CoreModule – child modules such as UserModule cannot\n    # resolve it.  Making CoreModule @Global() exposes the token app-wide.\n    if [[ ! -f /opt/docmost/apps/server/src/ee/ee.module.ts ]] \\\n      && ! grep -q '@Global()' /opt/docmost/apps/server/src/core/core.module.ts 2>/dev/null; then\n      sed -i '/^  Module,$/a\\  Global,' /opt/docmost/apps/server/src/core/core.module.ts\n      sed -i '/^@Module({$/i @Global()' /opt/docmost/apps/server/src/core/core.module.ts\n    fi\n\n    $STD pnpm install --force\n    $STD pnpm build\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start docmost\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/dokploy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://dokploy.com/\n\nAPP=\"Dokploy\"\nvar_tags=\"${var_tags:-docker;paas}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nADDON_SCRIPT=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/dokploy.sh\"\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /etc/dokploy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_warn \"⚠️  ${APP} has been migrated to an addon script.\"\n  echo \"\"\n  msg_info \"This is a one-time migration. After this, you can update ${APP} anytime with:\"\n  echo -e \"${TAB}${TAB}${GN}update_dokploy${CL}  or  ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}\"\n  echo \"\"\n  read -r -p \"${TAB}Migrate update function now? [y/N]: \" CONFIRM\n  if [[ ! \"${CONFIRM,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Migration skipped. The old update will continue to work for now.\"\n    msg_info \"Updating ${APP} (legacy)\"\n    curl -sSL https://dokploy.com/install.sh | $STD bash -s update\n    msg_ok \"Updated ${APP}\"\n    exit\n  fi\n\n  msg_info \"Migrating update function\"\n  TMP_UPDATE=$(mktemp)\n  cat <<'MIGRATION_EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/dokploy.sh)\"\nMIGRATION_EOF\n  mv \"$TMP_UPDATE\" /usr/bin/update\n  chmod +x /usr/bin/update\n\n  ln -sf /usr/bin/update /usr/bin/update_dokploy 2>/dev/null || true\n  msg_ok \"Migration complete\"\n\n  msg_info \"Running addon update\"\n  type=update bash <(curl -fsSL \"${ADDON_SCRIPT}\")\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/dolibarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dolibarr/dolibarr/\n\nAPP=\"Dolibarr\"\nvar_tags=\"${var_tags:-erp;accounting}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /usr/share/dolibarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  msg_error \"To update ${APP}, use the applications web interface.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/dolibarr/install${CL}\"\n"
  },
  {
    "path": "ct/domain-locker.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Lissy93/domain-locker\n\nAPP=\"Domain-Locker\"\nvar_tags=\"${var_tags:-Monitoring}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-10240}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/domain-locker ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies whois\n\n  if check_for_gh_release \"domain-locker\" \"Lissy93/domain-locker\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop domain-locker\n    msg_info \"Service stopped\"\n\n    PG_VERSION=\"17\" setup_postgresql\n    NODE_VERSION=\"22\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"domain-locker\" \"Lissy93/domain-locker\" \"tarball\"\n\n    msg_info \"Installing Modules (patience)\"\n    cd /opt/domain-locker\n    $STD npm install\n    msg_ok \"Installed Modules\"\n\n    msg_info \"Building Domain-Locker (a lot of patience)\"\n    set -a\n    source /opt/domain-locker.env\n    set +a\n    $STD npm run build\n    msg_info \"Built Domain-Locker\"\n\n    msg_info \"Restarting Services\"\n    systemctl start domain-locker\n    msg_ok \"Restarted Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/domain-monitor.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Hosteroid/domain-monitor\n\nAPP=\"Domain-Monitor\"\nvar_tags=\"${var_tags:-proxy}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/domain-monitor ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n\n  if grep -Fq \"root /usr/bin/php /opt/domain-monitor/cron/check_domains.php\" /etc/crontab; then\n    sed -i 's|root /usr/bin/php /opt/domain-monitor/cron/check_domains.php|www-data /usr/bin/php /opt/domain-monitor/cron/check_domains.php|' /etc/crontab\n  fi\n\n  if ! grep -Fq \"www-data /usr/bin/php /opt/domain-monitor/cron/check_domains.php\" /etc/crontab; then\n    echo \"0 0 * * * www-data /usr/bin/php /opt/domain-monitor/cron/check_domains.php\" >>/etc/crontab\n  fi\n\n  if check_for_gh_release \"domain-monitor\" \"Hosteroid/domain-monitor\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_info \"Service stopped\"\n\n    msg_info \"Creating backup\"\n    mv /opt/domain-monitor/.env /opt\n    msg_ok \"Created backup\"\n\n    setup_composer\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"domain-monitor\" \"Hosteroid/domain-monitor\" \"prebuild\" \"latest\" \"/opt/domain-monitor\" \"domain-monitor-v*.zip\"\n\n    msg_info \"Updating Domain Monitor\"\n    cd /opt/domain-monitor\n    $STD composer install\n    chown -R www-data:www-data /opt/domain-monitor\n    msg_ok \"Updated Domain Monitor\"\n\n    msg_info \"Restoring backup\"\n    mv /opt/.env /opt/domain-monitor\n    msg_ok \"Restored backup\"\n\n    msg_info \"Restarting Services\"\n    systemctl start apache2\n    msg_ok \"Restarted Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/donetick.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: fstof\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/donetick/donetick\n\nAPP=\"Donetick\"\nvar_tags=\"${var_tags:-productivity;tasks}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/donetick ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"donetick\" \"donetick/donetick\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop donetick\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing Up Configurations\"\n    mv /opt/donetick/config/selfhosted.yaml /opt/donetick/donetick.db /opt\n    msg_ok \"Backed Up Configurations\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"donetick\" \"donetick/donetick\" \"prebuild\" \"latest\" \"/opt/donetick\" \"donetick_Linux_arm64.tar.gz\"\n\n    msg_info \"Restoring Configurations\"\n    mv /opt/selfhosted.yaml /opt/donetick/config\n    grep -q 'http://localhost\"$' /opt/donetick/config/selfhosted.yaml || sed -i '/https:\\/\\/localhost\"$/a\\    - \"http://localhost\"' /opt/donetick/config/selfhosted.yaml\n    grep -q 'capacitor://localhost' /opt/donetick/config/selfhosted.yaml || sed -i '/http:\\/\\/localhost\"$/a\\    - \"capacitor://localhost\"' /opt/donetick/config/selfhosted.yaml\n    mv /opt/donetick.db /opt/donetick\n    msg_ok \"Restored Configurations\"\n\n    msg_info \"Starting Service\"\n    systemctl start donetick\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:2021${CL}\"\n"
  },
  {
    "path": "ct/dotnetaspwebapi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-9.0&tabs=linux-ubuntu\n\nAPP=\"Dotnet ASP Web API\"\nvar_tags=\"${var_tags:-web}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /var/www ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt-get update\n    $STD apt-get -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/drawdb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/drawdb-io/drawdb\n\nAPP=\"DrawDB\"\nvar_tags=\"${var_tags:-database;dev-tools}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/drawdb ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_tag \"drawdb\" \"drawdb-io/drawdb\"; then\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_tag \"drawdb\" \"drawdb-io/drawdb\" \"latest\" \"/opt/drawdb\"\n\n    msg_info \"Rebuilding Frontend\"\n    cd /opt/drawdb\n    $STD npm ci\n    NODE_OPTIONS=\"--max-old-space-size=4096\" $STD npm run build\n    sed -i '/<head>/a <script>if(!crypto.randomUUID){crypto.randomUUID=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,function(c){return(c^(crypto.getRandomValues(new Uint8Array(1))[0]&(15>>c/4))).toString(16)})}};</script>' /opt/drawdb/dist/index.html\n    msg_ok \"Rebuilt Frontend\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/drawio.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.drawio.com/\n\nAPP=\"DrawIO\"\nvar_tags=\"${var_tags:-diagrams}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /var/lib/tomcat11/webapps/draw.war ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"drawio\" \"jgraph/drawio\"; then\n    msg_info \"Stopping service\"\n    systemctl stop tomcat11\n    msg_ok \"Service stopped\"\n\n    msg_info \"Updating Debian LXC\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated Debian LXC\"\n\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"drawio\" \"jgraph/drawio\" \"singlefile\" \"latest\" \"/var/lib/tomcat11/webapps\" \"draw.war\"\n\n    msg_info \"Starting service\"\n    systemctl start tomcat11\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/draw${CL}\"\n"
  },
  {
    "path": "ct/duplicati.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/duplicati/duplicati/\n\nAPP=\"Duplicati\"\nvar_tags=\"${var_tags:-backup}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/bin/duplicati-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"duplicati\" \"duplicati/duplicati\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop duplicati\n    msg_info \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"duplicati\" \"duplicati/duplicati\" \"binary\" \"latest\" \"/opt/duplicati\" \"duplicati-*-linux-x64-gui.deb\"\n\n    msg_info \"Starting Service\"\n    systemctl start duplicati\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8200${CL}\"\n"
  },
  {
    "path": "ct/ebusd.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joerg Heinemann (heinemannj)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/john30/ebusd\n\nAPP=\"ebusd\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/default/ebusd ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"ebusd\" \"john30/ebusd\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop ebusd\n    msg_ok \"Stopped Services\"\n\n    fetch_and_deploy_gh_release \"ebusd\" \"john30/ebusd\" \"binary\" \"latest\" \"/opt/ebusd\" \"ebusd-*_arm64-trixie_mqtt1.deb\"\n\n    msg_info \"Starting Services\"\n    systemctl start ebusd\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/elementsynapse.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/element-hq/synapse\n\nAPP=\"Element Synapse\"\nvar_tags=\"${var_tags:-server}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/matrix-synapse ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n\n  msg_info \"Updating LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated LXC\"\n\n  if check_for_gh_release \"synapse-admin\" \"etkecc/synapse-admin\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop synapse-admin\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"synapse-admin\" \"etkecc/synapse-admin\" \"tarball\" \"latest\" \"/opt/synapse-admin\"\n\n    msg_info \"Building Synapse-Admin\"\n    cd /opt/synapse-admin\n    $STD yarn global add serve\n    $STD yarn install --ignore-engines\n    $STD yarn build\n    mv ./dist ../ && rm -rf * && mv ../dist ./\n    msg_ok \"Built Synapse-Admin\"\n\n    msg_info \"Starting Service\"\n    systemctl start synapse-admin\n    msg_ok \"Started Service\"\n    msg_ok \"Updated Synapse-Admin to ${CHECK_UPDATE_RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8008${CL}\"\n"
  },
  {
    "path": "ct/emby.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://emby.media/\n\nAPP=\"Emby\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/emby-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"emby\" \"MediaBrowser/Emby.Releases\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop emby-server\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"emby\" \"MediaBrowser/Emby.Releases\" \"binary\"\n\n    msg_info \"Starting Service\"\n    systemctl start emby-server\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8096${CL}\"\n"
  },
  {
    "path": "ct/emqx.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.emqx.com/en\n\nAPP=\"EMQX\"\nvar_tags=\"${var_tags:-mqtt}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  RELEASE=$(curl -fsSL https://www.emqx.com/en/downloads/enterprise | grep -oP '/en/downloads/enterprise/v\\K[0-9]+\\.[0-9]+\\.[0-9]+' | sort -V | tail -n1)\n  if [[ \"$RELEASE\" != \"$(cat ~/.emqx 2>/dev/null)\" ]] || [[ ! -f ~/.emqx ]]; then\n    msg_info \"Stopping EMQX\"\n    systemctl stop emqx\n    msg_ok \"Stopped EMQX\"\n\n    msg_info \"Removing old EMQX\"\n    if dpkg -l | grep -q \"^ii\\s\\+emqx\\s\"; then\n      $STD apt remove --purge -y emqx\n    elif dpkg -l | grep -q \"^ii\\s\\+emqx-enterprise\\s\"; then\n      $STD apt remove --purge -y emqx-enterprise\n    else\n      msg_ok \"No old EMQX package found\"\n    fi\n    msg_ok \"Removed old EMQX\"\n\n    msg_info \"Downloading EMQX v${RELEASE}\"\n    DEB_FILE=\"/tmp/emqx-enterprise-${RELEASE}-debian12-arm64.deb\"\n    curl -fsSL -o \"$DEB_FILE\" \"https://www.emqx.com/en/downloads/enterprise/v${RELEASE}/emqx-enterprise-${RELEASE}-debian12-arm64.deb\"\n    msg_ok \"Downloaded EMQX\"\n\n    msg_info \"Installing EMQX\"\n    $STD apt install -y \"$DEB_FILE\"\n    rm -f \"$DEB_FILE\"\n    echo \"$RELEASE\" >~/.emqx\n    msg_ok \"Installed EMQX v${RELEASE}\"\n\n    msg_info \"Starting EMQX\"\n    systemctl start emqx\n    msg_ok \"Started EMQX\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. EMQX is already at v${RELEASE}\"\n  fi\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:18083${CL}\"\n"
  },
  {
    "path": "ct/endurain.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: johanngrobe\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/joaovitoriasilva/endurain\n\nAPP=\"Endurain\"\nvar_tags=\"${var_tags:-sport;social-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/endurain ]]; then\n    msg_error \"No ${APP} installation found!\"\n    exit 233\n  fi\n  if check_for_codeberg_release \"endurain\" \"endurain-project/endurain\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop endurain\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/endurain/.env /opt/endurain.env\n    cp /opt/endurain/frontend/app/dist/env.js /opt/endurain.env.js\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_codeberg_release \"endurain\" \"endurain-project/endurain\" \"tarball\" \"latest\" \"/opt/endurain\"\n\n    msg_info \"Preparing Update\"\n    cd /opt/endurain\n    rm -rf \\\n      /opt/endurain/{docs,example.env,screenshot_01.png} \\\n      /opt/endurain/docker* \\\n      /opt/endurain/*.yml\n    cp /opt/endurain.env /opt/endurain/.env\n    rm /opt/endurain.env\n    msg_ok \"Prepared Update\"\n\n    msg_info \"Updating Frontend\"\n    cd /opt/endurain/frontend/app\n    $STD npm ci\n    $STD npm run build\n    cp /opt/endurain.env.js /opt/endurain/frontend/app/dist/env.js\n    rm /opt/endurain.env.js\n    msg_ok \"Updated Frontend\"\n\n    msg_info \"Updating Backend\"\n    cd /opt/endurain/backend\n    $STD poetry export -f requirements.txt --output requirements.txt --without-hashes\n    $STD uv venv --clear\n    $STD uv pip install -r requirements.txt\n    msg_ok \"Backend Updated\"\n\n    msg_info \"Starting Service\"\n    systemctl start endurain\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/erpnext.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/frappe/erpnext\n\nAPP=\"ERPNext\"\nvar_tags=\"${var_tags:-erp;business;accounting}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/frappe-bench ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ERPNext\"\n  $STD sudo -u frappe bash -c 'export PATH=\"$HOME/.local/bin:$PATH\"; cd /opt/frappe-bench && bench update --reset'\n  msg_ok \"Updated ERPNext\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\necho -e \"${INFO}${YW} Credentials:${CL}\"\necho -e \"${TAB}${BGN}Username: Administrator${CL}\"\necho -e \"${TAB}${BGN}Password: see ~/erpnext.creds${CL}\"\n"
  },
  {
    "path": "ct/ersatztv.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ersatztv.org/\n\nAPP=\"ErsatzTV\"\nvar_tags=\"${var_tags:-iptv}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/ErsatzTV ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"ersatztv\" \"ErsatzTV/ErsatzTV\"; then\n    msg_info \"Stopping ErsatzTV\"\n    systemctl stop ersatzTV\n    msg_ok \"Stopped ErsatzTV\"\n\n    fetch_and_deploy_gh_release \"ersatztv\" \"ErsatzTV/ErsatzTV\" \"prebuild\" \"latest\" \"/opt/ErsatzTV\" \"*linux-arm64.tar.gz\"\n\n    msg_info \"Starting ErsatzTV\"\n    systemctl start ersatzTV\n    msg_ok \"Started ErsatzTV\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n\n  if check_for_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\"; then\n    msg_info \"Stopping ErsatzTV\"\n    systemctl stop ersatzTV\n    msg_ok \"Stopped ErsatzTV\"\n\n    fetch_and_deploy_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\" \"prebuild\" \"latest\" \"/opt/ErsatzTV-ffmpeg\" \"*-linux64-gpl-7.1.tar.xz\"\n\n    msg_info \"Set ErsatzTV-ffmpeg links\"\n    chmod +x /opt/ErsatzTV-ffmpeg/bin/*\n    ln -sf /opt/ErsatzTV-ffmpeg/bin/ffmpeg /usr/local/bin/ffmpeg\n    ln -sf /opt/ErsatzTV-ffmpeg/bin/ffplay /usr/local/bin/ffplay\n    ln -sf /opt/ErsatzTV-ffmpeg/bin/ffprobe /usr/local/bin/ffprobe\n    msg_ok \"ffmpeg links set\"\n\n    msg_info \"Starting ErsatzTV\"\n    systemctl start ersatzTV\n    msg_ok \"Started ErsatzTV\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8409${CL}\"\n"
  },
  {
    "path": "ct/esphome.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://esphome.io/\n\nAPP=\"ESPHome\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/esphomeDashboard.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop esphomeDashboard\n  msg_ok \"Stopped Service\"\n\n  VENV_PATH=\"/opt/esphome/.venv\"\n  ESPHOME_BIN=\"${VENV_PATH}/bin/esphome\"\n  export PYTHON_VERSION=\"3.12\"\n\n  if [[ ! -d \"$VENV_PATH\" || ! -x \"$ESPHOME_BIN\" ]]; then\n    PYTHON_VERSION=\"3.12\" setup_uv\n    msg_info \"Migrating to uv/venv\"\n    rm -rf \"$VENV_PATH\"\n    mkdir -p /opt/esphome\n    cd /opt/esphome\n    $STD uv venv --clear \"$VENV_PATH\"\n    $STD \"$VENV_PATH/bin/python\" -m ensurepip --upgrade\n    $STD \"$VENV_PATH/bin/python\" -m pip install --upgrade pip\n    $STD \"$VENV_PATH/bin/python\" -m pip install esphome tornado esptool\n    msg_ok \"Migrated to uv/venv\"\n  else\n    msg_info \"Updating ESPHome\"\n    PYTHON_VERSION=\"3.12\" setup_uv\n    $STD \"$VENV_PATH/bin/python\" -m pip install --upgrade esphome tornado esptool\n    msg_ok \"Updated ESPHome\"\n  fi\n  SERVICE_FILE=\"/etc/systemd/system/esphomeDashboard.service\"\n  if ! grep -q \"${VENV_PATH}/bin/esphome\" \"$SERVICE_FILE\"; then\n    msg_info \"Updating systemd service\"\n    cat <<EOF >\"$SERVICE_FILE\"\n[Unit]\nDescription=ESPHome Dashboard\nAfter=network.target\n\n[Service]\nExecStart=${VENV_PATH}/bin/esphome dashboard /root/config/\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    $STD systemctl daemon-reload\n    msg_ok \"Updated systemd service\"\n  fi\n\n  msg_info \"Linking esphome to /usr/local/bin\"\n  rm -f /usr/local/bin/esphome\n  ln -s /opt/esphome/.venv/bin/esphome /usr/local/bin/esphome\n  msg_ok \"Linked esphome binary\"\n\n  msg_info \"Starting Service\"\n  systemctl start esphomeDashboard\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6052${CL}\"\n"
  },
  {
    "path": "ct/evcc.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://evcc.io/en/\n\nAPP=\"evcc\"\nvar_tags=\"${var_tags:-solar;ev;automation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if ! command -v evcc >/dev/null 2>&1; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit 233\n  fi\n\n  if [[ -f /etc/apt/sources.list.d/evcc-stable.list ]]; then\n    setup_deb822_repo \\\n      \"evcc-stable\" \\\n      \"https://dl.evcc.io/public/evcc/stable/gpg.EAD5D0E07B0EC0FD.key\" \\\n      \"https://dl.evcc.io/public/evcc/stable/deb/debian/\" \\\n      \"$(get_os_info codename)\" \\\n      \"main\"\n  fi\n  msg_info \"Updating evcc LXC\"\n  $STD apt update\n  $STD apt --only-upgrade install -y evcc\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7070${CL}\"\n"
  },
  {
    "path": "ct/excalidraw.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/excalidraw/excalidraw\n\nAPP=\"Excalidraw\"\nvar_tags=\"${var_tags:-diagrams}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/excalidraw ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"excalidraw\" \"excalidraw/excalidraw\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop excalidraw\n    msg_info \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"excalidraw\" \"excalidraw/excalidraw\" \"tarball\"\n\n    msg_info \"Updating Excalidraw\"\n    cd /opt/excalidraw\n    $STD yarn\n    msg_ok \"Updated Excalidraw\"\n\n    msg_info \"Starting Service\"\n    systemctl start excalidraw\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/fhem.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://fhem.de/\n\nAPP=\"FHEM\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /etc/systemd/system/fhem.service ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    msg_info \"Updating FHEM\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated FHEM\"\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8083${CL}\"\n"
  },
  {
    "path": "ct/fileflows.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kkroboth\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://fileflows.com/\n\nAPP=\"FileFlows\"\nvar_tags=\"${var_tags:-media;automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/fileflows ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  update_available=$(curl -fsSL -X 'GET' \"http://localhost:19200/api/status/update-available\" -H 'accept: application/json' | jq .UpdateAvailable)\n  if [[ \"${update_available}\" == \"true\" ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop fileflows*\n    msg_info \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    ls /opt/*.tar.gz &>/dev/null && rm -f /opt/*.tar.gz\n    backup_filename=\"/opt/${APP}_backup_$(date +%F).tar.gz\"\n    tar -czf \"$backup_filename\" -C /opt/fileflows Data\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_from_url \"https://fileflows.com/downloads/zip\" \"/opt/fileflows\"\n\n    msg_info \"Starting Service\"\n    systemctl start fileflows*\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at latest version\"\n  fi\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:19200${CL}\"\n"
  },
  {
    "path": "ct/firefly.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: quantumryuu | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://firefly-iii.org/\n\nAPP=\"Firefly\"\nvar_tags=\"${var_tags:-finance}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/firefly ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_mariadb\n  PHP_VERSION=\"8.5\" PHP_APACHE=\"YES\" setup_php\n\n  if check_for_gh_release \"firefly\" \"firefly-iii/firefly-iii\"; then\n    systemctl stop apache2\n    cp /opt/firefly/.env /opt/.env\n    rm -rf /opt/storage\n    cp -r /opt/firefly/storage /opt/storage\n\n    if [[ -d /opt/firefly/dataimporter ]]; then\n      cp /opt/firefly/dataimporter/.env /opt/dataimporter.env\n      IMPORTER_INSTALLED=1\n    fi\n\n    fetch_and_deploy_gh_release \"firefly\" \"firefly-iii/firefly-iii\" \"prebuild\" \"latest\" \"/opt/firefly\" \"FireflyIII-*.zip\"\n    setup_composer\n\n    msg_info \"Updating Firefly\"\n    rm -rf /opt/firefly/storage\n    cp -r /opt/storage /opt/firefly/storage\n    cp /opt/.env /opt/firefly/.env\n\n    chown -R www-data:www-data /opt/firefly\n    chmod -R 775 /opt/firefly/storage\n    mkdir -p /opt/firefly/storage/framework/cache/data\n    mkdir -p /opt/firefly/storage/framework/sessions\n    mkdir -p /opt/firefly/storage/framework/views\n    mkdir -p /opt/firefly/storage/logs\n    mkdir -p /opt/firefly/bootstrap/cache\n    chown -R www-data:www-data /opt/firefly/{storage,bootstrap/cache}\n    cd /opt/firefly\n    $STD runuser -u www-data -- composer install --no-dev --optimize-autoloader\n    $STD runuser -u www-data -- composer dump-autoload -o\n\n    $STD runuser -u www-data -- php artisan cache:clear\n    $STD runuser -u www-data -- php artisan config:clear\n    $STD runuser -u www-data -- php artisan route:clear\n    $STD runuser -u www-data -- php artisan view:clear\n\n    $STD runuser -u www-data -- php artisan migrate --seed --force\n    $STD runuser -u www-data -- php artisan firefly-iii:upgrade-database\n    $STD runuser -u www-data -- php artisan firefly-iii:laravel-passport-keys\n\n    $STD runuser -u www-data -- php artisan storage:link || true\n    $STD runuser -u www-data -- php artisan optimize\n    msg_ok \"Updated Firefly\"\n\n    if [[ \"${IMPORTER_INSTALLED:-0}\" -eq 1 ]]; then\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"dataimporter\" \"firefly-iii/data-importer\" \"prebuild\" \"latest\" \"/opt/firefly/dataimporter\" \"DataImporter-v*.tar.gz\"\n\n      msg_info \"Updating Firefly Importer\"\n      if [[ -f /opt/dataimporter.env ]]; then\n        cp /opt/dataimporter.env /opt/firefly/dataimporter/.env\n      fi\n      chown -R www-data:www-data /opt/firefly/dataimporter\n      msg_ok \"Updated Firefly Importer\"\n    fi\n    rm -rf /opt/storage /opt/.env /opt/dataimporter.env\n    systemctl start apache2\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/fireshare.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ShaneIsrael/fireshare\n\nAPP=\"Fireshare\"\nvar_tags=\"${var_tags:-sharing;video}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/fireshare ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"fireshare\" \"ShaneIsrael/fireshare\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop fireshare\n    msg_ok \"Stopped Service\"\n\n    mv /opt/fireshare/fireshare.env /opt\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"fireshare\" \"ShaneIsrael/fireshare\" \"tarball\"\n    mv /opt/fireshare.env /opt/fireshare\n    rm -f /usr/local/bin/fireshare\n\n    msg_info \"Updating Fireshare\"\n    cd /opt/fireshare\n    $STD uv venv --clear\n    $STD .venv/bin/python -m ensurepip --upgrade\n    $STD .venv/bin/python -m pip install --upgrade --break-system-packages pip\n    $STD .venv/bin/python -m pip install --no-cache-dir --break-system-packages --ignore-installed app/server\n    cp .venv/bin/fireshare /usr/local/bin/fireshare\n    export FLASK_APP=\"/opt/fireshare/app/server/fireshare:create_app()\"\n    export DATA_DIRECTORY=/opt/fireshare-data\n    export IMAGE_DIRECTORY=/opt/fireshare-images\n    export VIDEO_DIRECTORY=/opt/fireshare-videos\n    export PROCESSED_DIRECTORY=/opt/fireshare-processed\n    $STD uv run flask db upgrade\n    msg_ok \"Updated Fireshare\"\n\n    msg_info \"Starting Service\"\n    systemctl start fireshare\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  cleanup_lxc\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/fladder.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: wendyliga\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/DonutWare/Fladder\n\nAPP=\"Fladder\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/fladder ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Fladder\" \"DonutWare/Fladder\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nginx\n    msg_ok \"Stopped Service\"\n\n    if [[ -f /opt/fladder/assets/config/config.json ]]; then\n      msg_info \"Backing up configuration\"\n      cp /opt/fladder/assets/config/config.json /tmp/fladder_config.json.bak\n      msg_ok \"Configuration backed up\"\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Fladder\" \"DonutWare/Fladder\" \"prebuild\" \"latest\" \"/opt/fladder\" \"Fladder-Web-*.zip\"\n\n    if [[ -f /tmp/fladder_config.json.bak ]]; then\n      msg_info \"Restoring configuration\"\n      mkdir -p /opt/fladder/assets/config\n      cp /tmp/fladder_config.json.bak /opt/fladder/assets/config/config.json\n      rm -f /tmp/fladder_config.json.bak\n      msg_ok \"Configuration restored\"\n    fi\n\n    msg_info \"Starting Service\"\n    systemctl start nginx\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/flaresolverr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/FlareSolverr/FlareSolverr\n\nAPP=\"FlareSolverr\"\nvar_tags=\"${var_tags:-proxy}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/flaresolverr.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if [[ $(grep -E '^VERSION_ID=' /etc/os-release) == *\"12\"* ]]; then\n    msg_error \"Wrong Debian version detected!\"\n    msg_error \"You must upgrade your LXC to Debian Trixie before updating.\"\n    exit\n  fi\n  if check_for_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\"; then\n    msg_info \"Stopping service\"\n    systemctl stop flaresolverr\n    msg_ok \"Stopped service\"\n\n    rm -rf /opt/flaresolverr\n    fetch_and_deploy_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\" \"prebuild\" \"latest\" \"/opt/flaresolverr\" \"flaresolverr_linux_x64.tar.gz\"\n\n    msg_info \"Starting service\"\n    systemctl start flaresolverr\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8191${CL}\"\n"
  },
  {
    "path": "ct/flatnotes.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dullage/flatnotes\n\nAPP=\"Flatnotes\"\nvar_tags=\"${var_tags:-notes}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/flatnotes ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"flatnotes\" \"dullage/flatnotes\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop flatnotes\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration and Data\"\n    cp /opt/flatnotes/.env /opt/flatnotes.env\n    cp -r /opt/flatnotes/data /opt/flatnotes_data_backup\n    msg_ok \"Backed up Configuration and Data\"\n\n    fetch_and_deploy_gh_release \"flatnotes\" \"dullage/flatnotes\" \"tarball\"\n\n    msg_info \"Updating Flatnotes\"\n    cd /opt/flatnotes/client\n    $STD npm install\n    $STD npm run build\n    cd /opt/flatnotes\n    rm -f uv.lock\n    $STD /usr/local/bin/uvx migrate-to-uv\n    $STD /usr/local/bin/uv sync\n    msg_ok \"Updated Flatnotes\"\n\n    msg_info \"Restoring Configuration and Data\"\n    cp /opt/flatnotes.env /opt/flatnotes/.env\n    cp -r /opt/flatnotes_data_backup/. /opt/flatnotes/data\n    rm -f /opt/flatnotes.env\n    rm -r /opt/flatnotes_data_backup\n    msg_ok \"Restored Configuration and Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start flatnotes\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n\n"
  },
  {
    "path": "ct/flowiseai.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://flowiseai.com/\n\nAPP=\"FlowiseAI\"\nvar_tags=\"${var_tags:-low-code}\"\nvar_disk=\"${var_disk:-10}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/flowise.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"20\" NODE_MODULE=\"pnpm\" setup_nodejs\n\n  msg_info \"Updating FlowiseAI (this may take some time)\"\n  systemctl stop flowise\n  $STD pnpm add -g flowise\n  if grep -q 'ExecStart=npx flowise start' /etc/systemd/system/flowise.service; then\n    sed -i 's|ExecStart=npx flowise start|ExecStart=flowise start|' /etc/systemd/system/flowise.service\n    systemctl daemon-reload\n  fi\n  systemctl start flowise\n  msg_ok \"Updated FlowiseAI\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/fluid-calendar.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://fluidcalendar.com\n\nAPP=\"fluid-calendar\"\nvar_tags=\"${var_tags:-calendar;tasks}\"\nvar_cpu=\"${var_cpu:-3}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-7}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/fluid-calendar ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies build-essential\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"fluid-calendar\" \"dotnetfactory/fluid-calendar\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop fluid-calendar\n    msg_info \"Stopped Service\"\n\n    cp /opt/fluid-calendar/.env /opt/fluid.env\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"fluid-calendar\" \"dotnetfactory/fluid-calendar\" \"tarball\"\n    mv /opt/fluid.env /opt/fluid-calendar/.env\n\n    msg_info \"Updating Fluid Calendar\"\n    cd /opt/fluid-calendar\n    export NEXT_TELEMETRY_DISABLED=1\n    $STD npm install --legacy-peer-deps\n    $STD npm run prisma:generate\n    $STD npx prisma migrate deploy\n    $STD npm run build:os\n    msg_ok \"Updated Fluid Calendar\"\n\n    msg_info \"Starting Service\"\n    systemctl start fluid-calendar\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/foldergram.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/foldergram/foldergram\n\nAPP=\"Foldergram\"\nvar_tags=\"${var_tags:-photos}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/foldergram ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"foldergram\" \"foldergram/foldergram\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop foldergram\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"foldergram\" \"foldergram/foldergram\" \"tarball\"\n\n    msg_info \"Installing Foldergram\"\n    cd /opt/foldergram\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm run build\n    msg_ok \"Installed Foldergram\"\n\n    msg_info \"Starting Service\"\n    systemctl start foldergram\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  cleanup_lxc\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:4141${CL}\"\n"
  },
  {
    "path": "ct/forgejo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://forgejo.org/\n\nAPP=\"Forgejo\"\nvar_tags=\"${var_tags:-git}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/forgejo ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_codeberg_release \"forgejo\" \"forgejo/forgejo\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop forgejo\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_codeberg_release \"forgejo\" \"forgejo/forgejo\" \"singlefile\" \"latest\" \"/opt/forgejo\" \"forgejo-*-linux-arm64\"\n    ln -sf /opt/forgejo/forgejo /usr/local/bin/forgejo\n\n    if grep -q \"GITEA_WORK_DIR\" /etc/systemd/system/forgejo.service; then\n      msg_info \"Updating Service File\"\n      sed -i \"s/GITEA_WORK_DIR/FORGEJO_WORK_DIR/g\" /etc/systemd/system/forgejo.service\n      systemctl daemon-reload\n      msg_ok \"Updated Service File\"\n    fi\n\n    msg_info \"Starting Service\"\n    systemctl start forgejo\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at the latest version.\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/freepbx.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Arian Nasr (arian-nasr)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.freepbx.org/\n\nAPP=\"FreePBX\"\nvar_tags=\"pbx;voip;telephony\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n\n    if [[ ! -f /lib/systemd/system/freepbx.service ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_error \"Currently we don't provide an update function for this ${APP}.\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/freshrss.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/FreshRSS/FreshRSS\n\nAPP=\"FreshRSS\"\nvar_tags=\"${var_tags:-RSS}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/freshrss ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [ ! -x /opt/freshrss/cli/sensitive-log.sh ]; then\n    msg_info \"Fixing wrong permissions\"\n    chmod +x /opt/freshrss/cli/sensitive-log.sh\n    systemctl restart apache2\n    msg_ok \"Fixed wrong permissions\"\n  fi\n\n  if check_for_gh_release \"freshrss\" \"FreshRSS/FreshRSS\"; then\n    msg_info \"Stopping Apache2\"\n    systemctl stop apache2\n    msg_ok \"Stopped Apache2\"\n\n    msg_info \"Backing up FreshRSS\"\n    mv /opt/freshrss /opt/freshrss-backup\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_gh_release \"freshrss\" \"FreshRSS/FreshRSS\" \"tarball\"\n\n    msg_info \"Restoring data and configuration\"\n    if [[ -d /opt/freshrss-backup/data ]]; then\n      cp -a /opt/freshrss-backup/data/. /opt/freshrss/data/\n    fi\n    if [[ -d /opt/freshrss-backup/extensions ]]; then\n      cp -a /opt/freshrss-backup/extensions/. /opt/freshrss/extensions/\n    fi\n    msg_ok \"Data Restored\"\n\n    msg_info \"Setting permissions\"\n    chown -R www-data:www-data /opt/freshrss\n    chmod -R g+rX /opt/freshrss\n    chmod -R g+w /opt/freshrss/data/\n    msg_ok \"Permissions Set\"\n\n    msg_info \"Starting Apache2\"\n    systemctl start apache2\n    msg_ok \"Started Apache2\"\n\n    msg_info \"Cleaning up backup\"\n    rm -rf /opt/freshrss-backup\n    msg_ok \"Cleaned up backup\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/frigate.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Authors: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://frigate.video/\n\nAPP=\"Frigate\"\nvar_tags=\"${var_tags:-nvr}\"\nvar_cpu=\"${var_cpu:-8}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /etc/systemd/system/frigate.service ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_error \"To update Frigate, create a new container and transfer your configuration.\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/fumadocs.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/fuma-nama/fumadoc\n\nAPP=\"Fumadocs\"\nvar_tags=\"${var_tags:-documentation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/fumadocs ]]; then\n    msg_error \"No installation found in /opt/fumadocs!\"\n    exit\n  fi\n\n  if [[ ! -f /opt/fumadocs/.projectname ]]; then\n    msg_error \"Project name file not found: /opt/fumadocs/.projectname!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@latest\" setup_nodejs\n  PROJECT_NAME=$(</opt/fumadocs/.projectname)\n  PROJECT_DIR=\"/opt/fumadocs/${PROJECT_NAME}\"\n  SERVICE_NAME=\"fumadocs_${PROJECT_NAME}.service\"\n\n  if [[ ! -d \"$PROJECT_DIR\" ]]; then\n    msg_error \"Project directory does not exist: $PROJECT_DIR\"\n    exit\n  fi\n  ensure_dependencies git\n\n  msg_info \"Stopping service $SERVICE_NAME\"\n  systemctl stop \"$SERVICE_NAME\"\n  msg_ok \"Stopped service $SERVICE_NAME\"\n\n  msg_info \"Updating dependencies using pnpm\"\n  cd \"$PROJECT_DIR\"\n  $STD pnpm up --latest\n  $STD pnpm build\n  msg_ok \"Updated dependencies using pnpm\"\n\n  msg_info \"Starting service $SERVICE_NAME\"\n  systemctl start \"$SERVICE_NAME\"\n  msg_ok \"Started service $SERVICE_NAME\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/garage.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://garagehq.deuxfleurs.fr/\n\nAPP=\"Garage\"\nvar_tags=\"${var_tags:-object-storage}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/bin/garage ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  GITEA_RELEASE=$(curl -fsSL https://api.github.com/repos/deuxfleurs-org/garage/tags | jq -r '.[0].name')\n  if [[ \"${GITEA_RELEASE}\" != \"$(cat ~/.garage 2>/dev/null)\" ]] || [[ ! -f ~/.garage ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop garage\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing Up Data\"\n    cp /usr/local/bin/garage /usr/local/bin/garage.old 2>/dev/null || true\n    cp /etc/garage.toml /etc/garage.toml.bak 2>/dev/null || true\n    msg_ok \"Backed Up Data\"\n\n    msg_info \"Updating Garage\"\n    curl -fsSL \"https://garagehq.deuxfleurs.fr/_releases/${GITEA_RELEASE}/aarch64-unknown-linux-musl/garage\" -o /usr/local/bin/garage\n    chmod +x /usr/local/bin/garage\n    echo \"${GITEA_RELEASE}\" >~/.garage\n    msg_ok \"Updated Garage\"\n\n    msg_info \"Starting Service\"\n    systemctl start garage\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. Garage is already at ${GITEA_RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/gatus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TwiN/gatus\n\nAPP=\"gatus\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/gatus ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"gatus\" \"TwiN/gatus\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gatus\n    msg_ok \"Stopped Service\"\n\n    mv /opt/gatus/config/config.yaml /opt\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gatus\" \"TwiN/gatus\" \"tarball\"\n\n    msg_info \"Updating Gatus\"\n    cd /opt/gatus\n    $STD go mod tidy\n    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus .\n    setcap CAP_NET_RAW+ep gatus\n    mv /opt/config.yaml config\n    msg_ok \"Updated Gatus\"\n\n    msg_info \"Starting Service\"\n    systemctl start gatus\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/geopulse.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/tess1o/geopulse\n\nAPP=\"GeoPulse\"\nvar_tags=\"${var_tags:-location;tracking;gps}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/geopulse/backend/geopulse-backend ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"geopulse-backend\" \"tess1o/geopulse\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop geopulse-backend\n    msg_ok \"Stopped Service\"\n\n    if [[ \"$(uname -m)\" == \"aarch64\" ]]; then\n      if grep -qi \"raspberry\\|bcm\" /proc/cpuinfo 2>/dev/null; then\n        BINARY_PATTERN=\"geopulse-backend-native-arm64-compat-*\"\n      else\n        BINARY_PATTERN=\"geopulse-backend-native-arm64-[!c]*\"\n      fi\n    else\n      if grep -q avx2 /proc/cpuinfo && grep -q bmi2 /proc/cpuinfo && grep -q fma /proc/cpuinfo; then\n        BINARY_PATTERN=\"geopulse-backend-native-amd64-[!c]*\"\n      else\n        BINARY_PATTERN=\"geopulse-backend-native-amd64-compat-*\"\n      fi\n    fi\n\n    fetch_and_deploy_gh_release \"geopulse-backend\" \"tess1o/geopulse\" \"singlefile\" \"latest\" \"/opt/geopulse/backend\" \"${BINARY_PATTERN}\"\n    fetch_and_deploy_gh_release \"geopulse-frontend\" \"tess1o/geopulse\" \"prebuild\" \"latest\" \"/var/www/geopulse\" \"geopulse-frontend-*.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start geopulse-backend\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\necho -e \"${INFO}${YW} To create an admin account, run:${CL}\"\necho -e \"${TAB}${BGN}/usr/local/bin/create-geopulse-admin${CL}\"\n"
  },
  {
    "path": "ct/ghost.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: fabrice1236\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ghost.org/\n\nAPP=\"Ghost\"\nvar_tags=\"${var_tags:-cms;blog}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  setup_mariadb\n  NODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\n  ensure_dependencies git\n\n  msg_info \"Updating Ghost\"\n  if command -v ghost &>/dev/null; then\n    current_version=$(ghost version | grep 'Ghost-CLI version' | awk '{print $3}')\n    latest_version=$(npm show ghost-cli version)\n    if [ \"$current_version\" != \"$latest_version\" ]; then\n      msg_info \"Updating ${APP} from version v${current_version} to v${latest_version}\"\n      $STD npm install -g ghost-cli@latest\n      msg_ok \"Updated successfully!\"\n    else\n      msg_ok \"${APP} is already at v${current_version}\"\n    fi\n  else\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:2368${CL}\"\n"
  },
  {
    "path": "ct/ghostfolio.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: lucasfell\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ghostfol.io/\n\nAPP=\"Ghostfolio\"\nvar_tags=\"${var_tags:-finance;investment}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/ghostfolio/dist/apps/api/main.js ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"ghostfolio\" \"ghostfolio/ghostfolio\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop ghostfolio\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    tar -czf \"/opt/ghostfolio_backup_$(date +%F).tar.gz\" \\\n      -C /opt \\\n      --exclude=\"ghostfolio/node_modules\" \\\n      --exclude=\"ghostfolio/dist\" \\\n      ghostfolio\n    mv /opt/ghostfolio/.env /opt/env.backup\n    msg_ok \"Backup Created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"ghostfolio\" \"ghostfolio/ghostfolio\" \"tarball\" \"latest\" \"/opt/ghostfolio\"\n\n    msg_info \"Updating Ghostfolio\"\n    mv /opt/env.backup /opt/ghostfolio/.env\n    sed -i -E '/^DATABASE_URL=/ s/[?&]sslmode=prefer//g' /opt/ghostfolio/.env\n    cd /opt/ghostfolio\n    $STD npm ci\n    $STD npm run build:production\n    $STD npx prisma migrate deploy\n    msg_ok \"Updated Ghostfolio\"\n\n    msg_info \"Starting Service\"\n    systemctl start ghostfolio\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3333${CL}\"\n"
  },
  {
    "path": "ct/gitea-mirror.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/RayLabsHQ/gitea-mirror\n\nAPP=\"gitea-mirror\"\nvar_tags=\"${var_tags:-mirror;gitea}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\n\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/gitea-mirror ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  APP_VERSION=$(grep -o '\"version\": *\"[^\"]*\"' /opt/gitea-mirror/package.json | cut -d'\"' -f4)\n  if [[ $APP_VERSION =~ ^2\\. ]]; then\n    if [[ \"${PHS_SILENT:-0}\" == \"1\" ]]; then\n      msg_warn \"Version $APP_VERSION detected. Major version upgrade requires interactive confirmation, skipping.\"\n      exit 75\n    fi\n    msg_warn \"WARNING: Version $APP_VERSION detected!\"\n    msg_warn \"Updating from version 2.x will CLEAR ALL CONFIGURATION.\"\n    msg_warn \"This includes: API tokens, User settings, Repository configurations, All custom settings\"\n    echo \"\"\n    read -r -p \"Do you want to continue? (y/N): \" CONFIRM\n    if [[ ! \"$CONFIRM\" =~ ^[Yy]$ ]]; then\n      exit 0\n    fi\n    msg_warn \"FINAL WARNING: This update WILL clear all configuration!\"\n    msg_warn \"Please ensure you have backed up API tokens, custom configurations, and repository settings.\"\n    echo \"\"\n    read -r -p \"Final confirmation - proceed? (y/N): \" CONFIRM2\n    if [[ ! \"$CONFIRM2\" =~ ^[Yy]$ ]]; then\n      msg_info \"Update cancelled. Please backup your configuration before proceeding.\"\n      exit 0\n    fi\n    msg_info \"Proceeding with version $APP_VERSION update. All configuration will be cleared as warned.\"\n    rm -rf /opt/gitea-mirror\n  fi\n\n  if [[ ! -f /opt/gitea-mirror.env ]]; then\n    msg_info \"Detected old Enviroment, updating files\"\n    APP_SECRET=$(openssl rand -base64 32)\n    cat <<EOF >/opt/gitea-mirror.env\n# See here for config options: https://github.com/RayLabsHQ/gitea-mirror/blob/main/docs/ENVIRONMENT_VARIABLES.md\nNODE_ENV=production\nHOST=0.0.0.0\nPORT=4321\nDATABASE_URL=sqlite://data/gitea-mirror.db\nBETTER_AUTH_URL=http://${LOCAL_IP}:4321\nBETTER_AUTH_SECRET=${APP_SECRET}\nnpm_package_version=${APP_VERSION}\nEOF\n    rm /etc/systemd/system/gitea-mirror.service\n    cat <<EOF >/etc/systemd/system/gitea-mirror.service\n[Unit]\nDescription=Gitea Mirror\nAfter=network.target\n[Service]\nType=simple\nWorkingDirectory=/opt/gitea-mirror\nExecStart=/usr/local/bin/bun dist/server/entry.mjs\nRestart=on-failure\nRestartSec=10\nEnvironmentFile=/opt/gitea-mirror.env\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    msg_ok \"Old Enviroment fixed\"\n  fi\n\n  ensure_dependencies git\n\n  if check_for_gh_release \"gitea-mirror\" \"RayLabsHQ/gitea-mirror\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop gitea-mirror\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Backup Data\"\n    mkdir -p /opt/gitea-mirror-backup/data\n    cp -r /opt/gitea-mirror/data/* /opt/gitea-mirror-backup/data/\n    msg_ok \"Backup Data\"\n\n    msg_info \"Installing Bun\"\n    export BUN_INSTALL=/opt/bun\n    curl -fsSL https://bun.sh/install | $STD bash\n    ln -sf /opt/bun/bin/bun /usr/local/bin/bun\n    ln -sf /opt/bun/bin/bun /usr/local/bin/bunx\n    msg_ok \"Installed Bun\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gitea-mirror\" \"RayLabsHQ/gitea-mirror\" \"tarball\"\n\n    msg_info \"Updating and rebuilding ${APP}\"\n    cd /opt/gitea-mirror\n    $STD bun run setup\n    $STD bun run build\n    APP_VERSION=$(grep -o '\"version\": *\"[^\"]*\"' package.json | cut -d'\"' -f4)\n    sed -i.bak \"s|^npm_package_version=.*|npm_package_version=${APP_VERSION}|\" /opt/gitea-mirror.env\n    msg_ok \"Updated and rebuilt ${APP}\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/gitea-mirror-backup/data/* /opt/gitea-mirror/data\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start gitea-mirror\n    msg_ok \"Service Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:4321${CL}\"\n"
  },
  {
    "path": "ct/gitea.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: Rogue-King\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://about.gitea.com/\n\nAPP=\"Gitea\"\nvar_tags=\"${var_tags:-git}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/bin/gitea ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"gitea\" \"go-gitea/gitea\"; then\n    msg_info \"Stopping service\"\n    systemctl stop gitea\n    msg_ok \"Service stopped\"\n\n    rm -rf /usr/local/bin/gitea\n    fetch_and_deploy_gh_release \"gitea\" \"go-gitea/gitea\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"gitea-*-linux-arm64\"\n    chmod +x /usr/local/bin/gitea\n\n    msg_info \"Starting service\"\n    systemctl start gitea\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/github-runner.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/actions/runner\n\nAPP=\"GitHub-Runner\"\nvar_tags=\"${var_tags:-ci}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_nesting=\"${var_nesting:-1}\"\nvar_keyctl=\"${var_keyctl:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/actions-runner/run.sh ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit 1\n  fi\n\n  if check_for_gh_release \"actions-runner\" \"actions/runner\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop actions-runner\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up runner configuration\"\n    BACKUP_DIR=\"/opt/actions-runner.backup\"\n    mkdir -p \"$BACKUP_DIR\"\n    for f in .runner .credentials .credentials_rsaparams .env .path; do\n      [[ -f /opt/actions-runner/$f ]] && cp -a /opt/actions-runner/$f \"$BACKUP_DIR/\"\n    done\n    msg_ok \"Backed up configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"actions-runner\" \"actions/runner\" \"prebuild\" \"latest\" \"/opt/actions-runner\" \"actions-runner-linux-x64-*.tar.gz\"\n\n    msg_info \"Restoring runner configuration\"\n    for f in .runner .credentials .credentials_rsaparams .env .path; do\n      [[ -f \"$BACKUP_DIR/$f\" ]] && cp -a \"$BACKUP_DIR/$f\" /opt/actions-runner/\n    done\n    rm -rf \"$BACKUP_DIR\"\n    msg_ok \"Restored configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start actions-runner\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} After first boot, run config.sh with your token and start the service.${CL}\"\n"
  },
  {
    "path": "ct/glance.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/glanceapp/glance\n\nAPP=\"Glance\"\nvar_tags=\"${var_tags:-dashboard}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/glance.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"glance\" \"glanceapp/glance\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop glance\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"glance\" \"glanceapp/glance\" \"prebuild\" \"latest\" \"/opt/glance\" \"glance-linux-arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start glance\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/globaleaks.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Giovanni Pellerano (evilaliv3)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/globaleaks/globaleaks-whistleblowing-software\n\nAPP=\"GlobaLeaks\"\nvar_tags=\"${var_tags:-whistleblowing-software}\"\nvar_disk=\"${var_disk:-4}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/sbin/globaleaks ]]; then\n    msg_error \"No ${APP} installation found!\"\n    exit\n  fi\n\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN} ${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/glpi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.glpi-project.org/\n\nAPP=\"GLPI\"\nvar_tags=\"${var_tags:-asset-management;foss}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/glpi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  RELEASE=$(curl -fsSL https://api.github.com/repos/glpi-project/glpi/releases/latest | grep '\"tag_name\"' | sed -E 's/.*\"tag_name\": \"([^\"]+)\".*/\\1/')\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_error \"Currently we don't provide an update function for this ${APP}.\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}.\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/gluetun.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/qdm12/gluetun\n\nAPP=\"Gluetun\"\nvar_tags=\"${var_tags:-vpn;wireguard;openvpn}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_tun=\"${var_tun:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/bin/gluetun ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"gluetun\" \"qdm12/gluetun\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gluetun\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gluetun\" \"qdm12/gluetun\" \"tarball\"\n\n    msg_info \"Building Gluetun\"\n    cd /opt/gluetun\n    $STD go mod download\n    CGO_ENABLED=0 $STD go build -trimpath -ldflags=\"-s -w\" -o /usr/local/bin/gluetun ./cmd/gluetun/\n    msg_ok \"Built Gluetun\"\n\n    msg_info \"Starting Service\"\n    systemctl start gluetun\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/go2rtc.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/AlexxIT/go2rtc\n\nAPP=\"go2rtc\"\nvar_tags=\"${var_tags:-streaming;video}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/go2rtc ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"go2rtc\" \"AlexxIT/go2rtc\"; then\n    msg_info \"Stopping service\"\n    systemctl stop go2rtc\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"go2rtc\" \"AlexxIT/go2rtc\" \"singlefile\" \"latest\" \"/opt/go2rtc\" \"go2rtc_linux_arm64\"\n\n    msg_info \"Starting service\"\n    systemctl start go2rtc\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1984${CL}\"\n"
  },
  {
    "path": "ct/gogs.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gogs.io/\n\nAPP=\"Gogs\"\nvar_tags=\"${var_tags:-git;code;devops}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/gogs/gogs ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"gogs\" \"gogs/gogs\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gogs\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/gogs/custom /opt/gogs_custom_backup\n    cp -r /opt/gogs/data /opt/gogs_data_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gogs\" \"gogs/gogs\" \"prebuild\" \"latest\" \"/opt/gogs\" \"gogs_*_linux_amd64.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/gogs_custom_backup/. /opt/gogs/custom\n    cp -r /opt/gogs_data_backup/. /opt/gogs/data\n    rm -rf /opt/gogs_custom_backup /opt/gogs_data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start gogs\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/gokapi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Forceu/Gokapi\n\nAPP=\"Gokapi\"\nvar_tags=\"${var_tags:-file;sharing}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/gokapi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"gokapi\" \"Forceu/Gokapi\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gokapi\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"gokapi\" \"Forceu/Gokapi\" \"prebuild\" \"latest\" \"/opt/gokapi\" \"gokapi-linux_arm64.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl start gokapi\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:53842/setup${CL}\"\n"
  },
  {
    "path": "ct/gotify.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gotify.net/\n\nAPP=\"Gotify\"\nvar_tags=\"${var_tags:-notification}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/gotify ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"gotify\" \"gotify/server\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gotify\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"gotify\" \"gotify/server\" \"prebuild\" \"latest\" \"/opt/gotify\" \"gotify-linux-arm64.zip\"\n    chmod +x /opt/gotify/gotify-linux-arm64\n\n    msg_info \"Starting Service\"\n    systemctl start gotify\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/grafana.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://grafana.com/\n\nAPP=\"Grafana\"\nvar_tags=\"${var_tags:-monitoring;visualization}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if ! dpkg -s grafana >/dev/null 2>&1; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit 233\n  fi\n\n  if [[ -f /etc/apt/sources.list.d/grafana.list ]] || [[ ! -f /etc/apt/sources.list.d/grafana.sources ]]; then\n    setup_deb822_repo \\\n      \"grafana\" \\\n      \"https://apt.grafana.com/gpg.key\" \\\n      \"https://apt.grafana.com\" \\\n      \"stable\" \\\n      \"main\"\n  fi\n\n  msg_info \"Updating Grafana LXC\"\n  $STD apt update\n  $STD apt --only-upgrade install -y grafana\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/gramps-web.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.grampsweb.org/ | Github: https://github.com/gramps-project/gramps-web\n\nAPP=\"gramps-web\"\nvar_tags=\"${var_tags:-genealogy;family;collaboration}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/gramps-web-api ]] || [[ ! -d /opt/gramps-web/frontend ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  PYTHON_VERSION=\"3.12\" setup_uv\n  NODE_VERSION=\"22\" setup_nodejs\n\n  if check_for_gh_release \"gramps-web-api\" \"gramps-project/gramps-web-api\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gramps-web\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gramps-web-api\" \"gramps-project/gramps-web-api\" \"tarball\" \"latest\" \"/opt/gramps-web-api\"\n\n    msg_info \"Updating Gramps Web API\"\n    $STD uv venv -c -p python3.12 /opt/gramps-web/venv\n    source /opt/gramps-web/venv/bin/activate\n    $STD uv pip install --no-cache-dir --upgrade pip setuptools wheel\n    $STD uv pip install --no-cache-dir gunicorn\n    $STD uv pip install --no-cache-dir /opt/gramps-web-api\n    msg_ok \"Updated Gramps Web API\"\n\n    msg_info \"Applying Database Migration\"\n    cd /opt/gramps-web-api\n    GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \\\n      ALEMBIC_CONFIG=/opt/gramps-web-api/alembic.ini \\\n      GRAMPSHOME=/opt/gramps-web/data \\\n      GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \\\n      $STD /opt/gramps-web/venv/bin/python3 -m gramps_webapi user migrate\n    msg_ok \"Applied Database Migration\"\n\n    msg_info \"Updating Gramps Addons\"\n    GRAMPS_VERSION=$(/opt/gramps-web/venv/bin/python3 -c \"import gramps.version; print('%s%s' % (gramps.version.VERSION_TUPLE[0], gramps.version.VERSION_TUPLE[1]))\" 2>/dev/null || echo \"60\")\n    GRAMPS_PLUGINS_DIR=\"/opt/gramps-web/data/gramps/gramps${GRAMPS_VERSION}/plugins\"\n    mkdir -p \"$GRAMPS_PLUGINS_DIR\"\n    $STD wget -q https://github.com/gramps-project/addons/archive/refs/heads/master.zip -O /tmp/gramps-addons.zip\n    for addon in FilterRules JSON; do\n      unzip -p /tmp/gramps-addons.zip \"addons-master/gramps${GRAMPS_VERSION}/download/${addon}.addon.tgz\" |\n        tar -xz -C \"$GRAMPS_PLUGINS_DIR\"\n    done\n    rm -f /tmp/gramps-addons.zip\n    msg_ok \"Updated Gramps Addons\"\n\n    msg_info \"Starting Service\"\n    systemctl start gramps-web\n    msg_ok \"Started Service\"\n  fi\n\n  if check_for_gh_release \"gramps-web\" \"gramps-project/gramps-web\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop gramps-web\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gramps-web\" \"gramps-project/gramps-web\" \"tarball\" \"latest\" \"/opt/gramps-web/frontend\"\n\n    msg_info \"Updating Gramps Web Frontend\"\n    cd /opt/gramps-web/frontend\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    $STD corepack enable\n    $STD npm install\n    $STD npm run build\n    msg_ok \"Updated Gramps Web Frontend\"\n\n    msg_info \"Starting Service\"\n    systemctl start gramps-web\n    msg_ok \"Started Service\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/graylog.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://graylog.org/\n\nAPP=\"Graylog\"\nvar_tags=\"${var_tags:-logging}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-30}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /etc/graylog ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop graylog-datanode\n  systemctl stop graylog-server\n  msg_info \"Stopped Service\"\n\n  CURRENT_VERSION=$(apt list --installed 2>/dev/null | grep graylog-server | grep -oP '\\d+\\.\\d+\\.\\d+')\n\n  if dpkg --compare-versions \"$CURRENT_VERSION\" lt \"6.3\"; then\n    MONGO_VERSION=\"8.2\" setup_mongodb\n\n    msg_info \"Updating Graylog\"\n    $STD apt update\n    $STD apt upgrade -y\n    curl -fsSL \"https://packages.graylog2.org/repo/packages/graylog-7.0-repository_latest.deb\" -o \"graylog-7.0-repository_latest.deb\"\n    $STD dpkg -i graylog-7.0-repository_latest.deb\n    $STD apt update\n    ensure_dependencies graylog-server graylog-datanode\n    rm -f graylog-7.0-repository_latest.deb\n    msg_ok \"Updated Graylog\"\n  elif dpkg --compare-versions \"$CURRENT_VERSION\" ge \"7.0\"; then\n    msg_info \"Updating Graylog\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated Graylog\"\n  fi\n\n  msg_info \"Starting Service\"\n  systemctl start graylog-datanode\n  systemctl start graylog-server\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\n\nif [[ $(sysctl -n vm.max_map_count 2>/dev/null) -lt 262144 ]]; then\n  sysctl -w vm.max_map_count=262144 >/dev/null 2>&1\n  echo \"vm.max_map_count=262144\" >/etc/sysctl.d/graylog.conf\nfi\n\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/grist.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: cfurrow | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nAPP=\"Grist\"\nvar_tags=\"${var_tags:-database;spreadsheet}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/grist ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies git\n\n  if check_for_gh_release \"grist\" \"gristlabs/grist-core\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop grist\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    rm -rf /opt/grist_bak\n    mv /opt/grist /opt/grist_bak\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"grist\" \"gristlabs/grist-core\" \"tarball\"\n\n    msg_info \"Updating Grist\"\n    mkdir -p /opt/grist/docs\n    cp -n /opt/grist_bak/.env /opt/grist/.env\n    if ls /opt/grist_bak/docs/* &>/dev/null; then\n      cp -r /opt/grist_bak/docs/* /opt/grist/docs/\n    fi\n    [[ -f /opt/grist_bak/grist-sessions.db ]] && cp /opt/grist_bak/grist-sessions.db /opt/grist/grist-sessions.db\n    [[ -f /opt/grist_bak/landing.db ]] && cp /opt/grist_bak/landing.db /opt/grist/landing.db\n    cd /opt/grist\n    $STD yarn install\n    $STD yarn run build:prod\n    $STD yarn run install:python\n    msg_ok \"Updated Grist\"\n\n    msg_info \"Starting Service\"\n    systemctl start grist\n    msg_ok \"Started Service\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}Grist: http://${IP}:8484${CL}\"\n"
  },
  {
    "path": "ct/grocy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://grocy.info/\n\nAPP=\"grocy\"\nvar_tags=\"${var_tags:-grocery;household}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apache2/sites-available/grocy.conf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  php_ver=$(php -v | head -n 1 | awk '{print $2}')\n  if [[ ! $php_ver == \"8.5\"* ]]; then\n    PHP_VERSION=\"8.5\" PHP_APACHE=\"YES\" setup_php\n  fi\n  if check_for_gh_release \"grocy\" \"grocy/grocy\"; then\n    msg_info \"Updating grocy\"\n    bash /var/www/html/update.sh\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/guardian.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: HydroshieldMKII\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/HydroshieldMKII/Guardian\n\nAPP=\"Guardian\"\nvar_tags=\"${var_tags:-media;monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/guardian\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"guardian\" \"HydroshieldMKII/Guardian\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop guardian-backend guardian-frontend\n    msg_ok \"Stopped Services\"\n\n    if [[ -f \"/opt/guardian/backend/plex-guard.db\" ]]; then\n      msg_info \"Backing up Database\"\n      cp \"/opt/guardian/backend/plex-guard.db\" \"/tmp/plex-guard.db.backup\"\n      msg_ok \"Backed up Database\"\n    fi\n\n    [[ -f \"/opt/guardian/.env\" ]] && cp \"/opt/guardian/.env\" \"/opt\"\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"guardian\" \"HydroshieldMKII/Guardian\" \"tarball\" \"latest\" \"/opt/guardian\"\n    [[ -f \"/opt/.env\" ]] && mv \"/opt/.env\" \"/opt/guardian\"\n\n    if [[ -f \"/tmp/plex-guard.db.backup\" ]]; then\n      msg_info \"Restoring Database\"\n      cp \"/tmp/plex-guard.db.backup\" \"/opt/guardian/backend/plex-guard.db\"\n      rm \"/tmp/plex-guard.db.backup\"\n      msg_ok \"Restored Database\"\n    fi\n\n    msg_info \"Updating Guardian\"\n    cd /opt/guardian/backend\n    $STD npm ci\n    $STD npm run build\n\n    cd /opt/guardian/frontend\n    $STD npm ci\n    export DEPLOYMENT_MODE=standalone\n    $STD npm run build\n    msg_ok \"Updated Guardian\"\n\n    msg_info \"Starting Services\"\n    systemctl start guardian-backend guardian-frontend\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/gwn-manager.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.grandstream.com/products/networking-solutions/wi-fi-management/product/gwn-manager\n\nAPP=\"GWN-Manager\"\nvar_tags=\"${var_tags:-network;management}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /gwn ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_custom \"🚀\" \"${GN}\" \"The app offers a built-in updater. Please use it.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}\"\n"
  },
  {
    "path": "ct/headers/2fauth",
    "content": "   ___   _________         __  __  \n  |__ \\ / ____/   | __  __/ /_/ /_ \n  __/ // /_  / /| |/ / / / __/ __ \\\n / __// __/ / ___ / /_/ / /_/ / / /\n/____/_/   /_/  |_\\__,_/\\__/_/ /_/ \n                                   \n"
  },
  {
    "path": "ct/headers/actualbudget",
    "content": "    ___        __              __   ____            __           __ \n   /   | _____/ /___  ______ _/ /  / __ )__  ______/ /___ ____  / /_\n  / /| |/ ___/ __/ / / / __ `/ /  / __  / / / / __  / __ `/ _ \\/ __/\n / ___ / /__/ /_/ /_/ / /_/ / /  / /_/ / /_/ / /_/ / /_/ /  __/ /_  \n/_/  |_\\___/\\__/\\__,_/\\__,_/_/  /_____/\\__,_/\\__,_/\\__, /\\___/\\__/  \n                                                  /____/            \n"
  },
  {
    "path": "ct/headers/adguard",
    "content": "    ___       __                           __\n   /   | ____/ /___ ___  ______ __________/ /\n  / /| |/ __  / __ `/ / / / __ `/ ___/ __  / \n / ___ / /_/ / /_/ / /_/ / /_/ / /  / /_/ /  \n/_/  |_\\__,_/\\__, /\\__,_/\\__,_/_/   \\__,_/   \n            /____/                           \n"
  },
  {
    "path": "ct/headers/adventurelog",
    "content": "    ___       __                 __                  __               \n   /   | ____/ /   _____  ____  / /___  __________  / /   ____  ____ _\n  / /| |/ __  / | / / _ \\/ __ \\/ __/ / / / ___/ _ \\/ /   / __ \\/ __ `/\n / ___ / /_/ /| |/ /  __/ / / / /_/ /_/ / /  /  __/ /___/ /_/ / /_/ / \n/_/  |_\\__,_/ |___/\\___/_/ /_/\\__/\\__,_/_/   \\___/_____/\\____/\\__, /  \n                                                             /____/   \n"
  },
  {
    "path": "ct/headers/agentdvr",
    "content": "    ___                    __  ____ _    ______ \n   /   | ____ ____  ____  / /_/ __ \\ |  / / __ \\\n  / /| |/ __ `/ _ \\/ __ \\/ __/ / / / | / / /_/ /\n / ___ / /_/ /  __/ / / / /_/ /_/ /| |/ / _, _/ \n/_/  |_\\__, /\\___/_/ /_/\\__/_____/ |___/_/ |_|  \n      /____/                                    \n"
  },
  {
    "path": "ct/headers/alpine",
    "content": "    ___    __      _          \n   /   |  / /___  (_)___  ___ \n  / /| | / / __ \\/ / __ \\/ _ \\\n / ___ |/ / /_/ / / / / /  __/\n/_/  |_/_/ .___/_/_/ /_/\\___/ \n        /_/                   \n"
  },
  {
    "path": "ct/headers/alpine-adguard",
    "content": "    ___    __      _                  ___       ________                     __\n   /   |  / /___  (_)___  ___        /   | ____/ / ____/_  ______ __________/ /\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /| |/ __  / / __/ / / / __ `/ ___/ __  / \n / ___ |/ / /_/ / / / / /  __/_____/ ___ / /_/ / /_/ / /_/ / /_/ / /  / /_/ /  \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/  |_\\__,_/\\____/\\__,_/\\__,_/_/   \\__,_/   \n        /_/                                                                    \n"
  },
  {
    "path": "ct/headers/alpine-bitmagnet",
    "content": "    ___    __      _                  __    _ __                                   __ \n   /   |  / /___  (_)___  ___        / /_  (_) /_____ ___  ____ _____ _____  ___  / /_\n  / /| | / / __ \\/ / __ \\/ _ \\______/ __ \\/ / __/ __ `__ \\/ __ `/ __ `/ __ \\/ _ \\/ __/\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / / /_/ / / / / / /_/ / /_/ / / / /  __/ /_  \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_.___/_/\\__/_/ /_/ /_/\\__,_/\\__, /_/ /_/\\___/\\__/  \n        /_/                                                   /____/                  \n"
  },
  {
    "path": "ct/headers/alpine-borgbackup-server",
    "content": "    ___    __      _                  ____                   ____             __                    _____                          \n   /   |  / /___  (_)___  ___        / __ )____  _________ _/ __ )____ ______/ /____  ______       / ___/___  ______   _____  _____\n  / /| | / / __ \\/ / __ \\/ _ \\______/ __  / __ \\/ ___/ __ `/ __  / __ `/ ___/ //_/ / / / __ \\______\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / /_/ / /  / /_/ / /_/ / /_/ / /__/ ,< / /_/ / /_/ /_____/__/ /  __/ /   | |/ /  __/ /    \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_____/\\____/_/   \\__, /_____/\\__,_/\\___/_/|_|\\__,_/ .___/     /____/\\___/_/    |___/\\___/_/     \n        /_/                                        /____/                           /_/                                            \n"
  },
  {
    "path": "ct/headers/alpine-caddy",
    "content": "    ___    __      _                  ______          __    __     \n   /   |  / /___  (_)___  ___        / ____/___ _____/ /___/ /_  __\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /   / __ `/ __  / __  / / / /\n / ___ |/ / /_/ / / / / /  __/_____/ /___/ /_/ / /_/ / /_/ / /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/      \\____/\\__,_/\\__,_/\\__,_/\\__, /  \n        /_/                                               /____/   \n"
  },
  {
    "path": "ct/headers/alpine-docker",
    "content": "    ___    __      _                  ____             __            \n   /   |  / /___  (_)___  ___        / __ \\____  _____/ /_____  _____\n  / /| | / / __ \\/ / __ \\/ _ \\______/ / / / __ \\/ ___/ //_/ _ \\/ ___/\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / /_/ / /__/ ,< /  __/ /    \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_____/\\____/\\___/_/|_|\\___/_/     \n        /_/                                                          \n"
  },
  {
    "path": "ct/headers/alpine-forgejo",
    "content": "    ___    __      _                  ______                        _     \n   /   |  / /___  (_)___  ___        / ____/___  _________ ____    (_)___ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_  / __ \\/ ___/ __ `/ _ \\  / / __ \\\n / ___ |/ / /_/ / / / / /  __/_____/ __/ / /_/ / /  / /_/ /  __/ / / /_/ /\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/    \\____/_/   \\__, /\\___/_/ /\\____/ \n        /_/                                        /____/    /___/        \n"
  },
  {
    "path": "ct/headers/alpine-garage",
    "content": "    ___    __      _                  ______                          \n   /   |  / /___  (_)___  ___        / ____/___ __________ _____ ____ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ / __/ __ `/ ___/ __ `/ __ `/ _ \\\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / /_/ / /  / /_/ / /_/ /  __/\n/_/  |_/_/ .___/_/_/ /_/\\___/      \\____/\\__,_/_/   \\__,_/\\__, /\\___/ \n        /_/                                              /____/       \n"
  },
  {
    "path": "ct/headers/alpine-gatus",
    "content": "    ___    __      _                              __            \n   /   |  / /___  (_)___  ___        ____ _____ _/ /___  _______\n  / /| | / / __ \\/ / __ \\/ _ \\______/ __ `/ __ `/ __/ / / / ___/\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / /_/ / /_/ /_/ (__  ) \n/_/  |_/_/ .___/_/_/ /_/\\___/      \\__, /\\__,_/\\__/\\__,_/____/  \n        /_/                       /____/                        \n"
  },
  {
    "path": "ct/headers/alpine-gitea",
    "content": "    ___    __      _                  _______ __            \n   /   |  / /___  (_)___  ___        / ____(_) /____  ____ _\n  / /| | / / __ \\/ / __ \\/ _ \\______/ / __/ / __/ _ \\/ __ `/\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / / /_/  __/ /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/      \\____/_/\\__/\\___/\\__,_/  \n        /_/                                                 \n"
  },
  {
    "path": "ct/headers/alpine-grafana",
    "content": "    ___    __      _                  ______           ____                 \n   /   |  / /___  (_)___  ___        / ____/________ _/ __/___ _____  ____ _\n  / /| | / / __ \\/ / __ \\/ _ \\______/ / __/ ___/ __ `/ /_/ __ `/ __ \\/ __ `/\n / ___ |/ / /_/ / / / / /  __/_____/ /_/ / /  / /_/ / __/ /_/ / / / / /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/      \\____/_/   \\__,_/_/  \\__,_/_/ /_/\\__,_/  \n        /_/                                                                 \n"
  },
  {
    "path": "ct/headers/alpine-ironclaw",
    "content": "    ___    __      _                  ____                 ________              \n   /   |  / /___  (_)___  ___        /  _/________  ____  / ____/ /___ __      __\n  / /| | / / __ \\/ / __ \\/ _ \\______ / // ___/ __ \\/ __ \\/ /   / / __ `/ | /| / /\n / ___ |/ / /_/ / / / / /  __/_____// // /  / /_/ / / / / /___/ / /_/ /| |/ |/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/     /___/_/   \\____/_/ /_/\\____/_/\\__,_/ |__/|__/  \n        /_/                                                                      \n"
  },
  {
    "path": "ct/headers/alpine-it-tools",
    "content": "    ___    __      _                  __________  ______            __    \n   /   |  / /___  (_)___  ___        /  _/_  __/ /_  __/___  ____  / /____\n  / /| | / / __ \\/ / __ \\/ _ \\______ / /  / /_____/ / / __ \\/ __ \\/ / ___/\n / ___ |/ / /_/ / / / / /  __/_____// /  / /_____/ / / /_/ / /_/ / (__  ) \n/_/  |_/_/ .___/_/_/ /_/\\___/     /___/ /_/     /_/  \\____/\\____/_/____/  \n        /_/                                                               \n"
  },
  {
    "path": "ct/headers/alpine-komodo",
    "content": "    ___    __      _                  __ __                          __    \n   /   |  / /___  (_)___  ___        / //_/___  ____ ___  ____  ____/ /___ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ ,< / __ \\/ __ `__ \\/ __ \\/ __  / __ \\\n / ___ |/ / /_/ / / / / /  __/_____/ /| / /_/ / / / / / / /_/ / /_/ / /_/ /\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_\\____/_/ /_/ /_/\\____/\\__,_/\\____/ \n        /_/                                                                \n"
  },
  {
    "path": "ct/headers/alpine-loki",
    "content": "    ___    __      _                  __          __   _ \n   /   |  / /___  (_)___  ___        / /   ____  / /__(_)\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /   / __ \\/ //_/ / \n / ___ |/ / /_/ / / / / /  __/_____/ /___/ /_/ / ,< / /  \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_____/\\____/_/|_/_/   \n        /_/                                              \n"
  },
  {
    "path": "ct/headers/alpine-mariadb",
    "content": "    ___    __      _                  __  ___           _       ____  ____ \n   /   |  / /___  (_)___  ___        /  |/  /___ ______(_)___ _/ __ \\/ __ )\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /|_/ / __ `/ ___/ / __ `/ / / / __  |\n / ___ |/ / /_/ / / / / /  __/_____/ /  / / /_/ / /  / / /_/ / /_/ / /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/  /_/\\__,_/_/  /_/\\__,_/_____/_____/  \n        /_/                                                                \n"
  },
  {
    "path": "ct/headers/alpine-nextcloud",
    "content": "    ___    __      _                  _   __          __       __                __\n   /   |  / /___  (_)___  ___        / | / /__  _  __/ /______/ /___  __  ______/ /\n  / /| | / / __ \\/ / __ \\/ _ \\______/  |/ / _ \\| |/_/ __/ ___/ / __ \\/ / / / __  / \n / ___ |/ / /_/ / / / / /  __/_____/ /|  /  __/>  </ /_/ /__/ / /_/ / /_/ / /_/ /  \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_/\\___/_/|_|\\__/\\___/_/\\____/\\__,_/\\__,_/   \n        /_/                                                                        \n"
  },
  {
    "path": "ct/headers/alpine-node-red",
    "content": "    ___    __      _                  _   __          __           ____  __________ \n   /   |  / /___  (_)___  ___        / | / /___  ____/ /__        / __ \\/ ____/ __ \\\n  / /| | / / __ \\/ / __ \\/ _ \\______/  |/ / __ \\/ __  / _ \\______/ /_/ / __/ / / / /\n / ___ |/ / /_/ / / / / /  __/_____/ /|  / /_/ / /_/ /  __/_____/ _, _/ /___/ /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_/\\____/\\__,_/\\___/     /_/ |_/_____/_____/  \n        /_/                                                                         \n"
  },
  {
    "path": "ct/headers/alpine-ntfy",
    "content": "    ___    __      _                        __  ____     \n   /   |  / /___  (_)___  ___        ____  / /_/ __/_  __\n  / /| | / / __ \\/ / __ \\/ _ \\______/ __ \\/ __/ /_/ / / /\n / ___ |/ / /_/ / / / / /  __/_____/ / / / /_/ __/ /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ /_/\\__/_/  \\__, /  \n        /_/                                     /____/   \n"
  },
  {
    "path": "ct/headers/alpine-postgresql",
    "content": "    ___    __      _                  ____             __                 _____ ____    __ \n   /   |  / /___  (_)___  ___        / __ \\____  _____/ /_____ _________ / ___// __ \\  / / \n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_/ / __ \\/ ___/ __/ __ `/ ___/ _ \\\\__ \\/ / / / / /  \n / ___ |/ / /_/ / / / / /  __/_____/ ____/ /_/ (__  ) /_/ /_/ / /  /  __/__/ / /_/ / / /___\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/    \\____/____/\\__/\\__, /_/   \\___/____/\\___\\_\\/_____/\n        /_/                                            /____/                              \n"
  },
  {
    "path": "ct/headers/alpine-prometheus",
    "content": "    ___    __      _                  ____                            __  __                   \n   /   |  / /___  (_)___  ___        / __ \\_________  ____ ___  ___  / /_/ /_  ___  __  _______\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_/ / ___/ __ \\/ __ `__ \\/ _ \\/ __/ __ \\/ _ \\/ / / / ___/\n / ___ |/ / /_/ / / / / /  __/_____/ ____/ /  / /_/ / / / / / /  __/ /_/ / / /  __/ /_/ (__  ) \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/   /_/   \\____/_/ /_/ /_/\\___/\\__/_/ /_/\\___/\\__,_/____/  \n        /_/                                                                                    \n"
  },
  {
    "path": "ct/headers/alpine-rclone",
    "content": "    ___    __      _                            __               \n   /   |  / /___  (_)___  ___        __________/ /___  ____  ___ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ ___/ ___/ / __ \\/ __ \\/ _ \\\n / ___ |/ / /_/ / / / / /  __/_____/ /  / /__/ / /_/ / / / /  __/\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/   \\___/_/\\____/_/ /_/\\___/ \n        /_/                                                      \n"
  },
  {
    "path": "ct/headers/alpine-redis",
    "content": "    ___    __      _                  ____           ___     \n   /   |  / /___  (_)___  ___        / __ \\___  ____/ (_)____\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_/ / _ \\/ __  / / ___/\n / ___ |/ / /_/ / / / / /  __/_____/ _, _/  __/ /_/ / (__  ) \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_|\\___/\\__,_/_/____/  \n        /_/                                                  \n"
  },
  {
    "path": "ct/headers/alpine-redlib",
    "content": "    ___    __      _                  ____           _____ __  \n   /   |  / /___  (_)___  ___        / __ \\___  ____/ / (_) /_ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_/ / _ \\/ __  / / / __ \\\n / ___ |/ / /_/ / / / / /  __/_____/ _, _/  __/ /_/ / / / /_/ /\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_|\\___/\\__,_/_/_/_.___/ \n        /_/                                                    \n"
  },
  {
    "path": "ct/headers/alpine-rustdeskserver",
    "content": "    ___    __      _                  ____             __  ____            __   _____                          \n   /   |  / /___  (_)___  ___        / __ \\__  _______/ /_/ __ \\___  _____/ /__/ ___/___  ______   _____  _____\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_/ / / / / ___/ __/ / / / _ \\/ ___/ //_/\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / ___ |/ / /_/ / / / / /  __/_____/ _, _/ /_/ (__  ) /_/ /_/ /  __(__  ) ,<  ___/ /  __/ /   | |/ /  __/ /    \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_|\\__,_/____/\\__/_____/\\___/____/_/|_|/____/\\___/_/    |___/\\___/_/     \n        /_/                                                                                                    \n"
  },
  {
    "path": "ct/headers/alpine-rustypaste",
    "content": "    ___    __      _                  ____             __        ____             __     \n   /   |  / /___  (_)___  ___        / __ \\__  _______/ /___  __/ __ \\____ ______/ /____ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ /_/ / / / / ___/ __/ / / / /_/ / __ `/ ___/ __/ _ \\\n / ___ |/ / /_/ / / / / /  __/_____/ _, _/ /_/ (__  ) /_/ /_/ / ____/ /_/ (__  ) /_/  __/\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ |_|\\__,_/____/\\__/\\__, /_/    \\__,_/____/\\__/\\___/ \n        /_/                                            /____/                            \n"
  },
  {
    "path": "ct/headers/alpine-syncthing",
    "content": "    ___    __      _                 _____                  __  __    _            \n   /   |  / /___  (_)___  ___       / ___/__  ______  _____/ /_/ /_  (_)___  ____ _\n  / /| | / / __ \\/ / __ \\/ _ \\______\\__ \\/ / / / __ \\/ ___/ __/ __ \\/ / __ \\/ __ `/\n / ___ |/ / /_/ / / / / /  __/_____/__/ / /_/ / / / / /__/ /_/ / / / / / / / /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/     /____/\\__, /_/ /_/\\___/\\__/_/ /_/_/_/ /_/\\__, /  \n        /_/                            /____/                             /____/   \n"
  },
  {
    "path": "ct/headers/alpine-teamspeak-server",
    "content": "    ___    __      _                ______                    _____                  __        _____                          \n   /   |  / /___  (_)___  ___      /_  __/__  ____ _____ ___ / ___/____  ___  ____ _/ /__     / ___/___  ______   _____  _____\n  / /| | / / __ \\/ / __ \\/ _ \\______/ / / _ \\/ __ `/ __ `__ \\\\__ \\/ __ \\/ _ \\/ __ `/ //_/_____\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / ___ |/ / /_/ / / / / /  __/_____/ / /  __/ /_/ / / / / / /__/ / /_/ /  __/ /_/ / ,< /_____/__/ /  __/ /   | |/ /  __/ /    \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/  \\___/\\__,_/_/ /_/ /_/____/ .___/\\___/\\__,_/_/|_|     /____/\\___/_/    |___/\\___/_/     \n        /_/                                                    /_/                                                            \n"
  },
  {
    "path": "ct/headers/alpine-tinyauth",
    "content": "    ___    __      _                _______                         __  __  \n   /   |  / /___  (_)___  ___      /_  __(_)___  __  ______ ___  __/ /_/ /_ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ / / / __ \\/ / / / __ `/ / / / __/ __ \\\n / ___ |/ / /_/ / / / / /  __/_____/ / / / / / / /_/ / /_/ / /_/ / /_/ / / /\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ /_/_/ /_/\\__, /\\__,_/\\__,_/\\__/_/ /_/ \n        /_/                                   /____/                        \n"
  },
  {
    "path": "ct/headers/alpine-traefik",
    "content": "    ___    __      _                ______                _____ __  \n   /   |  / /___  (_)___  ___      /_  __/________ ____  / __(_) /__\n  / /| | / / __ \\/ / __ \\/ _ \\______/ / / ___/ __ `/ _ \\/ /_/ / //_/\n / ___ |/ / /_/ / / / / /  __/_____/ / / /  / /_/ /  __/ __/ / ,<   \n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ /_/   \\__,_/\\___/_/ /_/_/|_|  \n        /_/                                                         \n"
  },
  {
    "path": "ct/headers/alpine-transmission",
    "content": "    ___    __      _                ______                                _           _           \n   /   |  / /___  (_)___  ___      /_  __/________ _____  _________ ___  (_)_________(_)___  ____ \n  / /| | / / __ \\/ / __ \\/ _ \\______/ / / ___/ __ `/ __ \\/ ___/ __ `__ \\/ / ___/ ___/ / __ \\/ __ \\\n / ___ |/ / /_/ / / / / /  __/_____/ / / /  / /_/ / / / (__  ) / / / / / (__  |__  ) / /_/ / / / /\n/_/  |_/_/ .___/_/_/ /_/\\___/     /_/ /_/   \\__,_/_/ /_/____/_/ /_/ /_/_/____/____/_/\\____/_/ /_/ \n        /_/                                                                                       \n"
  },
  {
    "path": "ct/headers/alpine-valkey",
    "content": "    ___    __      _                _    __      ____             \n   /   |  / /___  (_)___  ___      | |  / /___ _/ / /_____  __  __\n  / /| | / / __ \\/ / __ \\/ _ \\_____| | / / __ `/ / //_/ _ \\/ / / /\n / ___ |/ / /_/ / / / / /  __/_____/ |/ / /_/ / / ,< /  __/ /_/ / \n/_/  |_/_/ .___/_/_/ /_/\\___/      |___/\\__,_/_/_/|_|\\___/\\__, /  \n        /_/                                              /____/   \n"
  },
  {
    "path": "ct/headers/alpine-vaultwarden",
    "content": "    ___    __      _                _    __            ____                          __         \n   /   |  / /___  (_)___  ___      | |  / /___ ___  __/ / /__      ______ __________/ /__  ____ \n  / /| | / / __ \\/ / __ \\/ _ \\_____| | / / __ `/ / / / / __/ | /| / / __ `/ ___/ __  / _ \\/ __ \\\n / ___ |/ / /_/ / / / / /  __/_____/ |/ / /_/ / /_/ / / /_ | |/ |/ / /_/ / /  / /_/ /  __/ / / /\n/_/  |_/_/ .___/_/_/ /_/\\___/      |___/\\__,_/\\__,_/_/\\__/ |__/|__/\\__,_/_/   \\__,_/\\___/_/ /_/ \n        /_/                                                                                     \n"
  },
  {
    "path": "ct/headers/alpine-wakapi",
    "content": "    ___    __      _                _       __      __               _ \n   /   |  / /___  (_)___  ___      | |     / /___ _/ /______ _____  (_)\n  / /| | / / __ \\/ / __ \\/ _ \\_____| | /| / / __ `/ //_/ __ `/ __ \\/ / \n / ___ |/ / /_/ / / / / /  __/_____/ |/ |/ / /_/ / ,< / /_/ / /_/ / /  \n/_/  |_/_/ .___/_/_/ /_/\\___/      |__/|__/\\__,_/_/|_|\\__,_/ .___/_/   \n        /_/                                               /_/          \n"
  },
  {
    "path": "ct/headers/alpine-wireguard",
    "content": "    ___    __      _                _       ___                                      __\n   /   |  / /___  (_)___  ___      | |     / (_)_______  ____ ___  ______ __________/ /\n  / /| | / / __ \\/ / __ \\/ _ \\_____| | /| / / / ___/ _ \\/ __ `/ / / / __ `/ ___/ __  / \n / ___ |/ / /_/ / / / / /  __/_____/ |/ |/ / / /  /  __/ /_/ / /_/ / /_/ / /  / /_/ /  \n/_/  |_/_/ .___/_/_/ /_/\\___/      |__/|__/_/_/   \\___/\\__, /\\__,_/\\__,_/_/   \\__,_/   \n        /_/                                           /____/                           \n"
  },
  {
    "path": "ct/headers/alpine-zigbee2mqtt",
    "content": "    ___    __      _               _____   _       __             ___   __  _______  ____________\n   /   |  / /___  (_)___  ___     /__  /  (_)___ _/ /_  ___  ___ |__ \\ /  |/  / __ \\/_  __/_  __/\n  / /| | / / __ \\/ / __ \\/ _ \\______/ /  / / __ `/ __ \\/ _ \\/ _ \\__/ // /|_/ / / / / / /   / /   \n / ___ |/ / /_/ / / / / /  __/_____/ /__/ / /_/ / /_/ /  __/  __/ __// /  / / /_/ / / /   / /    \n/_/  |_/_/ .___/_/_/ /_/\\___/     /____/_/\\__, /_.___/\\___/\\___/____/_/  /_/\\___\\_\\/_/   /_/     \n        /_/                              /____/                                                  \n"
  },
  {
    "path": "ct/headers/ampache",
    "content": "    ___                               __       \n   /   |  ____ ___  ____  ____ ______/ /_  ___ \n  / /| | / __ `__ \\/ __ \\/ __ `/ ___/ __ \\/ _ \\\n / ___ |/ / / / / / /_/ / /_/ / /__/ / / /  __/\n/_/  |_/_/ /_/ /_/ .___/\\__,_/\\___/_/ /_/\\___/ \n                /_/                            \n"
  },
  {
    "path": "ct/headers/anchor",
    "content": "    ___               __              \n   /   |  ____  _____/ /_  ____  _____\n  / /| | / __ \\/ ___/ __ \\/ __ \\/ ___/\n / ___ |/ / / / /__/ / / / /_/ / /    \n/_/  |_/_/ /_/\\___/_/ /_/\\____/_/     \n                                      \n"
  },
  {
    "path": "ct/headers/anytype-server",
    "content": "    ___                __                        _____                          \n   /   |  ____  __  __/ /___  ______  ___       / ___/___  ______   _____  _____\n  / /| | / __ \\/ / / / __/ / / / __ \\/ _ \\______\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / ___ |/ / / / /_/ / /_/ /_/ / /_/ /  __/_____/__/ /  __/ /   | |/ /  __/ /    \n/_/  |_/_/ /_/\\__, /\\__/\\__, / .___/\\___/     /____/\\___/_/    |___/\\___/_/     \n             /____/    /____/_/                                                 \n"
  },
  {
    "path": "ct/headers/apache-cassandra",
    "content": "    ___                     __               ______                                __          \n   /   |  ____  ____ ______/ /_  ___        / ____/___ _______________ _____  ____/ /________ _\n  / /| | / __ \\/ __ `/ ___/ __ \\/ _ \\______/ /   / __ `/ ___/ ___/ __ `/ __ \\/ __  / ___/ __ `/\n / ___ |/ /_/ / /_/ / /__/ / / /  __/_____/ /___/ /_/ (__  |__  ) /_/ / / / / /_/ / /  / /_/ / \n/_/  |_/ .___/\\__,_/\\___/_/ /_/\\___/      \\____/\\__,_/____/____/\\__,_/_/ /_/\\__,_/_/   \\__,_/  \n      /_/                                                                                      \n"
  },
  {
    "path": "ct/headers/apache-couchdb",
    "content": "    ___                     __               ______                 __    ____  ____ \n   /   |  ____  ____ ______/ /_  ___        / ____/___  __  _______/ /_  / __ \\/ __ )\n  / /| | / __ \\/ __ `/ ___/ __ \\/ _ \\______/ /   / __ \\/ / / / ___/ __ \\/ / / / __  |\n / ___ |/ /_/ / /_/ / /__/ / / /  __/_____/ /___/ /_/ / /_/ / /__/ / / / /_/ / /_/ / \n/_/  |_/ .___/\\__,_/\\___/_/ /_/\\___/      \\____/\\____/\\__,_/\\___/_/ /_/_____/_____/  \n      /_/                                                                            \n"
  },
  {
    "path": "ct/headers/apache-guacamole",
    "content": "    ___                     __               ______                                       __   \n   /   |  ____  ____ ______/ /_  ___        / ____/_  ______ __________ _____ ___  ____  / /__ \n  / /| | / __ \\/ __ `/ ___/ __ \\/ _ \\______/ / __/ / / / __ `/ ___/ __ `/ __ `__ \\/ __ \\/ / _ \\\n / ___ |/ /_/ / /_/ / /__/ / / /  __/_____/ /_/ / /_/ / /_/ / /__/ /_/ / / / / / / /_/ / /  __/\n/_/  |_/ .___/\\__,_/\\___/_/ /_/\\___/      \\____/\\__,_/\\__,_/\\___/\\__,_/_/ /_/ /_/\\____/_/\\___/ \n      /_/                                                                                      \n"
  },
  {
    "path": "ct/headers/apache-tika",
    "content": "    ___                     __             _______ __        \n   /   |  ____  ____ ______/ /_  ___      /_  __(_) /______ _\n  / /| | / __ \\/ __ `/ ___/ __ \\/ _ \\______/ / / / //_/ __ `/\n / ___ |/ /_/ / /_/ / /__/ / / /  __/_____/ / / / ,< / /_/ / \n/_/  |_/ .___/\\__,_/\\___/_/ /_/\\___/     /_/ /_/_/|_|\\__,_/  \n      /_/                                                    \n"
  },
  {
    "path": "ct/headers/apache-tomcat",
    "content": "    ___                     __             ______                           __ \n   /   |  ____  ____ ______/ /_  ___      /_  __/___  ____ ___  _________ _/ /_\n  / /| | / __ \\/ __ `/ ___/ __ \\/ _ \\______/ / / __ \\/ __ `__ \\/ ___/ __ `/ __/\n / ___ |/ /_/ / /_/ / /__/ / / /  __/_____/ / / /_/ / / / / / / /__/ /_/ / /_  \n/_/  |_/ .___/\\__,_/\\___/_/ /_/\\___/     /_/  \\____/_/ /_/ /_/\\___/\\__,_/\\__/  \n      /_/                                                                      \n"
  },
  {
    "path": "ct/headers/apprise-api",
    "content": "    ___                     _                 ___    ____  ____\n   /   |  ____  ____  _____(_)_______        /   |  / __ \\/  _/\n  / /| | / __ \\/ __ \\/ ___/ / ___/ _ \\______/ /| | / /_/ // /  \n / ___ |/ /_/ / /_/ / /  / (__  )  __/_____/ ___ |/ ____// /   \n/_/  |_/ .___/ .___/_/  /_/____/\\___/     /_/  |_/_/   /___/   \n      /_/   /_/                                                \n"
  },
  {
    "path": "ct/headers/apt-cacher-ng",
    "content": "    ___          __        ______           __                    _   ________\n   /   |  ____  / /_      / ____/___ ______/ /_  ___  _____      / | / / ____/\n  / /| | / __ \\/ __/_____/ /   / __ `/ ___/ __ \\/ _ \\/ ___/_____/  |/ / / __  \n / ___ |/ /_/ / /_/_____/ /___/ /_/ / /__/ / / /  __/ /  /_____/ /|  / /_/ /  \n/_/  |_/ .___/\\__/      \\____/\\__,_/\\___/_/ /_/\\___/_/        /_/ |_/\\____/   \n      /_/                                                                     \n"
  },
  {
    "path": "ct/headers/archivebox",
    "content": "    ___              __    _            ____            \n   /   |  __________/ /_  (_)   _____  / __ )____  _  __\n  / /| | / ___/ ___/ __ \\/ / | / / _ \\/ __  / __ \\| |/_/\n / ___ |/ /  / /__/ / / / /| |/ /  __/ /_/ / /_/ />  <  \n/_/  |_/_/   \\___/_/ /_/_/ |___/\\___/_____/\\____/_/|_|  \n                                                        \n"
  },
  {
    "path": "ct/headers/argus",
    "content": "    ___                         \n   /   |  _________ ___  _______\n  / /| | / ___/ __ `/ / / / ___/\n / ___ |/ /  / /_/ / /_/ (__  ) \n/_/  |_/_/   \\__, /\\__,_/____/  \n            /____/              \n"
  },
  {
    "path": "ct/headers/aria2",
    "content": "    ___         _      ___ \n   /   |  _____(_)___ |__ \\\n  / /| | / ___/ / __ `/_/ /\n / ___ |/ /  / / /_/ / __/ \n/_/  |_/_/  /_/\\__,_/____/ \n                           \n"
  },
  {
    "path": "ct/headers/asterisk",
    "content": "    ___         __            _      __  \n   /   |  _____/ /____  _____(_)____/ /__\n  / /| | / ___/ __/ _ \\/ ___/ / ___/ //_/\n / ___ |(__  ) /_/  __/ /  / (__  ) ,<   \n/_/  |_/____/\\__/\\___/_/  /_/____/_/|_|  \n                                         \n"
  },
  {
    "path": "ct/headers/audiobookshelf",
    "content": "                   ___       __                __        __         ______\n  ____ ___  ______/ (_)___  / /_  ____  ____  / /_______/ /_  ___  / / __/\n / __ `/ / / / __  / / __ \\/ __ \\/ __ \\/ __ \\/ //_/ ___/ __ \\/ _ \\/ / /_  \n/ /_/ / /_/ / /_/ / / /_/ / /_/ / /_/ / /_/ / ,< (__  ) / / /  __/ / __/  \n\\__,_/\\__,_/\\__,_/_/\\____/_.___/\\____/\\____/_/|_/____/_/ /_/\\___/_/_/     \n                                                                          \n"
  },
  {
    "path": "ct/headers/authelia",
    "content": "    ___         __  __         ___      \n   /   | __  __/ /_/ /_  ___  / (_)___ _\n  / /| |/ / / / __/ __ \\/ _ \\/ / / __ `/\n / ___ / /_/ / /_/ / / /  __/ / / /_/ / \n/_/  |_\\__,_/\\__/_/ /_/\\___/_/_/\\__,_/  \n                                        \n"
  },
  {
    "path": "ct/headers/autobrr",
    "content": "    ___         __        __             \n   /   | __  __/ /_____  / /_  __________\n  / /| |/ / / / __/ __ \\/ __ \\/ ___/ ___/\n / ___ / /_/ / /_/ /_/ / /_/ / /  / /    \n/_/  |_\\__,_/\\__/\\____/_.___/_/  /_/     \n                                         \n"
  },
  {
    "path": "ct/headers/autocaliweb",
    "content": "    ___         __                   ___               __  \n   /   | __  __/ /_____  _________ _/ (_)      _____  / /_ \n  / /| |/ / / / __/ __ \\/ ___/ __ `/ / / | /| / / _ \\/ __ \\\n / ___ / /_/ / /_/ /_/ / /__/ /_/ / / /| |/ |/ /  __/ /_/ /\n/_/  |_\\__,_/\\__/\\____/\\___/\\__,_/_/_/ |__/|__/\\___/_.___/ \n                                                           \n"
  },
  {
    "path": "ct/headers/babybuddy",
    "content": "    ____        __             ____            __    __     \n   / __ )____ _/ /_  __  __   / __ )__  ______/ /___/ /_  __\n  / __  / __ `/ __ \\/ / / /  / __  / / / / __  / __  / / / /\n / /_/ / /_/ / /_/ / /_/ /  / /_/ / /_/ / /_/ / /_/ / /_/ / \n/_____/\\__,_/_.___/\\__, /  /_____/\\__,_/\\__,_/\\__,_/\\__, /  \n                  /____/                           /____/   \n"
  },
  {
    "path": "ct/headers/backrest",
    "content": "    ____             __                  __ \n   / __ )____ ______/ /__________  _____/ /_\n  / __  / __ `/ ___/ //_/ ___/ _ \\/ ___/ __/\n / /_/ / /_/ / /__/ ,< / /  /  __(__  ) /_  \n/_____/\\__,_/\\___/_/|_/_/   \\___/____/\\__/  \n                                            \n"
  },
  {
    "path": "ct/headers/baikal",
    "content": "    ____        _ __         __\n   / __ )____ _(_) /______ _/ /\n  / __  / __ `/ / //_/ __ `/ / \n / /_/ / /_/ / / ,< / /_/ / /  \n/_____/\\__,_/_/_/|_|\\__,_/_/   \n                               \n"
  },
  {
    "path": "ct/headers/bambuddy",
    "content": "    ____                  __              __    __     \n   / __ )____ _____ ___  / /_  __  ______/ /___/ /_  __\n  / __  / __ `/ __ `__ \\/ __ \\/ / / / __  / __  / / / /\n / /_/ / /_/ / / / / / / /_/ / /_/ / /_/ / /_/ / /_/ / \n/_____/\\__,_/_/ /_/ /_/_.___/\\__,_/\\__,_/\\__,_/\\__, /  \n                                              /____/   \n"
  },
  {
    "path": "ct/headers/bar-assistant",
    "content": "    ____                   ___              _      __              __ \n   / __ )____ ______      /   |  __________(_)____/ /_____ _____  / /_\n  / __  / __ `/ ___/_____/ /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/\n / /_/ / /_/ / /  /_____/ ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_  \n/_____/\\__,_/_/        /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/  \n                                                                      \n"
  },
  {
    "path": "ct/headers/bazarr",
    "content": "    ____                             \n   / __ )____ _____  ____ ___________\n  / __  / __ `/_  / / __ `/ ___/ ___/\n / /_/ / /_/ / / /_/ /_/ / /  / /    \n/_____/\\__,_/ /___/\\__,_/_/  /_/     \n                                     \n"
  },
  {
    "path": "ct/headers/bentopdf",
    "content": "    ____             __        ____  ____  ______\n   / __ )___  ____  / /_____  / __ \\/ __ \\/ ____/\n  / __  / _ \\/ __ \\/ __/ __ \\/ /_/ / / / / /_    \n / /_/ /  __/ / / / /_/ /_/ / ____/ /_/ / __/    \n/_____/\\___/_/ /_/\\__/\\____/_/   /_____/_/       \n                                                 \n"
  },
  {
    "path": "ct/headers/beszel",
    "content": "    ____                       __\n   / __ )___  _________  ___  / /\n  / __  / _ \\/ ___/_  / / _ \\/ / \n / /_/ /  __(__  ) / /_/  __/ /  \n/_____/\\___/____/ /___/\\___/_/   \n                                 \n"
  },
  {
    "path": "ct/headers/bichon",
    "content": "    ____  _      __              \n   / __ )(_)____/ /_  ____  ____ \n  / __  / / ___/ __ \\/ __ \\/ __ \\\n / /_/ / / /__/ / / / /_/ / / / /\n/_____/_/\\___/_/ /_/\\____/_/ /_/ \n                                 \n"
  },
  {
    "path": "ct/headers/birdnet-go",
    "content": "    ____  _          ___   ______________    ______    \n   / __ )(_)________/ / | / / ____/_  __/   / ____/___ \n  / __  / / ___/ __  /  |/ / __/   / /_____/ / __/ __ \\\n / /_/ / / /  / /_/ / /|  / /___  / /_____/ /_/ / /_/ /\n/_____/_/_/   \\__,_/_/ |_/_____/ /_/      \\____/\\____/ \n                                                       \n"
  },
  {
    "path": "ct/headers/bitmagnet",
    "content": "    ____  _ __                                   __ \n   / __ )(_) /_____ ___  ____ _____ _____  ___  / /_\n  / __  / / __/ __ `__ \\/ __ `/ __ `/ __ \\/ _ \\/ __/\n / /_/ / / /_/ / / / / / /_/ / /_/ / / / /  __/ /_  \n/_____/_/\\__/_/ /_/ /_/\\__,_/\\__, /_/ /_/\\___/\\__/  \n                            /____/                  \n"
  },
  {
    "path": "ct/headers/blocky",
    "content": "    ____  __           __        \n   / __ )/ /___  _____/ /____  __\n  / __  / / __ \\/ ___/ //_/ / / /\n / /_/ / / /_/ / /__/ ,< / /_/ / \n/_____/_/\\____/\\___/_/|_|\\__, /  \n                        /____/   \n"
  },
  {
    "path": "ct/headers/bookstack",
    "content": "    ____              __        __             __  \n   / __ )____  ____  / /_______/ /_____ ______/ /__\n  / __  / __ \\/ __ \\/ //_/ ___/ __/ __ `/ ___/ //_/\n / /_/ / /_/ / /_/ / ,< (__  ) /_/ /_/ / /__/ ,<   \n/_____/\\____/\\____/_/|_/____/\\__/\\__,_/\\___/_/|_|  \n                                                   \n"
  },
  {
    "path": "ct/headers/bunkerweb",
    "content": "    ____              __            _       __     __  \n   / __ )__  ______  / /_____  ____| |     / /__  / /_ \n  / __  / / / / __ \\/ //_/ _ \\/ ___/ | /| / / _ \\/ __ \\\n / /_/ / /_/ / / / / ,< /  __/ /   | |/ |/ /  __/ /_/ /\n/_____/\\__,_/_/ /_/_/|_|\\___/_/    |__/|__/\\___/_.___/ \n                                                       \n"
  },
  {
    "path": "ct/headers/byparr",
    "content": "    ____                             \n   / __ )__  ______  ____ ___________\n  / __  / / / / __ \\/ __ `/ ___/ ___/\n / /_/ / /_/ / /_/ / /_/ / /  / /    \n/_____/\\__, / .___/\\__,_/_/  /_/     \n      /____/_/                       \n"
  },
  {
    "path": "ct/headers/bytestash",
    "content": "    ____        __      _____ __             __  \n   / __ )__  __/ /____ / ___// /_____ ______/ /_ \n  / __  / / / / __/ _ \\\\__ \\/ __/ __ `/ ___/ __ \\\n / /_/ / /_/ / /_/  __/__/ / /_/ /_/ (__  ) / / /\n/_____/\\__, /\\__/\\___/____/\\__/\\__,_/____/_/ /_/ \n      /____/                                     \n"
  },
  {
    "path": "ct/headers/caddy",
    "content": "   ______          __    __     \n  / ____/___ _____/ /___/ /_  __\n / /   / __ `/ __  / __  / / / /\n/ /___/ /_/ / /_/ / /_/ / /_/ / \n\\____/\\__,_/\\__,_/\\__,_/\\__, /  \n                       /____/   \n"
  },
  {
    "path": "ct/headers/calibre-web",
    "content": "              ___ __                                 __  \n  _________ _/ (_) /_  ________       _      _____  / /_ \n / ___/ __ `/ / / __ \\/ ___/ _ \\_____| | /| / / _ \\/ __ \\\n/ /__/ /_/ / / / /_/ / /  /  __/_____/ |/ |/ /  __/ /_/ /\n\\___/\\__,_/_/_/_.___/_/   \\___/      |__/|__/\\___/_.___/ \n                                                         \n"
  },
  {
    "path": "ct/headers/casaos",
    "content": "   ______                 ____  _____\n  / ____/___ __________ _/ __ \\/ ___/\n / /   / __ `/ ___/ __ `/ / / /\\__ \\ \n/ /___/ /_/ (__  ) /_/ / /_/ /___/ / \n\\____/\\__,_/____/\\__,_/\\____//____/  \n                                     \n"
  },
  {
    "path": "ct/headers/changedetection",
    "content": "   ________                              ____       __            __  _           \n  / ____/ /_  ____ _____  ____ ____     / __ \\___  / /____  _____/ /_(_)___  ____ \n / /   / __ \\/ __ `/ __ \\/ __ `/ _ \\   / / / / _ \\/ __/ _ \\/ ___/ __/ / __ \\/ __ \\\n/ /___/ / / / /_/ / / / / /_/ /  __/  / /_/ /  __/ /_/  __/ /__/ /_/ / /_/ / / / /\n\\____/_/ /_/\\__,_/_/ /_/\\__, /\\___/  /_____/\\___/\\__/\\___/\\___/\\__/_/\\____/_/ /_/ \n                       /____/                                                     \n"
  },
  {
    "path": "ct/headers/channels",
    "content": "   ________                           __    \n  / ____/ /_  ____ _____  ____  ___  / /____\n / /   / __ \\/ __ `/ __ \\/ __ \\/ _ \\/ / ___/\n/ /___/ / / / /_/ / / / / / / /  __/ (__  ) \n\\____/_/ /_/\\__,_/_/ /_/_/ /_/\\___/_/____/  \n                                            \n"
  },
  {
    "path": "ct/headers/checkmate",
    "content": "   ________              __                   __     \n  / ____/ /_  ___  _____/ /______ ___  ____ _/ /____ \n / /   / __ \\/ _ \\/ ___/ //_/ __ `__ \\/ __ `/ __/ _ \\\n/ /___/ / / /  __/ /__/ ,< / / / / / / /_/ / /_/  __/\n\\____/_/ /_/\\___/\\___/_/|_/_/ /_/ /_/\\__,_/\\__/\\___/ \n                                                     \n"
  },
  {
    "path": "ct/headers/checkmk",
    "content": "        __              __             __  \n  _____/ /_  ___  _____/ /______ ___  / /__\n / ___/ __ \\/ _ \\/ ___/ //_/ __ `__ \\/ //_/\n/ /__/ / / /  __/ /__/ ,< / / / / / / ,<   \n\\___/_/ /_/\\___/\\___/_/|_/_/ /_/ /_/_/|_|  \n                                           \n"
  },
  {
    "path": "ct/headers/cleanuparr",
    "content": "   ________                                            \n  / ____/ /__  ____ _____  __  ______  ____ ___________\n / /   / / _ \\/ __ `/ __ \\/ / / / __ \\/ __ `/ ___/ ___/\n/ /___/ /  __/ /_/ / / / / /_/ / /_/ / /_/ / /  / /    \n\\____/_/\\___/\\__,_/_/ /_/\\__,_/ .___/\\__,_/_/  /_/     \n                             /_/                       \n"
  },
  {
    "path": "ct/headers/cloudflare-ddns",
    "content": "   ________                ________                      ____  ____  _   _______\n  / ____/ /___  __  ______/ / __/ /___ _________        / __ \\/ __ \\/ | / / ___/\n / /   / / __ \\/ / / / __  / /_/ / __ `/ ___/ _ \\______/ / / / / / /  |/ /\\__ \\ \n/ /___/ / /_/ / /_/ / /_/ / __/ / /_/ / /  /  __/_____/ /_/ / /_/ / /|  /___/ / \n\\____/_/\\____/\\__,_/\\__,_/_/ /_/\\__,_/_/   \\___/     /_____/_____/_/ |_//____/  \n                                                                                \n"
  },
  {
    "path": "ct/headers/cloudflared",
    "content": "   ________                ________                    __\n  / ____/ /___  __  ______/ / __/ /___ _________  ____/ /\n / /   / / __ \\/ / / / __  / /_/ / __ `/ ___/ _ \\/ __  / \n/ /___/ / /_/ / /_/ / /_/ / __/ / /_/ / /  /  __/ /_/ /  \n\\____/_/\\____/\\__,_/\\__,_/_/ /_/\\__,_/_/   \\___/\\__,_/   \n                                                         \n"
  },
  {
    "path": "ct/headers/cloudreve",
    "content": "   ________                __                  \n  / ____/ /___  __  ______/ /_______ _   _____ \n / /   / / __ \\/ / / / __  / ___/ _ \\ | / / _ \\\n/ /___/ / /_/ / /_/ / /_/ / /  /  __/ |/ /  __/\n\\____/_/\\____/\\__,_/\\__,_/_/   \\___/|___/\\___/ \n                                               \n"
  },
  {
    "path": "ct/headers/cockpit",
    "content": "   ______           __         _ __ \n  / ____/___  _____/ /______  (_) /_\n / /   / __ \\/ ___/ //_/ __ \\/ / __/\n/ /___/ /_/ / /__/ ,< / /_/ / / /_  \n\\____/\\____/\\___/_/|_/ .___/_/\\__/  \n                    /_/             \n"
  },
  {
    "path": "ct/headers/comfyui",
    "content": "   ______                ____      __  ______\n  / ____/___  ____ ___  / __/_  __/ / / /  _/\n / /   / __ \\/ __ `__ \\/ /_/ / / / / / // /  \n/ /___/ /_/ / / / / / / __/ /_/ / /_/ // /   \n\\____/\\____/_/ /_/ /_/_/  \\__, /\\____/___/   \n                         /____/              \n"
  },
  {
    "path": "ct/headers/commafeed",
    "content": "   ______                                ______              __\n  / ____/___  ____ ___  ____ ___  ____ _/ ____/__  ___  ____/ /\n / /   / __ \\/ __ `__ \\/ __ `__ \\/ __ `/ /_  / _ \\/ _ \\/ __  / \n/ /___/ /_/ / / / / / / / / / / / /_/ / __/ /  __/  __/ /_/ /  \n\\____/\\____/_/ /_/ /_/_/ /_/ /_/\\__,_/_/    \\___/\\___/\\__,_/   \n                                                               \n"
  },
  {
    "path": "ct/headers/configarr",
    "content": "   ______            _____                      \n  / ____/___  ____  / __(_)___ _____ ___________\n / /   / __ \\/ __ \\/ /_/ / __ `/ __ `/ ___/ ___/\n/ /___/ /_/ / / / / __/ / /_/ / /_/ / /  / /    \n\\____/\\____/_/ /_/_/ /_/\\__, /\\__,_/_/  /_/     \n                       /____/                   \n"
  },
  {
    "path": "ct/headers/convertx",
    "content": "   ______                           __ _  __\n  / ____/___  ____ _   _____  _____/ /| |/ /\n / /   / __ \\/ __ \\ | / / _ \\/ ___/ __/   / \n/ /___/ /_/ / / / / |/ /  __/ /  / /_/   |  \n\\____/\\____/_/ /_/|___/\\___/_/   \\__/_/|_|  \n                                            \n"
  },
  {
    "path": "ct/headers/coolify",
    "content": "   ______            ___ ____     \n  / ____/___  ____  / (_) __/_  __\n / /   / __ \\/ __ \\/ / / /_/ / / /\n/ /___/ /_/ / /_/ / / / __/ /_/ / \n\\____/\\____/\\____/_/_/_/  \\__, /  \n                         /____/   \n"
  },
  {
    "path": "ct/headers/coredns",
    "content": "   ______                ____  _   _______\n  / ____/___  ________  / __ \\/ | / / ___/\n / /   / __ \\/ ___/ _ \\/ / / /  |/ /\\__ \\ \n/ /___/ /_/ / /  /  __/ /_/ / /|  /___/ / \n\\____/\\____/_/   \\___/_____/_/ |_//____/  \n                                          \n"
  },
  {
    "path": "ct/headers/cosmos",
    "content": "   ______                               \n  / ____/___  _________ ___  ____  _____\n / /   / __ \\/ ___/ __ `__ \\/ __ \\/ ___/\n/ /___/ /_/ (__  ) / / / / / /_/ (__  ) \n\\____/\\____/____/_/ /_/ /_/\\____/____/  \n                                        \n"
  },
  {
    "path": "ct/headers/crafty-controller",
    "content": "   ______           ______              ______            __             ____         \n  / ____/________ _/ __/ /___  __      / ____/___  ____  / /__________  / / /__  _____\n / /   / ___/ __ `/ /_/ __/ / / /_____/ /   / __ \\/ __ \\/ __/ ___/ __ \\/ / / _ \\/ ___/\n/ /___/ /  / /_/ / __/ /_/ /_/ /_____/ /___/ /_/ / / / / /_/ /  / /_/ / / /  __/ /    \n\\____/_/   \\__,_/_/  \\__/\\__, /      \\____/\\____/_/ /_/\\__/_/   \\____/_/_/\\___/_/     \n                        /____/                                                        \n"
  },
  {
    "path": "ct/headers/cronicle",
    "content": "   ______                 _      __   \n  / ____/________  ____  (_)____/ /__ \n / /   / ___/ __ \\/ __ \\/ / ___/ / _ \\\n/ /___/ /  / /_/ / / / / / /__/ /  __/\n\\____/_/   \\____/_/ /_/_/\\___/_/\\___/ \n                                      \n"
  },
  {
    "path": "ct/headers/cross-seed",
    "content": "                                                      __\n  ______________  __________      ________  ___  ____/ /\n / ___/ ___/ __ \\/ ___/ ___/_____/ ___/ _ \\/ _ \\/ __  / \n/ /__/ /  / /_/ (__  |__  )_____(__  )  __/  __/ /_/ /  \n\\___/_/   \\____/____/____/     /____/\\___/\\___/\\__,_/   \n                                                        \n"
  },
  {
    "path": "ct/headers/cryptpad",
    "content": "   ______                 __  ____            __\n  / ____/______  ______  / /_/ __ \\____ _____/ /\n / /   / ___/ / / / __ \\/ __/ /_/ / __ `/ __  / \n/ /___/ /  / /_/ / /_/ / /_/ ____/ /_/ / /_/ /  \n\\____/_/   \\__, / .___/\\__/_/    \\__,_/\\__,_/   \n          /____/_/                              \n"
  },
  {
    "path": "ct/headers/dagu",
    "content": "    ____                   \n   / __ \\____ _____ ___  __\n  / / / / __ `/ __ `/ / / /\n / /_/ / /_/ / /_/ / /_/ / \n/_____/\\__,_/\\__, /\\__,_/  \n            /____/         \n"
  },
  {
    "path": "ct/headers/dashy",
    "content": "    ____             __         \n   / __ \\____ ______/ /_  __  __\n  / / / / __ `/ ___/ __ \\/ / / /\n / /_/ / /_/ (__  ) / / / /_/ / \n/_____/\\__,_/____/_/ /_/\\__, /  \n                       /____/   \n"
  },
  {
    "path": "ct/headers/databasus",
    "content": "    ____        __        __                         \n   / __ \\____ _/ /_____ _/ /_  ____ ________  _______\n  / / / / __ `/ __/ __ `/ __ \\/ __ `/ ___/ / / / ___/\n / /_/ / /_/ / /_/ /_/ / /_/ / /_/ (__  ) /_/ (__  ) \n/_____/\\__,_/\\__/\\__,_/_.___/\\__,_/____/\\__,_/____/  \n                                                     \n"
  },
  {
    "path": "ct/headers/dawarich",
    "content": "    ____                            _      __  \n   / __ \\____ __      ______ ______(_)____/ /_ \n  / / / / __ `/ | /| / / __ `/ ___/ / ___/ __ \\\n / /_/ / /_/ /| |/ |/ / /_/ / /  / / /__/ / / /\n/_____/\\__,_/ |__/|__/\\__,_/_/  /_/\\___/_/ /_/ \n                                               \n"
  },
  {
    "path": "ct/headers/ddclient",
    "content": "       __    __     ___            __ \n  ____/ /___/ /____/ (_)__  ____  / /_\n / __  / __  / ___/ / / _ \\/ __ \\/ __/\n/ /_/ / /_/ / /__/ / /  __/ / / / /_  \n\\__,_/\\__,_/\\___/_/_/\\___/_/ /_/\\__/  \n                                      \n"
  },
  {
    "path": "ct/headers/debian",
    "content": "    ____       __    _           \n   / __ \\___  / /_  (_)___ _____ \n  / / / / _ \\/ __ \\/ / __ `/ __ \\\n / /_/ /  __/ /_/ / / /_/ / / / /\n/_____/\\___/_.___/_/\\__,_/_/ /_/ \n                                 \n"
  },
  {
    "path": "ct/headers/deconz",
    "content": "       __     __________  _   _______\n  ____/ /__  / ____/ __ \\/ | / /__  /\n / __  / _ \\/ /   / / / /  |/ /  / / \n/ /_/ /  __/ /___/ /_/ / /|  /  / /__\n\\__,_/\\___/\\____/\\____/_/ |_/  /____/\n                                     \n"
  },
  {
    "path": "ct/headers/deluge",
    "content": "    ____       __               \n   / __ \\___  / /_  ______ ____ \n  / / / / _ \\/ / / / / __ `/ _ \\\n / /_/ /  __/ / /_/ / /_/ /  __/\n/_____/\\___/_/\\__,_/\\__, /\\___/ \n                   /____/       \n"
  },
  {
    "path": "ct/headers/discopanel",
    "content": "    ____  _                 ____                   __\n   / __ \\(_)_____________  / __ \\____ _____  ___  / /\n  / / / / / ___/ ___/ __ \\/ /_/ / __ `/ __ \\/ _ \\/ / \n / /_/ / (__  ) /__/ /_/ / ____/ /_/ / / / /  __/ /  \n/_____/_/____/\\___/\\____/_/    \\__,_/_/ /_/\\___/_/   \n                                                     \n"
  },
  {
    "path": "ct/headers/dispatcharr",
    "content": "    ____  _                  __       __                   \n   / __ \\(_)________  ____ _/ /______/ /_  ____ ___________\n  / / / / / ___/ __ \\/ __ `/ __/ ___/ __ \\/ __ `/ ___/ ___/\n / /_/ / (__  ) /_/ / /_/ / /_/ /__/ / / / /_/ / /  / /    \n/_____/_/____/ .___/\\__,_/\\__/\\___/_/ /_/\\__,_/_/  /_/     \n            /_/                                            \n"
  },
  {
    "path": "ct/headers/docker",
    "content": "    ____             __            \n   / __ \\____  _____/ /_____  _____\n  / / / / __ \\/ ___/ //_/ _ \\/ ___/\n / /_/ / /_/ / /__/ ,< /  __/ /    \n/_____/\\____/\\___/_/|_|\\___/_/     \n                                   \n"
  },
  {
    "path": "ct/headers/dockge",
    "content": "    ____             __            \n   / __ \\____  _____/ /______ ____ \n  / / / / __ \\/ ___/ //_/ __ `/ _ \\\n / /_/ / /_/ / /__/ ,< / /_/ /  __/\n/_____/\\____/\\___/_/|_|\\__, /\\___/ \n                      /____/       \n"
  },
  {
    "path": "ct/headers/docmost",
    "content": "    ____                                  __ \n   / __ \\____  _________ ___  ____  _____/ /_\n  / / / / __ \\/ ___/ __ `__ \\/ __ \\/ ___/ __/\n / /_/ / /_/ / /__/ / / / / / /_/ (__  ) /_  \n/_____/\\____/\\___/_/ /_/ /_/\\____/____/\\__/  \n                                             \n"
  },
  {
    "path": "ct/headers/dokploy",
    "content": "    ____        __         __           \n   / __ \\____  / /______  / /___  __  __\n  / / / / __ \\/ //_/ __ \\/ / __ \\/ / / /\n / /_/ / /_/ / ,< / /_/ / / /_/ / /_/ / \n/_____/\\____/_/|_/ .___/_/\\____/\\__, /  \n                /_/            /____/   \n"
  },
  {
    "path": "ct/headers/dolibarr",
    "content": "    ____        ___ __                   \n   / __ \\____  / (_) /_  ____ ___________\n  / / / / __ \\/ / / __ \\/ __ `/ ___/ ___/\n / /_/ / /_/ / / / /_/ / /_/ / /  / /    \n/_____/\\____/_/_/_.___/\\__,_/_/  /_/     \n                                         \n"
  },
  {
    "path": "ct/headers/domain-locker",
    "content": "    ____                        _             __               __            \n   / __ \\____  ____ ___  ____ _(_)___        / /   ____  _____/ /_____  _____\n  / / / / __ \\/ __ `__ \\/ __ `/ / __ \\______/ /   / __ \\/ ___/ //_/ _ \\/ ___/\n / /_/ / /_/ / / / / / / /_/ / / / / /_____/ /___/ /_/ / /__/ ,< /  __/ /    \n/_____/\\____/_/ /_/ /_/\\__,_/_/_/ /_/     /_____/\\____/\\___/_/|_|\\___/_/     \n                                                                             \n"
  },
  {
    "path": "ct/headers/domain-monitor",
    "content": "    ____                        _             __  ___            _ __            \n   / __ \\____  ____ ___  ____ _(_)___        /  |/  /___  ____  (_) /_____  _____\n  / / / / __ \\/ __ `__ \\/ __ `/ / __ \\______/ /|_/ / __ \\/ __ \\/ / __/ __ \\/ ___/\n / /_/ / /_/ / / / / / / /_/ / / / / /_____/ /  / / /_/ / / / / / /_/ /_/ / /    \n/_____/\\____/_/ /_/ /_/\\__,_/_/_/ /_/     /_/  /_/\\____/_/ /_/_/\\__/\\____/_/     \n                                                                                 \n"
  },
  {
    "path": "ct/headers/donetick",
    "content": "    ____                   __  _      __  \n   / __ \\____  ____  ___  / /_(_)____/ /__\n  / / / / __ \\/ __ \\/ _ \\/ __/ / ___/ //_/\n / /_/ / /_/ / / / /  __/ /_/ / /__/ ,<   \n/_____/\\____/_/ /_/\\___/\\__/_/\\___/_/|_|  \n                                          \n"
  },
  {
    "path": "ct/headers/dotnetaspwebapi",
    "content": "    ____        __             __     ___   _____ ____     _       __     __       ___    ____  ____\n   / __ \\____  / /_____  ___  / /_   /   | / ___// __ \\   | |     / /__  / /_     /   |  / __ \\/  _/\n  / / / / __ \\/ __/ __ \\/ _ \\/ __/  / /| | \\__ \\/ /_/ /   | | /| / / _ \\/ __ \\   / /| | / /_/ // /  \n / /_/ / /_/ / /_/ / / /  __/ /_   / ___ |___/ / ____/    | |/ |/ /  __/ /_/ /  / ___ |/ ____// /   \n/_____/\\____/\\__/_/ /_/\\___/\\__/  /_/  |_/____/_/         |__/|__/\\___/_.___/  /_/  |_/_/   /___/   \n                                                                                                    \n"
  },
  {
    "path": "ct/headers/drawdb",
    "content": "    ____                      ____  ____ \n   / __ \\_________ __      __/ __ \\/ __ )\n  / / / / ___/ __ `/ | /| / / / / / __  |\n / /_/ / /  / /_/ /| |/ |/ / /_/ / /_/ / \n/_____/_/   \\__,_/ |__/|__/_____/_____/  \n                                         \n"
  },
  {
    "path": "ct/headers/drawio",
    "content": "    ____                      ________ \n   / __ \\_________ __      __/  _/ __ \\\n  / / / / ___/ __ `/ | /| / // // / / /\n / /_/ / /  / /_/ /| |/ |/ // // /_/ / \n/_____/_/   \\__,_/ |__/|__/___/\\____/  \n                                       \n"
  },
  {
    "path": "ct/headers/duplicati",
    "content": "    ____              ___            __  _ \n   / __ \\__  ______  / (_)________ _/ /_(_)\n  / / / / / / / __ \\/ / / ___/ __ `/ __/ / \n / /_/ / /_/ / /_/ / / / /__/ /_/ / /_/ /  \n/_____/\\__,_/ .___/_/_/\\___/\\__,_/\\__/_/   \n           /_/                             \n"
  },
  {
    "path": "ct/headers/ebusd",
    "content": "        __                   __\n  ___  / /_  __  ___________/ /\n / _ \\/ __ \\/ / / / ___/ __  / \n/  __/ /_/ / /_/ (__  ) /_/ /  \n\\___/_.___/\\__,_/____/\\__,_/   \n                               \n"
  },
  {
    "path": "ct/headers/elementsynapse",
    "content": "    ________                          __     _____                                 \n   / ____/ /__  ____ ___  ___  ____  / /_   / ___/__  ______  ____ _____  ________ \n  / __/ / / _ \\/ __ `__ \\/ _ \\/ __ \\/ __/   \\__ \\/ / / / __ \\/ __ `/ __ \\/ ___/ _ \\\n / /___/ /  __/ / / / / /  __/ / / / /_    ___/ / /_/ / / / / /_/ / /_/ (__  )  __/\n/_____/_/\\___/_/ /_/ /_/\\___/_/ /_/\\__/   /____/\\__, /_/ /_/\\__,_/ .___/____/\\___/ \n                                               /____/           /_/                \n"
  },
  {
    "path": "ct/headers/emby",
    "content": "    ______          __         \n   / ____/___ ___  / /_  __  __\n  / __/ / __ `__ \\/ __ \\/ / / /\n / /___/ / / / / / /_/ / /_/ / \n/_____/_/ /_/ /_/_.___/\\__, /  \n                      /____/   \n"
  },
  {
    "path": "ct/headers/emqx",
    "content": "    ________  _______   _  __\n   / ____/  |/  / __ \\ | |/ /\n  / __/ / /|_/ / / / / |   / \n / /___/ /  / / /_/ / /   |  \n/_____/_/  /_/\\___\\_\\/_/|_|  \n                             \n"
  },
  {
    "path": "ct/headers/endurain",
    "content": "    ______          __                 _     \n   / ____/___  ____/ /_  ___________ _(_)___ \n  / __/ / __ \\/ __  / / / / ___/ __ `/ / __ \\\n / /___/ / / / /_/ / /_/ / /  / /_/ / / / / /\n/_____/_/ /_/\\__,_/\\__,_/_/   \\__,_/_/_/ /_/ \n                                             \n"
  },
  {
    "path": "ct/headers/erpnext",
    "content": "    __________  ____  _   __          __ \n   / ____/ __ \\/ __ \\/ | / /__  _  __/ /_\n  / __/ / /_/ / /_/ /  |/ / _ \\| |/_/ __/\n / /___/ _, _/ ____/ /|  /  __/>  </ /_  \n/_____/_/ |_/_/   /_/ |_/\\___/_/|_|\\__/  \n                                         \n"
  },
  {
    "path": "ct/headers/ersatztv",
    "content": "    ______                __      _______    __\n   / ____/_____________ _/ /_____/_  __/ |  / /\n  / __/ / ___/ ___/ __ `/ __/_  / / /  | | / / \n / /___/ /  (__  ) /_/ / /_  / /_/ /   | |/ /  \n/_____/_/  /____/\\__,_/\\__/ /___/_/    |___/   \n                                               \n"
  },
  {
    "path": "ct/headers/esphome",
    "content": "    ___________ ____  __  __                   \n   / ____/ ___// __ \\/ / / /___  ____ ___  ___ \n  / __/  \\__ \\/ /_/ / /_/ / __ \\/ __ `__ \\/ _ \\\n / /___ ___/ / ____/ __  / /_/ / / / / / /  __/\n/_____//____/_/   /_/ /_/\\____/_/ /_/ /_/\\___/ \n                                               \n"
  },
  {
    "path": "ct/headers/evcc",
    "content": "                      \n  ___ _   ____________\n / _ \\ | / / ___/ ___/\n/  __/ |/ / /__/ /__  \n\\___/|___/\\___/\\___/  \n                      \n"
  },
  {
    "path": "ct/headers/excalidraw",
    "content": "    ______                ___     __                   \n   / ____/  ___________ _/ (_)___/ /________ __      __\n  / __/ | |/_/ ___/ __ `/ / / __  / ___/ __ `/ | /| / /\n / /____>  </ /__/ /_/ / / / /_/ / /  / /_/ /| |/ |/ / \n/_____/_/|_|\\___/\\__,_/_/_/\\__,_/_/   \\__,_/ |__/|__/  \n                                                       \n"
  },
  {
    "path": "ct/headers/fhem",
    "content": "    ________  __________  ___\n   / ____/ / / / ____/  |/  /\n  / /_  / /_/ / __/ / /|_/ / \n / __/ / __  / /___/ /  / /  \n/_/   /_/ /_/_____/_/  /_/   \n                             \n"
  },
  {
    "path": "ct/headers/fileflows",
    "content": "    _______ __     ________                  \n   / ____(_) /__  / ____/ /___ _      _______\n  / /_  / / / _ \\/ /_  / / __ \\ | /| / / ___/\n / __/ / / /  __/ __/ / / /_/ / |/ |/ (__  ) \n/_/   /_/_/\\___/_/   /_/\\____/|__/|__/____/  \n                                             \n"
  },
  {
    "path": "ct/headers/firefly",
    "content": "    _______           ______     \n   / ____(_)_______  / __/ /_  __\n  / /_  / / ___/ _ \\/ /_/ / / / /\n / __/ / / /  /  __/ __/ / /_/ / \n/_/   /_/_/   \\___/_/ /_/\\__, /  \n                        /____/   \n"
  },
  {
    "path": "ct/headers/fireshare",
    "content": "    _______                __                  \n   / ____(_)_______  _____/ /_  ____ _________ \n  / /_  / / ___/ _ \\/ ___/ __ \\/ __ `/ ___/ _ \\\n / __/ / / /  /  __(__  ) / / / /_/ / /  /  __/\n/_/   /_/_/   \\___/____/_/ /_/\\__,_/_/   \\___/ \n                                               \n"
  },
  {
    "path": "ct/headers/fladder",
    "content": "    ________          __    __         \n   / ____/ /___ _____/ /___/ /__  _____\n  / /_  / / __ `/ __  / __  / _ \\/ ___/\n / __/ / / /_/ / /_/ / /_/ /  __/ /    \n/_/   /_/\\__,_/\\__,_/\\__,_/\\___/_/     \n                                       \n"
  },
  {
    "path": "ct/headers/flaresolverr",
    "content": "    ________               _____       __                    \n   / ____/ /___ _________ / ___/____  / /   _____  __________\n  / /_  / / __ `/ ___/ _ \\\\__ \\/ __ \\/ / | / / _ \\/ ___/ ___/\n / __/ / / /_/ / /  /  __/__/ / /_/ / /| |/ /  __/ /  / /    \n/_/   /_/\\__,_/_/   \\___/____/\\____/_/ |___/\\___/_/  /_/     \n                                                             \n"
  },
  {
    "path": "ct/headers/flatnotes",
    "content": "    ________      __              __           \n   / ____/ /___ _/ /_____  ____  / /____  _____\n  / /_  / / __ `/ __/ __ \\/ __ \\/ __/ _ \\/ ___/\n / __/ / / /_/ / /_/ / / / /_/ / /_/  __(__  ) \n/_/   /_/\\__,_/\\__/_/ /_/\\____/\\__/\\___/____/  \n                                               \n"
  },
  {
    "path": "ct/headers/flowiseai",
    "content": "    ________              _           ___    ____\n   / ____/ /___ _      __(_)_______  /   |  /  _/\n  / /_  / / __ \\ | /| / / / ___/ _ \\/ /| |  / /  \n / __/ / / /_/ / |/ |/ / (__  )  __/ ___ |_/ /   \n/_/   /_/\\____/|__/|__/_/____/\\___/_/  |_/___/   \n                                                 \n"
  },
  {
    "path": "ct/headers/fluid-calendar",
    "content": "    ______      _     __                 __               __          \n   / __/ /_  __(_)___/ /     _________ _/ /__  ____  ____/ /___ ______\n  / /_/ / / / / / __  /_____/ ___/ __ `/ / _ \\/ __ \\/ __  / __ `/ ___/\n / __/ / /_/ / / /_/ /_____/ /__/ /_/ / /  __/ / / / /_/ / /_/ / /    \n/_/ /_/\\__,_/_/\\__,_/      \\___/\\__,_/_/\\___/_/ /_/\\__,_/\\__,_/_/     \n                                                                      \n"
  },
  {
    "path": "ct/headers/foldergram",
    "content": "    ______      __    __                                   \n   / ____/___  / /___/ /__  _________ __________ _____ ___ \n  / /_  / __ \\/ / __  / _ \\/ ___/ __ `/ ___/ __ `/ __ `__ \\\n / __/ / /_/ / / /_/ /  __/ /  / /_/ / /  / /_/ / / / / / /\n/_/    \\____/_/\\__,_/\\___/_/   \\__, /_/   \\__,_/_/ /_/ /_/ \n                              /____/                       \n"
  },
  {
    "path": "ct/headers/forgejo",
    "content": "    ______                        _     \n   / ____/___  _________ ____    (_)___ \n  / /_  / __ \\/ ___/ __ `/ _ \\  / / __ \\\n / __/ / /_/ / /  / /_/ /  __/ / / /_/ /\n/_/    \\____/_/   \\__, /\\___/_/ /\\____/ \n                 /____/    /___/        \n"
  },
  {
    "path": "ct/headers/freepbx",
    "content": "    ______               ____  ____ _  __\n   / ____/_______  ___  / __ \\/ __ ) |/ /\n  / /_  / ___/ _ \\/ _ \\/ /_/ / __  |   / \n / __/ / /  /  __/  __/ ____/ /_/ /   |  \n/_/   /_/   \\___/\\___/_/   /_____/_/|_|  \n                                         \n"
  },
  {
    "path": "ct/headers/freshrss",
    "content": "    ______               __    ____  __________\n   / ____/_______  _____/ /_  / __ \\/ ___/ ___/\n  / /_  / ___/ _ \\/ ___/ __ \\/ /_/ /\\__ \\\\__ \\ \n / __/ / /  /  __(__  ) / / / _, _/___/ /__/ / \n/_/   /_/   \\___/____/_/ /_/_/ |_|/____/____/  \n                                               \n"
  },
  {
    "path": "ct/headers/frigate",
    "content": "    ______     _             __     \n   / ____/____(_)___ _____ _/ /____ \n  / /_  / ___/ / __ `/ __ `/ __/ _ \\\n / __/ / /  / / /_/ / /_/ / /_/  __/\n/_/   /_/  /_/\\__, /\\__,_/\\__/\\___/ \n             /____/                 \n"
  },
  {
    "path": "ct/headers/fumadocs",
    "content": "    ______                          __               \n   / ____/_  ______ ___  ____ _____/ /___  __________\n  / /_  / / / / __ `__ \\/ __ `/ __  / __ \\/ ___/ ___/\n / __/ / /_/ / / / / / / /_/ / /_/ / /_/ / /__(__  ) \n/_/    \\__,_/_/ /_/ /_/\\__,_/\\__,_/\\____/\\___/____/  \n                                                     \n"
  },
  {
    "path": "ct/headers/garage",
    "content": "   ______                          \n  / ____/___ __________ _____ ____ \n / / __/ __ `/ ___/ __ `/ __ `/ _ \\\n/ /_/ / /_/ / /  / /_/ / /_/ /  __/\n\\____/\\__,_/_/   \\__,_/\\__, /\\___/ \n                      /____/       \n"
  },
  {
    "path": "ct/headers/gatus",
    "content": "                __            \n   ____ _____ _/ /___  _______\n  / __ `/ __ `/ __/ / / / ___/\n / /_/ / /_/ / /_/ /_/ (__  ) \n \\__, /\\__,_/\\__/\\__,_/____/  \n/____/                        \n"
  },
  {
    "path": "ct/headers/geopulse",
    "content": "   ______           ____        __        \n  / ____/__  ____  / __ \\__  __/ /_______ \n / / __/ _ \\/ __ \\/ /_/ / / / / / ___/ _ \\\n/ /_/ /  __/ /_/ / ____/ /_/ / (__  )  __/\n\\____/\\___/\\____/_/    \\__,_/_/____/\\___/ \n                                          \n"
  },
  {
    "path": "ct/headers/ghost",
    "content": "   ________               __ \n  / ____/ /_  ____  _____/ /_\n / / __/ __ \\/ __ \\/ ___/ __/\n/ /_/ / / / / /_/ (__  ) /_  \n\\____/_/ /_/\\____/____/\\__/  \n                             \n"
  },
  {
    "path": "ct/headers/ghostfolio",
    "content": "   ________               __  ____      ___     \n  / ____/ /_  ____  _____/ /_/ __/___  / (_)___ \n / / __/ __ \\/ __ \\/ ___/ __/ /_/ __ \\/ / / __ \\\n/ /_/ / / / / /_/ (__  ) /_/ __/ /_/ / / / /_/ /\n\\____/_/ /_/\\____/____/\\__/_/  \\____/_/_/\\____/ \n                                                \n"
  },
  {
    "path": "ct/headers/gitea",
    "content": "   _______ __            \n  / ____(_) /____  ____ _\n / / __/ / __/ _ \\/ __ `/\n/ /_/ / / /_/  __/ /_/ / \n\\____/_/\\__/\\___/\\__,_/  \n                         \n"
  },
  {
    "path": "ct/headers/gitea-mirror",
    "content": "          _ __                             _                     \n   ____ _(_) /____  ____ _      ____ ___  (_)_____________  _____\n  / __ `/ / __/ _ \\/ __ `/_____/ __ `__ \\/ / ___/ ___/ __ \\/ ___/\n / /_/ / / /_/  __/ /_/ /_____/ / / / / / / /  / /  / /_/ / /    \n \\__, /_/\\__/\\___/\\__,_/     /_/ /_/ /_/_/_/  /_/   \\____/_/     \n/____/                                                           \n"
  },
  {
    "path": "ct/headers/github-runner",
    "content": "   _______ __  __  __      __          ____                             \n  / ____(_) /_/ / / /_  __/ /_        / __ \\__  ______  ____  ___  _____\n / / __/ / __/ /_/ / / / / __ \\______/ /_/ / / / / __ \\/ __ \\/ _ \\/ ___/\n/ /_/ / / /_/ __  / /_/ / /_/ /_____/ _, _/ /_/ / / / / / / /  __/ /    \n\\____/_/\\__/_/ /_/\\__,_/_.___/     /_/ |_|\\__,_/_/ /_/_/ /_/\\___/_/     \n                                                                        \n"
  },
  {
    "path": "ct/headers/glance",
    "content": "   ________                    \n  / ____/ /___ _____  ________ \n / / __/ / __ `/ __ \\/ ___/ _ \\\n/ /_/ / / /_/ / / / / /__/  __/\n\\____/_/\\__,_/_/ /_/\\___/\\___/ \n                               \n"
  },
  {
    "path": "ct/headers/globaleaks",
    "content": "   ________      __          __               __       \n  / ____/ /___  / /_  ____ _/ /   ___  ____ _/ /_______\n / / __/ / __ \\/ __ \\/ __ `/ /   / _ \\/ __ `/ //_/ ___/\n/ /_/ / / /_/ / /_/ / /_/ / /___/  __/ /_/ / ,< (__  ) \n\\____/_/\\____/_.___/\\__,_/_____/\\___/\\__,_/_/|_/____/  \n                                                       \n"
  },
  {
    "path": "ct/headers/glpi",
    "content": "   ________    ____  ____\n  / ____/ /   / __ \\/  _/\n / / __/ /   / /_/ // /  \n/ /_/ / /___/ ____// /   \n\\____/_____/_/   /___/   \n                         \n"
  },
  {
    "path": "ct/headers/gluetun",
    "content": "   ________           __            \n  / ____/ /_  _____  / /___  ______ \n / / __/ / / / / _ \\/ __/ / / / __ \\\n/ /_/ / / /_/ /  __/ /_/ /_/ / / / /\n\\____/_/\\__,_/\\___/\\__/\\__,_/_/ /_/ \n                                    \n"
  },
  {
    "path": "ct/headers/go2rtc",
    "content": "               ___        __      \n   ____ _____ |__ \\ _____/ /______\n  / __ `/ __ \\__/ // ___/ __/ ___/\n / /_/ / /_/ / __// /  / /_/ /__  \n \\__, /\\____/____/_/   \\__/\\___/  \n/____/                            \n"
  },
  {
    "path": "ct/headers/gogs",
    "content": "   ______                \n  / ____/___  ____ ______\n / / __/ __ \\/ __ `/ ___/\n/ /_/ / /_/ / /_/ (__  ) \n\\____/\\____/\\__, /____/  \n           /____/        \n"
  },
  {
    "path": "ct/headers/gokapi",
    "content": "   ______      __               _ \n  / ____/___  / /______ _____  (_)\n / / __/ __ \\/ //_/ __ `/ __ \\/ / \n/ /_/ / /_/ / ,< / /_/ / /_/ / /  \n\\____/\\____/_/|_|\\__,_/ .___/_/   \n                     /_/          \n"
  },
  {
    "path": "ct/headers/gotify",
    "content": "   ______      __  _ ____     \n  / ____/___  / /_(_) __/_  __\n / / __/ __ \\/ __/ / /_/ / / /\n/ /_/ / /_/ / /_/ / __/ /_/ / \n\\____/\\____/\\__/_/_/  \\__, /  \n                     /____/   \n"
  },
  {
    "path": "ct/headers/grafana",
    "content": "   ______           ____                 \n  / ____/________ _/ __/___ _____  ____ _\n / / __/ ___/ __ `/ /_/ __ `/ __ \\/ __ `/\n/ /_/ / /  / /_/ / __/ /_/ / / / / /_/ / \n\\____/_/   \\__,_/_/  \\__,_/_/ /_/\\__,_/  \n                                         \n"
  },
  {
    "path": "ct/headers/gramps-web",
    "content": "                                                             __  \n   ____ __________ _____ ___  ____  _____     _      _____  / /_ \n  / __ `/ ___/ __ `/ __ `__ \\/ __ \\/ ___/____| | /| / / _ \\/ __ \\\n / /_/ / /  / /_/ / / / / / / /_/ (__  )_____/ |/ |/ /  __/ /_/ /\n \\__, /_/   \\__,_/_/ /_/ /_/ .___/____/      |__/|__/\\___/_.___/ \n/____/                    /_/                                    \n"
  },
  {
    "path": "ct/headers/graylog",
    "content": "   ______                 __           \n  / ____/________ ___  __/ /___  ____ _\n / / __/ ___/ __ `/ / / / / __ \\/ __ `/\n/ /_/ / /  / /_/ / /_/ / / /_/ / /_/ / \n\\____/_/   \\__,_/\\__, /_/\\____/\\__, /  \n                /____/        /____/   \n"
  },
  {
    "path": "ct/headers/grist",
    "content": "   ______     _      __ \n  / ____/____(_)____/ /_\n / / __/ ___/ / ___/ __/\n/ /_/ / /  / (__  ) /_  \n\\____/_/  /_/____/\\__/  \n                        \n"
  },
  {
    "path": "ct/headers/grocy",
    "content": "                               \n   ____ __________  _______  __\n  / __ `/ ___/ __ \\/ ___/ / / /\n / /_/ / /  / /_/ / /__/ /_/ / \n \\__, /_/   \\____/\\___/\\__, /  \n/____/                /____/   \n"
  },
  {
    "path": "ct/headers/guardian",
    "content": "   ______                     ___           \n  / ____/_  ______ __________/ (_)___ _____ \n / / __/ / / / __ `/ ___/ __  / / __ `/ __ \\\n/ /_/ / /_/ / /_/ / /  / /_/ / / /_/ / / / /\n\\____/\\__,_/\\__,_/_/   \\__,_/_/\\__,_/_/ /_/ \n                                            \n"
  },
  {
    "path": "ct/headers/gwn-manager",
    "content": "   _______       ___   __      __  ___                                 \n  / ____/ |     / / | / /     /  |/  /___ _____  ____ _____ ____  _____\n / / __ | | /| / /  |/ /_____/ /|_/ / __ `/ __ \\/ __ `/ __ `/ _ \\/ ___/\n/ /_/ / | |/ |/ / /|  /_____/ /  / / /_/ / / / / /_/ / /_/ /  __/ /    \n\\____/  |__/|__/_/ |_/     /_/  /_/\\__,_/_/ /_/\\__,_/\\__, /\\___/_/     \n                                                    /____/             \n"
  },
  {
    "path": "ct/headers/headscale",
    "content": "    __  __               __                __   \n   / / / /__  ____ _____/ /_____________ _/ /__ \n  / /_/ / _ \\/ __ `/ __  / ___/ ___/ __ `/ / _ \\\n / __  /  __/ /_/ / /_/ (__  ) /__/ /_/ / /  __/\n/_/ /_/\\___/\\__,_/\\__,_/____/\\___/\\__,_/_/\\___/ \n                                                \n"
  },
  {
    "path": "ct/headers/healthchecks",
    "content": "    __               ____  __         __              __       \n   / /_  ___  ____ _/ / /_/ /_  _____/ /_  ___  _____/ /_______\n  / __ \\/ _ \\/ __ `/ / __/ __ \\/ ___/ __ \\/ _ \\/ ___/ //_/ ___/\n / / / /  __/ /_/ / / /_/ / / / /__/ / / /  __/ /__/ ,< (__  ) \n/_/ /_/\\___/\\__,_/_/\\__/_/ /_/\\___/_/ /_/\\___/\\___/_/|_/____/  \n                                                               \n"
  },
  {
    "path": "ct/headers/heimdall-dashboard",
    "content": "    __  __     _               __      ____      ____             __    __                         __\n   / / / /__  (_)___ ___  ____/ /___ _/ / /     / __ \\____ ______/ /_  / /_  ____  ____ __________/ /\n  / /_/ / _ \\/ / __ `__ \\/ __  / __ `/ / /_____/ / / / __ `/ ___/ __ \\/ __ \\/ __ \\/ __ `/ ___/ __  / \n / __  /  __/ / / / / / / /_/ / /_/ / / /_____/ /_/ / /_/ (__  ) / / / /_/ / /_/ / /_/ / /  / /_/ /  \n/_/ /_/\\___/_/_/ /_/ /_/\\__,_/\\__,_/_/_/     /_____/\\__,_/____/_/ /_/_.___/\\____/\\__,_/_/   \\__,_/   \n                                                                                                     \n"
  },
  {
    "path": "ct/headers/hev-socks5-server",
    "content": "    __                                    __        ______                                    \n   / /_  ___ _   __      _________  _____/ /_______/ ____/     ________  ______   _____  _____\n  / __ \\/ _ \\ | / /_____/ ___/ __ \\/ ___/ //_/ ___/___ \\______/ ___/ _ \\/ ___/ | / / _ \\/ ___/\n / / / /  __/ |/ /_____(__  ) /_/ / /__/ ,< (__  )___/ /_____(__  )  __/ /   | |/ /  __/ /    \n/_/ /_/\\___/|___/     /____/\\____/\\___/_/|_/____/_____/     /____/\\___/_/    |___/\\___/_/     \n                                                                                              \n"
  },
  {
    "path": "ct/headers/hivemq",
    "content": "    __  ___            __  _______ \n   / / / (_)   _____  /  |/  / __ \\\n  / /_/ / / | / / _ \\/ /|_/ / / / /\n / __  / /| |/ /  __/ /  / / /_/ / \n/_/ /_/_/ |___/\\___/_/  /_/\\___\\_\\ \n                                   \n"
  },
  {
    "path": "ct/headers/homarr",
    "content": "    __                                   \n   / /_  ____  ____ ___  ____ ___________\n  / __ \\/ __ \\/ __ `__ \\/ __ `/ ___/ ___/\n / / / / /_/ / / / / / / /_/ / /  / /    \n/_/ /_/\\____/_/ /_/ /_/\\__,_/_/  /_/     \n                                         \n"
  },
  {
    "path": "ct/headers/homeassistant",
    "content": "    __  __                        ___              _      __              __ \n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_\n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/\n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_  \n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/  \n                                                                             \n"
  },
  {
    "path": "ct/headers/homebox",
    "content": "    __  __                     ____            \n   / / / /___  ____ ___  ___  / __ )____  _  __\n  / /_/ / __ \\/ __ `__ \\/ _ \\/ __  / __ \\| |/_/\n / __  / /_/ / / / / / /  __/ /_/ / /_/ />  <  \n/_/ /_/\\____/_/ /_/ /_/\\___/_____/\\____/_/|_|  \n                                               \n"
  },
  {
    "path": "ct/headers/homebridge",
    "content": "    __  __                     __         _     __         \n   / / / /___  ____ ___  ___  / /_  _____(_)___/ /___ ____ \n  / /_/ / __ \\/ __ `__ \\/ _ \\/ __ \\/ ___/ / __  / __ `/ _ \\\n / __  / /_/ / / / / / /  __/ /_/ / /  / / /_/ / /_/ /  __/\n/_/ /_/\\____/_/ /_/ /_/\\___/_.___/_/  /_/\\__,_/\\__, /\\___/ \n                                              /____/       \n"
  },
  {
    "path": "ct/headers/homelable",
    "content": "    __  __                     __      __    __   \n   / / / /___  ____ ___  ___  / /___ _/ /_  / /__ \n  / /_/ / __ \\/ __ `__ \\/ _ \\/ / __ `/ __ \\/ / _ \\\n / __  / /_/ / / / / / /  __/ / /_/ / /_/ / /  __/\n/_/ /_/\\____/_/ /_/ /_/\\___/_/\\__,_/_.___/_/\\___/ \n                                                  \n"
  },
  {
    "path": "ct/headers/homepage",
    "content": "    __  __                                          \n   / / / /___  ____ ___  ___  ____  ____ _____ ____ \n  / /_/ / __ \\/ __ `__ \\/ _ \\/ __ \\/ __ `/ __ `/ _ \\\n / __  / /_/ / / / / / /  __/ /_/ / /_/ / /_/ /  __/\n/_/ /_/\\____/_/ /_/ /_/\\___/ .___/\\__,_/\\__, /\\___/ \n                          /_/          /____/       \n"
  },
  {
    "path": "ct/headers/homer",
    "content": "    __  __                         \n   / / / /___  ____ ___  ___  _____\n  / /_/ / __ \\/ __ `__ \\/ _ \\/ ___/\n / __  / /_/ / / / / / /  __/ /    \n/_/ /_/\\____/_/ /_/ /_/\\___/_/     \n                                   \n"
  },
  {
    "path": "ct/headers/hoodik",
    "content": "    __  __                ___ __  \n   / / / /___  ____  ____/ (_) /__\n  / /_/ / __ \\/ __ \\/ __  / / //_/\n / __  / /_/ / /_/ / /_/ / / ,<   \n/_/ /_/\\____/\\____/\\__,_/_/_/|_|  \n                                  \n"
  },
  {
    "path": "ct/headers/hortusfox",
    "content": "    __  __           __             ______          \n   / / / /___  _____/ /___  _______/ ____/___  _  __\n  / /_/ / __ \\/ ___/ __/ / / / ___/ /_  / __ \\| |/_/\n / __  / /_/ / /  / /_/ /_/ (__  ) __/ / /_/ />  <  \n/_/ /_/\\____/_/   \\__/\\__,_/____/_/    \\____/_/|_|  \n                                                    \n"
  },
  {
    "path": "ct/headers/hyperhdr",
    "content": "    __  __                      __  ______  ____ \n   / / / /_  ______  ___  _____/ / / / __ \\/ __ \\\n  / /_/ / / / / __ \\/ _ \\/ ___/ /_/ / / / / /_/ /\n / __  / /_/ / /_/ /  __/ /  / __  / /_/ / _, _/ \n/_/ /_/\\__, / .___/\\___/_/  /_/ /_/_____/_/ |_|  \n      /____/_/                                   \n"
  },
  {
    "path": "ct/headers/hyperion",
    "content": "    __  __                      _           \n   / / / /_  ______  ___  _____(_)___  ____ \n  / /_/ / / / / __ \\/ _ \\/ ___/ / __ \\/ __ \\\n / __  / /_/ / /_/ /  __/ /  / / /_/ / / / /\n/_/ /_/\\__, / .___/\\___/_/  /_/\\____/_/ /_/ \n      /____/_/                              \n"
  },
  {
    "path": "ct/headers/igotify",
    "content": "    _ ______      __  _ ____     \n   (_) ____/___  / /_(_) __/_  __\n  / / / __/ __ \\/ __/ / /_/ / / /\n / / /_/ / /_/ / /_/ / __/ /_/ / \n/_/\\____/\\____/\\__/_/_/  \\__, /  \n                        /____/   \n"
  },
  {
    "path": "ct/headers/immich",
    "content": "    _                     _      __  \n   (_)___ ___  ____ ___  (_)____/ /_ \n  / / __ `__ \\/ __ `__ \\/ / ___/ __ \\\n / / / / / / / / / / / / / /__/ / / /\n/_/_/ /_/ /_/_/ /_/ /_/_/\\___/_/ /_/ \n                                     \n"
  },
  {
    "path": "ct/headers/immichframe",
    "content": "    ____                    _      __    ______                        \n   /  _/___ ___  ____ ___  (_)____/ /_  / ____/________ _____ ___  ___ \n   / // __ `__ \\/ __ `__ \\/ / ___/ __ \\/ /_  / ___/ __ `/ __ `__ \\/ _ \\\n _/ // / / / / / / / / / / / /__/ / / / __/ / /  / /_/ / / / / / /  __/\n/___/_/ /_/ /_/_/ /_/ /_/_/\\___/_/ /_/_/   /_/   \\__,_/_/ /_/ /_/\\___/ \n                                                                       \n"
  },
  {
    "path": "ct/headers/infisical",
    "content": "    ____      _____      _            __\n   /  _/___  / __(_)____(_)________ _/ /\n   / // __ \\/ /_/ / ___/ / ___/ __ `/ / \n _/ // / / / __/ (__  ) / /__/ /_/ / /  \n/___/_/ /_/_/ /_/____/_/\\___/\\__,_/_/   \n                                        \n"
  },
  {
    "path": "ct/headers/influxdb",
    "content": "    ____      ______           ____  ____ \n   /  _/___  / __/ /_  ___  __/ __ \\/ __ )\n   / // __ \\/ /_/ / / / / |/_/ / / / __  |\n _/ // / / / __/ / /_/ />  </ /_/ / /_/ / \n/___/_/ /_/_/ /_/\\__,_/_/|_/_____/_____/  \n                                          \n"
  },
  {
    "path": "ct/headers/inspircd",
    "content": "    ____                 ________  ______    __\n   /  _/___  _________  /  _/ __ \\/ ____/___/ /\n   / // __ \\/ ___/ __ \\ / // /_/ / /   / __  / \n _/ // / / (__  ) /_/ // // _, _/ /___/ /_/ /  \n/___/_/ /_/____/ .___/___/_/ |_|\\____/\\__,_/   \n              /_/                              \n"
  },
  {
    "path": "ct/headers/inventree",
    "content": "    ____                    ______             \n   /  _/___ _   _____  ____/_  __/_______  ___ \n   / // __ \\ | / / _ \\/ __ \\/ / / ___/ _ \\/ _ \\\n _/ // / / / |/ /  __/ / / / / / /  /  __/  __/\n/___/_/ /_/|___/\\___/_/ /_/_/ /_/   \\___/\\___/ \n                                               \n"
  },
  {
    "path": "ct/headers/investbrain",
    "content": "    ____                     __  __               _     \n   /  _/___ _   _____  _____/ /_/ /_  _________ _(_)___ \n   / // __ \\ | / / _ \\/ ___/ __/ __ \\/ ___/ __ `/ / __ \\\n _/ // / / / |/ /  __(__  ) /_/ /_/ / /  / /_/ / / / / /\n/___/_/ /_/|___/\\___/____/\\__/_.___/_/   \\__,_/_/_/ /_/ \n                                                        \n"
  },
  {
    "path": "ct/headers/invoiceninja",
    "content": "    ____                 _           _   ___         _      \n   /  _/___ _   ______  (_)_______  / | / (_)___    (_)___ _\n   / // __ \\ | / / __ \\/ / ___/ _ \\/  |/ / / __ \\  / / __ `/\n _/ // / / / |/ / /_/ / / /__/  __/ /|  / / / / / / / /_/ / \n/___/_/ /_/|___/\\____/_/\\___/\\___/_/ |_/_/_/ /_/_/ /\\__,_/  \n                                              /___/         \n"
  },
  {
    "path": "ct/headers/iobroker",
    "content": "    _       ____             __            \n   (_)___  / __ )_________  / /_____  _____\n  / / __ \\/ __  / ___/ __ \\/ //_/ _ \\/ ___/\n / / /_/ / /_/ / /  / /_/ / ,< /  __/ /    \n/_/\\____/_____/_/   \\____/_/|_|\\___/_/     \n                                           \n"
  },
  {
    "path": "ct/headers/ironclaw",
    "content": "    ____                 ________              \n   /  _/________  ____  / ____/ /___ __      __\n   / // ___/ __ \\/ __ \\/ /   / / __ `/ | /| / /\n _/ // /  / /_/ / / / / /___/ / /_/ /| |/ |/ / \n/___/_/   \\____/_/ /_/\\____/_/\\__,_/ |__/|__/  \n                                               \n"
  },
  {
    "path": "ct/headers/isponsorblocktv",
    "content": "    _ _____                                   ____  __           __  _______    __\n   (_) ___/____  ____  ____  _________  _____/ __ )/ /___  _____/ /_/_  __/ |  / /\n  / /\\__ \\/ __ \\/ __ \\/ __ \\/ ___/ __ \\/ ___/ __  / / __ \\/ ___/ //_// /  | | / / \n / /___/ / /_/ / /_/ / / / (__  ) /_/ / /  / /_/ / / /_/ / /__/ ,<  / /   | |/ /  \n/_//____/ .___/\\____/_/ /_/____/\\____/_/  /_____/_/\\____/\\___/_/|_|/_/    |___/   \n       /_/                                                                        \n"
  },
  {
    "path": "ct/headers/itsm-ng",
    "content": "    _______________ __  ___      _   ________\n   /  _/_  __/ ___//  |/  /     / | / / ____/\n   / /  / /  \\__ \\/ /|_/ /_____/  |/ / / __  \n _/ /  / /  ___/ / /  / /_____/ /|  / /_/ /  \n/___/ /_/  /____/_/  /_/     /_/ |_/\\____/   \n                                             \n"
  },
  {
    "path": "ct/headers/jackett",
    "content": "       __           __        __  __ \n      / /___ ______/ /_____  / /_/ /_\n __  / / __ `/ ___/ //_/ _ \\/ __/ __/\n/ /_/ / /_/ / /__/ ,< /  __/ /_/ /_  \n\\____/\\__,_/\\___/_/|_|\\___/\\__/\\__/  \n                                     \n"
  },
  {
    "path": "ct/headers/jeedom",
    "content": "       __              __              \n      / /__  ___  ____/ /___  ____ ___ \n __  / / _ \\/ _ \\/ __  / __ \\/ __ `__ \\\n/ /_/ /  __/  __/ /_/ / /_/ / / / / / /\n\\____/\\___/\\___/\\__,_/\\____/_/ /_/ /_/ \n                                       \n"
  },
  {
    "path": "ct/headers/jellyfin",
    "content": "       __     ____      _____     \n      / /__  / / /_  __/ __(_)___ \n __  / / _ \\/ / / / / / /_/ / __ \\\n/ /_/ /  __/ / / /_/ / __/ / / / /\n\\____/\\___/_/_/\\__, /_/ /_/_/ /_/ \n              /____/              \n"
  },
  {
    "path": "ct/headers/jellyseerr",
    "content": "       __     ____                              \n      / /__  / / /_  __________  ___  __________\n __  / / _ \\/ / / / / / ___/ _ \\/ _ \\/ ___/ ___/\n/ /_/ /  __/ / / /_/ (__  )  __/  __/ /  / /    \n\\____/\\___/_/_/\\__, /____/\\___/\\___/_/  /_/     \n              /____/                            \n"
  },
  {
    "path": "ct/headers/jenkins",
    "content": "       __           __   _           \n      / /__  ____  / /__(_)___  _____\n __  / / _ \\/ __ \\/ //_/ / __ \\/ ___/\n/ /_/ /  __/ / / / ,< / / / / (__  ) \n\\____/\\___/_/ /_/_/|_/_/_/ /_/____/  \n                                     \n"
  },
  {
    "path": "ct/headers/jitsi-meet",
    "content": "       ___ __       _       __  ___          __ \n      / (_) /______(_)     /  |/  /__  ___  / /_\n __  / / / __/ ___/ /_____/ /|_/ / _ \\/ _ \\/ __/\n/ /_/ / / /_(__  ) /_____/ /  / /  __/  __/ /_  \n\\____/_/\\__/____/_/     /_/  /_/\\___/\\___/\\__/  \n                                                \n"
  },
  {
    "path": "ct/headers/joplin-server",
    "content": "       __            ___            _____                          \n      / /___  ____  / (_)___       / ___/___  ______   _____  _____\n __  / / __ \\/ __ \\/ / / __ \\______\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n/ /_/ / /_/ / /_/ / / / / / /_____/__/ /  __/ /   | |/ /  __/ /    \n\\____/\\____/ .___/_/_/_/ /_/     /____/\\___/_/    |___/\\___/_/     \n          /_/                                                      \n"
  },
  {
    "path": "ct/headers/jotty",
    "content": "       _       __  __       \n      (_)___  / /_/ /___  __\n     / / __ \\/ __/ __/ / / /\n    / / /_/ / /_/ /_/ /_/ / \n __/ /\\____/\\__/\\__/\\__, /  \n/___/              /____/   \n"
  },
  {
    "path": "ct/headers/jupyternotebook",
    "content": "       __                  __            _   __      __       __                __  \n      / /_  ______  __  __/ /____  _____/ | / /___  / /____  / /_  ____  ____  / /__\n __  / / / / / __ \\/ / / / __/ _ \\/ ___/  |/ / __ \\/ __/ _ \\/ __ \\/ __ \\/ __ \\/ //_/\n/ /_/ / /_/ / /_/ / /_/ / /_/  __/ /  / /|  / /_/ / /_/  __/ /_/ / /_/ / /_/ / ,<   \n\\____/\\__,_/ .___/\\__, /\\__/\\___/_/  /_/ |_/\\____/\\__/\\___/_.___/\\____/\\____/_/|_|  \n          /_/    /____/                                                             \n"
  },
  {
    "path": "ct/headers/kapowarr",
    "content": "    __ __                                         \n   / //_/___ _____  ____ _      ______ ___________\n  / ,< / __ `/ __ \\/ __ \\ | /| / / __ `/ ___/ ___/\n / /| / /_/ / /_/ / /_/ / |/ |/ / /_/ / /  / /    \n/_/ |_\\__,_/ .___/\\____/|__/|__/\\__,_/_/  /_/     \n          /_/                                     \n"
  },
  {
    "path": "ct/headers/karakeep",
    "content": "    __                    __                 \n   / /______ __________ _/ /_____  ___  ____ \n  / //_/ __ `/ ___/ __ `/ //_/ _ \\/ _ \\/ __ \\\n / ,< / /_/ / /  / /_/ / ,< /  __/  __/ /_/ /\n/_/|_|\\__,_/_/   \\__,_/_/|_|\\___/\\___/ .___/ \n                                    /_/      \n"
  },
  {
    "path": "ct/headers/kasm",
    "content": "    __ __                   \n   / //_/___ __________ ___ \n  / ,< / __ `/ ___/ __ `__ \\\n / /| / /_/ (__  ) / / / / /\n/_/ |_\\__,_/____/_/ /_/ /_/ \n                            \n"
  },
  {
    "path": "ct/headers/kavita",
    "content": "    __ __            _ __       \n   / //_/___ __   __(_) /_____ _\n  / ,< / __ `/ | / / / __/ __ `/\n / /| / /_/ /| |/ / / /_/ /_/ / \n/_/ |_\\__,_/ |___/_/\\__/\\__,_/  \n                                \n"
  },
  {
    "path": "ct/headers/keycloak",
    "content": "    __ __                __            __  \n   / //_/__  __  _______/ /___  ____ _/ /__\n  / ,< / _ \\/ / / / ___/ / __ \\/ __ `/ //_/\n / /| /  __/ /_/ / /__/ / /_/ / /_/ / ,<   \n/_/ |_\\___/\\__, /\\___/_/\\____/\\__,_/_/|_|  \n          /____/                           \n"
  },
  {
    "path": "ct/headers/kima-hub",
    "content": "    __ __ _                       __  __      __  \n   / //_/(_)___ ___  ____ _      / / / /_  __/ /_ \n  / ,<  / / __ `__ \\/ __ `/_____/ /_/ / / / / __ \\\n / /| |/ / / / / / / /_/ /_____/ __  / /_/ / /_/ /\n/_/ |_/_/_/ /_/ /_/\\__,_/     /_/ /_/\\__,_/_.___/ \n                                                  \n"
  },
  {
    "path": "ct/headers/kimai",
    "content": "    __ __ _                 _ \n   / //_/(_)___ ___  ____ _(_)\n  / ,<  / / __ `__ \\/ __ `/ / \n / /| |/ / / / / / / /_/ / /  \n/_/ |_/_/_/ /_/ /_/\\__,_/_/   \n                              \n"
  },
  {
    "path": "ct/headers/kitchenowl",
    "content": "    __ __ _ __       __               ____           __\n   / //_/(_) /______/ /_  ___  ____  / __ \\_      __/ /\n  / ,<  / / __/ ___/ __ \\/ _ \\/ __ \\/ / / / | /| / / / \n / /| |/ / /_/ /__/ / / /  __/ / / / /_/ /| |/ |/ / /  \n/_/ |_/_/\\__/\\___/_/ /_/\\___/_/ /_/\\____/ |__/|__/_/   \n                                                       \n"
  },
  {
    "path": "ct/headers/koel",
    "content": "    __ __           __\n   / //_/___  ___  / /\n  / ,< / __ \\/ _ \\/ / \n / /| / /_/ /  __/ /  \n/_/ |_\\____/\\___/_/   \n                      \n"
  },
  {
    "path": "ct/headers/koillection",
    "content": "    __ __      _ ____          __  _           \n   / //_/___  (_) / /__  _____/ /_(_)___  ____ \n  / ,< / __ \\/ / / / _ \\/ ___/ __/ / __ \\/ __ \\\n / /| / /_/ / / / /  __/ /__/ /_/ / /_/ / / / /\n/_/ |_\\____/_/_/_/\\___/\\___/\\__/_/\\____/_/ /_/ \n                                               \n"
  },
  {
    "path": "ct/headers/kometa",
    "content": "    __ __                     __       \n   / //_/___  ____ ___  ___  / /_____ _\n  / ,< / __ \\/ __ `__ \\/ _ \\/ __/ __ `/\n / /| / /_/ / / / / / /  __/ /_/ /_/ / \n/_/ |_\\____/_/ /_/ /_/\\___/\\__/\\__,_/  \n                                       \n"
  },
  {
    "path": "ct/headers/komga",
    "content": "    __ __                           \n   / //_/___  ____ ___  ____ _____ _\n  / ,< / __ \\/ __ `__ \\/ __ `/ __ `/\n / /| / /_/ / / / / / / /_/ / /_/ / \n/_/ |_\\____/_/ /_/ /_/\\__, /\\__,_/  \n                     /____/         \n"
  },
  {
    "path": "ct/headers/komodo",
    "content": "    __ __                          __    \n   / //_/___  ____ ___  ____  ____/ /___ \n  / ,< / __ \\/ __ `__ \\/ __ \\/ __  / __ \\\n / /| / /_/ / / / / / / /_/ / /_/ / /_/ /\n/_/ |_\\____/_/ /_/ /_/\\____/\\__,_/\\____/ \n                                         \n"
  },
  {
    "path": "ct/headers/kubo",
    "content": "    __ __      __        \n   / //_/_  __/ /_  ____ \n  / ,< / / / / __ \\/ __ \\\n / /| / /_/ / /_/ / /_/ /\n/_/ |_\\__,_/_.___/\\____/ \n                         \n"
  },
  {
    "path": "ct/headers/kutt",
    "content": "    __ __      __  __ \n   / //_/_  __/ /_/ /_\n  / ,< / / / / __/ __/\n / /| / /_/ / /_/ /_  \n/_/ |_\\__,_/\\__/\\__/  \n                      \n"
  },
  {
    "path": "ct/headers/languagetool",
    "content": "    __                                           ______            __\n   / /   ____ _____  ____ ___  ______ _____ ____/_  __/___  ____  / /\n  / /   / __ `/ __ \\/ __ `/ / / / __ `/ __ `/ _ \\/ / / __ \\/ __ \\/ / \n / /___/ /_/ / / / / /_/ / /_/ / /_/ / /_/ /  __/ / / /_/ / /_/ / /  \n/_____/\\__,_/_/ /_/\\__, /\\__,_/\\__,_/\\__, /\\___/_/  \\____/\\____/_/   \n                  /____/            /____/                           \n"
  },
  {
    "path": "ct/headers/lazylibrarian",
    "content": "    __                      __    _ __                    _           \n   / /   ____ _____  __  __/ /   (_) /_  _________ ______(_)___ _____ \n  / /   / __ `/_  / / / / / /   / / __ \\/ ___/ __ `/ ___/ / __ `/ __ \\\n / /___/ /_/ / / /_/ /_/ / /___/ / /_/ / /  / /_/ / /  / / /_/ / / / /\n/_____/\\__,_/ /___/\\__, /_____/_/_.___/_/   \\__,_/_/  /_/\\__,_/_/ /_/ \n                  /____/                                              \n"
  },
  {
    "path": "ct/headers/leantime",
    "content": "    __                     __  _              \n   / /   ___  ____ _____  / /_(_)___ ___  ___ \n  / /   / _ \\/ __ `/ __ \\/ __/ / __ `__ \\/ _ \\\n / /___/  __/ /_/ / / / / /_/ / / / / / /  __/\n/_____/\\___/\\__,_/_/ /_/\\__/_/_/ /_/ /_/\\___/ \n                                              \n"
  },
  {
    "path": "ct/headers/librechat",
    "content": "    __    _ __              ________          __ \n   / /   (_) /_  ________  / ____/ /_  ____ _/ /_\n  / /   / / __ \\/ ___/ _ \\/ /   / __ \\/ __ `/ __/\n / /___/ / /_/ / /  /  __/ /___/ / / / /_/ / /_  \n/_____/_/_.___/_/   \\___/\\____/_/ /_/\\__,_/\\__/  \n                                                 \n"
  },
  {
    "path": "ct/headers/librenms",
    "content": "    __    _ __              _   ____  ________\n   / /   (_) /_  ________  / | / /  |/  / ___/\n  / /   / / __ \\/ ___/ _ \\/  |/ / /|_/ /\\__ \\ \n / /___/ / /_/ / /  /  __/ /|  / /  / /___/ / \n/_____/_/_.___/_/   \\___/_/ |_/_/  /_//____/  \n                                              \n"
  },
  {
    "path": "ct/headers/librespeed-rust",
    "content": "    __    _ __                                       __      ____             __ \n   / /   (_) /_  ________  _________  ___  ___  ____/ /     / __ \\__  _______/ /_\n  / /   / / __ \\/ ___/ _ \\/ ___/ __ \\/ _ \\/ _ \\/ __  /_____/ /_/ / / / / ___/ __/\n / /___/ / /_/ / /  /  __(__  ) /_/ /  __/  __/ /_/ /_____/ _, _/ /_/ (__  ) /_  \n/_____/_/_.___/_/   \\___/____/ .___/\\___/\\___/\\__,_/     /_/ |_|\\__,_/____/\\__/  \n                            /_/                                                  \n"
  },
  {
    "path": "ct/headers/libretranslate",
    "content": "    __    _ __            ______                      __      __     \n   / /   (_) /_  ________/_  __/________ _____  _____/ /___ _/ /____ \n  / /   / / __ \\/ ___/ _ \\/ / / ___/ __ `/ __ \\/ ___/ / __ `/ __/ _ \\\n / /___/ / /_/ / /  /  __/ / / /  / /_/ / / / (__  ) / /_/ / /_/  __/\n/_____/_/_.___/_/   \\___/_/ /_/   \\__,_/_/ /_/____/_/\\__,_/\\__/\\___/ \n                                                                     \n"
  },
  {
    "path": "ct/headers/lidarr",
    "content": "    __    _     __               \n   / /   (_)___/ /___ ___________\n  / /   / / __  / __ `/ ___/ ___/\n / /___/ / /_/ / /_/ / /  / /    \n/_____/_/\\__,_/\\__,_/_/  /_/     \n                                 \n"
  },
  {
    "path": "ct/headers/limesurvey",
    "content": "    __    _               _____                            \n   / /   (_)___ ___  ___ / ___/__  ________   _____  __  __\n  / /   / / __ `__ \\/ _ \\\\__ \\/ / / / ___/ | / / _ \\/ / / /\n / /___/ / / / / / /  __/__/ / /_/ / /   | |/ /  __/ /_/ / \n/_____/_/_/ /_/ /_/\\___/____/\\__,_/_/    |___/\\___/\\__, /  \n                                                  /____/   \n"
  },
  {
    "path": "ct/headers/linkding",
    "content": "    ___       __       ___            \n   / (_)___  / /______/ (_)___  ____ _\n  / / / __ \\/ //_/ __  / / __ \\/ __ `/\n / / / / / / ,< / /_/ / / / / / /_/ / \n/_/_/_/ /_/_/|_|\\__,_/_/_/ /_/\\__, /  \n                             /____/   \n"
  },
  {
    "path": "ct/headers/linkstack",
    "content": "    __    _       __   _____ __             __  \n   / /   (_)___  / /__/ ___// /_____ ______/ /__\n  / /   / / __ \\/ //_/\\__ \\/ __/ __ `/ ___/ //_/\n / /___/ / / / / ,<  ___/ / /_/ /_/ / /__/ ,<   \n/_____/_/_/ /_/_/|_|/____/\\__/\\__,_/\\___/_/|_|  \n                                                \n"
  },
  {
    "path": "ct/headers/linkwarden",
    "content": "    __    _       __                           __         \n   / /   (_)___  / /___      ______ __________/ /__  ____ \n  / /   / / __ \\/ //_/ | /| / / __ `/ ___/ __  / _ \\/ __ \\\n / /___/ / / / / ,<  | |/ |/ / /_/ / /  / /_/ /  __/ / / /\n/_____/_/_/ /_/_/|_| |__/|__/\\__,_/_/   \\__,_/\\___/_/ /_/ \n                                                          \n"
  },
  {
    "path": "ct/headers/listmonk",
    "content": "    ___      __                        __  \n   / (_)____/ /_____ ___  ____  ____  / /__\n  / / / ___/ __/ __ `__ \\/ __ \\/ __ \\/ //_/\n / / (__  ) /_/ / / / / / /_/ / / / / ,<   \n/_/_/____/\\__/_/ /_/ /_/\\____/_/ /_/_/|_|  \n                                           \n"
  },
  {
    "path": "ct/headers/livebook",
    "content": "    __    _            __                __  \n   / /   (_)   _____  / /_  ____  ____  / /__\n  / /   / / | / / _ \\/ __ \\/ __ \\/ __ \\/ //_/\n / /___/ /| |/ /  __/ /_/ / /_/ / /_/ / ,<   \n/_____/_/ |___/\\___/_.___/\\____/\\____/_/|_|  \n                                             \n"
  },
  {
    "path": "ct/headers/lldap",
    "content": "    ____    __          \n   / / /___/ /___ _____ \n  / / / __  / __ `/ __ \\\n / / / /_/ / /_/ / /_/ /\n/_/_/\\__,_/\\__,_/ .___/ \n               /_/      \n"
  },
  {
    "path": "ct/headers/loki",
    "content": "    __          __   _ \n   / /   ____  / /__(_)\n  / /   / __ \\/ //_/ / \n / /___/ /_/ / ,< / /  \n/_____/\\____/_/|_/_/   \n                       \n"
  },
  {
    "path": "ct/headers/lubelogger",
    "content": "    __          __         __                               \n   / /   __  __/ /_  ___  / /   ____  ____ _____ ____  _____\n  / /   / / / / __ \\/ _ \\/ /   / __ \\/ __ `/ __ `/ _ \\/ ___/\n / /___/ /_/ / /_/ /  __/ /___/ /_/ / /_/ / /_/ /  __/ /    \n/_____/\\__,_/_.___/\\___/_____/\\____/\\__, /\\__, /\\___/_/     \n                                   /____//____/             \n"
  },
  {
    "path": "ct/headers/lychee",
    "content": "    __               __            \n   / /   __  _______/ /_  ___  ___ \n  / /   / / / / ___/ __ \\/ _ \\/ _ \\\n / /___/ /_/ / /__/ / / /  __/  __/\n/_____/\\__, /\\___/_/ /_/\\___/\\___/ \n      /____/                       \n"
  },
  {
    "path": "ct/headers/lyrionmusicserver",
    "content": "    __               _                __  ___           _         _____                          \n   / /   __  _______(_)___  ____     /  |/  /_  _______(_)____   / ___/___  ______   _____  _____\n  / /   / / / / ___/ / __ \\/ __ \\   / /|_/ / / / / ___/ / ___/   \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / /___/ /_/ / /  / / /_/ / / / /  / /  / / /_/ (__  ) / /__    ___/ /  __/ /   | |/ /  __/ /    \n/_____/\\__, /_/  /_/\\____/_/ /_/  /_/  /_/\\__,_/____/_/\\___/   /____/\\___/_/    |___/\\___/_/     \n      /____/                                                                                     \n"
  },
  {
    "path": "ct/headers/mafl",
    "content": "    __  ___      ______\n   /  |/  /___ _/ __/ /\n  / /|_/ / __ `/ /_/ / \n / /  / / /_/ / __/ /  \n/_/  /_/\\__,_/_/ /_/   \n                       \n"
  },
  {
    "path": "ct/headers/magicmirror",
    "content": "    __  ___            _      __  ____                     \n   /  |/  /___ _____ _(_)____/  |/  (_)_____________  _____\n  / /|_/ / __ `/ __ `/ / ___/ /|_/ / / ___/ ___/ __ \\/ ___/\n / /  / / /_/ / /_/ / / /__/ /  / / / /  / /  / /_/ / /    \n/_/  /_/\\__,_/\\__, /_/\\___/_/  /_/_/_/  /_/   \\____/_/     \n             /____/                                        \n"
  },
  {
    "path": "ct/headers/mail-archiver",
    "content": "    __  ___      _ __      ___              __    _                \n   /  |/  /___ _(_) /     /   |  __________/ /_  (_)   _____  _____\n  / /|_/ / __ `/ / /_____/ /| | / ___/ ___/ __ \\/ / | / / _ \\/ ___/\n / /  / / /_/ / / /_____/ ___ |/ /  / /__/ / / / /| |/ /  __/ /    \n/_/  /_/\\__,_/_/_/     /_/  |_/_/   \\___/_/ /_/_/ |___/\\___/_/     \n                                                                   \n"
  },
  {
    "path": "ct/headers/managemydamnlife",
    "content": "    __  ___                                __  ___         ____                           __    _ ____   \n   /  |/  /___ _____  ____ _____ ____     /  |/  /_  __   / __ \\____ _____ ___  ____     / /   (_) __/__ \n  / /|_/ / __ `/ __ \\/ __ `/ __ `/ _ \\   / /|_/ / / / /  / / / / __ `/ __ `__ \\/ __ \\   / /   / / /_/ _ \\\n / /  / / /_/ / / / / /_/ / /_/ /  __/  / /  / / /_/ /  / /_/ / /_/ / / / / / / / / /  / /___/ / __/  __/\n/_/  /_/\\__,_/_/ /_/\\__,_/\\__, /\\___/  /_/  /_/\\__, /  /_____/\\__,_/_/ /_/ /_/_/ /_/  /_____/_/_/  \\___/ \n                         /____/               /____/                                                     \n"
  },
  {
    "path": "ct/headers/manyfold",
    "content": "    __  ___                  ____      __    __\n   /  |/  /___ _____  __  __/ __/___  / /___/ /\n  / /|_/ / __ `/ __ \\/ / / / /_/ __ \\/ / __  / \n / /  / / /_/ / / / / /_/ / __/ /_/ / / /_/ /  \n/_/  /_/\\__,_/_/ /_/\\__, /_/  \\____/_/\\__,_/   \n                   /____/                      \n"
  },
  {
    "path": "ct/headers/mariadb",
    "content": "    __  ___           _       ____  ____ \n   /  |/  /___ ______(_)___ _/ __ \\/ __ )\n  / /|_/ / __ `/ ___/ / __ `/ / / / __  |\n / /  / / /_/ / /  / / /_/ / /_/ / /_/ / \n/_/  /_/\\__,_/_/  /_/\\__,_/_____/_____/  \n                                         \n"
  },
  {
    "path": "ct/headers/matomo",
    "content": "    __  ___      __                      \n   /  |/  /___ _/ /_____  ____ ___  ____ \n  / /|_/ / __ `/ __/ __ \\/ __ `__ \\/ __ \\\n / /  / / /_/ / /_/ /_/ / / / / / / /_/ /\n/_/  /_/\\__,_/\\__/\\____/_/ /_/ /_/\\____/ \n                                         \n"
  },
  {
    "path": "ct/headers/matter-server",
    "content": "    __  ___      __  __                 _____                          \n   /  |/  /___ _/ /_/ /____  _____     / ___/___  ______   _____  _____\n  / /|_/ / __ `/ __/ __/ _ \\/ ___/_____\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / /  / / /_/ / /_/ /_/  __/ /  /_____/__/ /  __/ /   | |/ /  __/ /    \n/_/  /_/\\__,_/\\__/\\__/\\___/_/        /____/\\___/_/    |___/\\___/_/     \n                                                                       \n"
  },
  {
    "path": "ct/headers/matterbridge",
    "content": "    __  ___      __  __            __         _     __         \n   /  |/  /___ _/ /_/ /____  _____/ /_  _____(_)___/ /___ ____ \n  / /|_/ / __ `/ __/ __/ _ \\/ ___/ __ \\/ ___/ / __  / __ `/ _ \\\n / /  / / /_/ / /_/ /_/  __/ /  / /_/ / /  / / /_/ / /_/ /  __/\n/_/  /_/\\__,_/\\__/\\__/\\___/_/  /_.___/_/  /_/\\__,_/\\__, /\\___/ \n                                                  /____/       \n"
  },
  {
    "path": "ct/headers/mattermost",
    "content": "    __  ___      __  __                                 __ \n   /  |/  /___ _/ /_/ /____  _________ ___  ____  _____/ /_\n  / /|_/ / __ `/ __/ __/ _ \\/ ___/ __ `__ \\/ __ \\/ ___/ __/\n / /  / / /_/ / /_/ /_/  __/ /  / / / / / / /_/ (__  ) /_  \n/_/  /_/\\__,_/\\__/\\__/\\___/_/  /_/ /_/ /_/\\____/____/\\__/  \n                                                           \n"
  },
  {
    "path": "ct/headers/mealie",
    "content": "    __  ___           ___    \n   /  |/  /__  ____ _/ (_)__ \n  / /|_/ / _ \\/ __ `/ / / _ \\\n / /  / /  __/ /_/ / / /  __/\n/_/  /_/\\___/\\__,_/_/_/\\___/ \n                             \n"
  },
  {
    "path": "ct/headers/mediamanager",
    "content": "    __  ___         ___       __  ___                                 \n   /  |/  /__  ____/ (_)___ _/  |/  /___ _____  ____ _____ ____  _____\n  / /|_/ / _ \\/ __  / / __ `/ /|_/ / __ `/ __ \\/ __ `/ __ `/ _ \\/ ___/\n / /  / /  __/ /_/ / / /_/ / /  / / /_/ / / / / /_/ / /_/ /  __/ /    \n/_/  /_/\\___/\\__,_/_/\\__,_/_/  /_/\\__,_/_/ /_/\\__,_/\\__, /\\___/_/     \n                                                   /____/             \n"
  },
  {
    "path": "ct/headers/mediamtx",
    "content": "    __  ___         ___       __  __________  __\n   /  |/  /__  ____/ (_)___ _/  |/  /_  __/ |/ /\n  / /|_/ / _ \\/ __  / / __ `/ /|_/ / / /  |   / \n / /  / /  __/ /_/ / / /_/ / /  / / / /  /   |  \n/_/  /_/\\___/\\__,_/_/\\__,_/_/  /_/ /_/  /_/|_|  \n                                                \n"
  },
  {
    "path": "ct/headers/medusa",
    "content": "    __  ___         __                \n   /  |/  /__  ____/ /_  ___________ _\n  / /|_/ / _ \\/ __  / / / / ___/ __ `/\n / /  / /  __/ /_/ / /_/ (__  ) /_/ / \n/_/  /_/\\___/\\__,_/\\__,_/____/\\__,_/  \n                                      \n"
  },
  {
    "path": "ct/headers/meilisearch",
    "content": "    __  ___     _ ___                           __  \n   /  |/  /__  (_) (_)_______  ____ ___________/ /_ \n  / /|_/ / _ \\/ / / / ___/ _ \\/ __ `/ ___/ ___/ __ \\\n / /  / /  __/ / / (__  )  __/ /_/ / /  / /__/ / / /\n/_/  /_/\\___/_/_/_/____/\\___/\\__,_/_/   \\___/_/ /_/ \n                                                    \n"
  },
  {
    "path": "ct/headers/memos",
    "content": "    __  ___                         \n   /  |/  /__  ____ ___  ____  _____\n  / /|_/ / _ \\/ __ `__ \\/ __ \\/ ___/\n / /  / /  __/ / / / / / /_/ (__  ) \n/_/  /_/\\___/_/ /_/ /_/\\____/____/  \n                                    \n"
  },
  {
    "path": "ct/headers/meshcentral",
    "content": "    __  ___          __    ______           __             __\n   /  |/  /__  _____/ /_  / ____/__  ____  / /__________ _/ /\n  / /|_/ / _ \\/ ___/ __ \\/ /   / _ \\/ __ \\/ __/ ___/ __ `/ / \n / /  / /  __(__  ) / / / /___/  __/ / / / /_/ /  / /_/ / /  \n/_/  /_/\\___/____/_/ /_/\\____/\\___/_/ /_/\\__/_/   \\__,_/_/   \n                                                             \n"
  },
  {
    "path": "ct/headers/metabase",
    "content": "    __  ___     __        __                  \n   /  |/  /__  / /_____ _/ /_  ____ _________ \n  / /|_/ / _ \\/ __/ __ `/ __ \\/ __ `/ ___/ _ \\\n / /  / /  __/ /_/ /_/ / /_/ / /_/ (__  )  __/\n/_/  /_/\\___/\\__/\\__,_/_.___/\\__,_/____/\\___/ \n                                              \n"
  },
  {
    "path": "ct/headers/metube",
    "content": "    __  ___   ______      __       \n   /  |/  /__/_  __/_  __/ /_  ___ \n  / /|_/ / _ \\/ / / / / / __ \\/ _ \\\n / /  / /  __/ / / /_/ / /_/ /  __/\n/_/  /_/\\___/_/  \\__,_/_.___/\\___/ \n                                   \n"
  },
  {
    "path": "ct/headers/minarca",
    "content": "    __  ____                            \n   /  |/  (_)___  ____ _______________ _\n  / /|_/ / / __ \\/ __ `/ ___/ ___/ __ `/\n / /  / / / / / / /_/ / /  / /__/ /_/ / \n/_/  /_/_/_/ /_/\\__,_/_/   \\___/\\__,_/  \n                                        \n"
  },
  {
    "path": "ct/headers/mini-qr",
    "content": "    __  ____       _       ____    ____ \n   /  |/  (_)___  (_)     / __ \\  / __ \\\n  / /|_/ / / __ \\/ /_____/ / / / / /_/ /\n / /  / / / / / / /_____/ /_/ / / _, _/ \n/_/  /_/_/_/ /_/_/      \\___\\_\\/_/ |_|  \n                                        \n"
  },
  {
    "path": "ct/headers/miniflux",
    "content": "    __  ____       _ ______          \n   /  |/  (_)___  (_) __/ /_  ___  __\n  / /|_/ / / __ \\/ / /_/ / / / / |/_/\n / /  / / / / / / / __/ / /_/ />  <  \n/_/  /_/_/_/ /_/_/_/ /_/\\__,_/_/|_|  \n                                     \n"
  },
  {
    "path": "ct/headers/minio",
    "content": "    __  ____       ________ \n   /  |/  (_)___  /  _/ __ \\\n  / /|_/ / / __ \\ / // / / /\n / /  / / / / / // // /_/ / \n/_/  /_/_/_/ /_/___/\\____/  \n                            \n"
  },
  {
    "path": "ct/headers/minthcm",
    "content": "    __  ____       __  __  __________  ___\n   /  |/  (_)___  / /_/ / / / ____/  |/  /\n  / /|_/ / / __ \\/ __/ /_/ / /   / /|_/ / \n / /  / / / / / / /_/ __  / /___/ /  / /  \n/_/  /_/_/_/ /_/\\__/_/ /_/\\____/_/  /_/   \n                                          \n"
  },
  {
    "path": "ct/headers/mongodb",
    "content": "    __  ___                        ____  ____ \n   /  |/  /___  ____  ____ _____  / __ \\/ __ )\n  / /|_/ / __ \\/ __ \\/ __ `/ __ \\/ / / / __  |\n / /  / / /_/ / / / / /_/ / /_/ / /_/ / /_/ / \n/_/  /_/\\____/_/ /_/\\__, /\\____/_____/_____/  \n                   /____/                     \n"
  },
  {
    "path": "ct/headers/monica",
    "content": "    __  ___            _           \n   /  |/  /___  ____  (_)________ _\n  / /|_/ / __ \\/ __ \\/ / ___/ __ `/\n / /  / / /_/ / / / / / /__/ /_/ / \n/_/  /_/\\____/_/ /_/_/\\___/\\__,_/  \n                                   \n"
  },
  {
    "path": "ct/headers/motioneye",
    "content": "    __  ___      __  _                           \n   /  |/  /___  / /_(_)___  ____  ___  __  _____ \n  / /|_/ / __ \\/ __/ / __ \\/ __ \\/ _ \\/ / / / _ \\\n / /  / / /_/ / /_/ / /_/ / / / /  __/ /_/ /  __/\n/_/  /_/\\____/\\__/_/\\____/_/ /_/\\___/\\__, /\\___/ \n                                    /____/       \n"
  },
  {
    "path": "ct/headers/mqtt",
    "content": "    __  _______  ____________\n   /  |/  / __ \\/_  __/_  __/\n  / /|_/ / / / / / /   / /   \n / /  / / /_/ / / /   / /    \n/_/  /_/\\___\\_\\/_/   /_/     \n                             \n"
  },
  {
    "path": "ct/headers/myip",
    "content": "    __  ___      ________ \n   /  |/  /_  __/  _/ __ \\\n  / /|_/ / / / // // /_/ /\n / /  / / /_/ // // ____/ \n/_/  /_/\\__, /___/_/      \n       /____/             \n"
  },
  {
    "path": "ct/headers/mylar3",
    "content": "    __  ___      __          _____\n   /  |/  /_  __/ /___ _____|__  /\n  / /|_/ / / / / / __ `/ ___//_ < \n / /  / / /_/ / / /_/ / /  ___/ / \n/_/  /_/\\__, /_/\\__,_/_/  /____/  \n       /____/                     \n"
  },
  {
    "path": "ct/headers/myspeed",
    "content": "    __  ___      _____                     __\n   /  |/  /_  __/ ___/____  ___  ___  ____/ /\n  / /|_/ / / / /\\__ \\/ __ \\/ _ \\/ _ \\/ __  / \n / /  / / /_/ /___/ / /_/ /  __/  __/ /_/ /  \n/_/  /_/\\__, //____/ .___/\\___/\\___/\\__,_/   \n       /____/     /_/                        \n"
  },
  {
    "path": "ct/headers/mysql",
    "content": "    __  ___      _____ ____    __ \n   /  |/  /_  __/ ___// __ \\  / / \n  / /|_/ / / / /\\__ \\/ / / / / /  \n / /  / / /_/ /___/ / /_/ / / /___\n/_/  /_/\\__, //____/\\___\\_\\/_____/\n       /____/                     \n"
  },
  {
    "path": "ct/headers/n8n",
    "content": "          ____      \n   ____  ( __ )____ \n  / __ \\/ __  / __ \\\n / / / / /_/ / / / /\n/_/ /_/\\____/_/ /_/ \n                    \n"
  },
  {
    "path": "ct/headers/nagios",
    "content": "    _   __            _           \n   / | / /___ _____ _(_)___  _____\n  /  |/ / __ `/ __ `/ / __ \\/ ___/\n / /|  / /_/ / /_/ / / /_/ (__  ) \n/_/ |_/\\__,_/\\__, /_/\\____/____/  \n            /____/                \n"
  },
  {
    "path": "ct/headers/nametag",
    "content": "    _   __                     __             \n   / | / /___ _____ ___  ___  / /_____ _____ _\n  /  |/ / __ `/ __ `__ \\/ _ \\/ __/ __ `/ __ `/\n / /|  / /_/ / / / / / /  __/ /_/ /_/ / /_/ / \n/_/ |_/\\__,_/_/ /_/ /_/\\___/\\__/\\__,_/\\__, /  \n                                     /____/   \n"
  },
  {
    "path": "ct/headers/navidrome",
    "content": "    _   __            _     __                        \n   / | / /___ __   __(_)___/ /________  ____ ___  ___ \n  /  |/ / __ `/ | / / / __  / ___/ __ \\/ __ `__ \\/ _ \\\n / /|  / /_/ /| |/ / / /_/ / /  / /_/ / / / / / /  __/\n/_/ |_/\\__,_/ |___/_/\\__,_/_/   \\____/_/ /_/ /_/\\___/ \n                                                      \n"
  },
  {
    "path": "ct/headers/neko",
    "content": "    _   __     __       \n   / | / /__  / /______ \n  /  |/ / _ \\/ //_/ __ \\\n / /|  /  __/ ,< / /_/ /\n/_/ |_/\\___/_/|_|\\____/ \n                        \n"
  },
  {
    "path": "ct/headers/neo4j",
    "content": "    _   __           __ __  _ \n   / | / /__  ____  / // / (_)\n  /  |/ / _ \\/ __ \\/ // /_/ / \n / /|  /  __/ /_/ /__  __/ /  \n/_/ |_/\\___/\\____/  /_/_/ /   \n                     /___/    \n"
  },
  {
    "path": "ct/headers/netbird",
    "content": "    _   __     __  ____  _          __\n   / | / /__  / /_/ __ )(_)________/ /\n  /  |/ / _ \\/ __/ __  / / ___/ __  / \n / /|  /  __/ /_/ /_/ / / /  / /_/ /  \n/_/ |_/\\___/\\__/_____/_/_/   \\__,_/   \n                                      \n"
  },
  {
    "path": "ct/headers/netboot-xyz",
    "content": "               __  __                __                  \n   ____  ___  / /_/ /_  ____  ____  / /_  _  ____  ______\n  / __ \\/ _ \\/ __/ __ \\/ __ \\/ __ \\/ __/ | |/_/ / / /_  /\n / / / /  __/ /_/ /_/ / /_/ / /_/ / /__ _>  </ /_/ / / /_\n/_/ /_/\\___/\\__/_.___/\\____/\\____/\\__(_)_/|_|\\__, / /___/\n                                            /____/       \n"
  },
  {
    "path": "ct/headers/netbox",
    "content": "    _   __     __  ____            \n   / | / /__  / /_/ __ )____  _  __\n  /  |/ / _ \\/ __/ __  / __ \\| |/_/\n / /|  /  __/ /_/ /_/ / /_/ />  <  \n/_/ |_/\\___/\\__/_____/\\____/_/|_|  \n                                   \n"
  },
  {
    "path": "ct/headers/netvisor",
    "content": "   _____                                   \n  / ___/_________ _____  ____  ____  __  __\n  \\__ \\/ ___/ __ `/ __ \\/ __ \\/ __ \\/ / / /\n ___/ / /__/ /_/ / / / / /_/ / /_/ / /_/ / \n/____/\\___/\\__,_/_/ /_/\\____/ .___/\\__, /  \n                           /_/    /____/   \n"
  },
  {
    "path": "ct/headers/nextcloudpi",
    "content": "    _   __          __  ________                ______  _ \n   / | / /__  _  __/ /_/ ____/ /___  __  ______/ / __ \\(_)\n  /  |/ / _ \\| |/_/ __/ /   / / __ \\/ / / / __  / /_/ / / \n / /|  /  __/>  </ /_/ /___/ / /_/ / /_/ / /_/ / ____/ /  \n/_/ |_/\\___/_/|_|\\__/\\____/_/\\____/\\__,_/\\__,_/_/   /_/   \n                                                          \n"
  },
  {
    "path": "ct/headers/nextexplorer",
    "content": "                    __  ______           __                    \n   ____  ___  _  __/ /_/ ____/  ______  / /___  ________  _____\n  / __ \\/ _ \\| |/_/ __/ __/ | |/_/ __ \\/ / __ \\/ ___/ _ \\/ ___/\n / / / /  __/>  </ /_/ /____>  </ /_/ / / /_/ / /  /  __/ /    \n/_/ /_/\\___/_/|_|\\__/_____/_/|_/ .___/_/\\____/_/   \\___/_/     \n                              /_/                              \n"
  },
  {
    "path": "ct/headers/nextpvr",
    "content": "    _   __          __  ____ _    ______ \n   / | / /__  _  __/ /_/ __ \\ |  / / __ \\\n  /  |/ / _ \\| |/_/ __/ /_/ / | / / /_/ /\n / /|  /  __/>  </ /_/ ____/| |/ / _, _/ \n/_/ |_/\\___/_/|_|\\__/_/     |___/_/ |_|  \n                                         \n"
  },
  {
    "path": "ct/headers/nginx-ui",
    "content": "    _   __      _                  __  ______\n   / | / /___ _(_)___  _  __      / / / /  _/\n  /  |/ / __ `/ / __ \\| |/_/_____/ / / // /  \n / /|  / /_/ / / / / />  </_____/ /_/ // /   \n/_/ |_/\\__, /_/_/ /_/_/|_|      \\____/___/   \n      /____/                                 \n"
  },
  {
    "path": "ct/headers/nginxproxymanager",
    "content": "    _   __      _               ____                           __  ___                                 \n   / | / /___ _(_)___  _  __   / __ \\_________  _  ____  __   /  |/  /___ _____  ____ _____ ____  _____\n  /  |/ / __ `/ / __ \\| |/_/  / /_/ / ___/ __ \\| |/_/ / / /  / /|_/ / __ `/ __ \\/ __ `/ __ `/ _ \\/ ___/\n / /|  / /_/ / / / / />  <   / ____/ /  / /_/ />  </ /_/ /  / /  / / /_/ / / / / /_/ / /_/ /  __/ /    \n/_/ |_/\\__, /_/_/ /_/_/|_|  /_/   /_/   \\____/_/|_|\\__, /  /_/  /_/\\__,_/_/ /_/\\__,_/\\__, /\\___/_/     \n      /____/                                      /____/                            /____/             \n"
  },
  {
    "path": "ct/headers/nightscout",
    "content": "    _   ___       __    __                        __ \n   / | / (_)___ _/ /_  / /_______________  __  __/ /_\n  /  |/ / / __ `/ __ \\/ __/ ___/ ___/ __ \\/ / / / __/\n / /|  / / /_/ / / / / /_(__  ) /__/ /_/ / /_/ / /_  \n/_/ |_/_/\\__, /_/ /_/\\__/____/\\___/\\____/\\__,_/\\__/  \n        /____/                                       \n"
  },
  {
    "path": "ct/headers/nocodb",
    "content": "    _   __                 ____  ____ \n   / | / /___  _________  / __ \\/ __ )\n  /  |/ / __ \\/ ___/ __ \\/ / / / __  |\n / /|  / /_/ / /__/ /_/ / /_/ / /_/ / \n/_/ |_/\\____/\\___/\\____/_____/_____/  \n                                      \n"
  },
  {
    "path": "ct/headers/node-red",
    "content": "    _   __          __           ____           __\n   / | / /___  ____/ /__        / __ \\___  ____/ /\n  /  |/ / __ \\/ __  / _ \\______/ /_/ / _ \\/ __  / \n / /|  / /_/ / /_/ /  __/_____/ _, _/  __/ /_/ /  \n/_/ |_/\\____/\\__,_/\\___/     /_/ |_|\\___/\\__,_/   \n                                                  \n"
  },
  {
    "path": "ct/headers/nodebb",
    "content": "    _   __          __     ____  ____ \n   / | / /___  ____/ /__  / __ )/ __ )\n  /  |/ / __ \\/ __  / _ \\/ __  / __  |\n / /|  / /_/ / /_/ /  __/ /_/ / /_/ / \n/_/ |_/\\____/\\__,_/\\___/_____/_____/  \n                                      \n"
  },
  {
    "path": "ct/headers/nodecast-tv",
    "content": "                    __                     __        __       \n   ____  ____  ____/ /__  _________ ______/ /_      / /__   __\n  / __ \\/ __ \\/ __  / _ \\/ ___/ __ `/ ___/ __/_____/ __/ | / /\n / / / / /_/ / /_/ /  __/ /__/ /_/ (__  ) /_/_____/ /_ | |/ / \n/_/ /_/\\____/\\__,_/\\___/\\___/\\__,_/____/\\__/      \\__/ |___/  \n                                                              \n"
  },
  {
    "path": "ct/headers/notifiarr",
    "content": "    _   __      __  _ _____                \n   / | / /___  / /_(_) __(_)___ ___________\n  /  |/ / __ \\/ __/ / /_/ / __ `/ ___/ ___/\n / /|  / /_/ / /_/ / __/ / /_/ / /  / /    \n/_/ |_/\\____/\\__/_/_/ /_/\\__,_/_/  /_/     \n                                           \n"
  },
  {
    "path": "ct/headers/npmplus",
    "content": "    _   ______  __  ___      __          \n   / | / / __ \\/  |/  /___  / /_  _______\n  /  |/ / /_/ / /|_/ / __ \\/ / / / / ___/\n / /|  / ____/ /  / / /_/ / / /_/ (__  ) \n/_/ |_/_/   /_/  /_/ .___/_/\\__,_/____/  \n                  /_/                    \n"
  },
  {
    "path": "ct/headers/ntfy",
    "content": "          __  ____     \n   ____  / /_/ __/_  __\n  / __ \\/ __/ /_/ / / /\n / / / / /_/ __/ /_/ / \n/_/ /_/\\__/_/  \\__, /  \n              /____/   \n"
  },
  {
    "path": "ct/headers/nxwitness",
    "content": "    _   __    _       ___ __                      \n   / | / /  _| |     / (_) /_____  ___  __________\n  /  |/ / |/_/ | /| / / / __/ __ \\/ _ \\/ ___/ ___/\n / /|  />  < | |/ |/ / / /_/ / / /  __(__  |__  ) \n/_/ |_/_/|_| |__/|__/_/\\__/_/ /_/\\___/____/____/  \n                                                  \n"
  },
  {
    "path": "ct/headers/nzbget",
    "content": "    _   _______   ____  ______     __ \n   / | / /__  /  / __ )/ ____/__  / /_\n  /  |/ /  / /  / __  / / __/ _ \\/ __/\n / /|  /  / /__/ /_/ / /_/ /  __/ /_  \n/_/ |_/  /____/_____/\\____/\\___/\\__/  \n                                      \n"
  },
  {
    "path": "ct/headers/oauth2-proxy",
    "content": "                     __  __   ___                                   \n  ____  ____ ___  __/ /_/ /_ |__ \\      ____  _________  _  ____  __\n / __ \\/ __ `/ / / / __/ __ \\__/ /_____/ __ \\/ ___/ __ \\| |/_/ / / /\n/ /_/ / /_/ / /_/ / /_/ / / / __/_____/ /_/ / /  / /_/ />  </ /_/ / \n\\____/\\__,_/\\__,_/\\__/_/ /_/____/    / .___/_/   \\____/_/|_|\\__, /  \n                                    /_/                    /____/   \n"
  },
  {
    "path": "ct/headers/octoprint",
    "content": "   ____       __        ____       _       __ \n  / __ \\_____/ /_____  / __ \\_____(_)___  / /_\n / / / / ___/ __/ __ \\/ /_/ / ___/ / __ \\/ __/\n/ /_/ / /__/ /_/ /_/ / ____/ /  / / / / / /_  \n\\____/\\___/\\__/\\____/_/   /_/  /_/_/ /_/\\__/  \n                                              \n"
  },
  {
    "path": "ct/headers/odoo",
    "content": "   ____      __          \n  / __ \\____/ /___  ____ \n / / / / __  / __ \\/ __ \\\n/ /_/ / /_/ / /_/ / /_/ /\n\\____/\\__,_/\\____/\\____/ \n                         \n"
  },
  {
    "path": "ct/headers/ollama",
    "content": "   ____  ____                     \n  / __ \\/ / /___ _____ ___  ____ _\n / / / / / / __ `/ __ `__ \\/ __ `/\n/ /_/ / / / /_/ / / / / / / /_/ / \n\\____/_/_/\\__,_/_/ /_/ /_/\\__,_/  \n                                  \n"
  },
  {
    "path": "ct/headers/omada",
    "content": "   ____                      __     \n  / __ \\____ ___  ____ _____/ /___ _\n / / / / __ `__ \\/ __ `/ __  / __ `/\n/ /_/ / / / / / / /_/ / /_/ / /_/ / \n\\____/_/ /_/ /_/\\__,_/\\__,_/\\__,_/  \n                                    \n"
  },
  {
    "path": "ct/headers/ombi",
    "content": "   ____            __    _ \n  / __ \\____ ___  / /_  (_)\n / / / / __ `__ \\/ __ \\/ / \n/ /_/ / / / / / / /_/ / /  \n\\____/_/ /_/ /_/_.___/_/   \n                           \n"
  },
  {
    "path": "ct/headers/omv",
    "content": "   ____  __  ____    __\n  / __ \\/  |/  / |  / /\n / / / / /|_/ /| | / / \n/ /_/ / /  / / | |/ /  \n\\____/_/  /_/  |___/   \n                       \n"
  },
  {
    "path": "ct/headers/onedev",
    "content": "   ____             ____           \n  / __ \\____  ___  / __ \\___ _   __\n / / / / __ \\/ _ \\/ / / / _ \\ | / /\n/ /_/ / / / /  __/ /_/ /  __/ |/ / \n\\____/_/ /_/\\___/_____/\\___/|___/  \n                                   \n"
  },
  {
    "path": "ct/headers/onlyoffice",
    "content": "   ____  _   ______  ______  ____________________________\n  / __ \\/ | / / /\\ \\/ / __ \\/ ____/ ____/  _/ ____/ ____/\n / / / /  |/ / /  \\  / / / / /_  / /_   / // /   / __/   \n/ /_/ / /|  / /___/ / /_/ / __/ / __/ _/ // /___/ /___   \n\\____/_/ |_/_____/_/\\____/_/   /_/   /___/\\____/_____/   \n                                                         \n"
  },
  {
    "path": "ct/headers/open-archiver",
    "content": "   ____                         ___              __    _                \n  / __ \\____  ___  ____        /   |  __________/ /_  (_)   _____  _____\n / / / / __ \\/ _ \\/ __ \\______/ /| | / ___/ ___/ __ \\/ / | / / _ \\/ ___/\n/ /_/ / /_/ /  __/ / / /_____/ ___ |/ /  / /__/ / / / /| |/ /  __/ /    \n\\____/ .___/\\___/_/ /_/     /_/  |_/_/   \\___/_/ /_/_/ |___/\\___/_/     \n    /_/                                                                 \n"
  },
  {
    "path": "ct/headers/opencloud",
    "content": "   ____                   ________                __\n  / __ \\____  ___  ____  / ____/ /___  __  ______/ /\n / / / / __ \\/ _ \\/ __ \\/ /   / / __ \\/ / / / __  / \n/ /_/ / /_/ /  __/ / / / /___/ / /_/ / /_/ / /_/ /  \n\\____/ .___/\\___/_/ /_/\\____/_/\\____/\\__,_/\\__,_/   \n    /_/                                             \n"
  },
  {
    "path": "ct/headers/opengist",
    "content": "   ____                         _      __ \n  / __ \\____  ___  ____  ____ _(_)____/ /_\n / / / / __ \\/ _ \\/ __ \\/ __ `/ / ___/ __/\n/ /_/ / /_/ /  __/ / / / /_/ / (__  ) /_  \n\\____/ .___/\\___/_/ /_/\\__, /_/____/\\__/  \n    /_/               /____/              \n"
  },
  {
    "path": "ct/headers/openhab",
    "content": "                          __  _____    ____ \n  ____  ____  ___  ____  / / / /   |  / __ )\n / __ \\/ __ \\/ _ \\/ __ \\/ /_/ / /| | / __  |\n/ /_/ / /_/ /  __/ / / / __  / ___ |/ /_/ / \n\\____/ .___/\\___/_/ /_/_/ /_/_/  |_/_____/  \n    /_/                                     \n"
  },
  {
    "path": "ct/headers/openobserve",
    "content": "   ____                   ____  __                            \n  / __ \\____  ___  ____  / __ \\/ /_  ________  ______   _____ \n / / / / __ \\/ _ \\/ __ \\/ / / / __ \\/ ___/ _ \\/ ___/ | / / _ \\\n/ /_/ / /_/ /  __/ / / / /_/ / /_/ (__  )  __/ /   | |/ /  __/\n\\____/ .___/\\___/_/ /_/\\____/_.___/____/\\___/_/    |___/\\___/ \n    /_/                                                       \n"
  },
  {
    "path": "ct/headers/openproject",
    "content": "   ____                   ____               _           __ \n  / __ \\____  ___  ____  / __ \\_________    (_)__  _____/ /_\n / / / / __ \\/ _ \\/ __ \\/ /_/ / ___/ __ \\  / / _ \\/ ___/ __/\n/ /_/ / /_/ /  __/ / / / ____/ /  / /_/ / / /  __/ /__/ /_  \n\\____/ .___/\\___/_/ /_/_/   /_/   \\____/_/ /\\___/\\___/\\__/  \n    /_/                               /___/                 \n"
  },
  {
    "path": "ct/headers/openthread-br",
    "content": "   ____                 ________                        __      ____  ____ \n  / __ \\____  ___  ____/_  __/ /_  ________  ____ _____/ /     / __ )/ __ \\\n / / / / __ \\/ _ \\/ __ \\/ / / __ \\/ ___/ _ \\/ __ `/ __  /_____/ __  / /_/ /\n/ /_/ / /_/ /  __/ / / / / / / / / /  /  __/ /_/ / /_/ /_____/ /_/ / _, _/ \n\\____/ .___/\\___/_/ /_/_/ /_/ /_/_/   \\___/\\__,_/\\__,_/     /_____/_/ |_|  \n    /_/                                                                    \n"
  },
  {
    "path": "ct/headers/openwebui",
    "content": "   ____                      _       __     __    __  ______\n  / __ \\____  ___  ____     | |     / /__  / /_  / / / /  _/\n / / / / __ \\/ _ \\/ __ \\    | | /| / / _ \\/ __ \\/ / / // /  \n/ /_/ / /_/ /  __/ / / /    | |/ |/ /  __/ /_/ / /_/ // /   \n\\____/ .___/\\___/_/ /_/     |__/|__/\\___/_.___/\\____/___/   \n    /_/                                                     \n"
  },
  {
    "path": "ct/headers/openziti-controller",
    "content": "                                _ __  _                        __             ____         \n  ____  ____  ___  ____  ____  (_) /_(_)     _________  ____  / /__________  / / /__  _____\n / __ \\/ __ \\/ _ \\/ __ \\/_  / / / __/ /_____/ ___/ __ \\/ __ \\/ __/ ___/ __ \\/ / / _ \\/ ___/\n/ /_/ / /_/ /  __/ / / / / /_/ / /_/ /_____/ /__/ /_/ / / / / /_/ /  / /_/ / / /  __/ /    \n\\____/ .___/\\___/_/ /_/ /___/_/\\__/_/      \\___/\\____/_/ /_/\\__/_/   \\____/_/_/\\___/_/     \n    /_/                                                                                    \n"
  },
  {
    "path": "ct/headers/openziti-tunnel",
    "content": "                                _ __  _       __                         __\n  ____  ____  ___  ____  ____  (_) /_(_)     / /___  ______  ____  ___  / /\n / __ \\/ __ \\/ _ \\/ __ \\/_  / / / __/ /_____/ __/ / / / __ \\/ __ \\/ _ \\/ / \n/ /_/ / /_/ /  __/ / / / / /_/ / /_/ /_____/ /_/ /_/ / / / / / / /  __/ /  \n\\____/ .___/\\___/_/ /_/ /___/_/\\__/_/      \\__/\\__,_/_/ /_/_/ /_/\\___/_/   \n    /_/                                                                    \n"
  },
  {
    "path": "ct/headers/ots",
    "content": "   ____  ___________\n  / __ \\/_  __/ ___/\n / / / / / /  \\__ \\ \n/ /_/ / / /  ___/ / \n\\____/ /_/  /____/  \n                    \n"
  },
  {
    "path": "ct/headers/outline",
    "content": "   ____        __  ___          \n  / __ \\__  __/ /_/ (_)___  ___ \n / / / / / / / __/ / / __ \\/ _ \\\n/ /_/ / /_/ / /_/ / / / / /  __/\n\\____/\\__,_/\\__/_/_/_/ /_/\\___/ \n                                \n"
  },
  {
    "path": "ct/headers/overseerr",
    "content": "   ____                                          \n  / __ \\_   _____  _____________  ___  __________\n / / / / | / / _ \\/ ___/ ___/ _ \\/ _ \\/ ___/ ___/\n/ /_/ /| |/ /  __/ /  (__  )  __/  __/ /  / /    \n\\____/ |___/\\___/_/  /____/\\___/\\___/_/  /_/     \n                                                 \n"
  },
  {
    "path": "ct/headers/owncast",
    "content": "   ____                                 __ \n  / __ \\_      ______  _________ ______/ /_\n / / / / | /| / / __ \\/ ___/ __ `/ ___/ __/\n/ /_/ /| |/ |/ / / / / /__/ /_/ (__  ) /_  \n\\____/ |__/|__/_/ /_/\\___/\\__,_/____/\\__/  \n                                           \n"
  },
  {
    "path": "ct/headers/ownfoil",
    "content": "                       ____      _ __\n  ____ _      ______  / __/___  (_) /\n / __ \\ | /| / / __ \\/ /_/ __ \\/ / / \n/ /_/ / |/ |/ / / / / __/ /_/ / / /  \n\\____/|__/|__/_/ /_/_/  \\____/_/_/   \n                                     \n"
  },
  {
    "path": "ct/headers/pairdrop",
    "content": "    ____        _      ____                 \n   / __ \\____ _(_)____/ __ \\_________  ____ \n  / /_/ / __ `/ / ___/ / / / ___/ __ \\/ __ \\\n / ____/ /_/ / / /  / /_/ / /  / /_/ / /_/ /\n/_/    \\__,_/_/_/  /_____/_/   \\____/ .___/ \n                                   /_/      \n"
  },
  {
    "path": "ct/headers/pangolin",
    "content": "    ____                          ___     \n   / __ \\____ _____  ____ _____  / (_)___ \n  / /_/ / __ `/ __ \\/ __ `/ __ \\/ / / __ \\\n / ____/ /_/ / / / / /_/ / /_/ / / / / / /\n/_/    \\__,_/_/ /_/\\__, /\\____/_/_/_/ /_/ \n                  /____/                  \n"
  },
  {
    "path": "ct/headers/paperless-ai",
    "content": "    ____                        __                     ___    ____\n   / __ \\____ _____  ___  _____/ /__  __________      /   |  /  _/\n  / /_/ / __ `/ __ \\/ _ \\/ ___/ / _ \\/ ___/ ___/_____/ /| |  / /  \n / ____/ /_/ / /_/ /  __/ /  / /  __(__  |__  )_____/ ___ |_/ /   \n/_/    \\__,_/ .___/\\___/_/  /_/\\___/____/____/     /_/  |_/___/   \n           /_/                                                    \n"
  },
  {
    "path": "ct/headers/paperless-gpt",
    "content": "    ____                        __                     __________  ______\n   / __ \\____ _____  ___  _____/ /__  __________      / ____/ __ \\/_  __/\n  / /_/ / __ `/ __ \\/ _ \\/ ___/ / _ \\/ ___/ ___/_____/ / __/ /_/ / / /   \n / ____/ /_/ / /_/ /  __/ /  / /  __(__  |__  )_____/ /_/ / ____/ / /    \n/_/    \\__,_/ .___/\\___/_/  /_/\\___/____/____/      \\____/_/     /_/     \n           /_/                                                           \n"
  },
  {
    "path": "ct/headers/paperless-ngx",
    "content": "    ____                        __                                     \n   / __ \\____ _____  ___  _____/ /__  __________      ____  ____ __  __\n  / /_/ / __ `/ __ \\/ _ \\/ ___/ / _ \\/ ___/ ___/_____/ __ \\/ __ `/ |/_/\n / ____/ /_/ / /_/ /  __/ /  / /  __(__  |__  )_____/ / / / /_/ />  <  \n/_/    \\__,_/ .___/\\___/_/  /_/\\___/____/____/     /_/ /_/\\__, /_/|_|  \n           /_/                                           /____/        \n"
  },
  {
    "path": "ct/headers/papra",
    "content": "    ____                        \n   / __ \\____ _____  _________ _\n  / /_/ / __ `/ __ \\/ ___/ __ `/\n / ____/ /_/ / /_/ / /  / /_/ / \n/_/    \\__,_/ .___/_/   \\__,_/  \n           /_/                  \n"
  },
  {
    "path": "ct/headers/part-db",
    "content": "    ____             __        ____  ____ \n   / __ \\____ ______/ /_      / __ \\/ __ )\n  / /_/ / __ `/ ___/ __/_____/ / / / __  |\n / ____/ /_/ / /  / /_/_____/ /_/ / /_/ / \n/_/    \\__,_/_/   \\__/     /_____/_____/  \n                                          \n"
  },
  {
    "path": "ct/headers/passbolt",
    "content": "    ____                  __          ____ \n   / __ \\____ ___________/ /_  ____  / / /_\n  / /_/ / __ `/ ___/ ___/ __ \\/ __ \\/ / __/\n / ____/ /_/ (__  |__  ) /_/ / /_/ / / /_  \n/_/    \\__,_/____/____/_.___/\\____/_/\\__/  \n                                           \n"
  },
  {
    "path": "ct/headers/patchmon",
    "content": "    ____        __       __    __  ___          \n   / __ \\____ _/ /______/ /_  /  |/  /___  ____ \n  / /_/ / __ `/ __/ ___/ __ \\/ /|_/ / __ \\/ __ \\\n / ____/ /_/ / /_/ /__/ / / / /  / / /_/ / / / /\n/_/    \\__,_/\\__/\\___/_/ /_/_/  /_/\\____/_/ /_/ \n                                                \n"
  },
  {
    "path": "ct/headers/paymenter",
    "content": "    ____                                   __           \n   / __ \\____ ___  ______ ___  ___  ____  / /____  _____\n  / /_/ / __ `/ / / / __ `__ \\/ _ \\/ __ \\/ __/ _ \\/ ___/\n / ____/ /_/ / /_/ / / / / / /  __/ / / / /_/  __/ /    \n/_/    \\__,_/\\__, /_/ /_/ /_/\\___/_/ /_/\\__/\\___/_/     \n            /____/                                      \n"
  },
  {
    "path": "ct/headers/peanut",
    "content": "    ____             _   ____  ________\n   / __ \\___  ____ _/ | / / / / /_  __/\n  / /_/ / _ \\/ __ `/  |/ / / / / / /   \n / ____/  __/ /_/ / /|  / /_/ / / /    \n/_/    \\___/\\__,_/_/ |_/\\____/ /_/     \n                                       \n"
  },
  {
    "path": "ct/headers/pelican-panel",
    "content": "    ____       ___                        ____                   __\n   / __ \\___  / (_)________ _____        / __ \\____ _____  ___  / /\n  / /_/ / _ \\/ / / ___/ __ `/ __ \\______/ /_/ / __ `/ __ \\/ _ \\/ / \n / ____/  __/ / / /__/ /_/ / / / /_____/ ____/ /_/ / / / /  __/ /  \n/_/    \\___/_/_/\\___/\\__,_/_/ /_/     /_/    \\__,_/_/ /_/\\___/_/   \n                                                                   \n"
  },
  {
    "path": "ct/headers/pelican-wings",
    "content": "    ____       ___                      _       ___                 \n   / __ \\___  / (_)________ _____      | |     / (_)___  ____ ______\n  / /_/ / _ \\/ / / ___/ __ `/ __ \\_____| | /| / / / __ \\/ __ `/ ___/\n / ____/  __/ / / /__/ /_/ / / / /_____/ |/ |/ / / / / / /_/ (__  ) \n/_/    \\___/_/_/\\___/\\__,_/_/ /_/      |__/|__/_/_/ /_/\\__, /____/  \n                                                      /____/        \n"
  },
  {
    "path": "ct/headers/photoprism",
    "content": "    ____  __          __        ____       _              \n   / __ \\/ /_  ____  / /_____  / __ \\_____(_)________ ___ \n  / /_/ / __ \\/ __ \\/ __/ __ \\/ /_/ / ___/ / ___/ __ `__ \\\n / ____/ / / / /_/ / /_/ /_/ / ____/ /  / (__  ) / / / / /\n/_/   /_/ /_/\\____/\\__/\\____/_/   /_/  /_/____/_/ /_/ /_/ \n                                                          \n"
  },
  {
    "path": "ct/headers/pialert",
    "content": "    ____  _ ___    __          __ \n   / __ \\(_)   |  / /__  _____/ /_\n  / /_/ / / /| | / / _ \\/ ___/ __/\n / ____/ / ___ |/ /  __/ /  / /_  \n/_/   /_/_/  |_/_/\\___/_/   \\__/  \n                                  \n"
  },
  {
    "path": "ct/headers/pihole",
    "content": "    ____  _ __          __   \n   / __ \\(_) /_  ____  / /__ \n  / /_/ / / __ \\/ __ \\/ / _ \\\n / ____/ / / / / /_/ / /  __/\n/_/   /_/_/ /_/\\____/_/\\___/ \n                             \n"
  },
  {
    "path": "ct/headers/planka",
    "content": "    ____  __    ___    _   ____ __ ___ \n   / __ \\/ /   /   |  / | / / //_//   |\n  / /_/ / /   / /| | /  |/ / ,<  / /| |\n / ____/ /___/ ___ |/ /|  / /| |/ ___ |\n/_/   /_____/_/  |_/_/ |_/_/ |_/_/  |_|\n                                       \n"
  },
  {
    "path": "ct/headers/plant-it",
    "content": "    ____  __            __        _ __ \n   / __ \\/ /___ _____  / /_      (_) /_\n  / /_/ / / __ `/ __ \\/ __/_____/ / __/\n / ____/ / /_/ / / / / /_/_____/ / /_  \n/_/   /_/\\__,_/_/ /_/\\__/     /_/\\__/  \n                                       \n"
  },
  {
    "path": "ct/headers/plex",
    "content": "    ____  __         \n   / __ \\/ /__  _  __\n  / /_/ / / _ \\| |/_/\n / ____/ /  __/>  <  \n/_/   /_/\\___/_/|_|  \n                     \n"
  },
  {
    "path": "ct/headers/pocketbase",
    "content": "    ____             __        __  __                  \n   / __ \\____  _____/ /_____  / /_/ /_  ____ _________ \n  / /_/ / __ \\/ ___/ //_/ _ \\/ __/ __ \\/ __ `/ ___/ _ \\\n / ____/ /_/ / /__/ ,< /  __/ /_/ /_/ / /_/ (__  )  __/\n/_/    \\____/\\___/_/|_|\\___/\\__/_.___/\\__,_/____/\\___/ \n                                                       \n"
  },
  {
    "path": "ct/headers/pocketid",
    "content": "    ____             __        __  ________ \n   / __ \\____  _____/ /_____  / /_/  _/ __ \\\n  / /_/ / __ \\/ ___/ //_/ _ \\/ __// // / / /\n / ____/ /_/ / /__/ ,< /  __/ /__/ // /_/ / \n/_/    \\____/\\___/_/|_|\\___/\\__/___/_____/  \n                                            \n"
  },
  {
    "path": "ct/headers/podman",
    "content": "    ____            __                    \n   / __ \\____  ____/ /___ ___  ____ _____ \n  / /_/ / __ \\/ __  / __ `__ \\/ __ `/ __ \\\n / ____/ /_/ / /_/ / / / / / / /_/ / / / /\n/_/    \\____/\\__,_/_/ /_/ /_/\\__,_/_/ /_/ \n                                          \n"
  },
  {
    "path": "ct/headers/podman-homeassistant",
    "content": "    ____            __                            __  __                        ___              _      __              __ \n   / __ \\____  ____/ /___ ___  ____ _____        / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_\n  / /_/ / __ \\/ __  / __ `__ \\/ __ `/ __ \\______/ /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/\n / ____/ /_/ / /_/ / / / / / / /_/ / / / /_____/ __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_  \n/_/    \\____/\\__,_/_/ /_/ /_/\\__,_/_/ /_/     /_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/  \n                                                                                                                           \n"
  },
  {
    "path": "ct/headers/postgresql",
    "content": "    ____             __                 _____ ____    __ \n   / __ \\____  _____/ /_____ _________ / ___// __ \\  / / \n  / /_/ / __ \\/ ___/ __/ __ `/ ___/ _ \\\\__ \\/ / / / / /  \n / ____/ /_/ (__  ) /_/ /_/ / /  /  __/__/ / /_/ / / /___\n/_/    \\____/____/\\__/\\__, /_/   \\___/____/\\___\\_\\/_____/\n                     /____/                              \n"
  },
  {
    "path": "ct/headers/powerdns",
    "content": "    ____                          ____  _   _______\n   / __ \\____ _      _____  _____/ __ \\/ | / / ___/\n  / /_/ / __ \\ | /| / / _ \\/ ___/ / / /  |/ /\\__ \\ \n / ____/ /_/ / |/ |/ /  __/ /  / /_/ / /|  /___/ / \n/_/    \\____/|__/|__/\\___/_/  /_____/_/ |_//____/  \n                                                   \n"
  },
  {
    "path": "ct/headers/privatebin",
    "content": "    ____       _             __       ____  _     \n   / __ \\_____(_)   ______ _/ /____  / __ )(_)___ \n  / /_/ / ___/ / | / / __ `/ __/ _ \\/ __  / / __ \\\n / ____/ /  / /| |/ / /_/ / /_/  __/ /_/ / / / / /\n/_/   /_/  /_/ |___/\\__,_/\\__/\\___/_____/_/_/ /_/ \n                                                  \n"
  },
  {
    "path": "ct/headers/profilarr",
    "content": "    ____             _____ __               \n   / __ \\_________  / __(_) /___ ___________\n  / /_/ / ___/ __ \\/ /_/ / / __ `/ ___/ ___/\n / ____/ /  / /_/ / __/ / / /_/ / /  / /    \n/_/   /_/   \\____/_/ /_/_/\\__,_/_/  /_/     \n                                            \n"
  },
  {
    "path": "ct/headers/projectsend",
    "content": "    ____               _           __  _____                __\n   / __ \\_________    (_)__  _____/ /_/ ___/___  ____  ____/ /\n  / /_/ / ___/ __ \\  / / _ \\/ ___/ __/\\__ \\/ _ \\/ __ \\/ __  / \n / ____/ /  / /_/ / / /  __/ /__/ /_ ___/ /  __/ / / / /_/ /  \n/_/   /_/   \\____/_/ /\\___/\\___/\\__//____/\\___/_/ /_/\\__,_/   \n                /___/                                         \n"
  },
  {
    "path": "ct/headers/prometheus",
    "content": "    ____                            __  __                   \n   / __ \\_________  ____ ___  ___  / /_/ /_  ___  __  _______\n  / /_/ / ___/ __ \\/ __ `__ \\/ _ \\/ __/ __ \\/ _ \\/ / / / ___/\n / ____/ /  / /_/ / / / / / /  __/ /_/ / / /  __/ /_/ (__  ) \n/_/   /_/   \\____/_/ /_/ /_/\\___/\\__/_/ /_/\\___/\\__,_/____/  \n                                                             \n"
  },
  {
    "path": "ct/headers/prometheus-alertmanager",
    "content": "    ____                            __  __                          ___    __          __                                             \n   / __ \\_________  ____ ___  ___  / /_/ /_  ___  __  _______      /   |  / /__  _____/ /_____ ___  ____ _____  ____ _____ ____  _____\n  / /_/ / ___/ __ \\/ __ `__ \\/ _ \\/ __/ __ \\/ _ \\/ / / / ___/_____/ /| | / / _ \\/ ___/ __/ __ `__ \\/ __ `/ __ \\/ __ `/ __ `/ _ \\/ ___/\n / ____/ /  / /_/ / / / / / /  __/ /_/ / / /  __/ /_/ (__  )_____/ ___ |/ /  __/ /  / /_/ / / / / / /_/ / / / / /_/ / /_/ /  __/ /    \n/_/   /_/   \\____/_/ /_/ /_/\\___/\\__/_/ /_/\\___/\\__,_/____/     /_/  |_/_/\\___/_/   \\__/_/ /_/ /_/\\__,_/_/ /_/\\__,_/\\__, /\\___/_/     \n                                                                                                                   /____/             \n"
  },
  {
    "path": "ct/headers/prometheus-blackbox-exporter",
    "content": "    ____                            __  __                          ____  __           __   __                     ______                      __           \n   / __ \\_________  ____ ___  ___  / /_/ /_  ___  __  _______      / __ )/ /___ ______/ /__/ /_  ____  _  __      / ____/  ______  ____  _____/ /____  _____\n  / /_/ / ___/ __ \\/ __ `__ \\/ _ \\/ __/ __ \\/ _ \\/ / / / ___/_____/ __  / / __ `/ ___/ //_/ __ \\/ __ \\| |/_/_____/ __/ | |/_/ __ \\/ __ \\/ ___/ __/ _ \\/ ___/\n / ____/ /  / /_/ / / / / / /  __/ /_/ / / /  __/ /_/ (__  )_____/ /_/ / / /_/ / /__/ ,< / /_/ / /_/ />  </_____/ /____>  </ /_/ / /_/ / /  / /_/  __/ /    \n/_/   /_/   \\____/_/ /_/ /_/\\___/\\__/_/ /_/\\___/\\__,_/____/     /_____/_/\\__,_/\\___/_/|_/_.___/\\____/_/|_|     /_____/_/|_/ .___/\\____/_/   \\__/\\___/_/     \n                                                                                                                         /_/                                \n"
  },
  {
    "path": "ct/headers/prometheus-pve-exporter",
    "content": "    ____                            __  __                          ____ _    ________     ______                      __           \n   / __ \\_________  ____ ___  ___  / /_/ /_  ___  __  _______      / __ \\ |  / / ____/    / ____/  ______  ____  _____/ /____  _____\n  / /_/ / ___/ __ \\/ __ `__ \\/ _ \\/ __/ __ \\/ _ \\/ / / / ___/_____/ /_/ / | / / __/______/ __/ | |/_/ __ \\/ __ \\/ ___/ __/ _ \\/ ___/\n / ____/ /  / /_/ / / / / / /  __/ /_/ / / /  __/ /_/ (__  )_____/ ____/| |/ / /__/_____/ /____>  </ /_/ / /_/ / /  / /_/  __/ /    \n/_/   /_/   \\____/_/ /_/ /_/\\___/\\__/_/ /_/\\___/\\__,_/____/     /_/     |___/_____/    /_____/_/|_/ .___/\\____/_/   \\__/\\___/_/     \n                                                                                                 /_/                                \n"
  },
  {
    "path": "ct/headers/protonmail-bridge",
    "content": "    ____             __              __  ___      _ __      ____       _     __         \n   / __ \\_________  / /_____  ____  /  |/  /___ _(_) /     / __ )_____(_)___/ /___ ____ \n  / /_/ / ___/ __ \\/ __/ __ \\/ __ \\/ /|_/ / __ `/ / /_____/ __  / ___/ / __  / __ `/ _ \\\n / ____/ /  / /_/ / /_/ /_/ / / / / /  / / /_/ / / /_____/ /_/ / /  / / /_/ / /_/ /  __/\n/_/   /_/   \\____/\\__/\\____/_/ /_/_/  /_/\\__,_/_/_/     /_____/_/  /_/\\__,_/\\__, /\\___/ \n                                                                           /____/       \n"
  },
  {
    "path": "ct/headers/prowlarr",
    "content": "    ____                     __               \n   / __ \\_________ _      __/ /___ ___________\n  / /_/ / ___/ __ \\ | /| / / / __ `/ ___/ ___/\n / ____/ /  / /_/ / |/ |/ / / /_/ / /  / /    \n/_/   /_/   \\____/|__/|__/_/\\__,_/_/  /_/     \n                                              \n"
  },
  {
    "path": "ct/headers/proxmox-backup-server",
    "content": "    ____                                             ____             __                    _____                          \n   / __ \\_________  _  ______ ___  ____  _  __      / __ )____ ______/ /____  ______       / ___/___  ______   _____  _____\n  / /_/ / ___/ __ \\| |/_/ __ `__ \\/ __ \\| |/_/_____/ __  / __ `/ ___/ //_/ / / / __ \\______\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / ____/ /  / /_/ />  </ / / / / / /_/ />  </_____/ /_/ / /_/ / /__/ ,< / /_/ / /_/ /_____/__/ /  __/ /   | |/ /  __/ /    \n/_/   /_/   \\____/_/|_/_/ /_/ /_/\\____/_/|_|     /_____/\\__,_/\\___/_/|_|\\__,_/ .___/     /____/\\___/_/    |___/\\___/_/     \n                                                                            /_/                                            \n"
  },
  {
    "path": "ct/headers/proxmox-datacenter-manager",
    "content": "    ____                                             ____        __                        __                  __  ___                                 \n   / __ \\_________  _  ______ ___  ____  _  __      / __ \\____ _/ /_____ _________  ____  / /____  _____      /  |/  /___ _____  ____ _____ ____  _____\n  / /_/ / ___/ __ \\| |/_/ __ `__ \\/ __ \\| |/_/_____/ / / / __ `/ __/ __ `/ ___/ _ \\/ __ \\/ __/ _ \\/ ___/_____/ /|_/ / __ `/ __ \\/ __ `/ __ `/ _ \\/ ___/\n / ____/ /  / /_/ />  </ / / / / / /_/ />  </_____/ /_/ / /_/ / /_/ /_/ / /__/  __/ / / / /_/  __/ /  /_____/ /  / / /_/ / / / / /_/ / /_/ /  __/ /    \n/_/   /_/   \\____/_/|_/_/ /_/ /_/\\____/_/|_|     /_____/\\__,_/\\__/\\__,_/\\___/\\___/_/ /_/\\__/\\___/_/        /_/  /_/\\__,_/_/ /_/\\__,_/\\__, /\\___/_/     \n                                                                                                                                    /____/             \n"
  },
  {
    "path": "ct/headers/proxmox-mail-gateway",
    "content": "    ____                                             __  ___      _ __      ______      __                          \n   / __ \\_________  _  ______ ___  ____  _  __      /  |/  /___ _(_) /     / ____/___ _/ /____ _      ______ ___  __\n  / /_/ / ___/ __ \\| |/_/ __ `__ \\/ __ \\| |/_/_____/ /|_/ / __ `/ / /_____/ / __/ __ `/ __/ _ \\ | /| / / __ `/ / / /\n / ____/ /  / /_/ />  </ / / / / / /_/ />  </_____/ /  / / /_/ / / /_____/ /_/ / /_/ / /_/  __/ |/ |/ / /_/ / /_/ / \n/_/   /_/   \\____/_/|_/_/ /_/ /_/\\____/_/|_|     /_/  /_/\\__,_/_/_/      \\____/\\__,_/\\__/\\___/|__/|__/\\__,_/\\__, /  \n                                                                                                           /____/   \n"
  },
  {
    "path": "ct/headers/ps5-mqtt",
    "content": "    ____  _____ ______      __  _______  ____________\n   / __ \\/ ___// ____/     /  |/  / __ \\/_  __/_  __/\n  / /_/ /\\__ \\/___ \\______/ /|_/ / / / / / /   / /   \n / ____/___/ /___/ /_____/ /  / / /_/ / / /   / /    \n/_/    /____/_____/     /_/  /_/\\___\\_\\/_/   /_/     \n                                                     \n"
  },
  {
    "path": "ct/headers/pterodactyl-panel",
    "content": "    ____  __                      __           __        __      ____                   __\n   / __ \\/ /____  _________  ____/ /___ ______/ /___  __/ /     / __ \\____ _____  ___  / /\n  / /_/ / __/ _ \\/ ___/ __ \\/ __  / __ `/ ___/ __/ / / / /_____/ /_/ / __ `/ __ \\/ _ \\/ / \n / ____/ /_/  __/ /  / /_/ / /_/ / /_/ / /__/ /_/ /_/ / /_____/ ____/ /_/ / / / /  __/ /  \n/_/    \\__/\\___/_/   \\____/\\__,_/\\__,_/\\___/\\__/\\__, /_/     /_/    \\__,_/_/ /_/\\___/_/   \n                                               /____/                                     \n"
  },
  {
    "path": "ct/headers/pterodactyl-wings",
    "content": "    ____  __                      __           __        __    _       ___                 \n   / __ \\/ /____  _________  ____/ /___ ______/ /___  __/ /   | |     / (_)___  ____ ______\n  / /_/ / __/ _ \\/ ___/ __ \\/ __  / __ `/ ___/ __/ / / / /____| | /| / / / __ \\/ __ `/ ___/\n / ____/ /_/  __/ /  / /_/ / /_/ / /_/ / /__/ /_/ /_/ / /_____/ |/ |/ / / / / / /_/ (__  ) \n/_/    \\__/\\___/_/   \\____/\\__,_/\\__,_/\\___/\\__/\\__, /_/      |__/|__/_/_/ /_/\\__, /____/  \n                                               /____/                        /____/        \n"
  },
  {
    "path": "ct/headers/pulse",
    "content": "    ____        __        \n   / __ \\__  __/ /_______ \n  / /_/ / / / / / ___/ _ \\\n / ____/ /_/ / (__  )  __/\n/_/    \\__,_/_/____/\\___/ \n                          \n"
  },
  {
    "path": "ct/headers/pve-scripts-local",
    "content": "    ____ _    ________    _____           _       __             __                     __\n   / __ \\ |  / / ____/   / ___/__________(_)___  / /______      / /   ____  _________ _/ /\n  / /_/ / | / / __/______\\__ \\/ ___/ ___/ / __ \\/ __/ ___/_____/ /   / __ \\/ ___/ __ `/ / \n / ____/| |/ / /__/_____/__/ / /__/ /  / / /_/ / /_(__  )_____/ /___/ /_/ / /__/ /_/ / /  \n/_/     |___/_____/    /____/\\___/_/  /_/ .___/\\__/____/     /_____/\\____/\\___/\\__,_/_/   \n                                       /_/                                                \n"
  },
  {
    "path": "ct/headers/qbittorrent",
    "content": "         ____  _ __  __                             __ \n  ____ _/ __ )(_) /_/ /_____  _____________  ____  / /_\n / __ `/ __  / / __/ __/ __ \\/ ___/ ___/ _ \\/ __ \\/ __/\n/ /_/ / /_/ / / /_/ /_/ /_/ / /  / /  /  __/ / / / /_  \n\\__, /_____/_/\\__/\\__/\\____/_/  /_/   \\___/_/ /_/\\__/  \n  /_/                                                  \n"
  },
  {
    "path": "ct/headers/qdrant",
    "content": "   ____      __                 __ \n  / __ \\____/ /________ _____  / /_\n / / / / __  / ___/ __ `/ __ \\/ __/\n/ /_/ / /_/ / /  / /_/ / / / / /_  \n\\___\\_\\__,_/_/   \\__,_/_/ /_/\\__/  \n                                   \n"
  },
  {
    "path": "ct/headers/qui",
    "content": "   ____        _ \n  / __ \\__  __(_)\n / / / / / / / / \n/ /_/ / /_/ / /  \n\\___\\_\\__,_/_/   \n                 \n"
  },
  {
    "path": "ct/headers/rabbitmq",
    "content": "    ____        __    __    _ __  __  _______ \n   / __ \\____ _/ /_  / /_  (_) /_/  |/  / __ \\\n  / /_/ / __ `/ __ \\/ __ \\/ / __/ /|_/ / / / /\n / _, _/ /_/ / /_/ / /_/ / / /_/ /  / / /_/ / \n/_/ |_|\\__,_/_.___/_.___/_/\\__/_/  /_/\\___\\_\\ \n                                              \n"
  },
  {
    "path": "ct/headers/radarr",
    "content": "    ____            __               \n   / __ \\____ _____/ /___ ___________\n  / /_/ / __ `/ __  / __ `/ ___/ ___/\n / _, _/ /_/ / /_/ / /_/ / /  / /    \n/_/ |_|\\__,_/\\__,_/\\__,_/_/  /_/     \n                                     \n"
  },
  {
    "path": "ct/headers/radicale",
    "content": "    ____            ___            __   \n   / __ \\____ _____/ (_)________ _/ /__ \n  / /_/ / __ `/ __  / / ___/ __ `/ / _ \\\n / _, _/ /_/ / /_/ / / /__/ /_/ / /  __/\n/_/ |_|\\__,_/\\__,_/_/\\___/\\__,_/_/\\___/ \n                                        \n"
  },
  {
    "path": "ct/headers/rclone",
    "content": "    ____       __               \n   / __ \\_____/ /___  ____  ___ \n  / /_/ / ___/ / __ \\/ __ \\/ _ \\\n / _, _/ /__/ / /_/ / / / /  __/\n/_/ |_|\\___/_/\\____/_/ /_/\\___/ \n                                \n"
  },
  {
    "path": "ct/headers/rdtclient",
    "content": "    ____  ____  _______________            __ \n   / __ \\/ __ \\/_  __/ ____/ (_)__  ____  / /_\n  / /_/ / / / / / / / /   / / / _ \\/ __ \\/ __/\n / _, _/ /_/ / / / / /___/ / /  __/ / / / /_  \n/_/ |_/_____/ /_/  \\____/_/_/\\___/_/ /_/\\__/  \n                                              \n"
  },
  {
    "path": "ct/headers/reactive-resume",
    "content": "    ____                  __  _                  ____                               \n   / __ \\___  ____ ______/ /_(_)   _____        / __ \\___  _______  ______ ___  ___ \n  / /_/ / _ \\/ __ `/ ___/ __/ / | / / _ \\______/ /_/ / _ \\/ ___/ / / / __ `__ \\/ _ \\\n / _, _/  __/ /_/ / /__/ /_/ /| |/ /  __/_____/ _, _/  __(__  ) /_/ / / / / / /  __/\n/_/ |_|\\___/\\__,_/\\___/\\__/_/ |___/\\___/     /_/ |_|\\___/____/\\__,_/_/ /_/ /_/\\___/ \n                                                                                    \n"
  },
  {
    "path": "ct/headers/readarr",
    "content": "    ____                 __               \n   / __ \\___  ____ _____/ /___ ___________\n  / /_/ / _ \\/ __ `/ __  / __ `/ ___/ ___/\n / _, _/  __/ /_/ / /_/ / /_/ / /  / /    \n/_/ |_|\\___/\\__,_/\\__,_/\\__,_/_/  /_/     \n                                          \n"
  },
  {
    "path": "ct/headers/readeck",
    "content": "    ____                 __          __  \n   / __ \\___  ____ _____/ /__  _____/ /__\n  / /_/ / _ \\/ __ `/ __  / _ \\/ ___/ //_/\n / _, _/  __/ /_/ / /_/ /  __/ /__/ ,<   \n/_/ |_|\\___/\\__,_/\\__,_/\\___/\\___/_/|_|  \n                                         \n"
  },
  {
    "path": "ct/headers/recyclarr",
    "content": "    ____                       __               \n   / __ \\___  _______  _______/ /___ ___________\n  / /_/ / _ \\/ ___/ / / / ___/ / __ `/ ___/ ___/\n / _, _/  __/ /__/ /_/ / /__/ / /_/ / /  / /    \n/_/ |_|\\___/\\___/\\__, /\\___/_/\\__,_/_/  /_/     \n                /____/                          \n"
  },
  {
    "path": "ct/headers/redis",
    "content": "    ____           ___     \n   / __ \\___  ____/ (_)____\n  / /_/ / _ \\/ __  / / ___/\n / _, _/  __/ /_/ / (__  ) \n/_/ |_|\\___/\\__,_/_/____/  \n                           \n"
  },
  {
    "path": "ct/headers/reitti",
    "content": "    ____       _ __  __  _ \n   / __ \\___  (_) /_/ /_(_)\n  / /_/ / _ \\/ / __/ __/ / \n / _, _/  __/ / /_/ /_/ /  \n/_/ |_|\\___/_/\\__/\\__/_/   \n                           \n"
  },
  {
    "path": "ct/headers/resiliosync",
    "content": "    ____            _ ___          _____                 \n   / __ \\___  _____(_) (_)___     / ___/__  ______  _____\n  / /_/ / _ \\/ ___/ / / / __ \\    \\__ \\/ / / / __ \\/ ___/\n / _, _/  __(__  ) / / / /_/ /   ___/ / /_/ / / / / /__  \n/_/ |_|\\___/____/_/_/_/\\____/   /____/\\__, /_/ /_/\\___/  \n                                     /____/              \n"
  },
  {
    "path": "ct/headers/revealjs",
    "content": "    ____                       __    _______\n   / __ \\___ _   _____  ____ _/ /   / / ___/\n  / /_/ / _ \\ | / / _ \\/ __ `/ /_  / /\\__ \\ \n / _, _/  __/ |/ /  __/ /_/ / / /_/ /___/ / \n/_/ |_|\\___/|___/\\___/\\__,_/_/\\____//____/  \n                                            \n"
  },
  {
    "path": "ct/headers/romm",
    "content": "    ____                  __  ___\n   / __ \\____  ____ ___  /  |/  /\n  / /_/ / __ \\/ __ `__ \\/ /|_/ / \n / _, _/ /_/ / / / / / / /  / /  \n/_/ |_|\\____/_/ /_/ /_/_/  /_/   \n                                 \n"
  },
  {
    "path": "ct/headers/runtipi",
    "content": "    ____              __  _       _ \n   / __ \\__  ______  / /_(_)___  (_)\n  / /_/ / / / / __ \\/ __/ / __ \\/ / \n / _, _/ /_/ / / / / /_/ / /_/ / /  \n/_/ |_|\\__,_/_/ /_/\\__/_/ .___/_/   \n                       /_/          \n"
  },
  {
    "path": "ct/headers/rustdeskserver",
    "content": "    ____             __  ____            __      _____                          \n   / __ \\__  _______/ /_/ __ \\___  _____/ /__   / ___/___  ______   _____  _____\n  / /_/ / / / / ___/ __/ / / / _ \\/ ___/ //_/   \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / _, _/ /_/ (__  ) /_/ /_/ /  __(__  ) ,<     ___/ /  __/ /   | |/ /  __/ /    \n/_/ |_|\\__,_/____/\\__/_____/\\___/____/_/|_|   /____/\\___/_/    |___/\\___/_/     \n                                                                                \n"
  },
  {
    "path": "ct/headers/rustypaste",
    "content": "                    __                         __     \n   _______  _______/ /___  ______  ____ ______/ /____ \n  / ___/ / / / ___/ __/ / / / __ \\/ __ `/ ___/ __/ _ \\\n / /  / /_/ (__  ) /_/ /_/ / /_/ / /_/ (__  ) /_/  __/\n/_/   \\__,_/____/\\__/\\__, / .___/\\__,_/____/\\__/\\___/ \n                    /____/_/                          \n"
  },
  {
    "path": "ct/headers/rwmarkable",
    "content": "                  __  ___           __         __    __   \n   ______      __/  |/  /___ ______/ /______ _/ /_  / /__ \n  / ___/ | /| / / /|_/ / __ `/ ___/ //_/ __ `/ __ \\/ / _ \\\n / /   | |/ |/ / /  / / /_/ / /  / ,< / /_/ / /_/ / /  __/\n/_/    |__/|__/_/  /_/\\__,_/_/  /_/|_|\\__,_/_.___/_/\\___/ \n                                                          \n"
  },
  {
    "path": "ct/headers/sabnzbd",
    "content": "   _____ ___    ____              __        __\n  / ___//   |  / __ )____  ____  / /_  ____/ /\n  \\__ \\/ /| | / __  / __ \\/_  / / __ \\/ __  / \n ___/ / ___ |/ /_/ / / / / / /_/ /_/ / /_/ /  \n/____/_/  |_/_____/_/ /_/ /___/_.___/\\__,_/   \n                                              \n"
  },
  {
    "path": "ct/headers/salt",
    "content": "   _____       ____ \n  / ___/____ _/ / /_\n  \\__ \\/ __ `/ / __/\n ___/ / /_/ / / /_  \n/____/\\__,_/_/\\__/  \n                    \n"
  },
  {
    "path": "ct/headers/scanopy",
    "content": "   _____                                   \n  / ___/_________ _____  ____  ____  __  __\n  \\__ \\/ ___/ __ `/ __ \\/ __ \\/ __ \\/ / / /\n ___/ / /__/ /_/ / / / / /_/ / /_/ / /_/ / \n/____/\\___/\\__,_/_/ /_/\\____/ .___/\\__, /  \n                           /_/    /____/   \n"
  },
  {
    "path": "ct/headers/scraparr",
    "content": "   _____                                      \n  / ___/______________ _____  ____ ___________\n  \\__ \\/ ___/ ___/ __ `/ __ \\/ __ `/ ___/ ___/\n ___/ / /__/ /  / /_/ / /_/ / /_/ / /  / /    \n/____/\\___/_/   \\__,_/ .___/\\__,_/_/  /_/     \n                    /_/                       \n"
  },
  {
    "path": "ct/headers/searxng",
    "content": "   _____                _  __ _   ________\n  / ___/___  ____ _____| |/ // | / / ____/\n  \\__ \\/ _ \\/ __ `/ ___/   //  |/ / / __  \n ___/ /  __/ /_/ / /  /   |/ /|  / /_/ /  \n/____/\\___/\\__,_/_/  /_/|_/_/ |_/\\____/   \n                                          \n"
  },
  {
    "path": "ct/headers/seaweedfs",
    "content": "   _____                                   _____________\n  / ___/___  ____ __      _____  ___  ____/ / ____/ ___/\n  \\__ \\/ _ \\/ __ `/ | /| / / _ \\/ _ \\/ __  / /_   \\__ \\ \n ___/ /  __/ /_/ /| |/ |/ /  __/  __/ /_/ / __/  ___/ / \n/____/\\___/\\__,_/ |__/|__/\\___/\\___/\\__,_/_/    /____/  \n                                                        \n"
  },
  {
    "path": "ct/headers/seelf",
    "content": "                   ______\n   ________  ___  / / __/\n  / ___/ _ \\/ _ \\/ / /_  \n (__  )  __/  __/ / __/  \n/____/\\___/\\___/_/_/     \n                         \n"
  },
  {
    "path": "ct/headers/seerr",
    "content": "   _____                    \n  / ___/___  ___  __________\n  \\__ \\/ _ \\/ _ \\/ ___/ ___/\n ___/ /  __/  __/ /  / /    \n/____/\\___/\\___/_/  /_/     \n                            \n"
  },
  {
    "path": "ct/headers/semaphore",
    "content": "   _____                            __                  \n  / ___/___  ____ ___  ____ _____  / /_  ____  ________ \n  \\__ \\/ _ \\/ __ `__ \\/ __ `/ __ \\/ __ \\/ __ \\/ ___/ _ \\\n ___/ /  __/ / / / / / /_/ / /_/ / / / / /_/ / /  /  __/\n/____/\\___/_/ /_/ /_/\\__,_/ .___/_/ /_/\\____/_/   \\___/ \n                         /_/                            \n"
  },
  {
    "path": "ct/headers/sftpgo",
    "content": "   _____ ________________  ______    \n  / ___// ____/_  __/ __ \\/ ____/___ \n  \\__ \\/ /_    / / / /_/ / / __/ __ \\\n ___/ / __/   / / / ____/ /_/ / /_/ /\n/____/_/     /_/ /_/    \\____/\\____/ \n                                     \n"
  },
  {
    "path": "ct/headers/shelfmark",
    "content": "         __         ______                     __  \n   _____/ /_  ___  / / __/___ ___  ____ ______/ /__\n  / ___/ __ \\/ _ \\/ / /_/ __ `__ \\/ __ `/ ___/ //_/\n (__  ) / / /  __/ / __/ / / / / / /_/ / /  / ,<   \n/____/_/ /_/\\___/_/_/ /_/ /_/ /_/\\__,_/_/  /_/|_|  \n                                                   \n"
  },
  {
    "path": "ct/headers/shinobi",
    "content": "   _____ __    _             __    _ \n  / ___// /_  (_)___  ____  / /_  (_)\n  \\__ \\/ __ \\/ / __ \\/ __ \\/ __ \\/ / \n ___/ / / / / / / / / /_/ / /_/ / /  \n/____/_/ /_/_/_/ /_/\\____/_.___/_/   \n                                     \n"
  },
  {
    "path": "ct/headers/shlink",
    "content": "   _____ __    ___       __  \n  / ___// /_  / (_)___  / /__\n  \\__ \\/ __ \\/ / / __ \\/ //_/\n ___/ / / / / / / / / / ,<   \n/____/_/ /_/_/_/_/ /_/_/|_|  \n                             \n"
  },
  {
    "path": "ct/headers/signoz",
    "content": "   _____ _       _   __         \n  / ___/(_)___ _/ | / /___  ____\n  \\__ \\/ / __ `/  |/ / __ \\/_  /\n ___/ / / /_/ / /|  / /_/ / / /_\n/____/_/\\__, /_/ |_/\\____/ /___/\n       /____/                   \n"
  },
  {
    "path": "ct/headers/silverbullet",
    "content": "   _____ _ __                __          ____     __ \n  / ___/(_) /   _____  _____/ /_  __  __/ / /__  / /_\n  \\__ \\/ / / | / / _ \\/ ___/ __ \\/ / / / / / _ \\/ __/\n ___/ / / /| |/ /  __/ /  / /_/ / /_/ / / /  __/ /_  \n/____/_/_/ |___/\\___/_/  /_.___/\\__,_/_/_/\\___/\\__/  \n                                                     \n"
  },
  {
    "path": "ct/headers/slskd",
    "content": "         __     __       __\n   _____/ /____/ /______/ /\n  / ___/ / ___/ //_/ __  / \n (__  ) (__  ) ,< / /_/ /  \n/____/_/____/_/|_|\\__,_/   \n                           \n"
  },
  {
    "path": "ct/headers/smokeping",
    "content": "   _____                 __        ____  _            \n  / ___/____ ___  ____  / /_____  / __ \\(_)___  ____ _\n  \\__ \\/ __ `__ \\/ __ \\/ //_/ _ \\/ /_/ / / __ \\/ __ `/\n ___/ / / / / / / /_/ / ,< /  __/ ____/ / / / / /_/ / \n/____/_/ /_/ /_/\\____/_/|_|\\___/_/   /_/_/ /_/\\__, /  \n                                             /____/   \n"
  },
  {
    "path": "ct/headers/snipeit",
    "content": "   _____       _            __________\n  / ___/____  (_)___  ___  /  _/_  __/\n  \\__ \\/ __ \\/ / __ \\/ _ \\ / /  / /   \n ___/ / / / / / /_/ /  __// /  / /    \n/____/_/ /_/_/ .___/\\___/___/ /_/     \n            /_/                       \n"
  },
  {
    "path": "ct/headers/snowshare",
    "content": "   _____                     _____ __                  \n  / ___/____  ____ _      __/ ___// /_  ____ _________ \n  \\__ \\/ __ \\/ __ \\ | /| / /\\__ \\/ __ \\/ __ `/ ___/ _ \\\n ___/ / / / / /_/ / |/ |/ /___/ / / / / /_/ / /  /  __/\n/____/_/ /_/\\____/|__/|__//____/_/ /_/\\__,_/_/   \\___/ \n                                                       \n"
  },
  {
    "path": "ct/headers/solidtime",
    "content": "   _____       ___     _________              \n  / ___/____  / (_)___/ /_  __(_)___ ___  ___ \n  \\__ \\/ __ \\/ / / __  / / / / / __ `__ \\/ _ \\\n ___/ / /_/ / / / /_/ / / / / / / / / / /  __/\n/____/\\____/_/_/\\__,_/ /_/ /_/_/ /_/ /_/\\___/ \n                                              \n"
  },
  {
    "path": "ct/headers/sonarqube",
    "content": "   _____                        ____        __       \n  / ___/____  ____  ____ ______/ __ \\__  __/ /_  ___ \n  \\__ \\/ __ \\/ __ \\/ __ `/ ___/ / / / / / / __ \\/ _ \\\n ___/ / /_/ / / / / /_/ / /  / /_/ / /_/ / /_/ /  __/\n/____/\\____/_/ /_/\\__,_/_/   \\___\\_\\__,_/_.___/\\___/ \n                                                     \n"
  },
  {
    "path": "ct/headers/sonarr",
    "content": "   _____                            \n  / ___/____  ____  ____ ___________\n  \\__ \\/ __ \\/ __ \\/ __ `/ ___/ ___/\n ___/ / /_/ / / / / /_/ / /  / /    \n/____/\\____/_/ /_/\\__,_/_/  /_/     \n                                    \n"
  },
  {
    "path": "ct/headers/sonobarr",
    "content": "                           __                   \n   _________  ____  ____  / /_  ____ ___________\n  / ___/ __ \\/ __ \\/ __ \\/ __ \\/ __ `/ ___/ ___/\n (__  ) /_/ / / / / /_/ / /_/ / /_/ / /  / /    \n/____/\\____/_/ /_/\\____/_.___/\\__,_/_/  /_/     \n                                                \n"
  },
  {
    "path": "ct/headers/soulsync",
    "content": "   _____             _______                 \n  / ___/____  __  __/ / ___/__  ______  _____\n  \\__ \\/ __ \\/ / / / /\\__ \\/ / / / __ \\/ ___/\n ___/ / /_/ / /_/ / /___/ / /_/ / / / / /__  \n/____/\\____/\\__,_/_//____/\\__, /_/ /_/\\___/  \n                         /____/              \n"
  },
  {
    "path": "ct/headers/sparkyfitness",
    "content": "   _____                  __         _______ __                      \n  / ___/____  ____ ______/ /____  __/ ____(_) /_____  ___  __________\n  \\__ \\/ __ \\/ __ `/ ___/ //_/ / / / /_  / / __/ __ \\/ _ \\/ ___/ ___/\n ___/ / /_/ / /_/ / /  / ,< / /_/ / __/ / / /_/ / / /  __(__  |__  ) \n/____/ .___/\\__,_/_/  /_/|_|\\__, /_/   /_/\\__/_/ /_/\\___/____/____/  \n    /_/                    /____/                                    \n"
  },
  {
    "path": "ct/headers/speedtest-tracker",
    "content": "   _____                     ____            __      ______                __            \n  / ___/____  ___  ___  ____/ / /____  _____/ /_    /_  __/________ ______/ /_____  _____\n  \\__ \\/ __ \\/ _ \\/ _ \\/ __  / __/ _ \\/ ___/ __/_____/ / / ___/ __ `/ ___/ //_/ _ \\/ ___/\n ___/ / /_/ /  __/  __/ /_/ / /_/  __(__  ) /_/_____/ / / /  / /_/ / /__/ ,< /  __/ /    \n/____/ .___/\\___/\\___/\\__,_/\\__/\\___/____/\\__/     /_/ /_/   \\__,_/\\___/_/|_|\\___/_/     \n    /_/                                                                                  \n"
  },
  {
    "path": "ct/headers/split-pro",
    "content": "   _____       ___ __        ____           \n  / ___/____  / (_) /_      / __ \\_________ \n  \\__ \\/ __ \\/ / / __/_____/ /_/ / ___/ __ \\\n ___/ / /_/ / / / /_/_____/ ____/ /  / /_/ /\n/____/ .___/_/_/\\__/     /_/   /_/   \\____/ \n    /_/                                     \n"
  },
  {
    "path": "ct/headers/splunk-enterprise",
    "content": "   _____       __            __         ______      __                       _         \n  / ___/____  / /_  ______  / /__      / ____/___  / /____  _________  _____(_)_______ \n  \\__ \\/ __ \\/ / / / / __ \\/ //_/_____/ __/ / __ \\/ __/ _ \\/ ___/ __ \\/ ___/ / ___/ _ \\\n ___/ / /_/ / / /_/ / / / / ,< /_____/ /___/ / / / /_/  __/ /  / /_/ / /  / (__  )  __/\n/____/ .___/_/\\__,_/_/ /_/_/|_|     /_____/_/ /_/\\__/\\___/_/  / .___/_/  /_/____/\\___/ \n    /_/                                                      /_/                       \n"
  },
  {
    "path": "ct/headers/spoolman",
    "content": "   _____                   __                    \n  / ___/____  ____  ____  / /___ ___  ____ _____ \n  \\__ \\/ __ \\/ __ \\/ __ \\/ / __ `__ \\/ __ `/ __ \\\n ___/ / /_/ / /_/ / /_/ / / / / / / / /_/ / / / /\n/____/ .___/\\____/\\____/_/_/ /_/ /_/\\__,_/_/ /_/ \n    /_/                                          \n"
  },
  {
    "path": "ct/headers/sportarr",
    "content": "   _____                  __                 \n  / ___/____  ____  _____/ /_____ ___________\n  \\__ \\/ __ \\/ __ \\/ ___/ __/ __ `/ ___/ ___/\n ___/ / /_/ / /_/ / /  / /_/ /_/ / /  / /    \n/____/ .___/\\____/_/   \\__/\\__,_/_/  /_/     \n    /_/                                      \n"
  },
  {
    "path": "ct/headers/sqlserver2022",
    "content": "   _____ ____    __       _____                              ___   ____ ___  ___ \n  / ___// __ \\  / /      / ___/___  ______   _____  _____   |__ \\ / __ \\__ \\|__ \\\n  \\__ \\/ / / / / /       \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/   __/ // / / /_/ /__/ /\n ___/ / /_/ / / /___    ___/ /  __/ /   | |/ /  __/ /      / __// /_/ / __// __/ \n/____/\\___\\_\\/_____/   /____/\\___/_/    |___/\\___/_/      /____/\\____/____/____/ \n                                                                                 \n"
  },
  {
    "path": "ct/headers/sqlserver2025",
    "content": "   _____ ____    __       _____                              ___   ____ ___   ______\n  / ___// __ \\  / /      / ___/___  ______   _____  _____   |__ \\ / __ \\__ \\ / ____/\n  \\__ \\/ / / / / /       \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/   __/ // / / /_/ //___ \\  \n ___/ / /_/ / / /___    ___/ /  __/ /   | |/ /  __/ /      / __// /_/ / __/____/ /  \n/____/\\___\\_\\/_____/   /____/\\___/_/    |___/\\___/_/      /____/\\____/____/_____/   \n                                                                                    \n"
  },
  {
    "path": "ct/headers/step-ca",
    "content": "         __                             \n   _____/ /____  ____        _________ _\n  / ___/ __/ _ \\/ __ \\______/ ___/ __ `/\n (__  ) /_/  __/ /_/ /_____/ /__/ /_/ / \n/____/\\__/\\___/ .___/      \\___/\\__,_/  \n             /_/                        \n"
  },
  {
    "path": "ct/headers/stirling-pdf",
    "content": "   _____ __  _      ___                   ____  ____  ______\n  / ___// /_(_)____/ (_)___  ____ _      / __ \\/ __ \\/ ____/\n  \\__ \\/ __/ / ___/ / / __ \\/ __ `/_____/ /_/ / / / / /_    \n ___/ / /_/ / /  / / / / / / /_/ /_____/ ____/ /_/ / __/    \n/____/\\__/_/_/  /_/_/_/ /_/\\__, /     /_/   /_____/_/       \n                          /____/                            \n"
  },
  {
    "path": "ct/headers/storybook",
    "content": "   _____ __                   __                __  \n  / ___// /_____  _______  __/ /_  ____  ____  / /__\n  \\__ \\/ __/ __ \\/ ___/ / / / __ \\/ __ \\/ __ \\/ //_/\n ___/ / /_/ /_/ / /  / /_/ / /_/ / /_/ / /_/ / ,<   \n/____/\\__/\\____/_/   \\__, /_.___/\\____/\\____/_/|_|  \n                    /____/                          \n"
  },
  {
    "path": "ct/headers/storyteller",
    "content": "   _____ __                   __       ____         \n  / ___// /_____  _______  __/ /____  / / /__  _____\n  \\__ \\/ __/ __ \\/ ___/ / / / __/ _ \\/ / / _ \\/ ___/\n ___/ / /_/ /_/ / /  / /_/ / /_/  __/ / /  __/ /    \n/____/\\__/\\____/_/   \\__, /\\__/\\___/_/_/\\___/_/     \n                    /____/                          \n"
  },
  {
    "path": "ct/headers/strapi",
    "content": "   _____ __                   _ \n  / ___// /__________ _____  (_)\n  \\__ \\/ __/ ___/ __ `/ __ \\/ / \n ___/ / /_/ /  / /_/ / /_/ / /  \n/____/\\__/_/   \\__,_/ .___/_/   \n                   /_/          \n"
  },
  {
    "path": "ct/headers/streamlink-webui",
    "content": "         __                            ___       __                      __          _ \n   _____/ /_________  ____ _____ ___  / (_)___  / /__     _      _____  / /_  __  __(_)\n  / ___/ __/ ___/ _ \\/ __ `/ __ `__ \\/ / / __ \\/ //_/____| | /| / / _ \\/ __ \\/ / / / / \n (__  ) /_/ /  /  __/ /_/ / / / / / / / / / / / ,< /_____/ |/ |/ /  __/ /_/ / /_/ / /  \n/____/\\__/_/   \\___/\\__,_/_/ /_/ /_/_/_/_/ /_/_/|_|      |__/|__/\\___/_.___/\\__,_/_/   \n                                                                                       \n"
  },
  {
    "path": "ct/headers/stylus",
    "content": "   _____ __        __          \n  / ___// /___  __/ /_  _______\n  \\__ \\/ __/ / / / / / / / ___/\n ___/ / /_/ /_/ / / /_/ (__  ) \n/____/\\__/\\__, /_/\\__,_/____/  \n         /____/                \n"
  },
  {
    "path": "ct/headers/sure",
    "content": "   _____               \n  / ___/__  __________ \n  \\__ \\/ / / / ___/ _ \\\n ___/ / /_/ / /  /  __/\n/____/\\__,_/_/   \\___/ \n                       \n"
  },
  {
    "path": "ct/headers/swizzin",
    "content": "   _____         _           _     \n  / ___/      __(_)_______  (_)___ \n  \\__ \\ | /| / / /_  /_  / / / __ \\\n ___/ / |/ |/ / / / /_/ /_/ / / / /\n/____/|__/|__/_/ /___/___/_/_/ /_/ \n                                   \n"
  },
  {
    "path": "ct/headers/syncthing",
    "content": "   _____                  __  __    _            \n  / ___/__  ______  _____/ /_/ /_  (_)___  ____ _\n  \\__ \\/ / / / __ \\/ ___/ __/ __ \\/ / __ \\/ __ `/\n ___/ / /_/ / / / / /__/ /_/ / / / / / / / /_/ / \n/____/\\__, /_/ /_/\\___/\\__/_/ /_/_/_/ /_/\\__, /  \n     /____/                             /____/   \n"
  },
  {
    "path": "ct/headers/tandoor",
    "content": "  ______                __                \n /_  __/___ _____  ____/ /___  ____  _____\n  / / / __ `/ __ \\/ __  / __ \\/ __ \\/ ___/\n / / / /_/ / / / / /_/ / /_/ / /_/ / /    \n/_/  \\__,_/_/ /_/\\__,_/\\____/\\____/_/     \n                                          \n"
  },
  {
    "path": "ct/headers/tasmoadmin",
    "content": "  ______                           ___       __          _     \n /_  __/___ __________ ___  ____  /   | ____/ /___ ___  (_)___ \n  / / / __ `/ ___/ __ `__ \\/ __ \\/ /| |/ __  / __ `__ \\/ / __ \\\n / / / /_/ (__  ) / / / / / /_/ / ___ / /_/ / / / / / / / / / /\n/_/  \\__,_/____/_/ /_/ /_/\\____/_/  |_\\__,_/_/ /_/ /_/_/_/ /_/ \n                                                               \n"
  },
  {
    "path": "ct/headers/tasmocompiler",
    "content": "  ______                           ______                      _ __         \n /_  __/___ __________ ___  ____  / ____/___  ____ ___  ____  (_) /__  _____\n  / / / __ `/ ___/ __ `__ \\/ __ \\/ /   / __ \\/ __ `__ \\/ __ \\/ / / _ \\/ ___/\n / / / /_/ (__  ) / / / / / /_/ / /___/ /_/ / / / / / / /_/ / / /  __/ /    \n/_/  \\__,_/____/_/ /_/ /_/\\____/\\____/\\____/_/ /_/ /_/ .___/_/_/\\___/_/     \n                                                    /_/                     \n"
  },
  {
    "path": "ct/headers/tautulli",
    "content": "  ______            __        _____ \n /_  __/___ ___  __/ /___  __/ / (_)\n  / / / __ `/ / / / __/ / / / / / / \n / / / /_/ / /_/ / /_/ /_/ / / / /  \n/_/  \\__,_/\\__,_/\\__/\\__,_/_/_/_/   \n                                    \n"
  },
  {
    "path": "ct/headers/tdarr",
    "content": "  ______    __               \n /_  __/___/ /___ ___________\n  / / / __  / __ `/ ___/ ___/\n / / / /_/ / /_/ / /  / /    \n/_/  \\__,_/\\__,_/_/  /_/     \n                             \n"
  },
  {
    "path": "ct/headers/teable",
    "content": "  ______           __    __   \n /_  __/__  ____ _/ /_  / /__ \n  / / / _ \\/ __ `/ __ \\/ / _ \\\n / / /  __/ /_/ / /_/ / /  __/\n/_/  \\___/\\__,_/_.___/_/\\___/ \n                              \n"
  },
  {
    "path": "ct/headers/teamspeak-server",
    "content": "  ______                                           __        _____                          \n /_  __/__  ____ _____ ___  _________  ___  ____ _/ /__     / ___/___  ______   _____  _____\n  / / / _ \\/ __ `/ __ `__ \\/ ___/ __ \\/ _ \\/ __ `/ //_/_____\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n / / /  __/ /_/ / / / / / (__  ) /_/ /  __/ /_/ / ,< /_____/__/ /  __/ /   | |/ /  __/ /    \n/_/  \\___/\\__,_/_/ /_/ /_/____/ .___/\\___/\\__,_/_/|_|     /____/\\___/_/    |___/\\___/_/     \n                             /_/                                                            \n"
  },
  {
    "path": "ct/headers/technitiumdns",
    "content": "  ______          __          _ __  _                    ____  _   _______\n /_  __/__  _____/ /_  ____  (_) /_(_)_  ______ ___     / __ \\/ | / / ___/\n  / / / _ \\/ ___/ __ \\/ __ \\/ / __/ / / / / __ `__ \\   / / / /  |/ /\\__ \\ \n / / /  __/ /__/ / / / / / / / /_/ / /_/ / / / / / /  / /_/ / /|  /___/ / \n/_/  \\___/\\___/_/ /_/_/ /_/_/\\__/_/\\__,_/_/ /_/ /_/  /_____/_/ |_//____/  \n                                                                          \n"
  },
  {
    "path": "ct/headers/teddycloud",
    "content": "  ______         __    __      ________                __\n /_  __/__  ____/ /___/ /_  __/ ____/ /___  __  ______/ /\n  / / / _ \\/ __  / __  / / / / /   / / __ \\/ / / / __  / \n / / /  __/ /_/ / /_/ / /_/ / /___/ / /_/ / /_/ / /_/ /  \n/_/  \\___/\\__,_/\\__,_/\\__, /\\____/_/\\____/\\__,_/\\__,_/   \n                     /____/                              \n"
  },
  {
    "path": "ct/headers/telegraf",
    "content": "   __       __                      ____\n  / /____  / /__  ____ __________ _/ __/\n / __/ _ \\/ / _ \\/ __ `/ ___/ __ `/ /_  \n/ /_/  __/ /  __/ /_/ / /  / /_/ / __/  \n\\__/\\___/_/\\___/\\__, /_/   \\__,_/_/     \n               /____/                   \n"
  },
  {
    "path": "ct/headers/teleport",
    "content": "  ______     __                      __ \n /_  __/__  / /__  ____  ____  _____/ /_\n  / / / _ \\/ / _ \\/ __ \\/ __ \\/ ___/ __/\n / / /  __/ /  __/ /_/ / /_/ / /  / /_  \n/_/  \\___/_/\\___/ .___/\\____/_/   \\__/  \n               /_/                      \n"
  },
  {
    "path": "ct/headers/termix",
    "content": "  ______                    _     \n /_  __/__  _________ ___  (_)  __\n  / / / _ \\/ ___/ __ `__ \\/ / |/_/\n / / /  __/ /  / / / / / / />  <  \n/_/  \\___/_/  /_/ /_/ /_/_/_/|_|  \n                                  \n"
  },
  {
    "path": "ct/headers/the-lounge",
    "content": "  ________               __                               \n /_  __/ /_  ___        / /   ____  __  ______  ____ ____ \n  / / / __ \\/ _ \\______/ /   / __ \\/ / / / __ \\/ __ `/ _ \\\n / / / / / /  __/_____/ /___/ /_/ / /_/ / / / / /_/ /  __/\n/_/ /_/ /_/\\___/     /_____/\\____/\\__,_/_/ /_/\\__, /\\___/ \n                                             /____/       \n"
  },
  {
    "path": "ct/headers/thingsboard",
    "content": "  ________    _                  ____                       __\n /_  __/ /_  (_)___  ____ ______/ __ )____  ____ __________/ /\n  / / / __ \\/ / __ \\/ __ `/ ___/ __  / __ \\/ __ `/ ___/ __  / \n / / / / / / / / / / /_/ (__  ) /_/ / /_/ / /_/ / /  / /_/ /  \n/_/ /_/ /_/_/_/ /_/\\__, /____/_____/\\____/\\__,_/_/   \\__,_/   \n                  /____/                                      \n"
  },
  {
    "path": "ct/headers/threadfin",
    "content": "  ________                        _______     \n /_  __/ /_  ________  ____ _____/ / __(_)___ \n  / / / __ \\/ ___/ _ \\/ __ `/ __  / /_/ / __ \\\n / / / / / / /  /  __/ /_/ / /_/ / __/ / / / /\n/_/ /_/ /_/_/   \\___/\\__,_/\\__,_/_/ /_/_/ /_/ \n                                              \n"
  },
  {
    "path": "ct/headers/tianji",
    "content": "  _______               _ _ \n /_  __(_)___ _____    (_|_)\n  / / / / __ `/ __ \\  / / / \n / / / / /_/ / / / / / / /  \n/_/ /_/\\__,_/_/ /_/_/ /_/   \n                 /___/      \n"
  },
  {
    "path": "ct/headers/tinyauth",
    "content": "  _______                         __  __  \n /_  __(_)___  __  ______ ___  __/ /_/ /_ \n  / / / / __ \\/ / / / __ `/ / / / __/ __ \\\n / / / / / / / /_/ / /_/ / /_/ / /_/ / / /\n/_/ /_/_/ /_/\\__, /\\__,_/\\__,_/\\__/_/ /_/ \n            /____/                        \n"
  },
  {
    "path": "ct/headers/traccar",
    "content": "  ______                               \n /_  __/________ _______________ ______\n  / / / ___/ __ `/ ___/ ___/ __ `/ ___/\n / / / /  / /_/ / /__/ /__/ /_/ / /    \n/_/ /_/   \\__,_/\\___/\\___/\\__,_/_/     \n                                       \n"
  },
  {
    "path": "ct/headers/tracearr",
    "content": "  ______                                    \n /_  __/________ _________  ____ ___________\n  / / / ___/ __ `/ ___/ _ \\/ __ `/ ___/ ___/\n / / / /  / /_/ / /__/  __/ /_/ / /  / /    \n/_/ /_/   \\__,_/\\___/\\___/\\__,_/_/  /_/     \n                                            \n"
  },
  {
    "path": "ct/headers/tracktor",
    "content": "   __                  __   __            \n  / /__________ ______/ /__/ /_____  _____\n / __/ ___/ __ `/ ___/ //_/ __/ __ \\/ ___/\n/ /_/ /  / /_/ / /__/ ,< / /_/ /_/ / /    \n\\__/_/   \\__,_/\\___/_/|_|\\__/\\____/_/     \n                                          \n"
  },
  {
    "path": "ct/headers/traefik",
    "content": "  ______                _____ __  \n /_  __/________ ____  / __(_) /__\n  / / / ___/ __ `/ _ \\/ /_/ / //_/\n / / / /  / /_/ /  __/ __/ / ,<   \n/_/ /_/   \\__,_/\\___/_/ /_/_/|_|  \n                                  \n"
  },
  {
    "path": "ct/headers/transmission",
    "content": "  ______                                _           _           \n /_  __/________ _____  _________ ___  (_)_________(_)___  ____ \n  / / / ___/ __ `/ __ \\/ ___/ __ `__ \\/ / ___/ ___/ / __ \\/ __ \\\n / / / /  / /_/ / / / (__  ) / / / / / (__  |__  ) / /_/ / / / /\n/_/ /_/   \\__,_/_/ /_/____/_/ /_/ /_/_/____/____/_/\\____/_/ /_/ \n                                                                \n"
  },
  {
    "path": "ct/headers/transmute",
    "content": "  ______                                      __     \n /_  __/________ _____  _________ ___  __  __/ /____ \n  / / / ___/ __ `/ __ \\/ ___/ __ `__ \\/ / / / __/ _ \\\n / / / /  / /_/ / / / (__  ) / / / / / /_/ / /_/  __/\n/_/ /_/   \\__,_/_/ /_/____/_/ /_/ /_/\\__,_/\\__/\\___/ \n                                                     \n"
  },
  {
    "path": "ct/headers/trek",
    "content": "  __________  ________ __\n /_  __/ __ \\/ ____/ //_/\n  / / / /_/ / __/ / ,<   \n / / / _, _/ /___/ /| |  \n/_/ /_/ |_/_____/_/ |_|  \n                         \n"
  },
  {
    "path": "ct/headers/trilium",
    "content": "  ______     _ ___               \n /_  __/____(_) (_)_  ______ ___ \n  / / / ___/ / / / / / / __ `__ \\\n / / / /  / / / / /_/ / / / / / /\n/_/ /_/  /_/_/_/\\__,_/_/ /_/ /_/ \n                                 \n"
  },
  {
    "path": "ct/headers/trip",
    "content": "  __________  ________ \n /_  __/ __ \\/  _/ __ \\\n  / / / /_/ // // /_/ /\n / / / _, _// // ____/ \n/_/ /_/ |_/___/_/      \n                       \n"
  },
  {
    "path": "ct/headers/tubearchivist",
    "content": "  ______      __            ___              __    _       _      __ \n /_  __/_  __/ /_  ___     /   |  __________/ /_  (_)   __(_)____/ /_\n  / / / / / / __ \\/ _ \\   / /| | / ___/ ___/ __ \\/ / | / / / ___/ __/\n / / / /_/ / /_/ /  __/  / ___ |/ /  / /__/ / / / /| |/ / (__  ) /_  \n/_/  \\__,_/_.___/\\___/  /_/  |_/_/   \\___/_/ /_/_/ |___/_/____/\\__/  \n                                                                     \n"
  },
  {
    "path": "ct/headers/tududi",
    "content": "  ______          __          ___ \n /_  __/_  ______/ /_  ______/ (_)\n  / / / / / / __  / / / / __  / / \n / / / /_/ / /_/ / /_/ / /_/ / /  \n/_/  \\__,_/\\__,_/\\__,_/\\__,_/_/   \n                                  \n"
  },
  {
    "path": "ct/headers/tunarr",
    "content": "  ______                           \n /_  __/_  ______  ____ ___________\n  / / / / / / __ \\/ __ `/ ___/ ___/\n / / / /_/ / / / / /_/ / /  / /    \n/_/  \\__,_/_/ /_/\\__,_/_/  /_/     \n                                   \n"
  },
  {
    "path": "ct/headers/twingate-connector",
    "content": "  ______         _                   __             ______                            __            \n /_  __/      __(_)___  ____ _____ _/ /____        / ____/___  ____  ____  ___  _____/ /_____  _____\n  / / | | /| / / / __ \\/ __ `/ __ `/ __/ _ \\______/ /   / __ \\/ __ \\/ __ \\/ _ \\/ ___/ __/ __ \\/ ___/\n / /  | |/ |/ / / / / / /_/ / /_/ / /_/  __/_____/ /___/ /_/ / / / / / / /  __/ /__/ /_/ /_/ / /    \n/_/   |__/|__/_/_/ /_/\\__, /\\__,_/\\__/\\___/      \\____/\\____/_/ /_/_/ /_/\\___/\\___/\\__/\\____/_/     \n                     /____/                                                                         \n"
  },
  {
    "path": "ct/headers/ubuntu",
    "content": "   __  ____                __       \n  / / / / /_  __  ______  / /___  __\n / / / / __ \\/ / / / __ \\/ __/ / / /\n/ /_/ / /_/ / /_/ / / / / /_/ /_/ / \n\\____/_.___/\\__,_/_/ /_/\\__/\\__,_/  \n                                    \n"
  },
  {
    "path": "ct/headers/uhf",
    "content": "   __  ____  ________\n  / / / / / / / ____/\n / / / / /_/ / /_    \n/ /_/ / __  / __/    \n\\____/_/ /_/_/       \n                     \n"
  },
  {
    "path": "ct/headers/umami",
    "content": "   __  __                          _ \n  / / / /___ ___  ____ _____ ___  (_)\n / / / / __ `__ \\/ __ `/ __ `__ \\/ / \n/ /_/ / / / / / / /_/ / / / / / / /  \n\\____/_/ /_/ /_/\\__,_/_/ /_/ /_/_/   \n                                     \n"
  },
  {
    "path": "ct/headers/umlautadaptarr",
    "content": "   __  __          __            __  ___       __            __                 \n  / / / /___ ___  / /___ ___  __/ /_/   | ____/ /___ _____  / /_____ ___________\n / / / / __ `__ \\/ / __ `/ / / / __/ /| |/ __  / __ `/ __ \\/ __/ __ `/ ___/ ___/\n/ /_/ / / / / / / / /_/ / /_/ / /_/ ___ / /_/ / /_/ / /_/ / /_/ /_/ / /  / /    \n\\____/_/ /_/ /_/_/\\__,_/\\__,_/\\__/_/  |_\\__,_/\\__,_/ .___/\\__/\\__,_/_/  /_/     \n                                                  /_/                           \n"
  },
  {
    "path": "ct/headers/unbound",
    "content": "   __  __      __                          __\n  / / / /___  / /_  ____  __  ______  ____/ /\n / / / / __ \\/ __ \\/ __ \\/ / / / __ \\/ __  / \n/ /_/ / / / / /_/ / /_/ / /_/ / / / / /_/ /  \n\\____/_/ /_/_.___/\\____/\\__,_/_/ /_/\\__,_/   \n                                             \n"
  },
  {
    "path": "ct/headers/unifi-os-server",
    "content": "   __  __      _ _______       ____  _____      _____                          \n  / / / /___  (_) ____(_)     / __ \\/ ___/     / ___/___  ______   _____  _____\n / / / / __ \\/ / /_  / /_____/ / / /\\__ \\______\\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n/ /_/ / / / / / __/ / /_____/ /_/ /___/ /_____/__/ /  __/ /   | |/ /  __/ /    \n\\____/_/ /_/_/_/   /_/      \\____//____/     /____/\\___/_/    |___/\\___/_/     \n                                                                               \n"
  },
  {
    "path": "ct/headers/unmanic",
    "content": "   __  __                            _     \n  / / / /___  ____ ___  ____ _____  (_)____\n / / / / __ \\/ __ `__ \\/ __ `/ __ \\/ / ___/\n/ /_/ / / / / / / / / / /_/ / / / / / /__  \n\\____/_/ /_/_/ /_/ /_/\\__,_/_/ /_/_/\\___/  \n                                           \n"
  },
  {
    "path": "ct/headers/upgopher",
    "content": "   __  __                        __             \n  / / / /___  ____ _____  ____  / /_  ___  _____\n / / / / __ \\/ __ `/ __ \\/ __ \\/ __ \\/ _ \\/ ___/\n/ /_/ / /_/ / /_/ / /_/ / /_/ / / / /  __/ /    \n\\____/ .___/\\__, /\\____/ .___/_/ /_/\\___/_/     \n    /_/    /____/     /_/                       \n"
  },
  {
    "path": "ct/headers/upsnap",
    "content": "   __  __     _____                 \n  / / / /___ / ___/____  ____ _____ \n / / / / __ \\\\__ \\/ __ \\/ __ `/ __ \\\n/ /_/ / /_/ /__/ / / / / /_/ / /_/ /\n\\____/ .___/____/_/ /_/\\__,_/ .___/ \n    /_/                    /_/      \n"
  },
  {
    "path": "ct/headers/uptimekuma",
    "content": "   __  __      __  _                   __ __                     \n  / / / /___  / /_(_)___ ___  ___     / //_/_  ______ ___  ____ _\n / / / / __ \\/ __/ / __ `__ \\/ _ \\   / ,< / / / / __ `__ \\/ __ `/\n/ /_/ / /_/ / /_/ / / / / / /  __/  / /| / /_/ / / / / / / /_/ / \n\\____/ .___/\\__/_/_/ /_/ /_/\\___/  /_/ |_\\__,_/_/ /_/ /_/\\__,_/  \n    /_/                                                          \n"
  },
  {
    "path": "ct/headers/urbackupserver",
    "content": "   __  __     ____             __                  _____                          \n  / / / /____/ __ )____ ______/ /____  ______     / ___/___  ______   _____  _____\n / / / / ___/ __  / __ `/ ___/ //_/ / / / __ \\    \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n/ /_/ / /  / /_/ / /_/ / /__/ ,< / /_/ / /_/ /   ___/ /  __/ /   | |/ /  __/ /    \n\\____/_/  /_____/\\__,_/\\___/_/|_|\\__,_/ .___/   /____/\\___/_/    |___/\\___/_/     \n                                     /_/                                          \n"
  },
  {
    "path": "ct/headers/valkey",
    "content": " _    __      ____             \n| |  / /___ _/ / /_____  __  __\n| | / / __ `/ / //_/ _ \\/ / / /\n| |/ / /_/ / / ,< /  __/ /_/ / \n|___/\\__,_/_/_/|_|\\___/\\__, /  \n                      /____/   \n"
  },
  {
    "path": "ct/headers/vaultwarden",
    "content": " _    __            ____                          __         \n| |  / /___ ___  __/ / /__      ______ __________/ /__  ____ \n| | / / __ `/ / / / / __/ | /| / / __ `/ ___/ __  / _ \\/ __ \\\n| |/ / /_/ / /_/ / / /_ | |/ |/ / /_/ / /  / /_/ /  __/ / / /\n|___/\\__,_/\\__,_/_/\\__/ |__/|__/\\__,_/_/   \\__,_/\\___/_/ /_/ \n                                                             \n"
  },
  {
    "path": "ct/headers/versitygw",
    "content": " _    __               _ __        _______       __\n| |  / /__  __________(_) /___  __/ ____/ |     / /\n| | / / _ \\/ ___/ ___/ / __/ / / / / __ | | /| / / \n| |/ /  __/ /  (__  ) / /_/ /_/ / /_/ / | |/ |/ /  \n|___/\\___/_/  /____/_/\\__/\\__, /\\____/  |__/|__/   \n                         /____/                    \n"
  },
  {
    "path": "ct/headers/victoriametrics",
    "content": " _    ___      __             _       __  ___     __       _          \n| |  / (_)____/ /_____  _____(_)___ _/  |/  /__  / /______(_)_________\n| | / / / ___/ __/ __ \\/ ___/ / __ `/ /|_/ / _ \\/ __/ ___/ / ___/ ___/\n| |/ / / /__/ /_/ /_/ / /  / / /_/ / /  / /  __/ /_/ /  / / /__(__  ) \n|___/_/\\___/\\__/\\____/_/  /_/\\__,_/_/  /_/\\___/\\__/_/  /_/\\___/____/  \n                                                                      \n"
  },
  {
    "path": "ct/headers/vikunja",
    "content": " _    ___ __                 _      \n| |  / (_) /____  ______    (_)___ _\n| | / / / //_/ / / / __ \\  / / __ `/\n| |/ / / ,< / /_/ / / / / / / /_/ / \n|___/_/_/|_|\\__,_/_/ /_/_/ /\\__,_/  \n                      /___/         \n"
  },
  {
    "path": "ct/headers/wallabag",
    "content": " _       __      ____      __               \n| |     / /___ _/ / /___ _/ /_  ____ _____ _\n| | /| / / __ `/ / / __ `/ __ \\/ __ `/ __ `/\n| |/ |/ / /_/ / / / /_/ / /_/ / /_/ / /_/ / \n|__/|__/\\__,_/_/_/\\__,_/_.___/\\__,_/\\__, /  \n                                   /____/   \n"
  },
  {
    "path": "ct/headers/wallos",
    "content": " _       __      ____          \n| |     / /___ _/ / /___  _____\n| | /| / / __ `/ / / __ \\/ ___/\n| |/ |/ / /_/ / / / /_/ (__  ) \n|__/|__/\\__,_/_/_/\\____/____/  \n                               \n"
  },
  {
    "path": "ct/headers/wanderer",
    "content": " _       __                __                   \n| |     / /___ _____  ____/ /__  ________  _____\n| | /| / / __ `/ __ \\/ __  / _ \\/ ___/ _ \\/ ___/\n| |/ |/ / /_/ / / / / /_/ /  __/ /  /  __/ /    \n|__/|__/\\__,_/_/ /_/\\__,_/\\___/_/   \\___/_/     \n                                                \n"
  },
  {
    "path": "ct/headers/warracker",
    "content": " _       __                           __            \n| |     / /___ _______________ ______/ /_____  _____\n| | /| / / __ `/ ___/ ___/ __ `/ ___/ //_/ _ \\/ ___/\n| |/ |/ / /_/ / /  / /  / /_/ / /__/ ,< /  __/ /    \n|__/|__/\\__,_/_/  /_/   \\__,_/\\___/_/|_|\\___/_/     \n                                                    \n"
  },
  {
    "path": "ct/headers/wastebin",
    "content": " _       __           __       __    _     \n| |     / /___ ______/ /____  / /_  (_)___ \n| | /| / / __ `/ ___/ __/ _ \\/ __ \\/ / __ \\\n| |/ |/ / /_/ (__  ) /_/  __/ /_/ / / / / /\n|__/|__/\\__,_/____/\\__/\\___/_.___/_/_/ /_/ \n                                           \n"
  },
  {
    "path": "ct/headers/watcharr",
    "content": " _       __      __       __                   \n| |     / /___ _/ /______/ /_  ____ ___________\n| | /| / / __ `/ __/ ___/ __ \\/ __ `/ ___/ ___/\n| |/ |/ / /_/ / /_/ /__/ / / / /_/ / /  / /    \n|__/|__/\\__,_/\\__/\\___/_/ /_/\\__,_/_/  /_/     \n                                               \n"
  },
  {
    "path": "ct/headers/watchyourlan",
    "content": " _       __      __       ____  __                 __    ___    _   __\n| |     / /___ _/ /______/ /\\ \\/ /___  __  _______/ /   /   |  / | / /\n| | /| / / __ `/ __/ ___/ __ \\  / __ \\/ / / / ___/ /   / /| | /  |/ / \n| |/ |/ / /_/ / /_/ /__/ / / / / /_/ / /_/ / /  / /___/ ___ |/ /|  /  \n|__/|__/\\__,_/\\__/\\___/_/ /_/_/\\____/\\__,_/_/  /_____/_/  |_/_/ |_/   \n                                                                      \n"
  },
  {
    "path": "ct/headers/wavelog",
    "content": " _       __                 __           \n| |     / /___ __   _____  / /___  ____ _\n| | /| / / __ `/ | / / _ \\/ / __ \\/ __ `/\n| |/ |/ / /_/ /| |/ /  __/ / /_/ / /_/ / \n|__/|__/\\__,_/ |___/\\___/_/\\____/\\__, /  \n                                /____/   \n"
  },
  {
    "path": "ct/headers/wazuh",
    "content": " _       __                  __  \n| |     / /___ _____  __  __/ /_ \n| | /| / / __ `/_  / / / / / __ \\\n| |/ |/ / /_/ / / /_/ /_/ / / / /\n|__/|__/\\__,_/ /___/\\__,_/_/ /_/ \n                                 \n"
  },
  {
    "path": "ct/headers/wealthfolio",
    "content": " _       __           ____  __    ____      ___     \n| |     / /__  ____ _/ / /_/ /_  / __/___  / (_)___ \n| | /| / / _ \\/ __ `/ / __/ __ \\/ /_/ __ \\/ / / __ \\\n| |/ |/ /  __/ /_/ / / /_/ / / / __/ /_/ / / / /_/ /\n|__/|__/\\___/\\__,_/_/\\__/_/ /_/_/  \\____/_/_/\\____/ \n                                                    \n"
  },
  {
    "path": "ct/headers/web-check",
    "content": "                __               __              __  \n _      _____  / /_        _____/ /_  ___  _____/ /__\n| | /| / / _ \\/ __ \\______/ ___/ __ \\/ _ \\/ ___/ //_/\n| |/ |/ /  __/ /_/ /_____/ /__/ / / /  __/ /__/ ,<   \n|__/|__/\\___/_.___/      \\___/_/ /_/\\___/\\___/_/|_|  \n                                                     \n"
  },
  {
    "path": "ct/headers/wger",
    "content": "                          \n _      ______ ____  _____\n| | /| / / __ `/ _ \\/ ___/\n| |/ |/ / /_/ /  __/ /    \n|__/|__/\\__, /\\___/_/     \n       /____/             \n"
  },
  {
    "path": "ct/headers/whisparr",
    "content": " _       ____    _                           \n| |     / / /_  (_)________  ____ ___________\n| | /| / / __ \\/ / ___/ __ \\/ __ `/ ___/ ___/\n| |/ |/ / / / / (__  ) /_/ / /_/ / /  / /    \n|__/|__/_/ /_/_/____/ .___/\\__,_/_/  /_/     \n                   /_/                       \n"
  },
  {
    "path": "ct/headers/whodb",
    "content": " _       ____          ____  ____ \n| |     / / /_  ____  / __ \\/ __ )\n| | /| / / __ \\/ __ \\/ / / / __  |\n| |/ |/ / / / / /_/ / /_/ / /_/ / \n|__/|__/_/ /_/\\____/_____/_____/  \n                                  \n"
  },
  {
    "path": "ct/headers/wikijs",
    "content": " _       ___ __   _   _     \n| |     / (_) /__(_) (_)____\n| | /| / / / //_/ / / / ___/\n| |/ |/ / / ,< / / / (__  ) \n|__/|__/_/_/|_/_/_/ /____/  \n               /___/        \n"
  },
  {
    "path": "ct/headers/wireguard",
    "content": " _       ___                                      __\n| |     / (_)_______  ____ ___  ______ __________/ /\n| | /| / / / ___/ _ \\/ __ `/ / / / __ `/ ___/ __  / \n| |/ |/ / / /  /  __/ /_/ / /_/ / /_/ / /  / /_/ /  \n|__/|__/_/_/   \\___/\\__, /\\__,_/\\__,_/_/   \\__,_/   \n                   /____/                           \n"
  },
  {
    "path": "ct/headers/wishlist",
    "content": " _       ___      __    ___      __ \n| |     / (_)____/ /_  / (_)____/ /_\n| | /| / / / ___/ __ \\/ / / ___/ __/\n| |/ |/ / (__  ) / / / / (__  ) /_  \n|__/|__/_/____/_/ /_/_/_/____/\\__/  \n                                    \n"
  },
  {
    "path": "ct/headers/wizarr",
    "content": " _       ___                      \n| |     / (_)___  ____ ___________\n| | /| / / /_  / / __ `/ ___/ ___/\n| |/ |/ / / / /_/ /_/ / /  / /    \n|__/|__/_/ /___/\\__,_/_/  /_/     \n                                  \n"
  },
  {
    "path": "ct/headers/wordpress",
    "content": " _       __               __                         \n| |     / /___  _________/ /___  ________  __________\n| | /| / / __ \\/ ___/ __  / __ \\/ ___/ _ \\/ ___/ ___/\n| |/ |/ / /_/ / /  / /_/ / /_/ / /  /  __(__  |__  ) \n|__/|__/\\____/_/   \\__,_/ .___/_/   \\___/____/____/  \n                       /_/                           \n"
  },
  {
    "path": "ct/headers/writefreely",
    "content": " _       __     _ __       ______               __     \n| |     / /____(_) /____  / ____/_______  ___  / /_  __\n| | /| / / ___/ / __/ _ \\/ /_  / ___/ _ \\/ _ \\/ / / / /\n| |/ |/ / /  / / /_/  __/ __/ / /  /  __/  __/ / /_/ / \n|__/|__/_/  /_/\\__/\\___/_/   /_/   \\___/\\___/_/\\__, /  \n                                              /____/   \n"
  },
  {
    "path": "ct/headers/yamtrack",
    "content": "__  __                __                  __  \n\\ \\/ /___ _____ ___  / /__________ ______/ /__\n \\  / __ `/ __ `__ \\/ __/ ___/ __ `/ ___/ //_/\n / / /_/ / / / / / / /_/ /  / /_/ / /__/ ,<   \n/_/\\__,_/_/ /_/ /_/\\__/_/   \\__,_/\\___/_/|_|  \n                                              \n"
  },
  {
    "path": "ct/headers/yourls",
    "content": "__  ______  __  ______  __   _____\n\\ \\/ / __ \\/ / / / __ \\/ /  / ___/\n \\  / / / / / / / /_/ / /   \\__ \\ \n / / /_/ / /_/ / _, _/ /______/ / \n/_/\\____/\\____/_/ |_/_____/____/  \n                                  \n"
  },
  {
    "path": "ct/headers/yt-dlp-webui",
    "content": "          __            ____                         __          _ \n   __  __/ /_      ____/ / /___       _      _____  / /_  __  __(_)\n  / / / / __/_____/ __  / / __ \\_____| | /| / / _ \\/ __ \\/ / / / / \n / /_/ / /_/_____/ /_/ / / /_/ /_____/ |/ |/ /  __/ /_/ / /_/ / /  \n \\__, /\\__/      \\__,_/_/ .___/      |__/|__/\\___/_.___/\\__,_/_/   \n/____/                 /_/                                         \n"
  },
  {
    "path": "ct/headers/yubal",
    "content": "__  __      __          __\n\\ \\/ /_  __/ /_  ____ _/ /\n \\  / / / / __ \\/ __ `/ / \n / / /_/ / /_/ / /_/ / /  \n/_/\\__,_/_.___/\\__,_/_/   \n                          \n"
  },
  {
    "path": "ct/headers/yunohost",
    "content": "__  __                  __  __           __ \n\\ \\/ /_  ______  ____  / / / /___  _____/ /_\n \\  / / / / __ \\/ __ \\/ /_/ / __ \\/ ___/ __/\n / / /_/ / / / / /_/ / __  / /_/ (__  ) /_  \n/_/\\__,_/_/ /_/\\____/_/ /_/\\____/____/\\__/  \n                                            \n"
  },
  {
    "path": "ct/headers/zabbix",
    "content": " _____         __    __    _     \n/__  /  ____ _/ /_  / /_  (_)  __\n  / /  / __ `/ __ \\/ __ \\/ / |/_/\n / /__/ /_/ / /_/ / /_/ / />  <  \n/____/\\__,_/_.___/_.___/_/_/|_|  \n                                 \n"
  },
  {
    "path": "ct/headers/zammad",
    "content": " _____                                       __\n/__  /  ____ _____ ___  ____ ___  ____ _____/ /\n  / /  / __ `/ __ `__ \\/ __ `__ \\/ __ `/ __  / \n / /__/ /_/ / / / / / / / / / / / /_/ / /_/ /  \n/____/\\__,_/_/ /_/ /_/_/ /_/ /_/\\__,_/\\__,_/   \n                                               \n"
  },
  {
    "path": "ct/headers/zerobyte",
    "content": " _____                   __          __     \n/__  /  ___  _________  / /_  __  __/ /____ \n  / /  / _ \\/ ___/ __ \\/ __ \\/ / / / __/ _ \\\n / /__/  __/ /  / /_/ / /_/ / /_/ / /_/  __/\n/____/\\___/_/   \\____/_.___/\\__, /\\__/\\___/ \n                           /____/           \n"
  },
  {
    "path": "ct/headers/zerotier-one",
    "content": " _____                   __  _                 ____           \n/__  /  ___  _________  / /_(_)__  _____      / __ \\____  ___ \n  / /  / _ \\/ ___/ __ \\/ __/ / _ \\/ ___/_____/ / / / __ \\/ _ \\\n / /__/  __/ /  / /_/ / /_/ /  __/ /  /_____/ /_/ / / / /  __/\n/____/\\___/_/   \\____/\\__/_/\\___/_/         \\____/_/ /_/\\___/ \n                                                              \n"
  },
  {
    "path": "ct/headers/zigbee2mqtt",
    "content": " _____   _       __             ___   __  _______  ____________\n/__  /  (_)___ _/ /_  ___  ___ |__ \\ /  |/  / __ \\/_  __/_  __/\n  / /  / / __ `/ __ \\/ _ \\/ _ \\__/ // /|_/ / / / / / /   / /   \n / /__/ / /_/ / /_/ /  __/  __/ __// /  / / /_/ / / /   / /    \n/____/_/\\__, /_.___/\\___/\\___/____/_/  /_/\\___\\_\\/_/   /_/     \n       /____/                                                  \n"
  },
  {
    "path": "ct/headers/zipline",
    "content": " _____   _       ___          \n/__  /  (_)___  / (_)___  ___ \n  / /  / / __ \\/ / / __ \\/ _ \\\n / /__/ / /_/ / / / / / /  __/\n/____/_/ .___/_/_/_/ /_/\\___/ \n      /_/                     \n"
  },
  {
    "path": "ct/headers/zitadel",
    "content": " _____   _ __            __     __\n/__  /  (_) /_____ _____/ /__  / /\n  / /  / / __/ __ `/ __  / _ \\/ / \n / /__/ / /_/ /_/ / /_/ /  __/ /  \n/____/_/\\__/\\__,_/\\__,_/\\___/_/   \n                                  \n"
  },
  {
    "path": "ct/headers/zoraxy",
    "content": " _____                              \n/__  /  ____  _________ __  ____  __\n  / /  / __ \\/ ___/ __ `/ |/_/ / / /\n / /__/ /_/ / /  / /_/ />  </ /_/ / \n/____/\\____/_/   \\__,_/_/|_|\\__, /  \n                           /____/   \n"
  },
  {
    "path": "ct/headers/zot-registry",
    "content": " _____         __        ____             _      __            \n/__  /  ____  / /_      / __ \\___  ____ _(_)____/ /________  __\n  / /  / __ \\/ __/_____/ /_/ / _ \\/ __ `/ / ___/ __/ ___/ / / /\n / /__/ /_/ / /_/_____/ _, _/  __/ /_/ / (__  ) /_/ /  / /_/ / \n/____/\\____/\\__/     /_/ |_|\\___/\\__, /_/____/\\__/_/   \\__, /  \n                                /____/                /____/   \n"
  },
  {
    "path": "ct/headers/zwave-js-ui",
    "content": " _____                                     _______       __  ______\n/__  /_      ______ __   _____            / / ___/      / / / /  _/\n  / /| | /| / / __ `/ | / / _ \\________  / /\\__ \\______/ / / // /  \n / /_| |/ |/ / /_/ /| |/ /  __/_____/ /_/ /___/ /_____/ /_/ // /   \n/____/__/|__/\\__,_/ |___/\\___/      \\____//____/      \\____/___/   \n                                                                   \n"
  },
  {
    "path": "ct/headscale.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/juanfont/headscale\n\nAPP=\"Headscale\"\nvar_tags=\"${var_tags:-tailscale}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_tun=\"${var_tun:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/headscale ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if [[ -f /opt/${APP}_version.txt ]]; then\n    mv /opt/\"${APP}_version.txt\" ~/.headscale\n  fi\n\n  if check_for_gh_release \"headscale\" \"juanfont/headscale\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop headscale\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"headscale\" \"juanfont/headscale\" \"binary\"\n    fetch_and_deploy_gh_release \"headscale-admin\" \"GoodiesHQ/headscale-admin\" \"prebuild\" \"latest\" \"/opt/headscale-admin\" \"admin.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl enable -q --now headscale\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}Headscale API: ${IP}/api (no Frontend) | headscale-admin: http://${IP}/admin/${CL}\"\n"
  },
  {
    "path": "ct/healthchecks.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://healthchecks.io/\n\nAPP=\"healthchecks\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/healthchecks ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"healthchecks\" \"healthchecks/healthchecks\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop healthchecks\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up existing installation\"\n    BACKUP=\"/opt/healthchecks-backup-$(date +%F-%H%M)\"\n    cp -a /opt/healthchecks \"$BACKUP\"\n    msg_ok \"Backup created at $BACKUP\"\n\n    fetch_and_deploy_gh_release \"healthchecks\" \"healthchecks/healthchecks\" \"tarball\"\n\n    cd /opt/healthchecks\n    if [[ -d venv ]]; then\n      rm -rf venv\n    fi\n    msg_info \"Recreating Python venv\"\n    $STD python3 -m venv venv\n    $STD source venv/bin/activate\n    $STD pip install --upgrade pip wheel\n    msg_ok \"Created venv\"\n\n    msg_info \"Installing requirements\"\n    $STD pip install gunicorn -r requirements.txt\n    msg_ok \"Installed requirements\"\n\n    msg_info \"Running Django migrations\"\n    $STD python manage.py migrate --noinput\n    $STD python manage.py collectstatic --noinput\n    $STD python manage.py compress\n    msg_ok \"Completed Django migrations and static build\"\n\n    msg_info \"Starting Services\"\n    systemctl start healthchecks\n    systemctl reload caddy\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/heimdall-dashboard.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://heimdall.site/\n\nAPP=\"Heimdall-Dashboard\"\nvar_tags=\"${var_tags:-dashboard}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/Heimdall ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Heimdall\" \"linuxserver/Heimdall\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop heimdall\n    sleep 1\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -R /opt/Heimdall/database database-backup\n    cp -R /opt/Heimdall/public public-backup\n    sleep 1\n    msg_ok \"Backed up Data\"\n\n    setup_composer\n    fetch_and_deploy_gh_release \"Heimdall\" \"linuxserver/Heimdall\" \"tarball\"\n\n    msg_info \"Updating Heimdall-Dashboard\"\n    cd /opt/Heimdall\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer dump-autoload\n    msg_ok \"Updated Heimdall-Dashboard\"\n\n    msg_info \"Restoring Data\"\n    cd ~\n    cp -R database-backup/* /opt/Heimdall/database\n    cp -R public-backup/* /opt/Heimdall/public\n    sleep 1\n    msg_ok \"Restored Data\"\n\n    msg_info \"Cleaning Up\"\n    rm -rf {public-backup,database-backup}\n    sleep 1\n    msg_ok \"Cleaned Up\"\n\n    msg_info \"Starting Service\"\n    systemctl start heimdall.service\n    sleep 2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7990${CL}\"\n"
  },
  {
    "path": "ct/hev-socks5-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: miviro\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/heiher/hev-socks5-server\n\nAPP=\"hev-socks5-server\"\nvar_tags=\"${var_tags:-proxy;socks5}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/${APP} ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  \n  if check_for_gh_release \"hev-socks5-server\" \"heiher/hev-socks5-server\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop hev-socks5-server\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"hev-socks5-server\" \"heiher/hev-socks5-server\" \"singlefile\" \"latest\" \"/opt\" \"hev-socks5-server-linux-aarch64\"\n\n    msg_info \"Starting Service\"\n    systemctl start hev-socks5-server\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it with a SOCKS5 client using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:1080${CL}\"\necho -e \"${INFO}${YW} and the credentials stored at /root/hev.creds${CL}\"\n"
  },
  {
    "path": "ct/hivemq.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.hivemq.com/\n\nAPP=\"HiveMQ\"\nvar_tags=\"${var_tags:-mqtt}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_error \"Currently we don't provide an update function for this ${APP}.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/homarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://homarr.dev/\n\nAPP=\"homarr\"\nvar_tags=\"${var_tags:-arr;dashboard}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/homarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"homarr\" \"homarr-labs/homarr\"; then\n    msg_info \"Stopping Services (Patience)\"\n    systemctl stop homarr\n    systemctl stop redis-server\n    msg_ok \"Services Stopped\"\n\n    if ! { grep -q '^REDIS_IS_EXTERNAL=' /opt/homarr/.env 2>/dev/null || grep -q '^REDIS_IS_EXTERNAL=' /opt/homarr.env 2>/dev/null; }; then\n      msg_info \"Fixing old structure\"\n      systemctl disable -q --now nginx\n      cp /opt/homarr/.env /opt/homarr.env\n      echo \"REDIS_IS_EXTERNAL='true'\" >> /opt/homarr.env\n      sed -i '/^\\[Unit\\]/a Requires=redis-server.service\\nAfter=redis-server.service' /etc/systemd/system/homarr.service\n      sed -i 's|^ExecStart=.*|ExecStart=/opt/homarr/run.sh|' /etc/systemd/system/homarr.service\n      sed -i 's|^EnvironmentFile=.*|EnvironmentFile=-/opt/homarr.env|' /etc/systemd/system/homarr.service\n      chown -R redis:redis /appdata/redis\n      chmod 744 /appdata/redis\n      mkdir -p /etc/systemd/system/redis-server.service.d/\n      cat <<EOF >/etc/systemd/system/redis-server.service.d/override.conf\n[Service]\nReadWritePaths=-/appdata/redis -/var/lib/redis -/var/log/redis -/var/run/redis -/etc/redis\nEOF\n      systemctl daemon-reload\n      rm /opt/run_homarr.sh\n      msg_ok \"Fixed old structure\"\n    fi\n\n    msg_info \"Updating Nodejs\"\n    $STD apt update\n    $STD apt upgrade nodejs -y\n    msg_ok \"Updated Nodejs\"\n\n    NODE_VERSION=$(curl -s https://raw.githubusercontent.com/homarr-labs/homarr/dev/package.json | jq -r '.engines.node | split(\">=\")[1] | split(\".\")[0]')\n    setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"homarr\" \"homarr-labs/homarr\" \"prebuild\" \"latest\" \"/opt/homarr\" \"build-debian-arm64.tar.gz\"\n\n    msg_info \"Updating Homarr\"\n    cp /opt/homarr/redis.conf /etc/redis/redis.conf\n    sed -i -e '$a\\' /etc/redis/redis.conf\n    grep -q '^bind 127.0.0.1 -::1$' /etc/redis/redis.conf || echo \"bind 127.0.0.1 -::1\" >> /etc/redis/redis.conf\n    rm /etc/nginx/nginx.conf\n    cp /opt/homarr/nginx.conf /etc/nginx/templates/nginx.conf\n    msg_ok \"Updated Homarr\"\n\n    msg_info \"Starting Services\"\n    chmod +x /opt/homarr/run.sh\n    systemctl start homarr\n    systemctl start redis-server\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7575${CL}\"\n"
  },
  {
    "path": "ct/homeassistant.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.home-assistant.io/\n\nAPP=\"Home Assistant\"\nvar_tags=\"${var_tags:-automation;smarthome}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/docker/volumes/hass_config/_data ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  UPD=$(msg_menu \"Home Assistant Update Options\" \\\n    \"1\" \"Update ALL Containers\" \\\n    \"2\" \"Remove ALL Unused Images\" \\\n    \"3\" \"Install HACS\" \\\n    \"4\" \"Install FileBrowser\")\n\n  if [ \"$UPD\" == \"1\" ]; then\n    msg_info \"Updating All Containers\"\n    CONTAINER_LIST=\"${1:-$(docker ps -q)}\"\n    for container in ${CONTAINER_LIST}; do\n      CONTAINER_IMAGE=\"$(docker inspect --format \"{{.Config.Image}}\" --type container \"${container}\")\"\n      RUNNING_IMAGE=\"$(docker inspect --format \"{{.Image}}\" --type container \"${container}\")\"\n      docker pull \"${CONTAINER_IMAGE}\"\n      LATEST_IMAGE=\"$(docker inspect --format \"{{.Id}}\" --type image \"${CONTAINER_IMAGE}\")\"\n      if [[ \"${RUNNING_IMAGE}\" != \"${LATEST_IMAGE}\" ]]; then\n        pip install -U runlike\n        echo \"Updating ${container} image ${CONTAINER_IMAGE}\"\n        DOCKER_COMMAND=\"$(runlike --use-volume-id \"${container}\")\"\n        docker rm --force \"${container}\"\n        eval \"${DOCKER_COMMAND}\"\n      fi\n    done\n    msg_ok \"Updated All Containers\"\n    exit\n  fi\n  if [ \"$UPD\" == \"2\" ]; then\n    msg_info \"Removing ALL Unused Images\"\n    docker image prune -af\n    msg_ok \"Removed ALL Unused Images\"\n    exit\n  fi\n  if [ \"$UPD\" == \"3\" ]; then\n    msg_info \"Installing Home Assistant Community Store (HACS)\"\n    $STD apt update\n    cd /var/lib/docker/volumes/hass_config/_data\n    $STD bash <(curl -fsSL https://get.hacs.xyz)\n    msg_ok \"Installed Home Assistant Community Store (HACS)\"\n    echo -e \"\\n Reboot Home Assistant and clear browser cache then Add HACS integration.\\n\"\n    exit\n  fi\n  if [ \"$UPD\" == \"4\" ]; then\n    msg_info \"Installing FileBrowser\"\n    RELEASE=$(curl -fsSL https://api.github.com/repos/filebrowser/filebrowser/releases/latest | grep -o '\"tag_name\": \".*\"' | sed 's/\"//g' | sed 's/tag_name: //g')\n    $STD curl -fsSL https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-arm64-filebrowser.tar.gz | tar -xzv -C /usr/local/bin\n    $STD filebrowser config init -a '0.0.0.0'\n    $STD filebrowser config set -a '0.0.0.0'\n    $STD filebrowser users add admin community-scripts.org --perm.admin\n    msg_ok \"Installed FileBrowser\"\n\n    msg_info \"Creating Service\"\n    service_path=\"/etc/systemd/system/filebrowser.service\"\n    echo \"[Unit]\nDescription=Filebrowser\nAfter=network-online.target\n[Service]\nUser=root\nWorkingDirectory=/root/\nExecStart=/usr/local/bin/filebrowser -r /\n[Install]\nWantedBy=default.target\" >$service_path\n\n    $STD systemctl enable --now filebrowser\n    msg_ok \"Created Service\"\n\n    msg_ok \"Completed successfully!\\n\"\n    echo -e \"FileBrowser should be reachable by going to the following URL.\n         ${BL}http://$LOCAL_IP:8080${CL}   admin|community-scripts.org\\n\"\n    exit\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}HA: http://${IP}:8123${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}Portainer: https://${IP}:9443${CL}\"\n"
  },
  {
    "path": "ct/homebox.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://homebox.software/en/\n\nAPP=\"HomeBox\"\nvar_tags=\"${var_tags:-inventory;household}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/homebox.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if [[ -x /opt/homebox ]]; then\n    sed -i 's|WorkingDirectory=/opt$|WorkingDirectory=/opt/homebox|' /etc/systemd/system/homebox.service\n    sed -i 's|ExecStart=/opt/homebox$|ExecStart=/opt/homebox/homebox|' /etc/systemd/system/homebox.service\n    sed -i 's|EnvironmentFile=/opt/.env$|EnvironmentFile=/opt/homebox/.env|' /etc/systemd/system/homebox.service\n    systemctl daemon-reload\n  fi\n\n  if check_for_gh_release \"homebox\" \"sysadminsmedia/homebox\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop homebox\n    msg_ok \"Stopped Service\"\n\n    if [ -f /opt/homebox ] && [ -x /opt/homebox ]; then\n      rm -f /opt/homebox\n    fi\n    fetch_and_deploy_gh_release \"homebox\" \"sysadminsmedia/homebox\" \"prebuild\" \"latest\" \"/opt/homebox\" \"homebox_Linux_x86_64.tar.gz\"\n    chmod +x /opt/homebox/homebox\n    [ -f /opt/.env ] && mv /opt/.env /opt/homebox/.env\n    [ -d /opt/.data ] && mv /opt/.data /opt/homebox/.data\n\n    msg_info \"Starting Service\"\n    systemctl start homebox\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7745${CL}\"\n"
  },
  {
    "path": "ct/homebridge.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://homebridge.io/\n\nAPP=\"Homebridge\"\nvar_tags=\"${var_tags:-smarthome;homekit}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if ! dpkg -s homebridge >/dev/null 2>&1; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt install -y homebridge\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8581${CL}\"\n"
  },
  {
    "path": "ct/homelable.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/Pouzor/homelable\n\nAPP=\"Homelable\"\nvar_tags=\"${var_tags:-monitoring;network;visualization}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/homelable ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"homelable\" \"Pouzor/homelable\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop homelable\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration and Data\"\n    cp /opt/homelable/backend/.env /opt/homelable.env.bak\n    cp -r /opt/homelable/data /opt/homelable_data_bak\n    msg_ok \"Backed up Configuration and Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"homelable\" \"Pouzor/homelable\" \"tarball\" \"latest\" \"/opt/homelable\"\n\n    msg_info \"Updating Python Dependencies\"\n    cd /opt/homelable/backend\n    $STD uv venv --clear /opt/homelable/backend/.venv\n    $STD uv pip install --python /opt/homelable/backend/.venv/bin/python -r requirements.txt\n    msg_ok \"Updated Python Dependencies\"\n\n    msg_info \"Rebuilding Frontend\"\n    cd /opt/homelable/frontend\n    $STD npm ci\n    $STD npm run build\n    msg_ok \"Rebuilt Frontend\"\n\n    msg_info \"Restoring Configuration and Data\"\n    cp /opt/homelable.env.bak /opt/homelable/backend/.env\n    cp -r /opt/homelable_data_bak/. /opt/homelable/data/\n    rm -f /opt/homelable.env.bak\n    rm -rf /opt/homelable_data_bak\n    msg_ok \"Restored Configuration and Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start homelable\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/homepage.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gethomepage.dev/\n\nAPP=\"Homepage\"\nvar_tags=\"${var_tags:-dashboard}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/homepage ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@latest\" setup_nodejs\n  ensure_dependencies jq\n\n  if check_for_gh_release \"homepage\" \"gethomepage/homepage\"; then\n    msg_info \"Stopping service\"\n    systemctl stop homepage\n    msg_ok \"Stopped service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/homepage/.env /opt/homepage.env\n    cp -r /opt/homepage/config /opt/homepage_config_backup\n    [[ -d /opt/homepage/public/images ]] && cp -r /opt/homepage/public/images /opt/homepage_images_backup\n    [[ -d /opt/homepage/public/icons ]] && cp -r /opt/homepage/public/icons /opt/homepage_icons_backup\n    msg_ok \"Created Backup\"\n    \n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"homepage\" \"gethomepage/homepage\" \"tarball\"\n    \n    msg_info \"Restoring Backup\"\n    mv /opt/homepage.env /opt/homepage\n    rm -rf /opt/homepage/config\n    mv /opt/homepage_config_backup /opt/homepage/config\n    msg_ok \"Restored Backup\"\n\n    msg_info \"Updating Homepage (Patience)\"\n    RELEASE=$(get_latest_github_release \"gethomepage/homepage\")\n    cd /opt/homepage\n    echo 'onlyBuiltDependencies=*' >> .npmrc\n    $STD pnpm install\n    $STD pnpm update --no-save caniuse-lite\n    export NEXT_PUBLIC_VERSION=\"v$RELEASE\"\n    export NEXT_PUBLIC_REVISION=\"source\"\n    export NEXT_PUBLIC_BUILDTIME=$(curl -fsSL https://api.github.com/repos/gethomepage/homepage/releases/latest | jq -r '.published_at')\n    export NEXT_TELEMETRY_DISABLED=1\n    $STD pnpm build\n    [[ -d /opt/homepage_images_backup ]] && mv /opt/homepage_images_backup /opt/homepage/public/images\n    [[ -d /opt/homepage_icons_backup ]] && mv /opt/homepage_icons_backup /opt/homepage/public/icons\n    msg_ok \"Updated Homepage\"\n\n    msg_info \"Starting service\"\n    systemctl start homepage\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/homer.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bastienwirtz/homer\n\nAPP=\"Homer\"\nvar_tags=\"${var_tags:-dashboard}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/homer ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    if check_for_gh_release \"homer\" \"bastienwirtz/homer\"; then\n      msg_info \"Stopping Service\"\n      systemctl stop homer\n      msg_ok \"Stopped Service\"\n\n      msg_info \"Backing up assets directory\"\n      cd ~\n      mkdir -p assets-backup\n      cp -R /opt/homer/assets/. assets-backup\n      msg_ok \"Backed up assets directory\"\n\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"homer\" \"bastienwirtz/homer\" \"prebuild\" \"latest\" \"/opt/homer\" \"homer.zip\"\n\n      msg_info \"Restoring assets directory\"\n      cd ~\n      cp -Rf assets-backup/. /opt/homer/assets/\n      rm -rf assets-backup\n      msg_ok \"Restored assets directory\"\n    \n      msg_info \"Starting Service\"\n      systemctl start homer\n      msg_ok \"Started Service\"\n      msg_ok \"Updated successfully!\"\n    fi\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8010${CL}\"\n"
  },
  {
    "path": "ct/hoodik.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/hudikhq/hoodik\n\nAPP=\"Hoodik\"\nvar_tags=\"${var_tags:-cloud;storage}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/hoodik/hoodik ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"hoodik\" \"hudikhq/hoodik\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop hoodik\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /opt/hoodik/.env /opt/hoodik.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"hoodik\" \"hudikhq/hoodik\" \"prebuild\" \"latest\" \"/opt/hoodik\" \"*x86_64.tar.gz\"\n\n    msg_info \"Restoring Configuration\"\n    cp /opt/hoodik.env.bak /opt/hoodik/.env\n    rm -f /opt/hoodik.env.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start hoodik\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5443/auth/register${CL}\"\n"
  },
  {
    "path": "ct/hortusfox.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/danielbrendel/hortusfox-web\n\nAPP=\"HortusFox\"\nvar_tags=\"${var_tags:-plants}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/hortusfox ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"hortusfox\" \"danielbrendel/hortusfox-web\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up current HortusFox installation\"\n    cd /opt\n    mv /opt/hortusfox/ /opt/hortusfox-backup\n    msg_ok \"Backed up current HortusFox installation\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"hortusfox\" \"danielbrendel/hortusfox-web\" \"tarball\"\n\n    msg_info \"Updating HortusFox\"\n    cd /opt/hortusfox\n    cp /opt/hortusfox-backup/.env /opt/hortusfox/.env\n    cp -a /opt/hortusfox-backup/public/img/. /opt/hortusfox/public/img/\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-dev --optimize-autoloader\n    $STD php asatru migrate:upgrade\n    $STD php asatru plants:attributes\n    $STD php asatru calendar:classes\n    chown -R www-data:www-data /opt/hortusfox\n    rm -r /opt/hortusfox-backup\n    msg_ok \"Updated HortusFox\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/hyperhdr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.hyperhdr.eu/\n\nAPP=\"HyperHDR\"\nvar_tags=\"${var_tags:-ambient-lightning}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/hyperion.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://hyperion-project.org/forum/\n\nAPP=\"Hyperion\"\nvar_tags=\"${var_tags:-ambient-lightning}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apt/sources.list.d/hyperion.list ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt install -y hyperion\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/igotify.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pfassina\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/androidseb25/iGotify-Notification-Assistent\n\nAPP=\"iGotify\"\nvar_tags=\"${var_tags:-notifications;gotify}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/igotify ]]; then\n    msg_error \"No iGotify Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"igotify\" \"androidseb25/iGotify-Notification-Assistent\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop igotify\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /opt/igotify/.env /opt/igotify.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"igotify\" \"androidseb25/iGotify-Notification-Assistent\" \"prebuild\" \"latest\" \"/opt/igotify\" \"iGotify-Notification-Service-amd64-v*.zip\"\n\n    msg_info \"Restoring Configuration\"\n    cp /opt/igotify.env.bak /opt/igotify/.env\n    rm -f /opt/igotify.env.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start igotify\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/immich.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://immich.app\n\nAPP=\"immich\"\nvar_tags=\"${var_tags:-photos}\"\nvar_disk=\"${var_disk:-20}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/immich ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if [[ -f /etc/apt/sources.list.d/immich.list ]]; then\n    msg_error \"Wrong Debian version detected!\"\n    msg_error \"You must upgrade your LXC to Debian Trixie before updating.\"\n    msg_error \"Please visit https://github.com/community-scripts/ProxmoxVE/discussions/7726 for details.\"\n    echo \"${TAB3}  If you have upgraded your LXC to Trixie and you still see this message, please open an Issue in the Community-Scripts repo.\"\n    exit\n  fi\n\n  if ! grep -qE '(^|[[:space:]])testing([[:space:]]|$)' /etc/apt/sources.list.d/debian.sources 2>/dev/null; then\n    msg_info \"Adding Debian Testing repo\"\n    if grep -q \"trixie-updates\" /etc/apt/sources.list.d/debian.sources 2>/dev/null; then\n      sed -i 's/ trixie-updates/ trixie-updates testing/g' /etc/apt/sources.list.d/debian.sources\n    else\n      sed -i '/^[[:space:]]*Suites:.*trixie/ s/$/ testing/' /etc/apt/sources.list.d/debian.sources\n    fi\n    cat <<EOF >/etc/apt/preferences.d/preferences\nPackage: *\nPin: release a=unstable\nPin-Priority: 450\n\nPackage: *\nPin:release a=testing\nPin-Priority: 450\nEOF\n    [[ -f /etc/apt/preferences.d/immich ]] && rm /etc/apt/preferences.d/immich\n    $STD apt update\n    msg_ok \"Added Debian Testing repo\"\n  fi\n\n  if ! dpkg -l \"libmimalloc3\" | grep -q '3.1' || ! dpkg -l \"libde265-dev\" | grep -q '1.0.16'; then\n    msg_info \"Installing/upgrading Testing repo packages\"\n    $STD apt install -t testing libmimalloc3 libde265-dev -y\n    msg_ok \"Installed/upgraded Testing repo packages\"\n  fi\n\n  if [[ ! -f /etc/apt/sources.list.d/mise.list ]]; then\n    msg_info \"Installing Mise\"\n    curl -fSs https://mise.jdx.dev/gpg-key.pub | tee /etc/apt/keyrings/mise-archive-keyring.pub 1>/dev/null\n    echo \"deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=arm64] https://mise.jdx.dev/deb stable main\" >/etc/apt/sources.list.d/mise.list\n    ensure_dependencies mise\n    msg_ok \"Installed Mise\"\n  fi\n\n  STAGING_DIR=/opt/staging\n  BASE_DIR=${STAGING_DIR}/base-images\n  SOURCE_DIR=${STAGING_DIR}/image-source\n  cd /tmp\n  if [[ -f ~/.intel_version ]]; then\n    curl_with_retry \"https://raw.githubusercontent.com/immich-app/immich/refs/heads/main/machine-learning/Dockerfile\" \"Dockerfile\"\n    readarray -t INTEL_URLS < <(\n      sed -n \"/intel-[igc|opencl]/p\" ./Dockerfile | awk '{print $3}'\n      sed -n \"/libigdgmm12/p\" ./Dockerfile | awk '{print $3}'\n    )\n    INTEL_RELEASE=\"$(grep \"intel-opencl-icd_\" ./Dockerfile | awk -F '_' '{print $2}')\"\n    if [[ \"$INTEL_RELEASE\" != \"$(cat ~/.intel_version)\" ]]; then\n      msg_info \"Updating Intel OpenVINO dependencies\"\n      for url in \"${INTEL_URLS[@]}\"; do\n        curl_with_retry \"$url\" \"$(basename \"$url\")\"\n      done\n      $STD apt-mark unhold libigdgmm12\n      $STD apt install -y --allow-downgrades ./libigdgmm12*.deb\n      rm ./libigdgmm12*.deb\n      $STD apt install -y ./*.deb\n      rm ./*.deb\n      $STD apt-mark hold libigdgmm12\n      dpkg-query -W -f='${Version}\\n' intel-opencl-icd >~/.intel_version\n      rm -f ./Dockerfile\n      msg_ok \"Updated Intel OpenVINO dependencies\"\n    fi\n  fi\n  if [[ -f ~/.immich_library_revisions ]]; then\n    libraries=(\"libjxl\" \"libheif\" \"libraw\" \"imagemagick\" \"libvips\")\n    cd \"$BASE_DIR\"\n    msg_warn \"Checking for updates to custom image-processing libraries (recompile time: 2-15min per library)\"\n    $STD git pull\n    for library in \"${libraries[@]}\"; do\n      compile_\"$library\"\n    done\n    msg_ok \"Image-processing libraries up to date\"\n  fi\n\n  RELEASE=\"v2.7.5\"\n  if check_for_gh_release \"Immich\" \"immich-app/immich\" \"${RELEASE}\" \"each release is tested individually before the version is updated. Please do not open issues for this\"; then\n    if [[ $(cat ~/.immich) > \"2.5.1\" ]]; then\n      msg_info \"Enabling Maintenance Mode\"\n      cd /opt/immich/app/bin\n      $STD ./immich-admin enable-maintenance-mode\n      export MAINT_MODE=1\n      $STD cd -\n      msg_ok \"Enabled Maintenance Mode\"\n    fi\n    msg_info \"Stopping Services\"\n    systemctl stop immich-web\n    systemctl stop immich-ml\n    msg_ok \"Stopped Services\"\n    VCHORD_RELEASE=\"0.5.3\"\n    if [[ ! -f ~/.vchord_version ]] || [[ \"$VCHORD_RELEASE\" != \"$(cat ~/.vchord_version)\" ]]; then\n      msg_info \"Upgrading VectorChord\"\n      curl -fsSL \"https://github.com/tensorchord/vectorchord/releases/download/${VCHORD_RELEASE}/postgresql-16-vchord_${VCHORD_RELEASE}-1_arm64.deb\" -o vchord.deb\n      $STD apt install -y ./vchord.deb\n      systemctl restart postgresql\n      $STD sudo -u postgres psql -d immich -c \"ALTER EXTENSION vector UPDATE;\"\n      $STD sudo -u postgres psql -d immich -c \"ALTER EXTENSION vchord UPDATE;\"\n      $STD sudo -u postgres psql -d immich -c \"REINDEX INDEX face_index;\"\n      $STD sudo -u postgres psql -d immich -c \"REINDEX INDEX clip_index;\"\n    fi\n    ensure_dependencies ccache gcc-13 g++-13\n\n    INSTALL_DIR=\"/opt/${APP}\"\n    UPLOAD_DIR=\"$(sed -n '/^IMMICH_MEDIA_LOCATION/s/[^=]*=//p' /opt/immich/.env)\"\n    SRC_DIR=\"${INSTALL_DIR}/source\"\n    APP_DIR=\"${INSTALL_DIR}/app\"\n    PLUGIN_DIR=\"${APP_DIR}/corePlugin\"\n    ML_DIR=\"${APP_DIR}/machine-learning\"\n    GEO_DIR=\"${INSTALL_DIR}/geodata\"\n\n    [[ -f \"$ML_DIR\"/ml_start.sh ]] && cp \"$ML_DIR\"/ml_start.sh \"$INSTALL_DIR\"\n    if grep -qs \"set -a\" \"$APP_DIR\"/bin/start.sh && grep -qs \"warnings\" \"$APP_DIR\"/bin/start.sh; then\n      cp \"$APP_DIR\"/bin/start.sh \"$INSTALL_DIR\"\n    else\n      cat <<EOF >\"$INSTALL_DIR\"/start.sh\n#!/usr/bin/env bash\n\nset -a\n. ${INSTALL_DIR}/.env\nset +a\n\n/usr/bin/node --no-warnings ${APP_DIR}/dist/main.js \"\\$@\"\nEOF\n      chmod +x \"$INSTALL_DIR\"/start.sh\n    fi\n\n    (\n      shopt -s dotglob\n      rm -rf \"${APP_DIR:?}\"/*\n    )\n\n    setup_uv\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Immich\" \"immich-app/immich\" \"tarball\" \"${RELEASE}\" \"$SRC_DIR\"\n    PNPM_VERSION=\"$(jq -r '.packageManager | split(\"@\")[1] | split(\"+\")[0]' ${SRC_DIR}/package.json)\"\n    NODE_VERSION=\"24\" NODE_MODULE=\"pnpm@${PNPM_VERSION}\" setup_nodejs\n\n    msg_info \"Updating Immich web and microservices\"\n    cd \"$SRC_DIR\"/server\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    export CI=1\n    corepack enable\n\n    # server build\n    export SHARP_IGNORE_GLOBAL_LIBVIPS=true\n    $STD pnpm --filter immich --frozen-lockfile build\n    unset SHARP_IGNORE_GLOBAL_LIBVIPS\n    export SHARP_FORCE_GLOBAL_LIBVIPS=true\n    $STD pnpm --filter immich --frozen-lockfile --prod --no-optional deploy \"$APP_DIR\"\n\n    # Patch helmet.json: disable upgrade-insecure-requests for HTTP access\n    if [[ -f \"$APP_DIR/helmet.json\" ]]; then\n      jq '.contentSecurityPolicy.directives[\"upgrade-insecure-requests\"] = null' \"$APP_DIR/helmet.json\" >\"$APP_DIR/helmet.json.tmp\" && mv \"$APP_DIR/helmet.json.tmp\" \"$APP_DIR/helmet.json\"\n    fi\n\n    cp \"$APP_DIR\"/package.json \"$APP_DIR\"/bin\n    sed -i \"s|^start|${APP_DIR}/bin/start|\" \"$APP_DIR\"/bin/immich-admin\n\n    # openapi & web build\n    cd \"$SRC_DIR\"\n    echo \"packageImportMethod: hardlink\" >>./pnpm-workspace.yaml\n    $STD pnpm --filter @immich/sdk --filter immich-web --frozen-lockfile --force install\n    unset SHARP_FORCE_GLOBAL_LIBVIPS\n    export SHARP_IGNORE_GLOBAL_LIBVIPS=true\n    $STD pnpm --filter @immich/sdk --filter immich-web build\n    cp -a web/build \"$APP_DIR\"/www\n    cp LICENSE \"$APP_DIR\"\n\n    # cli build\n    $STD pnpm --filter @immich/sdk --filter @immich/cli --frozen-lockfile install\n    $STD pnpm --filter @immich/sdk --filter @immich/cli build\n    $STD pnpm --filter @immich/cli --prod --no-optional deploy \"$APP_DIR\"/cli\n    [[ -f \"$INSTALL_DIR\"/start.sh ]] && mv \"$INSTALL_DIR\"/start.sh \"$APP_DIR\"/bin\n\n    # plugins\n    cd \"$SRC_DIR\"\n    $STD mise trust --ignore ./mise.toml\n    $STD mise trust ./plugins/mise.toml\n    cd plugins\n    $STD mise install\n    $STD mise run build\n    mkdir -p \"$PLUGIN_DIR\"\n    cp -r ./dist \"$PLUGIN_DIR\"/dist\n    cp ./manifest.json \"$PLUGIN_DIR\"\n    msg_ok \"Updated Immich server, web, cli and plugins\"\n\n    cd \"$SRC_DIR\"/machine-learning\n    mkdir -p \"$ML_DIR\"\n    # chown excluding upload dir contents (may be a mount with restricted permissions)\n    chown immich:immich \"$INSTALL_DIR\"\n    find \"$INSTALL_DIR\" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +\n    chown immich:immich \"${UPLOAD_DIR:-$INSTALL_DIR/upload}\" 2>/dev/null || true\n    chown immich:immich ./uv.lock\n    export VIRTUAL_ENV=\"${ML_DIR}\"/ml-venv\n    export UV_HTTP_TIMEOUT=300\n    if [[ -f ~/.openvino ]]; then\n      msg_info \"Updating HW-accelerated machine-learning\"\n      $STD uv add --no-sync --optional openvino onnxruntime-openvino==1.24.1 --active -n -p python3.13 --managed-python\n      $STD sudo --preserve-env=VIRTUAL_ENV -nu immich uv sync --extra openvino --no-dev --active --link-mode copy -n -p python3.13 --managed-python\n      local sofile\n      if [[ \"$(uname -m)\" == \"aarch64\" || \"$(uname -m)\" == \"arm64\" ]]; then\n        sofile=\"${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-aarch64-linux-gnu.so\"\n      else\n        sofile=\"${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-x86_64-linux-gnu.so\"\n      fi\n      patchelf --clear-execstack \"$sofile\"\n      msg_ok \"Updated HW-accelerated machine-learning\"\n    else\n      ML_PYTHON=\"python3.11\"\n      msg_info \"Pre-installing Python ${ML_PYTHON} for machine-learning\"\n      for attempt in $(seq 1 3); do\n        $STD sudo --preserve-env=VIRTUAL_ENV -nu immich uv python install \"${ML_PYTHON}\" && break\n        [[ $attempt -lt 3 ]] && msg_warn \"Python download attempt $attempt failed, retrying...\" && sleep 5\n      done\n      msg_ok \"Pre-installed Python ${ML_PYTHON}\"\n      msg_info \"Updating machine-learning\"\n      for attempt in $(seq 1 3); do\n        $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -nu immich uv sync --extra cpu --no-dev --active --link-mode copy -n -p \"${ML_PYTHON}\" --managed-python && break\n        [[ $attempt -lt 3 ]] && msg_warn \"uv sync attempt $attempt failed, retrying...\" && sleep 10\n      done\n      msg_ok \"Updated machine-learning\"\n    fi\n    cd \"$SRC_DIR\"\n    cp -a machine-learning/{ann,immich_ml} \"$ML_DIR\"\n    [[ -f \"$INSTALL_DIR\"/ml_start.sh ]] && mv \"$INSTALL_DIR\"/ml_start.sh \"$ML_DIR\"\n    [[ -f ~/.openvino ]] && sed -i \"/intra_op/s/int = 0/int = os.cpu_count() or 0/\" \"$ML_DIR\"/immich_ml/config.py\n    ln -sf \"$APP_DIR\"/resources \"$INSTALL_DIR\"\n    cd \"$APP_DIR\"\n    grep -rl /usr/src | xargs -n1 sed -i \"s|\\/usr/src|$INSTALL_DIR|g\"\n    grep -rlE \"'/build'\" | xargs -n1 sed -i \"s|'/build'|'$APP_DIR'|g\"\n    sed -i \"s@\\\"/cache\\\"@\\\"$INSTALL_DIR/cache\\\"@g\" \"$ML_DIR\"/immich_ml/config.py\n    ln -s \"${UPLOAD_DIR:-/opt/immich/upload}\" \"$APP_DIR\"/upload\n    ln -s \"${UPLOAD_DIR:-/opt/immich/upload}\" \"$ML_DIR\"/upload\n    ln -s \"$GEO_DIR\" \"$APP_DIR\"\n    [[ ! -f /usr/bin/immich ]] && ln -sf \"$APP_DIR\"/cli/bin/immich /usr/bin/immich\n    [[ ! -f /usr/bin/immich-admin ]] && ln -sf \"$APP_DIR\"/bin/immich-admin /usr/bin/immich-admin\n\n    if ! grep -q '^DB_HOSTNAME=' \"$INSTALL_DIR\"/.env; then\n      sed -i '/^DB_DATABASE_NAME/a DB_HOSTNAME=127.0.0.1' \"$INSTALL_DIR\"/.env\n    fi\n    if ! grep -q 'HELMET_FILE' \"$INSTALL_DIR\"/.env; then\n      sed -i -e '$a\\' \"$INSTALL_DIR\"/.env\n      echo \"IMMICH_HELMET_FILE=true\" >>\"$INSTALL_DIR\"/.env\n    fi\n\n    if grep -q 'ExecStart=/usr/bin/node' /etc/systemd/system/immich-web.service; then\n      sed -i '/^EnvironmentFile=/d' /etc/systemd/system/immich-web.service\n      sed -i \"s|^ExecStart=.*|ExecStart=${APP_DIR}/bin/start.sh|\" /etc/systemd/system/immich-web.service\n      systemctl daemon-reload\n    fi\n\n    # chown excluding upload dir contents (may be a mount with restricted permissions)\n    chown immich:immich \"$INSTALL_DIR\"\n    find \"$INSTALL_DIR\" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +\n    chown immich:immich \"${UPLOAD_DIR:-$INSTALL_DIR/upload}\" 2>/dev/null || true\n    if [[ \"${MAINT_MODE:-0}\" == 1 ]]; then\n      msg_info \"Disabling Maintenance Mode\"\n      cd /opt/immich/app/bin\n      $STD ./immich-admin disable-maintenance-mode || true\n      unset MAINT_MODE\n      $STD cd -\n      msg_ok \"Disabled Maintenance Mode\"\n    fi\n    systemctl restart immich-ml immich-web\n    [[ -f /etc/systemd/system/immich-proxy.service ]] && systemctl restart immich-proxy\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nfunction compile_libjxl() {\n  SOURCE=${SOURCE_DIR}/libjxl\n  JPEGLI_LIBJPEG_LIBRARY_SOVERSION=\"62\"\n  JPEGLI_LIBJPEG_LIBRARY_VERSION=\"62.3.0\"\n  LIBJXL_REVISION=\"794a5dcf0d54f9f0b20d288a12e87afb91d20dfc\"\n  # : \"${LIBJXL_REVISION:=$(jq -cr '.revision' \"$BASE_DIR\"/server/sources/libjxl.json)}\"\n  if [[ \"$LIBJXL_REVISION\" != \"$(grep 'libjxl' ~/.immich_library_revisions | awk '{print $2}')\" ]]; then\n    msg_info \"Recompiling libjxl\"\n    [[ -d \"$SOURCE\" ]] && rm -rf \"$SOURCE\"\n    $STD git clone https://github.com/libjxl/libjxl.git \"$SOURCE\"\n    cd \"$SOURCE\"\n    $STD git reset --hard \"$LIBJXL_REVISION\"\n    $STD git submodule update --init --recursive --depth 1 --recommend-shallow\n    $STD git apply \"$BASE_DIR\"/server/sources/libjxl-patches/jpegli-empty-dht-marker.patch\n    $STD git apply \"$BASE_DIR\"/server/sources/libjxl-patches/jpegli-icc-warning.patch\n    mkdir build\n    cd build\n    $STD cmake \\\n      -DCMAKE_BUILD_TYPE=Release \\\n      -DBUILD_TESTING=OFF \\\n      -DJPEGXL_ENABLE_DOXYGEN=OFF \\\n      -DJPEGXL_ENABLE_MANPAGES=OFF \\\n      -DJPEGXL_ENABLE_PLUGIN_GIMP210=OFF \\\n      -DJPEGXL_ENABLE_BENCHMARK=OFF \\\n      -DJPEGXL_ENABLE_EXAMPLES=OFF \\\n      -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \\\n      -DJPEGXL_FORCE_SYSTEM_HWY=ON \\\n      -DJPEGXL_ENABLE_JPEGLI=ON \\\n      -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=ON \\\n      -DJPEGXL_INSTALL_JPEGLI_LIBJPEG=ON \\\n      -DJPEGXL_ENABLE_PLUGINS=ON \\\n      -DJPEGLI_LIBJPEG_LIBRARY_SOVERSION=\"$JPEGLI_LIBJPEG_LIBRARY_SOVERSION\" \\\n      -DJPEGLI_LIBJPEG_LIBRARY_VERSION=\"$JPEGLI_LIBJPEG_LIBRARY_VERSION\" \\\n      -DLIBJPEG_TURBO_VERSION_NUMBER=2001005 \\\n      ..\n    $STD cmake --build . -- -j\"$(nproc)\"\n    $STD cmake --install .\n    ldconfig /usr/local/lib\n    $STD make clean\n    cd \"$STAGING_DIR\"\n    rm -rf \"$SOURCE\"/{build,third_party}\n    sed -i \"s/libjxl: .*$/libjxl: $LIBJXL_REVISION/\" ~/.immich_library_revisions\n    msg_ok \"Recompiled libjxl\"\n  fi\n}\n\nfunction compile_libheif() {\n  SOURCE=${SOURCE_DIR}/libheif\n  ensure_dependencies libaom-dev\n  LIBHEIF_REVISION=\"35dad50a9145332a7bfdf1ff6aef6801fb613d68\"\n  # : \"${LIBHEIF_REVISION:=$(jq -cr '.revision' \"$BASE_DIR\"/server/sources/libheif.json)}\"\n  if [[ \"${update:-}\" ]] || [[ \"$LIBHEIF_REVISION\" != \"$(grep 'libheif' ~/.immich_library_revisions | awk '{print $2}')\" ]]; then\n    msg_info \"Recompiling libheif\"\n    [[ -d \"$SOURCE\" ]] && rm -rf \"$SOURCE\"\n    $STD git clone https://github.com/strukturag/libheif.git \"$SOURCE\"\n    cd \"$SOURCE\"\n    $STD git reset --hard \"$LIBHEIF_REVISION\"\n    mkdir build\n    cd build\n    $STD cmake --preset=release-noplugins \\\n      -DWITH_DAV1D=ON \\\n      -DENABLE_PARALLEL_TILE_DECODING=ON \\\n      -DWITH_LIBSHARPYUV=ON \\\n      -DWITH_LIBDE265=ON \\\n      -DWITH_AOM_DECODER=OFF \\\n      -DWITH_AOM_ENCODER=ON \\\n      -DWITH_X265=OFF \\\n      -DWITH_EXAMPLES=OFF \\\n      ..\n    $STD make install -j\"$(nproc)\"\n    ldconfig /usr/local/lib\n    $STD make clean\n    cd \"$STAGING_DIR\"\n    rm -rf \"$SOURCE\"/build\n    sed -i \"s/libheif: .*$/libheif: $LIBHEIF_REVISION/\" ~/.immich_library_revisions\n    msg_ok \"Recompiled libheif\"\n  fi\n}\n\nfunction compile_libraw() {\n  SOURCE=${SOURCE_DIR}/libraw\n  LIBRAW_REVISION=\"0b56545a4f828743f28a4345cdfdd4c49f9f9a2a\"\n  # : \"${LIBRAW_REVISION:=$(jq -cr '.revision' \"$BASE_DIR\"/server/sources/libraw.json)}\"\n  if [[ \"$LIBRAW_REVISION\" != \"$(grep 'libraw' ~/.immich_library_revisions | awk '{print $2}')\" ]]; then\n    msg_info \"Recompiling libraw\"\n    [[ -d \"$SOURCE\" ]] && rm -rf \"$SOURCE\"\n    $STD git clone https://github.com/LibRaw/LibRaw.git \"$SOURCE\"\n    cd \"$SOURCE\"\n    $STD git reset --hard \"$LIBRAW_REVISION\"\n    $STD autoreconf --install\n    $STD ./configure --disable-examples\n    $STD make -j\"$(nproc)\"\n    $STD make install\n    ldconfig /usr/local/lib\n    $STD make clean\n    cd \"$STAGING_DIR\"\n    sed -i \"s/libraw: .*$/libraw: $LIBRAW_REVISION/\" ~/.immich_library_revisions\n    msg_ok \"Recompiled libraw\"\n  fi\n}\n\nfunction compile_imagemagick() {\n  SOURCE=$SOURCE_DIR/imagemagick\n  : \"${IMAGEMAGICK_REVISION:=$(jq -cr '.revision' \"$BASE_DIR\"/server/sources/imagemagick.json)}\"\n  if [[ \"$IMAGEMAGICK_REVISION\" != \"$(grep 'imagemagick' ~/.immich_library_revisions | awk '{print $2}')\" ]] ||\n    ! grep -q 'DMAGICK_LIBRAW' /usr/local/lib/ImageMagick-7*/config-Q16HDRI/configure.xml; then\n    msg_info \"Recompiling ImageMagick\"\n    [[ -d \"$SOURCE\" ]] && rm -rf \"$SOURCE\"\n    $STD git clone https://github.com/ImageMagick/ImageMagick.git \"$SOURCE\"\n    cd \"$SOURCE\"\n    $STD git reset --hard \"$IMAGEMAGICK_REVISION\"\n    $STD ./configure --with-modules CPPFLAGS=\"-DMAGICK_LIBRAW_VERSION_TAIL=202502\"\n    $STD make -j\"$(nproc)\"\n    $STD make install\n    ldconfig /usr/local/lib\n    $STD make clean\n    cd \"$STAGING_DIR\"\n    sed -i \"s/imagemagick: .*$/imagemagick: $IMAGEMAGICK_REVISION/\" ~/.immich_library_revisions\n    msg_ok \"Recompiled ImageMagick\"\n  fi\n}\n\nfunction compile_libvips() {\n  SOURCE=$SOURCE_DIR/libvips\n  LIBVIPS_REVISION=\"0c9151a4f416d2f8ae20a755db218f6637050eec\"\n  if [[ \"$LIBVIPS_REVISION\" != \"$(grep 'libvips' ~/.immich_library_revisions | awk '{print $2}')\" ]]; then\n    msg_info \"Recompiling libvips\"\n    [[ -d \"$SOURCE\" ]] && rm -rf \"$SOURCE\"\n    $STD git clone https://github.com/libvips/libvips.git \"$SOURCE\"\n    cd \"$SOURCE\"\n    $STD git reset --hard \"$LIBVIPS_REVISION\"\n    $STD meson setup build --buildtype=release --libdir=lib -Dintrospection=disabled -Dtiff=disabled\n    cd build\n    $STD ninja install\n    ldconfig /usr/local/lib\n    cd \"$STAGING_DIR\"\n    rm -rf \"$SOURCE\"/build\n    sed -i \"s/libvips: .*$/libvips: $LIBVIPS_REVISION/\" ~/.immich_library_revisions\n    msg_ok \"Recompiled libvips\"\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:2283${CL}\"\n"
  },
  {
    "path": "ct/immichframe.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Thiago Canozzo Lahr (tclahr)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/immichFrame/ImmichFrame\n\nAPP=\"ImmichFrame\"\nvar_tags=\"${var_tags:-photos;slideshow}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/immichframe ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"immichframe\" \"immichFrame/ImmichFrame\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop immichframe\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp -r /opt/immichframe/Config /tmp/immichframe_config.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"immichframe\" \"immichFrame/ImmichFrame\" \"tarball\" \"latest\" \"/tmp/immichframe\"\n\n    msg_info \"Setting up ImmichFrame\"\n    cd /tmp/immichframe\n    $STD dotnet publish ImmichFrame.WebApi/ImmichFrame.WebApi.csproj \\\n      --configuration Release \\\n      --runtime linux-x64 \\\n      --self-contained false \\\n      --output /opt/immichframe\n\n    cd /tmp/immichframe/immichFrame.Web\n    $STD npm ci --silent\n    $STD npm run build\n    rm -rf /opt/immichframe/wwwroot/*\n    cp -r build/* /opt/immichframe/wwwroot\n    rm -rf /tmp/immichframe\n    msg_ok \"Setup ImmichFrame\"\n\n    msg_info \"Restoring Configuration\"\n    cp -r /tmp/immichframe_config.bak/* /opt/immichframe/Config/\n    rm -rf /tmp/immichframe_config.bak\n    chown -R immichframe:immichframe /opt/immichframe\n    msg_ok \"Restored Configuration\"\n\n\n    msg_info \"Starting Service\"\n    systemctl start immichframe\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/infisical.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://infisical.com/\n\nAPP=\"Infisical\"\nvar_tags=\"${var_tags:-auth}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/infisical ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping service\"\n  $STD infisical-ctl stop\n  msg_ok \"Service stopped\"\n\n  msg_info \"Creating backup\"\n  [[ -f /opt/infisical_backup.sql ]] && rm -f /opt/infisical_backup.sql\n  DB_PASS=$(grep -Po '(?<=^Database Password:\\s).*' ~/infisical.creds | head -n1)\n  PGPASSWORD=$DB_PASS pg_dump -U infisical -h localhost -d infisical_db > /opt/infisical_backup.sql\n  msg_ok \"Created backup\"\n\n  msg_info \"Updating Infisical\"\n  $STD apt update\n  $STD apt install -y infisical-core\n  $STD infisical-ctl reconfigure\n  msg_ok \"Updated Infisical\"\n\n  msg_info \"Starting service\"\n  infisical-ctl start\n  msg_ok \"Started service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/influxdb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.influxdata.com/\n\nAPP=\"InfluxDB\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/bin/influxd && ! -f /usr/bin/influxdb3 ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  \n  msg_info \"Updating InfluxDB\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated InfluxDB\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP} and Port 8888 for v1, Port 8086 for v2 or Port 8181 for v3${CL}\"\n"
  },
  {
    "path": "ct/inspircd.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.inspircd.org/\n\nAPP=\"InspIRCd\"\nvar_tags=\"${var_tags:-IRC}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /lib/systemd/system/inspircd.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"inspircd\" \"inspircd/inspircd\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop inspircd\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"inspircd\" \"inspircd/inspircd\" \"binary\" \"latest\" \"/opt/inspircd\" \"inspircd_*.deb13u1_amd64.deb\"\n\n    msg_info \"Starting Service\"\n    systemctl start inspircd\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Server-Acces it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:6667${CL}\"\n"
  },
  {
    "path": "ct/inventree.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/inventree/InvenTree\n\nAPP=\"InvenTree\"\nvar_tags=\"${var_tags:-inventory}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/inventree\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if ! grep -qE \"^ID=(ubuntu)$\" /etc/os-release; then\n    msg_error \"Unsupported OS. InvenTree requires Ubuntu (20.04/22.04/24.04).\"\n    exit\n  fi\n\n  msg_info \"Updating InvenTree\"\n  $STD apt update\n  $STD apt install --only-upgrade inventree -y\n  msg_ok \"Updated InvenTree\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/investbrain.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Benito Rodríguez (b3ni)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/investbrainapp/investbrain\n\nAPP=\"Investbrain\"\nvar_tags=\"${var_tags:-finance;portfolio;investing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/investbrain ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Investbrain\" \"investbrainapp/investbrain\"; then\n    PHP_VERSION=\"8.4\"\n    msg_info \"Stopping Services\"\n    systemctl stop nginx php${PHP_VERSION}-fpm\n    $STD supervisorctl stop all\n    msg_ok \"Services Stopped\"\n\n    setup_composer\n    NODE_VERSION=\"22\" setup_nodejs\n    PG_VERSION=\"17\" setup_postgresql\n\n    msg_info \"Creating Backup\"\n    rm -f /opt/.env.backup\n    rm -rf /opt/investbrain_backup\n    cp /opt/investbrain/.env /opt/.env.backup\n    cp -r /opt/investbrain/storage /opt/investbrain_backup\n    msg_ok \"Created Backup\"\n\n    fetch_and_deploy_gh_release \"Investbrain\" \"investbrainapp/investbrain\" \"tarball\" \"latest\" \"/opt/investbrain\"\n\n    msg_info \"Updating Investbrain\"\n    cd /opt/investbrain\n    rm -rf /opt/investbrain/storage\n    cp /opt/.env.backup /opt/investbrain/.env\n    cp -r /opt/investbrain_backup/ /opt/investbrain/storage\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD /usr/local/bin/composer install --no-interaction --no-dev --optimize-autoloader\n    $STD npm install\n    $STD npm run build\n    $STD php artisan storage:link\n    $STD php artisan migrate --force\n    $STD php artisan cache:clear\n    $STD php artisan view:clear\n    $STD php artisan route:clear\n    $STD php artisan event:clear\n    $STD php artisan route:cache\n    $STD php artisan event:cache\n    chown -R www-data:www-data /opt/investbrain\n    chmod -R 775 /opt/investbrain/storage /opt/investbrain/bootstrap/cache\n    rm -rf /opt/.env.backup /opt/investbrain_backup\n    msg_ok \"Updated Investbrain\"\n\n    msg_info \"Starting Services\"\n    systemctl start php${PHP_VERSION}-fpm nginx\n    $STD supervisorctl start all\n    msg_ok \"Services Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/invoiceninja.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://invoiceninja.com/\n\nAPP=\"InvoiceNinja\"\nvar_tags=\"${var_tags:-invoicing;business}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/invoiceninja ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"invoiceninja\" \"invoiceninja/invoiceninja\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop supervisor nginx php8.4-fpm\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /tmp/invoiceninja_backup\n    cp /opt/invoiceninja/.env /tmp/invoiceninja_backup/\n    cp -r /opt/invoiceninja/storage /tmp/invoiceninja_backup/ 2>/dev/null || true\n    cp -r /opt/invoiceninja/public/storage /tmp/invoiceninja_backup/public_storage 2>/dev/null || true\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"invoiceninja\" \"invoiceninja/invoiceninja\" \"prebuild\" \"latest\" \"/opt/invoiceninja\" \"invoiceninja.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    cp /tmp/invoiceninja_backup/.env /opt/invoiceninja/\n    cp -r /tmp/invoiceninja_backup/storage/* /opt/invoiceninja/storage/ 2>/dev/null || true\n    cp -r /tmp/invoiceninja_backup/public_storage/* /opt/invoiceninja/public/storage/ 2>/dev/null || true\n    rm -rf /tmp/invoiceninja_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Running Migrations\"\n    cd /opt/invoiceninja \n    $STD php artisan migrate --force\n    $STD php artisan config:clear\n    $STD php artisan cache:clear\n    $STD php artisan optimize\n    chown -R www-data:www-data /opt/invoiceninja\n    chmod -R 755 /opt/invoiceninja/storage\n    msg_ok \"Ran Migrations\"\n\n    msg_info \"Starting Services\"\n    systemctl start php8.4-fpm nginx supervisor\n    msg_ok \"Started Services\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/setup${CL}\"\n"
  },
  {
    "path": "ct/iobroker.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.iobroker.net/#en/intro\n\nAPP=\"ioBroker\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/iobroker ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8081${CL}\"\n"
  },
  {
    "path": "ct/ironclaw.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/nearai/ironclaw\n\nAPP=\"IronClaw\"\nvar_tags=\"${var_tags:-ai;agent;security}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/bin/ironclaw ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"ironclaw-bin\" \"nearai/ironclaw\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop ironclaw\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /root/.ironclaw/.env /root/ironclaw.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    fetch_and_deploy_gh_release \"ironclaw-bin\" \"nearai/ironclaw\" \"prebuild\" \"latest\" \"/usr/local/bin\" \\\n      \"ironclaw-$(uname -m)-unknown-linux-$([[ -f /etc/alpine-release ]] && echo \"musl\" || echo \"gnu\").tar.gz\"\n    chmod +x /usr/local/bin/ironclaw\n\n    msg_info \"Restoring Configuration\"\n    cp /root/ironclaw.env.bak /root/.ironclaw/.env\n    rm -f /root/ironclaw.env.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start ironclaw\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Complete setup by running:${CL}\"\necho -e \"${TAB}${BGN}ironclaw onboard${CL}\"\necho -e \"${INFO}${YW} Then start the service:${CL}\"\necho -e \"${TAB}${BGN}systemctl start ironclaw${CL}\"\necho -e \"${INFO}${YW} Access the Web UI at:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\necho -e \"${INFO}${YW} Auth token and database credentials:${CL}\"\necho -e \"${TAB}${BGN}cat /root/.ironclaw/.env${CL}\"\n"
  },
  {
    "path": "ct/isponsorblocktv.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Matthew Stern (sternma) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/dmunozv04/iSponsorBlockTV\n\nAPP=\"iSponsorBlockTV\"\nvar_tags=\"${var_tags:-media;automation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/isponsorblocktv ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"isponsorblocktv\" \"dmunozv04/iSponsorBlockTV\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop isponsorblocktv\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"isponsorblocktv\" \"dmunozv04/iSponsorBlockTV\" \"singlefile\" \"latest\" \"/opt/isponsorblocktv\" \"iSponsorBlockTV-x86_64-linux\"\n\n    msg_info \"Starting Service\"\n    systemctl start isponsorblocktv\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Run the setup wizard inside the container with:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}iSponsorBlockTV setup${CL}\"\n"
  },
  {
    "path": "ct/itsm-ng.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Florianb63\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://itsm-ng.com/\n\nAPP=\"ITSM-NG\"\nvar_tags=\"${var_tags:-asset-management;foss}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/itsm-ng/config_db.php ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit 233\n  fi\n  setup_mariadb\n\n  msg_info \"Updating ITSM-NG\"\n  $STD apt update\n  $STD apt -y upgrade\n  chown -R www-data:www-data /var/lib/itsm-ng\n  mkdir -p /usr/share/itsm-ng/css/palettes\n  chown -R www-data:www-data /usr/share/itsm-ng/css\n  chown -R www-data:www-data /usr/share/itsm-ng/css_compiled\n  chown www-data:www-data /etc/itsm-ng/config_db.php\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/jackett.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Jackett/Jackett\n\nAPP=\"Jackett\"\nvar_tags=\"${var_tags:-torrent}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/jackett.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Jackett\" \"Jackett/Jackett\"; then\n    if [ ! -f /opt/.env ]; then\n      sed -i 's|^Environment=\"DisableRootWarning=true\"$|EnvironmentFile=\"/opt/.env\"|' /etc/systemd/system/jackett.service\n      cat <<EOF >/opt/.env\nDisableRootWarning=true\nEOF\n    fi\n\n    msg_info \"Stopping Service\"\n    systemctl stop jackett\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"jackett\" \"Jackett/Jackett\" \"prebuild\" \"latest\" \"/opt/Jackett\" \"Jackett.Binaries.LinuxARM64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start jackett\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9117${CL}\"\n"
  },
  {
    "path": "ct/jeedom.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Mips2648\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://jeedom.com/\n\nAPP=\"Jeedom\"\nvar_tags=\"${var_tags:-automation;smarthome}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /var/www/html/core/config/version ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating OS\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"OS updated, you can now update Jeedom from the Web UI.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/jellyfin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://jellyfin.org/\n\nAPP=\"Jellyfin\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /usr/lib/jellyfin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if ! grep -qEi 'ubuntu' /etc/os-release; then\n    msg_info \"Updating Intel Dependencies\"\n    rm -f ~/.intel-* || true\n\n    fetch_and_deploy_gh_release \"intel-libgdgmm12\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"libigdgmm12_*_amd64.deb\"\n    fetch_and_deploy_gh_release \"intel-opencl-icd\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"intel-opencl-icd_*_amd64.deb\"\n\n    local igc_tag\n    _resolve_igc_tag igc_tag\n\n    fetch_and_deploy_gh_release \"intel-igc-core-2\" \"intel/intel-graphics-compiler\" \"binary\" \"$igc_tag\" \"\" \"intel-igc-core-2_*_amd64.deb\"\n    fetch_and_deploy_gh_release \"intel-igc-opencl-2\" \"intel/intel-graphics-compiler\" \"binary\" \"$igc_tag\" \"\" \"intel-igc-opencl-2_*_amd64.deb\"\n    msg_ok \"Updated Intel Dependencies\"\n  fi\n\n  msg_info \"Setting up Jellyfin Repository\"\n  setup_deb822_repo \\\n    \"jellyfin\" \\\n    \"https://repo.jellyfin.org/jellyfin_team.gpg.key\" \\\n    \"https://repo.jellyfin.org/$(get_os_info id)\" \\\n    \"$(get_os_info codename)\"\n  msg_ok \"Set up Jellyfin Repository\"\n  msg_info \"Updating Jellyfin\"\n  ensure_dependencies libjemalloc2\n  if [[ ! -f /usr/lib/libjemalloc.so ]]; then\n    ln -sf /usr/lib/aarch64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so\n  fi\n  $STD apt -y upgrade\n  $STD apt -y --with-new-pkgs upgrade jellyfin jellyfin-server jellyfin-ffmpeg7\n  ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin/ffmpeg\n  ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/bin/ffprobe\n  msg_ok \"Updated Jellyfin\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8096${CL}\"\n"
  },
  {
    "path": "ct/jellyseerr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.jellyseerr.dev/\n\nAPP=\"Jellyseerr\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/jellyseerr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [[ -f \"/opt/jellyseerr/package.json\" ]] && [[ \"$(grep -m1 '\"version\"' /opt/jellyseerr/package.json | awk -F'\"' '{print $4}')\" == \"2.7.3\" ]]; then\n    echo\n    echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n    echo \"Jellyseerr v2.7.3 detected.\"\n    echo\n    echo \"Seerr is the new unified Jellyseerr and Overseerr.\"\n    echo \"More info: https://docs.seerr.dev/blog/seerr-release\"\n    echo\n    read -rp \"Do you want to migrate to Seerr now? (y/N): \" MIGRATE\n    echo\n    if [[ ! \"$MIGRATE\" =~ ^[Yy]$ ]]; then\n      msg_info \"Migration cancelled. Exiting.\"\n      exit 0\n    fi\n\n    msg_info \"Switching update script to Seerr\"\n    TMP_UPDATE=$(mktemp)\n    cat <<'EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/seerr.sh)\"\nEOF\n    mv \"$TMP_UPDATE\" /usr/bin/update\n    chmod +x /usr/bin/update\n    msg_ok \"Switched update script to Seerr\"\n    msg_warn \"Please type 'update' again to complete the migration\"\n    exit 0\n  fi\n\n  msg_info \"Updating Jellyseerr\"\n  cd /opt/jellyseerr\n  systemctl stop jellyseerr\n  output=$(git pull --no-rebase)\n  pnpm_desired=$(grep -Po '\"pnpm\":\\s*\"\\K[^\"]+' /opt/jellyseerr/package.json)\n  NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$pnpm_desired\" setup_nodejs\n  if echo \"$output\" | grep -q \"Already up to date.\"; then\n    msg_ok \"$APP is already up to date.\"\n    exit\n  fi\n  rm -rf dist .next node_modules\n  export CYPRESS_INSTALL_BINARY=0\n  cd /opt/jellyseerr\n  $STD pnpm install --frozen-lockfile\n  export NODE_OPTIONS=\"--max-old-space-size=3072\"\n  $STD pnpm build\n  cat <<EOF >/etc/systemd/system/jellyseerr.service\n[Unit]\nDescription=jellyseerr Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/etc/jellyseerr/jellyseerr.conf\nEnvironment=NODE_ENV=production\nType=exec\nWorkingDirectory=/opt/jellyseerr\nExecStart=/usr/bin/node dist/index.js\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl daemon-reload\n  systemctl start jellyseerr\n  msg_ok \"Updated Jellyseerr\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5055${CL}\"\n"
  },
  {
    "path": "ct/jenkins.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.jenkins.io/\n\nAPP=\"Jenkins\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/jenkins ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  JAVA_VERSION=\"21\" setup_java\n\n  msg_info \"Updating Jenkins\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated Jenkins\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/jitsi-meet.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://jitsi.org/\n\nAPP=\"Jitsi-Meet\"\nvar_tags=\"${var_tags:-video;conference;communication}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /etc/jitsi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Jitsi Meet\"\n  $STD apt update\n  $STD apt install -y --only-upgrade \\\n    jitsi-meet \\\n    jicofo \\\n    jitsi-videobridge2 \\\n    prosody\n  msg_ok \"Updated Jitsi Meet\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/joplin-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://joplinapp.org/\n\nAPP=\"Joplin-Server\"\nvar_tags=\"${var_tags:-notes}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/joplin-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" NODE_MODULE=\"yarn,npm,pm2\" setup_nodejs\n\n  if check_for_gh_release \"joplin-server\" \"laurent22/joplin\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop joplin-server\n    msg_ok \"Stopped Services\"\n\n    cp /opt/joplin-server/.env /opt\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"joplin-server\" \"laurent22/joplin\" \"tarball\"\n    mv /opt/.env /opt/joplin-server\n\n    msg_info \"Updating Joplin-Server\"\n    cd /opt/joplin-server\n    sed -i \"/onenote-converter/d\" packages/lib/package.json\n    $STD yarn config set --home enableTelemetry 0\n    export BUILD_SEQUENCIAL=1\n    $STD yarn workspaces focus @joplin/server\n    $STD yarn workspaces foreach -R --topological-dev --from @joplin/server run build\n    $STD yarn workspaces foreach -R --topological-dev --from @joplin/server run tsc\n    msg_ok \"Updated Joplin-Server\"\n\n    msg_info \"Starting Services\"\n    systemctl start joplin-server\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:22300${CL}\"\n"
  },
  {
    "path": "ct/jotty.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/fccview/jotty\n\nAPP=\"jotty\"\nvar_tags=\"${var_tags:-tasks;notes}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/jotty ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"jotty\" \"fccview/jotty\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop jotty\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up configuration & data\"\n    cp /opt/jotty/.env /opt/app.env\n    [[ -d /opt/jotty/data ]] && mv /opt/jotty/data /opt/data\n    [[ -d /opt/jotty/config ]] && mv /opt/jotty/config /opt/config\n    msg_ok \"Backed up configuration & data\"\n\n    NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"jotty\" \"fccview/jotty\" \"prebuild\" \"latest\" \"/opt/jotty\" \"jotty_*_prebuild.tar.gz\"\n\n    msg_info \"Restoring configuration & data\"\n    mv /opt/app.env /opt/jotty/.env\n    [[ -d /opt/data ]] && mv /opt/data /opt/jotty/data\n    [[ -d /opt/jotty/config ]] && cp -a /opt/config/* /opt/jotty/config && rm -rf /opt/config\n    msg_ok \"Restored configuration & data\"\n\n    msg_info \"Starting Service\"\n    systemctl start jotty\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/jupyternotebook.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dave-code-creater (Tan Dat, Ta)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://jupyter.org/\n\nAPP=\"JupyterNotebook\"\nvar_tags=\"${var_tags:-ai;dev-tools}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  INSTALL_DIR=\"/opt/jupyter\"\n  VENV_PYTHON=\"${INSTALL_DIR}/.venv/bin/python\"\n  VENV_JUPYTER=\"${INSTALL_DIR}/.venv/bin/jupyter\"\n  SERVICE_FILE=\"/etc/systemd/system/jupyternotebook.service\"\n\n  if [[ ! -x \"$VENV_JUPYTER\" ]]; then\n    msg_info \"Migrating to uv venv\"\n    PYTHON_VERSION=\"3.12\" setup_uv\n    mkdir -p \"$INSTALL_DIR\"\n    cd \"$INSTALL_DIR\"\n    $STD uv venv --clear .venv\n    $STD \"$VENV_PYTHON\" -m ensurepip --upgrade\n    $STD \"$VENV_PYTHON\" -m pip install --upgrade pip\n    $STD \"$VENV_PYTHON\" -m pip install jupyter\n    msg_ok \"Migrated to uv and installed Jupyter\"\n  else\n    msg_info \"Updating Jupyter\"\n    $STD \"$VENV_PYTHON\" -m pip install --upgrade pip\n    $STD \"$VENV_PYTHON\" -m pip install --upgrade jupyter\n    msg_ok \"Jupyter updated\"\n  fi\n\n  if [[ -f \"$SERVICE_FILE\" && \"$(grep ExecStart \"$SERVICE_FILE\")\" != *\".venv/bin/jupyter\"* ]]; then\n    msg_info \"Updating systemd service to use .venv\"\n    cat <<EOF >\"$SERVICE_FILE\"\n[Unit]\nDescription=Jupyter Notebook Server\nAfter=network.target\n[Service]\nType=simple\nWorkingDirectory=${INSTALL_DIR}\nExecStart=${VENV_JUPYTER} notebook --ip=0.0.0.0 --port=8888 --allow-root\nRestart=always\nRestartSec=10\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reexec\n    systemctl restart jupyternotebook\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8888${CL}\"\n"
  },
  {
    "path": "ct/kapowarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Casvt/Kapowarr\n\nAPP=\"Kapowarr\"\nvar_tags=\"${var_tags:-Arr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-256}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/kapowarr.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_uv\n\n  if check_for_gh_release \"kapowarr\" \"Casvt/Kapowarr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop kapowarr\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    mv /opt/kapowarr/db /opt/\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_gh_release \"kapowarr\" \"Casvt/Kapowarr\" \"tarball\"\n\n    msg_info \"Updating Kapowarr\"\n    mv /opt/db /opt/kapowarr\n    msg_ok \"Updated Kapowarr\"\n\n    msg_info \"Starting Service\"\n    systemctl start kapowarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5656${CL}\"\n"
  },
  {
    "path": "ct/karakeep.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz) & vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://karakeep.app/\n\nAPP=\"karakeep\"\nvar_tags=\"${var_tags:-bookmark}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/karakeep ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"karakeep\" \"karakeep-app/karakeep\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop karakeep-web karakeep-workers karakeep-browser\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Updating yt-dlp\"\n    $STD yt-dlp --update-to nightly\n    msg_ok \"Updated yt-dlp\"\n\n    msg_info \"Prepare update\"\n    ensure_dependencies graphicsmagick ghostscript\n    if [[ -f /opt/karakeep/.env ]] && [[ ! -f /etc/karakeep/karakeep.env ]]; then\n      mkdir -p /etc/karakeep\n      mv /opt/karakeep/.env /etc/karakeep/karakeep.env\n    fi\n    msg_ok \"Update prepared\"\n\n    if grep -q \"start:prod\" /etc/systemd/system/karakeep-workers.service; then\n      sed -i 's|^ExecStart=.*$|ExecStart=/usr/bin/node dist/index.mjs|' /etc/systemd/system/karakeep-workers.service\n      systemctl daemon-reload\n    fi\n\n    if grep -q '^ExecStart=/usr/bin/node\\s\\+dist/index\\.mjs$' /etc/systemd/system/karakeep-workers.service; then\n      sed -i -E 's#^(ExecStart=/usr/bin/node\\s+dist/)index\\.mjs$#\\1index.js#' /etc/systemd/system/karakeep-workers.service\n      systemctl daemon-reload\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"karakeep\" \"karakeep-app/karakeep\" \"tarball\"\n    if command -v corepack >/dev/null; then\n      $STD corepack disable\n    fi\n    sed -i \"s/^SERVER_VERSION=.*$/SERVER_VERSION=${CHECK_UPDATE_RELEASE#v}/\" /etc/karakeep/karakeep.env\n    MODULE_VERSION=\"$(jq -r '.packageManager | split(\"@\")[1]' /opt/karakeep/package.json)\"\n    NODE_VERSION=\"24\" NODE_MODULE=\"pnpm@${MODULE_VERSION}\" setup_nodejs\n    setup_meilisearch\n\n    msg_info \"Updating Karakeep\"\n    corepack enable\n    export PUPPETEER_SKIP_DOWNLOAD=\"true\"\n    export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=\"true\"\n    export NEXT_TELEMETRY_DISABLED=1\n    export CI=\"true\"\n    cd /opt/karakeep/apps/web \n    $STD pnpm install --frozen-lockfile\n    $STD pnpm build\n    cd /opt/karakeep/apps/workers \n    $STD pnpm install --frozen-lockfile\n    $STD pnpm build\n    cd /opt/karakeep/apps/cli \n    $STD pnpm install --frozen-lockfile\n    $STD pnpm build\n    DATA_DIR=\"$(sed -n '/^DATA_DIR/p' /etc/karakeep/karakeep.env | awk -F= '{print $2}' | tr -d '=\"=')\"\n    export DATA_DIR=\"${DATA_DIR:-/opt/karakeep_data}\"\n    cd /opt/karakeep/packages/db \n    $STD pnpm migrate\n    $STD pnpm store prune\n    msg_ok \"Updated Karakeep\"\n\n    msg_info \"Starting Services\"\n    systemctl start karakeep-browser karakeep-workers karakeep-web\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/kasm.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.kasmweb.com/docs/latest/index.html\n\nAPP=\"Kasm\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-30}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_fuse=\"${var_fuse:-yes}\"\nvar_tun=\"${var_tun:-yes}\"\nvar_kasm_version=\"${var_kasm_version:-}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/kasm/current ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Checking for new version\"\n  CURRENT_VERSION=$(readlink -f /opt/kasm/current | awk -F'/' '{print $4}')\n  KASM_VERSION=$(curl -s https://kasm.com/downloads | grep -oP '<h1[^>]*>.*?</h1>' | sed -E 's/<\\/?h1[^>]*>//g' | grep -oP '\\d+\\.\\d+\\.\\d+')\n  KASM_URL=\"https://kasm-static-content.s3.amazonaws.com/kasm_release_${KASM_VERSION:-var_kasm_version}.tar.gz\"\n  \n  # KASM_URL=$(curl -fsSL \"https://www.kasm.com/downloads\" | tr '\\n' ' ' | grep -oE 'https://kasm-static-content[^\"]*kasm_release_[0-9]+\\.[0-9]+\\.[0-9]+\\.[a-z0-9]+\\.tar\\.gz' | head -n 1)\n  # if [[ -z \"$KASM_URL\" ]]; then\n  #   SERVICE_IMAGE_URL=$(curl -fsSL \"https://www.kasm.com/downloads\" | tr '\\n' ' ' | grep -oE 'https://kasm-static-content[^\"]*kasm_release_service_images_amd64_[0-9]+\\.[0-9]+\\.[0-9]+\\.tar\\.gz' | head -n 1)\n  #   if [[ -n \"$SERVICE_IMAGE_URL\" ]]; then\n  #     KASM_VERSION=$(echo \"$SERVICE_IMAGE_URL\" | sed -E 's/.*kasm_release_service_images_amd64_([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\n  #     KASM_URL=\"https://kasm-static-content.s3.amazonaws.com/kasm_release_${KASM_VERSION}.tar.gz\"\n  #   fi\n  # else\n  #   KASM_VERSION=$(echo \"$KASM_URL\" | sed -E 's/.*kasm_release_([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\n  # fi\n  \n  if [[ -z \"$KASM_VERSION\" ]] || [[ -z \"$KASM_URL\" ]]; then\n    msg_error \"Unable to detect latest Kasm release URL.\"\n    exit 250\n  fi\n  msg_info \"Checked for new version\"\n\n  msg_info \"Removing outdated docker-compose plugin\"\n  [ -f ~/.docker/cli-plugins/docker-compose ] && rm -rf ~/.docker/cli-plugins/docker-compose\n  msg_ok \"Removed outdated docker-compose plugin\"\n  \n  if [[ -z \"$CURRENT_VERSION\" ]] || [[ \"$KASM_VERSION\" != \"$CURRENT_VERSION\" ]]; then\n    msg_info \"Updating Kasm\"\n    cd /tmp \n\n    msg_warn \"WARNING: This script will run an external installer from a third-party source (https://www.kasmweb.com/).\"\n    msg_warn \"The following code is NOT maintained or audited by our repository.\"\n    msg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\n    msg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  upgrade.sh inside tar.gz $KASM_URL\"\n    echo\n    read -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\n    if [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n      msg_error \"Aborted by user. No changes have been made.\"\n      exit 10\n    fi\n    curl -fsSL -o \"/tmp/kasm_release_${KASM_VERSION}.tar.gz\" \"$KASM_URL\"\n    tar -xf \"kasm_release_${KASM_VERSION}.tar.gz\"\n    chmod +x /tmp/kasm_release/install.sh\n    rm -f /tmp/kasm_release_\"${KASM_VERSION}\".tar.gz\n  \n    bash /tmp/kasm_release/upgrade.sh --proxy-port 443\n    rm -rf /tmp/kasm_release\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. Kasm is already at v${KASM_VERSION}\"\n  \n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/kavita.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.kavitareader.com/\n\nAPP=\"Kavita\"\nvar_tags=\"${var_tags:-reader}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/Kavita ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"kavita\" \"Kareadita/Kavita\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop kavita\n    msg_ok \"Service Stopped\"\n\n    fetch_and_deploy_gh_release \"kavita\" \"Kareadita/Kavita\" \"prebuild\" \"latest\" \"/opt/Kavita\" \"kavita-linux-x64.tar.gz\"\n    chmod +x /opt/Kavita/Kavita && chown root:root /opt/Kavita/Kavita\n\n    msg_info \"Starting Service\"\n    systemctl start kavita\n    msg_ok \"Service Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/keycloak.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.keycloak.org/\n\nAPP=\"Keycloak\"\nvar_tags=\"${var_tags:-access-management}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/keycloak ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"keycloak_app\" \"keycloak/keycloak\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop keycloak\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating packages\"\n    $STD apt-get update\n    $STD apt-get -y upgrade\n    msg_ok \"Updated packages\"\n\n    msg_info \"Backup old Keycloak\"\n    cd /opt\n    mv keycloak keycloak.old\n    msg_ok \"Backup done\"\n\n    fetch_and_deploy_gh_release \"keycloak_app\" \"keycloak/keycloak\" \"prebuild\" \"latest\" \"/opt/keycloak\" \"keycloak-*.tar.gz\"\n\n    msg_info \"Updating Keycloak\"\n    cd /opt\n    cp -a keycloak.old/conf/. keycloak/conf/\n    cp -a keycloak.old/providers/. keycloak/providers/ 2>/dev/null || true\n    cp -a keycloak.old/themes/. keycloak/themes/ 2>/dev/null || true\n    rm -rf keycloak.old\n    msg_ok \"Updated Keycloak\"\n\n    msg_info \"Restarting Service\"\n    systemctl restart keycloak\n    msg_ok \"Restarted Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/admin${CL}\"\n"
  },
  {
    "path": "ct/kima-hub.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Chevron7Locked/kima-hub\n\nAPP=\"Kima-Hub\"\nvar_tags=\"${var_tags:-music;streaming;media}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/kima-hub ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" setup_nodejs\n\n  if check_for_gh_release \"kima-hub\" \"Chevron7Locked/kima-hub\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop kima-frontend kima-backend kima-analyzer kima-analyzer-clap\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/kima-hub/backend/.env /opt/kima-hub-backend-env.bak\n    cp /opt/kima-hub/frontend/.env /opt/kima-hub-frontend-env.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"kima-hub\" \"Chevron7Locked/kima-hub\" \"tarball\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/kima-hub-backend-env.bak /opt/kima-hub/backend/.env\n    cp /opt/kima-hub-frontend-env.bak /opt/kima-hub/frontend/.env\n    rm -f /opt/kima-hub-backend-env.bak /opt/kima-hub-frontend-env.bak\n    msg_ok \"Restored Data\"\n\n    msg_info \"Rebuilding Backend\"\n    cd /opt/kima-hub/backend\n    $STD npm install\n    $STD npm run build\n    $STD npx prisma generate\n    $STD npx prisma migrate deploy\n    msg_ok \"Rebuilt Backend\"\n\n    msg_info \"Rebuilding Frontend\"\n    cd /opt/kima-hub/frontend\n    $STD npm install\n    $STD npm run build\n    msg_ok \"Rebuilt Frontend\"\n\n    msg_info \"Starting Services\"\n    systemctl start kima-backend kima-frontend kima-analyzer kima-analyzer-clap\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3030${CL}\"\n"
  },
  {
    "path": "ct/kimai.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.kimai.org/\n\nAPP=\"Kimai\"\nvar_tags=\"${var_tags:-time-tracking}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-7}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  ensure_dependencies lsb-release\n  if [[ ! -d /opt/kimai ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n\n  PHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\n  setup_composer\n\n  if check_for_gh_release \"kimai\" \"kimai/kimai\"; then\n    BACKUP_DIR=\"/opt/kimai_backup\"\n\n    msg_info \"Stopping Apache2\"\n    systemctl stop apache2\n    msg_ok \"Stopped Apache2\"\n\n    msg_info \"Backing up Kimai configuration and var directory\"\n    mkdir -p \"$BACKUP_DIR\"\n    [ -d /opt/kimai/var ] && cp -r /opt/kimai/var \"$BACKUP_DIR/\"\n    [ -f /opt/kimai/.env ] && cp /opt/kimai/.env \"$BACKUP_DIR/\"\n    [ -f /opt/kimai/config/packages/local.yaml ] && cp /opt/kimai/config/packages/local.yaml \"$BACKUP_DIR/\"\n    msg_ok \"Backup completed\"\n\n    fetch_and_deploy_gh_release \"kimai\" \"kimai/kimai\" \"tarball\"\n\n    msg_info \"Updating Kimai\"\n    [ -d \"$BACKUP_DIR/var\" ] && cp -r \"$BACKUP_DIR/var\" /opt/kimai/\n    [ -f \"$BACKUP_DIR/.env\" ] && cp \"$BACKUP_DIR/.env\" /opt/kimai/\n    [ -f \"$BACKUP_DIR/local.yaml\" ] && cp \"$BACKUP_DIR/local.yaml\" /opt/kimai/config/packages/\n    rm -rf \"$BACKUP_DIR\"\n    cd /opt/kimai \n    sed -i '/^admin_lte:/,/^[^[:space:]]/d' config/packages/local.yaml\n    $STD composer install --no-dev --optimize-autoloader\n    $STD bin/console kimai:update\n    msg_ok \"Updated Kimai\"\n\n    msg_info \"Starting Apache2\"\n    systemctl start apache2\n    msg_ok \"Started Apache2\"\n\n    msg_info \"Setup Permissions\"\n    chown -R :www-data /opt/*\n    chmod -R g+r /opt/*\n    chmod -R g+rw /opt/*\n    chown -R www-data:www-data /opt/*\n    chmod -R 777 /opt/*\n    rm -rf \"$BACKUP_DIR\"\n    msg_ok \"Setup Permissions\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/kitchenowl.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: snazzybean\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TomBursch/kitchenowl\n\nAPP=\"KitchenOwl\"\nvar_tags=\"${var_tags:-food;recipes}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/kitchenowl ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"kitchenowl\" \"TomBursch/kitchenowl\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop kitchenowl\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /opt/kitchenowl_backup\n    cp -r /opt/kitchenowl/data /opt/kitchenowl_backup/\n    cp -f /opt/kitchenowl/kitchenowl.env /opt/kitchenowl_backup/\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"kitchenowl\" \"TomBursch/kitchenowl\" \"tarball\" \"latest\" \"/opt/kitchenowl\"\n    rm -rf /opt/kitchenowl/web\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"kitchenowl-web\" \"TomBursch/kitchenowl\" \"prebuild\" \"latest\" \"/opt/kitchenowl/web\" \"kitchenowl_Web.tar.gz\"\n\n    msg_info \"Restoring data\"\n    sed -i 's/default=True/default=False/' /opt/kitchenowl/backend/wsgi.py\n    cp -r /opt/kitchenowl_backup/data /opt/kitchenowl/\n    cp -f /opt/kitchenowl_backup/kitchenowl.env /opt/kitchenowl/\n    rm -rf /opt/kitchenowl_backup\n    msg_ok \"Restored data\"\n\n    msg_info \"Updating KitchenOwl\"\n    cd /opt/kitchenowl/backend\n    $STD uv sync --frozen\n    cd /opt/kitchenowl/backend\n    set -a\n    source /opt/kitchenowl/kitchenowl.env\n    set +a\n    $STD uv run flask db upgrade\n    msg_ok \"Updated KitchenOwl\"\n\n    msg_info \"Starting Service\"\n    systemctl start kitchenowl\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/koel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://koel.dev/\n\nAPP=\"Koel\"\nvar_tags=\"${var_tags:-music;streaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/koel ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"koel\" \"koel/koel\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop nginx php8.4-fpm\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /tmp/koel_backup\n    cp /opt/koel/.env /tmp/koel_backup/\n    cp -r /opt/koel/storage /tmp/koel_backup/ 2>/dev/null || true\n    cp -r /opt/koel/public/img /tmp/koel_backup/ 2>/dev/null || true\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"koel\" \"koel/koel\" \"prebuild\" \"latest\" \"/opt/koel\" \"koel-*.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    cp /tmp/koel_backup/.env /opt/koel/\n    cp -r /tmp/koel_backup/storage/* /opt/koel/storage/ 2>/dev/null || true\n    cp -r /tmp/koel_backup/img/* /opt/koel/public/img/ 2>/dev/null || true\n    rm -rf /tmp/koel_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Running Migrations\"\n    cd /opt/koel \n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-interaction --no-dev --optimize-autoloader\n    $STD php artisan migrate --force\n    $STD php artisan config:clear\n    $STD php artisan cache:clear\n    $STD php artisan view:clear\n    $STD php artisan koel:init --no-assets --no-interaction\n    chown -R www-data:www-data /opt/koel\n    chmod -R 775 /opt/koel/storage\n    msg_ok \"Ran Migrations\"\n\n    msg_info \"Starting Services\"\n    systemctl start php8.4-fpm nginx\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/koillection.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://koillection.github.io/\n\nAPP=\"Koillection\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/koillection ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"koillection\" \"benjaminjonard/koillection\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    PHP_VERSION=\"8.5\" PHP_APACHE=\"YES\" setup_php\n    setup_composer\n\n    msg_info \"Creating a backup\"\n    mv /opt/koillection/ /opt/koillection-backup\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"koillection\" \"benjaminjonard/koillection\" \"tarball\"\n\n    msg_info \"Updating Koillection\"\n    cd /opt/koillection \n    cp -r /opt/koillection-backup/.env.local /opt/koillection\n    cp -r /opt/koillection-backup/public/uploads/. /opt/koillection/public/uploads/\n    \n    # Ensure APP_RUNTIME is in .env.local for CLI commands (upgrades from older versions)\n    if ! grep -q \"APP_RUNTIME\" /opt/koillection/.env.local 2>/dev/null; then\n      # Ensure file ends with newline before appending to avoid concatenation\n      [[ -s /opt/koillection/.env.local && -n \"$(tail -c 1 /opt/koillection/.env.local)\" ]] && echo \"\" >>/opt/koillection/.env.local\n      echo 'APP_RUNTIME=\"Symfony\\Component\\Runtime\\SymfonyRuntime\"' >>/opt/koillection/.env.local\n    fi\n    \n    export COMPOSER_ALLOW_SUPERUSER=1\n    export APP_RUNTIME='Symfony\\Component\\Runtime\\SymfonyRuntime'\n    $STD composer install --no-dev -o --no-interaction --classmap-authoritative\n    $STD php bin/console doctrine:migrations:migrate --no-interaction\n    $STD php bin/console app:translations:dump\n    cd assets/ \n    $STD yarn install\n    $STD yarn build\n    mkdir -p /opt/koillection/public/uploads\n    mkdir -p /opt/koillection/var/log\n    chown -R www-data:www-data /opt/koillection/var/log\n    chown -R www-data:www-data /opt/koillection/public/uploads\n    rm -r /opt/koillection-backup\n    \n    # Ensure APP_RUNTIME is set in Apache config (for upgrades from older versions)\n    if ! grep -q \"APP_RUNTIME\" /etc/apache2/sites-available/koillection.conf 2>/dev/null; then\n      sed -i '/<VirtualHost/a\\    SetEnv APP_RUNTIME \"Symfony\\\\Component\\\\Runtime\\\\SymfonyRuntime\"' /etc/apache2/sites-available/koillection.conf\n    fi\n    msg_ok \"Updated Koillection\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/kometa.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Kometa-Team/Kometa\n\nAPP=\"Kometa\"\nvar_tags=\"${var_tags:-media;streaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/kometa\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"kometa\" \"Kometa-Team/Kometa\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop kometa\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    cp /opt/kometa/config/config.yml /opt\n    msg_ok \"Backup completed\"\n\n    PYTHON_VERSION=\"3.13\" setup_uv\n    fetch_and_deploy_gh_release \"kometa\" \"Kometa-Team/Kometa\" \"tarball\"\n\n    msg_info \"Updating Kometa\"\n    cd /opt/kometa \n    $STD uv pip install -r requirements.txt --system\n    mkdir -p config/assets\n    cp /opt/config.yml config/config.yml\n    msg_ok \"Updated Kometa\"\n\n    msg_info \"Starting Service\"\n    systemctl start kometa\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access Kometa Quickstart:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7171${CL}\"\n"
  },
  {
    "path": "ct/komga.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: madelyn (DysfunctionalProgramming)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://komga.org/\n\nAPP=\"Komga\"\nvar_tags=\"${var_tags:-media;eBook;comic}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /opt/komga/komga.jar ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"komga-org\" \"gotson/komga\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop komga\n    msg_ok \"Stopped Service\"\n\n    rm -f /opt/komga/komga.jar\n    USE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"komga-org\" \"gotson/komga\" \"singlefile\" \"latest\" \"/opt/komga\" \"komga*.jar\"\n    mv /opt/komga/komga-*.jar /opt/komga/komga.jar\n\n    msg_info \"Starting Service\"\n    systemctl start komga\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:25600${CL}\"\n"
  },
  {
    "path": "ct/komodo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://komo.do\n\nAPP=\"Komodo\"\nvar_tags=\"${var_tags:-docker}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nADDON_SCRIPT=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/komodo.sh\"\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/komodo ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_warn \"⚠️  ${APP} has been migrated to an addon script.\"\n  echo \"\"\n  msg_info \"This is a one-time migration. After this, you can update ${APP} anytime with:\"\n  echo -e \"${TAB}${TAB}${GN}update_komodo${CL}  or  ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}\"\n  echo \"\"\n  read -r -p \"${TAB}Migrate update function now? [y/N]: \" CONFIRM\n  if [[ ! \"${CONFIRM,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Migration skipped. The old update will continue to work for now.\"\n    msg_warn \"⚠️  Komodo v2 uses :2 image tags. The :latest tag is deprecated and will not receive v2 updates.\"\n    msg_warn \"Please migrate to the addon script to receive Komodo v2.\"\n    msg_info \"Updating ${APP} (legacy)\"\n    COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)\n    if [[ -z \"$COMPOSE_FILE\" ]]; then\n      msg_error \"No valid compose file found in /opt/komodo!\"\n      exit 252\n    fi\n    $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file /opt/komodo/compose.env pull\n    $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file /opt/komodo/compose.env up -d\n    msg_ok \"Updated ${APP}\"\n    exit\n  fi\n\n  msg_info \"Migrating update function\"\n  TMP_UPDATE=$(mktemp)\n  cat <<'MIGRATION_EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/komodo.sh)\"\nMIGRATION_EOF\n  mv \"$TMP_UPDATE\" /usr/bin/update\n  chmod +x /usr/bin/update\n\n  ln -sf /usr/bin/update /usr/bin/update_komodo 2>/dev/null || true\n  msg_ok \"Migration complete\"\n\n  msg_info \"Running addon update\"\n  type=update bash <(curl -fsSL \"${ADDON_SCRIPT}\")\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9120${CL}\"\n"
  },
  {
    "path": "ct/kubo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: ulmentflam\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ipfs/kubo\n\nAPP=\"Kubo\"\nvar_tags=\"${var_tags:-sharing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/kubo/ipfs ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"kubo\" \"ipfs/kubo\"; then\n    msg_info \"Stopping service\"\n    systemctl stop ipfs\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"kubo\" \"ipfs/kubo\" \"prebuild\" \"latest\" \"/usr/local/kubo\" \"kubo*linux-arm64.tar.gz\"\n\n    msg_info \"Starting service\"\n    systemctl start ipfs\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5001/webui${CL}\"\n"
  },
  {
    "path": "ct/kutt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tomfrenzel\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/thedevs-network/kutt\n\nAPP=\"Kutt\"\nvar_tags=\"${var_tags:-sharing}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/kutt ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"kutt\" \"thedevs-network/kutt\"; then\n    msg_info \"Stopping services\"\n    systemctl stop kutt\n    msg_ok \"Stopped services\"\n\n    msg_info \"Backing up data\"\n    mkdir -p /opt/kutt-backup\n    [ -d /opt/kutt/custom ] && cp -r /opt/kutt/custom /opt/kutt-backup/\n    [ -d /opt/kutt/db ] && cp -r /opt/kutt/db /opt/kutt-backup/\n    cp /opt/kutt/.env /opt/kutt-backup/\n    msg_ok \"Backed up data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"kutt\" \"thedevs-network/kutt\" \"tarball\" \"latest\"\n\n    msg_info \"Restoring data\"\n    [ -d /opt/kutt-backup/custom ] && cp -r /opt/kutt-backup/custom /opt/kutt/\n    [ -d /opt/kutt-backup/db ] && cp -r /opt/kutt-backup/db /opt/kutt/\n    [ -f /opt/kutt-backup/.env ] && cp /opt/kutt-backup/.env /opt/kutt/\n    rm -rf /opt/kutt-backup\n    msg_ok \"Restored data\"\n\n    msg_info \"Configuring Kutt\"\n    cd /opt/kutt\n    $STD npm install\n    $STD npm run migrate\n    msg_ok \"Configured Kutt\"\n\n    msg_info \"Starting services\"\n    systemctl start kutt\n    msg_ok \"Started services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP} or https://<your-Kutt-domain>${CL}\"\n"
  },
  {
    "path": "ct/languagetool.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://languagetool.org/\n\nAPP=\"LanguageTool\"\nvar_tags=\"${var_tags:-spellcheck}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/LanguageTool ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -fsSL https://languagetool.org/download/ | grep -oP 'LanguageTool-\\K[0-9]+\\.[0-9]+(\\.[0-9]+)?(?=\\.zip)' | sort -V | tail -n1)\n  if [[ \"${RELEASE}\" != \"$(cat ~/.languagetool 2>/dev/null)\" ]] || [[ ! -f ~/.languagetool ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop language-tool\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/LanguageTool/server.properties /opt/server.properties\n    msg_ok \"Created Backup\"\n\n    msg_info \"Updating LanguageTool\"\n    rm -rf /opt/LanguageTool\n    download_file \"https://languagetool.org/download/LanguageTool-stable.zip\" /tmp/LanguageTool-stable.zip\n    unzip -q /tmp/LanguageTool-stable.zip -d /opt\n    mv /opt/LanguageTool-*/ /opt/LanguageTool/\n    mv /opt/server.properties /opt/LanguageTool/server.properties\n    rm -f /tmp/LanguageTool-stable.zip\n    echo \"${RELEASE}\" >~/.languagetool\n    msg_ok \"Updated LanguageTool\"\n\n    msg_info \"Starting Service\"\n    systemctl start language-tool\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfuly!\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8081/v2${CL}\"\n"
  },
  {
    "path": "ct/lazylibrarian.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MountyMapleSyrup (MountyMapleSyrup)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gitlab.com/LazyLibrarian/LazyLibrarian\n\nAPP=\"LazyLibrarian\"\nvar_tags=\"${var_tags:-eBook}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/LazyLibrarian/ ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Stopping LazyLibrarian\"\n    systemctl stop lazylibrarian\n    msg_ok \"LazyLibrarian Stopped\"\n\n    msg_info \"Updating $APP LXC\"\n    $STD git -C /opt/LazyLibrarian pull origin master\n    msg_ok \"Updated $APP LXC\"\n\n    msg_info \"Starting LazyLibrarian\"\n    systemctl start lazylibrarian\n    msg_ok \"Started LazyLibrarian\"\n\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5299${CL}\"\n"
  },
  {
    "path": "ct/leantime.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Stroopwafe1\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://leantime.io\n\nAPP=\"Leantime\"\nvar_tags=\"${var_tags:-productivity}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/leantime ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"leantime\" \"Leantime/leantime\"; then\n    msg_info \"Creating Backup\"\n    mariadb-dump leantime >\"/opt/leantime_db_backup_$(date +%F).sql\"\n    tar -czf \"/opt/leantime_backup_$(date +%F).tar.gz\" \"/opt/leantime\"\n    mv /opt/leantime /opt/leantime_bak\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_gh_release \"leantime\" \"Leantime/leantime\" \"prebuild\" \"latest\" \"/opt/leantime\" Leantime*.tar.gz\n\n    msg_info \"Restoring Config & Permissions\"\n    mv /opt/leantime_bak/config/.env /opt/leantime/config/.env\n    chown -R www-data:www-data \"/opt/leantime\"\n    chmod -R 750 \"/opt/leantime\"\n    msg_ok \"Restored Config & Permissions\"\n\n    msg_info \"Removing Backup\"\n    rm -rf /opt/leantime_bak\n    msg_ok \"Removed Backup\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/install${CL}\"\n"
  },
  {
    "path": "ct/librechat.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/danny-avila/LibreChat\n\nAPP=\"LibreChat\"\nvar_tags=\"${var_tags:-ai;chat}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/librechat ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_tag \"librechat\" \"danny-avila/LibreChat\" \"v\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop librechat rag-api\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Configuration\"\n    cp /opt/librechat/.env /opt/librechat.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_tag \"librechat\" \"danny-avila/LibreChat\"\n\n    msg_info \"Installing Dependencies\"\n    cd /opt/librechat\n    $STD npm ci\n    msg_ok \"Installed Dependencies\"\n\n    msg_info \"Building Frontend\"\n    $STD npm run frontend\n    $STD npm prune --production\n    $STD npm cache clean --force\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Restoring Configuration\"\n    cp /opt/librechat.env.bak /opt/librechat/.env\n    rm -f /opt/librechat.env.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Services\"\n    systemctl start rag-api librechat\n    msg_ok \"Started Services\"\n    msg_ok \"Updated LibreChat Successfully!\"\n  fi\n\n  if check_for_gh_release \"rag-api\" \"danny-avila/rag_api\"; then\n    msg_info \"Stopping RAG API\"\n    systemctl stop rag-api\n    msg_ok \"Stopped RAG API\"\n\n    msg_info \"Backing up RAG API Configuration\"\n    cp /opt/rag-api/.env /opt/rag-api.env.bak\n    msg_ok \"Backed up RAG API Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"rag-api\" \"danny-avila/rag_api\" \"tarball\"\n\n    msg_info \"Updating RAG API Dependencies\"\n    cd /opt/rag-api\n    $STD .venv/bin/pip install -r requirements.lite.txt\n    msg_ok \"Updated RAG API Dependencies\"\n\n    msg_info \"Restoring RAG API Configuration\"\n    cp /opt/rag-api.env.bak /opt/rag-api/.env\n    rm -f /opt/rag-api.env.bak\n    msg_ok \"Restored RAG API Configuration\"\n\n    msg_info \"Starting RAG API\"\n    systemctl start rag-api\n    msg_ok \"Started RAG API\"\n    msg_ok \"Updated RAG API Successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3080${CL}\"\n"
  },
  {
    "path": "ct/librenms.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://librenms.org\n\nAPP=\"LibreNMS\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [ ! -d /opt/librenms ]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  msg_info \"Updating LibreNMS\"\n  su librenms\n  cd /opt/librenms\n  ./daily.sh\n  msg_ok \"Updated LibreNMS\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/librespeed-rust.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joseph Stubberfield (stubbers)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/librespeed/speedtest-rust\n\nAPP=\"Librespeed-Rust\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/librespeed-rs ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"librespeed-rust\" \"librespeed/speedtest-rust\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop speedtest_rs\n    msg_ok \"Services Stopped\"\n\n    fetch_and_deploy_gh_release \"librespeed-rust\" \"librespeed/speedtest-rust\" \"binary\" \"latest\" \"/opt/librespeed-rust\" \"librespeed-rs-aarch64-unknown-linux-gnu.deb\"\n\n    msg_info \"Starting Service\"\n    systemctl start speedtest_rs\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/libretranslate.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/LibreTranslate/LibreTranslate\n\nAPP=\"LibreTranslate\"\nvar_tags=\"${var_tags:-Arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/libretranslate ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  PYTHON_VERSION=\"3.12\" setup_uv\n\n  if check_for_gh_release \"libretranslate\" \"LibreTranslate/LibreTranslate\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop libretranslate\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating LibreTranslate\"\n    cd /opt/libretranslate\n    source .venv/bin/activate\n    $STD uv pip install -U libretranslate\n    msg_ok \"Updated LibreTranslate\"\n\n    msg_info \"Starting Service\"\n    systemctl start libretranslate\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/lidarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://lidarr.audio/\n\nAPP=\"Lidarr\"\nvar_tags=\"${var_tags:-arr;torrent;usenet}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /var/lib/lidarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"lidarr\" \"Lidarr/Lidarr\"; then\n    apt-get install -y libicu76 &>/dev/null\n    msg_info \"Stopping service\"\n    systemctl stop lidarr\n    msg_ok \"Service stopped\"\n\n    fetch_and_deploy_gh_release \"lidarr\" \"Lidarr/Lidarr\" \"prebuild\" \"latest\" \"/opt/Lidarr\" \"Lidarr.master*linux-core-arm64.tar.gz\"\n    chmod 775 /opt/Lidarr\n\n    msg_info \"Starting service\"\n    systemctl start lidarr\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8686${CL}\"\n"
  },
  {
    "path": "ct/limesurvey.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://community.limesurvey.org/\n\nAPP=\"LimeSurvey\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/limesurvey ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n\n  msg_warn \"Application is updated via Web Interface\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/linkding.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (MickLesk)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://linkding.link/\n\nAPP=\"linkding\"\nvar_tags=\"${var_tags:-bookmarks;management}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/linkding ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"linkding\" \"sissbruecker/linkding\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop nginx linkding linkding-tasks\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/linkding/data /opt/linkding_data_backup\n    cp /opt/linkding/.env /opt/linkding_env_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"linkding\" \"sissbruecker/linkding\" \"tarball\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/linkding_data_backup/. /opt/linkding/data\n    cp /opt/linkding_env_backup /opt/linkding/.env\n    rm -rf /opt/linkding_data_backup /opt/linkding_env_backup\n    ln -sf /usr/lib/aarch64-linux-gnu/mod_icu.so /opt/linkding/libicu.so\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating LinkDing\"\n    cd /opt/linkding\n    rm -f bookmarks/settings/dev.py\n    touch bookmarks/settings/custom.py\n    $STD npm ci\n    $STD npm run build\n    $STD uv sync --no-dev --frozen\n    $STD uv pip install gunicorn\n    set -a && source /opt/linkding/.env && set +a\n    $STD /opt/linkding/.venv/bin/python manage.py migrate\n    $STD /opt/linkding/.venv/bin/python manage.py collectstatic --no-input\n    msg_ok \"Updated LinkDing\"\n\n    msg_info \"Starting Services\"\n    systemctl start nginx linkding linkding-tasks\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9090${CL}\"\n"
  },
  {
    "path": "ct/linkstack.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://linkstack.org/\n\nAPP=\"LinkStack\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f ~/.linkstack ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  PHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" setup_php\n  msg_warn \"LinkStack should be updated via the user interface.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/linkwarden.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://linkwarden.app/\n\nAPP=\"Linkwarden\"\nvar_tags=\"${var_tags:-bookmark}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/linkwarden ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"linkwarden\" \"linkwarden/linkwarden\"; then\n    NODE_VERSION=\"22\" NODE_MODULE=\"yarn@latest\" setup_nodejs\n    msg_info \"Stopping Service\"\n    systemctl stop linkwarden\n    msg_ok \"Stopped Service\"\n\n    RUST_CRATES=\"monolith\" setup_rust\n\n    msg_info \"Backing up data\"\n    mv /opt/linkwarden/.env /opt/.env\n    [ -d /opt/linkwarden/data ] && mv /opt/linkwarden/data /opt/data.bak\n    rm -rf /opt/linkwarden\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"linkwarden\" \"linkwarden/linkwarden\" \"tarball\"\n\n    msg_info \"Updating Linkwarden\"\n    cd /opt/linkwarden\n    yarn_ver=\"4.12.0\"\n    if [[ -f package.json ]]; then\n      pkg_manager=$(jq -r '.packageManager // empty' package.json 2>/dev/null || true)\n      if [[ -n \"$pkg_manager\" && \"$pkg_manager\" == yarn@* ]]; then\n        yarn_spec=\"${pkg_manager#yarn@}\"\n        yarn_ver=\"${yarn_spec%%+*}\"\n      fi\n    fi\n    if command -v corepack >/dev/null 2>&1; then\n      $STD corepack enable\n      $STD corepack prepare \"yarn@${yarn_ver}\" --activate || true\n    fi\n    $STD yarn\n    $STD npx playwright install-deps\n    $STD npx playwright install\n    mv /opt/.env /opt/linkwarden/.env\n    $STD yarn prisma:generate\n    $STD yarn web:build\n    $STD yarn prisma:deploy\n    [ -d /opt/data.bak ] && mv /opt/data.bak /opt/linkwarden/data\n    rm -rf ~/.cargo/registry ~/.cargo/git ~/.cargo/.package-cache\n    rm -rf /root/.cache/yarn\n    rm -rf /opt/linkwarden/.next/cache\n    msg_ok \"Updated Linkwarden\"\n\n    msg_info \"Starting Service\"\n    systemctl start linkwarden\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/listmonk.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://listmonk.app/\n\nAPP=\"listmonk\"\nvar_tags=\"${var_tags:-newsletter}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/listmonk.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"listmonk\" \"knadh/listmonk\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop listmonk\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    mv /opt/listmonk/ /opt/listmonk-backup\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"listmonk\" \"knadh/listmonk\" \"prebuild\" \"latest\" \"/opt/listmonk\" \"listmonk*linux_arm64.tar.gz\"\n\n    msg_info \"Configuring listmonk\"\n    mv /opt/listmonk-backup/config.toml /opt/listmonk/config.toml\n    mv /opt/listmonk-backup/uploads /opt/listmonk/uploads\n    $STD /opt/listmonk/listmonk --upgrade --yes --config /opt/listmonk/config.toml\n    rm -rf /opt/listmonk-backup/\n    msg_ok \"Configured listmonk\"\n\n    msg_info \"Starting Service\"\n    systemctl start listmonk\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/livebook.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: dkuku\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/livebook-dev/livebook\n\nAPP=\"Livebook\"\nvar_tags=\"${var_tags:-development}\"\nvar_disk=\"${var_disk:-4}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/livebook/.mix/escripts/livebook ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"livebook\" \"livebook-dev/livebook\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop livebook\n    msg_info \"Stopped Service\"\n\n    msg_info \"Updating Container\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated Container\"\n\n    msg_info \"Updating Livebook\"\n    source /opt/livebook/.env\n    cd /opt/livebook\n    $STD mix escript.install hex livebook --force\n\n    chown -R livebook:livebook /opt/livebook /data\n\n    msg_info \"Starting Service\"\n    systemctl start livebook\n    msg_info \"Started Service\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/lldap.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/lldap/lldap\n\nAPP=\"lldap\"\nvar_tags=\"${var_tags:-ldap}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/lldap.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating lldap\"\n  $STD apt update\n  $STD apt upgrade -y lldap\n  msg_ok \"Updated lldap\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:17170${CL}\"\n"
  },
  {
    "path": "ct/loki.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: hoholms\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/grafana/loki\n\nAPP=\"Loki\"\nvar_tags=\"${var_tags:-monitoring;logs}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if ! dpkg -s loki >/dev/null 2>&1; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit 233\n  fi\n\n  CHOICE=$(msg_menu \"Loki Update Options\" \\\n    \"1\" \"Update Loki & Promtail\" \\\n    \"2\" \"Allow 0.0.0.0 for listening\" \\\n    \"3\" \"Allow only ${LOCAL_IP} for listening\")\n\n  case $CHOICE in\n  1)\n      msg_info \"Stopping Loki\"\n      systemctl stop loki\n      if systemctl is-active --quiet promtail 2>/dev/null || dpkg -s promtail >/dev/null 2>&1; then\n        systemctl stop promtail\n      fi\n      msg_ok \"Stopped Loki\"\n\n      msg_info \"Updating Loki\"\n      $STD apt update\n      $STD apt install -y --only-upgrade loki\n      if dpkg -s promtail >/dev/null 2>&1; then\n        $STD apt install -y --only-upgrade promtail\n      fi\n      msg_ok \"Updated Loki\"\n\n      msg_info \"Starting Loki\"\n      systemctl start loki\n      if dpkg -s promtail >/dev/null 2>&1; then\n        systemctl start promtail\n      fi\n      msg_ok \"Started Loki\"\n      msg_ok \"Updated successfully!\"\n      exit\n      ;;\n    2)\n      msg_info \"Configuring Loki to listen on 0.0.0.0\"\n      sed -i 's/http_listen_address:.*/http_listen_address: 0.0.0.0/' /etc/loki/config.yml\n      sed -i 's/http_listen_port:.*/http_listen_port: 3100/' /etc/loki/config.yml\n      systemctl restart loki\n      msg_ok \"Configured Loki to listen on 0.0.0.0\"\n      exit\n      ;;\n    3)\n      msg_info \"Configuring Loki to listen on ${LOCAL_IP}\"\n      sed -i \"s/http_listen_address:.*/http_listen_address: $LOCAL_IP/\" /etc/loki/config.yml\n      sed -i 's/http_listen_port:.*/http_listen_port: 3100/' /etc/loki/config.yml\n      systemctl restart loki\n      msg_ok \"Configured Loki to listen on ${LOCAL_IP}\"\n      exit\n      ;;\n    esac\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access loki using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3100${CL}\\n\"\nif dpkg -s promtail >/dev/null 2>&1; then\n  echo -e \"${INFO}${YW} Access promtail using the following URL:${CL}\"\n  echo -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9080${CL}\"\nfi\n"
  },
  {
    "path": "ct/lubelogger.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://lubelogger.com/\n\nAPP=\"LubeLogger\"\nvar_tags=\"${var_tags:-vehicle;car}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/lubelogger.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"lubelogger\" \"hargata/lubelog\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop lubelogger\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    mkdir -p /tmp/lubeloggerData/data\n    cp /opt/lubelogger/appsettings.json /tmp/lubeloggerData/appsettings.json\n    cp -r /opt/lubelogger/data/ /tmp/lubeloggerData/\n\n    # Lubelogger has moved multiples folders to the 'data' folder, and we need to move them before the update to keep the user data\n    # Github Discussion: https://github.com/hargata/lubelog/discussions/787\n    [[ -e /opt/lubelogger/config ]] && cp -r /opt/lubelogger/config /tmp/lubeloggerData/data/\n    [[ -e /opt/lubelogger/wwwroot/translations ]] && cp -r /opt/lubelogger/wwwroot/translations /tmp/lubeloggerData/data/\n    [[ -e /opt/lubelogger/wwwroot/documents ]] && cp -r /opt/lubelogger/wwwroot/documents /tmp/lubeloggerData/data/\n    [[ -e /opt/lubelogger/wwwroot/images ]] && cp -r /opt/lubelogger/wwwroot/images /tmp/lubeloggerData/data/\n    [[ -e /opt/lubelogger/wwwroot/temp ]] && cp -r /opt/lubelogger/wwwroot/temp /tmp/lubeloggerData/data/\n    [[ -e /opt/lubelogger/log ]] && cp -r /opt/lubelogger/log /tmp/lubeloggerData/\n    rm -rf /opt/lubelogger\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"lubelogger\" \"hargata/lubelog\" \"prebuild\" \"latest\" \"/opt/lubelogger\" \"LubeLogger*linux_x64.zip\"\n\n    msg_info \"Configuring LubeLogger\"\n    chmod 700 /opt/lubelogger/CarCareTracker\n    cp -rf /tmp/lubeloggerData/* /opt/lubelogger/\n    rm -rf /tmp/lubeloggerData\n    msg_ok \"Configured LubeLogger\"\n\n    msg_info \"Starting Service\"\n    systemctl start lubelogger\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/lychee.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/LycheeOrg/Lychee\n\nAPP=\"Lychee\"\nvar_tags=\"${var_tags:-media;photos;gallery}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/lychee ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"lychee\" \"LycheeOrg/Lychee\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop caddy\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/lychee/.env /opt/lychee.env.bak\n    cp -r /opt/lychee/storage /opt/lychee_storage_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"lychee\" \"LycheeOrg/Lychee\" \"prebuild\" \"latest\" \"/opt/lychee\" \"Lychee.zip\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/lychee.env.bak /opt/lychee/.env\n    rm -f /opt/lychee.env.bak\n    cp -r /opt/lychee_storage_backup/. /opt/lychee/storage\n    rm -rf /opt/lychee_storage_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating Application\"\n    cd /opt/lychee\n    $STD php artisan migrate --force\n    $STD php artisan optimize:clear\n    chmod -R 775 /opt/lychee/storage /opt/lychee/bootstrap/cache\n    msg_ok \"Updated Application\"\n\n    msg_info \"Starting Services\"\n    systemctl start caddy\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/lyrionmusicserver.sh",
    "content": "#!/usr/bin/env bash\n\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://lyrion.org/getting-started/\n\nAPP=\"Lyrion Music Server\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /lib/systemd/system/lyrionmusicserver.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  DEB_URL=$(curl -s 'https://lyrion.org/getting-started/' | grep -oP '<a\\s[^>]*href=\"\\K[^\"]*arm\\.deb(?=\"[^>]*>)' | head -n 1)\n  RELEASE=$(echo \"$DEB_URL\" | grep -oP 'lyrionmusicserver_\\K[0-9.]+(?=_arm\\.deb)')\n  DEB_FILE=\"/tmp/lyrionmusicserver_${RELEASE}_arm.deb\"\n  if [[ ! -f /opt/lyrion_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/lyrion_version.txt)\" ]]; then\n    msg_info \"Updating $APP to ${RELEASE}\"\n    curl_with_retry \"$DEB_URL\" \"$DEB_FILE\"\n    $STD apt install \"$DEB_FILE\" -y\n    systemctl restart lyrionmusicserver\n    rm -f \"$DEB_FILE\"\n    echo \"${RELEASE}\" >/opt/lyrion_version.txt\n    msg_ok \"Updated $APP to ${RELEASE}\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"$APP is already up to date (${RELEASE})\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access the web interface at:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/mafl.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mafl.hywax.space/\n\nAPP=\"Mafl\"\nvar_tags=\"${var_tags:-dashboard}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/mafl ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"mafl\" \"hywax/mafl\"; then\n    msg_info \"Stopping Mafl service\"\n    systemctl stop mafl\n    msg_ok \"Service stopped\"\n\n    msg_info \"Backing up data\"\n    mkdir -p /opt/mafl-backup/data\n    mv /opt/mafl/data /opt/mafl-backup/data\n    rm -rf /opt/mafl\n    msg_ok \"Backup complete\"\n\n    fetch_and_deploy_gh_release \"mafl\" \"hywax/mafl\" \"tarball\"\n\n    msg_info \"Updating Mafl\"\n    cd /opt/mafl\n    $STD yarn install\n    $STD yarn build\n    mv /opt/mafl-backup/data /opt/mafl/data\n    msg_ok \"Mafl updated\"\n\n    msg_info \"Starting Service\"\n    systemctl start mafl\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/magicmirror.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://magicmirror.builders/\n\nAPP=\"MagicMirror\"\nvar_tags=\"${var_tags:-smarthome}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/magicmirror ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"magicmirror\" \"MagicMirrorOrg/MagicMirror\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop magicmirror\n    msg_ok \"Stopped Service\"\n\n    NODE_VERSION=\"24\" setup_nodejs\n\n    msg_info \"Backing up data\"\n    rm -rf /opt/magicmirror-backup\n    mkdir /opt/magicmirror-backup\n    cp /opt/magicmirror/config/config.js /opt/magicmirror-backup\n    if [[ -f /opt/magicmirror/css/custom.css ]]; then\n      cp /opt/magicmirror/css/custom.css /opt/magicmirror-backup\n    fi\n    cp -r /opt/magicmirror/modules /opt/magicmirror-backup\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"magicmirror\" \"MagicMirrorOrg/MagicMirror\" \"tarball\"\n\n    msg_info \"Configuring MagicMirror\"\n    cd /opt/magicmirror\n    sed -i -E 's/(\"postinstall\": )\".*\"/\\1\"\"/; s/(\"prepare\": )\".*\"/\\1\"\"/' package.json\n    $STD npm run install-mm\n    cp /opt/magicmirror-backup/config.js /opt/magicmirror/config/\n    if [[ -f /opt/magicmirror-backup/custom.css ]]; then\n      cp /opt/magicmirror-backup/custom.css /opt/magicmirror/css/\n    fi\n    msg_ok \"Configured MagicMirror\"\n\n    msg_info \"Starting Service\"\n    systemctl start magicmirror\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/mail-archiver.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/s1t5/mail-archiver\n\nAPP=\"Mail-Archiver\"\nvar_tags=\"${var_tags:-mail-archiver}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/mail-archiver ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies libgssapi-krb5-2\n\n  if check_for_gh_release \"mail-archiver\" \"s1t5/mail-archiver\"; then\n    msg_info \"Stopping Mail-Archiver\"\n    systemctl stop mail-archiver\n    msg_ok \"Stopped Mail-Archiver\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/mail-archiver/appsettings.json /opt/mail-archiver/.env /opt/\n    [[ -d /opt/mail-archiver/DataProtection-Keys ]] && cp -r /opt/mail-archiver/DataProtection-Keys /opt\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"mail-archiver\" \"s1t5/mail-archiver\" \"tarball\"\n\n    msg_info \"Updating Mail-Archiver\"\n    mv /opt/mail-archiver /opt/mail-archiver-build\n    cd /opt/mail-archiver-build\n    $STD dotnet restore\n    $STD dotnet publish -c Release -o /opt/mail-archiver\n    rm -rf /opt/mail-archiver-build\n    msg_ok \"Updated Mail-Archiver\"\n\n    msg_info \"Restoring Backup\"\n    cp /opt/appsettings.json /opt/.env /opt/mail-archiver\n    [[ -d /opt/DataProtection-Keys ]] && cp -r /opt/DataProtection-Keys /opt/mail-archiver/\n    msg_ok \"Restored Backup\"\n\n    msg_info \"Starting Mail-Archiver\"\n    systemctl start mail-archiver\n    msg_ok \"Started Mail-Archiver\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/managemydamnlife.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/intri-in/manage-my-damn-life-nextjs\n\nAPP=\"Manage My Damn Life\"\nvar_tags=\"${var_tags:-calendar;tasks}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/mmdl ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"mmdl\" \"intri-in/manage-my-damn-life-nextjs\"; then\n    msg_info \"Stopping service\"\n    systemctl stop mmdl\n    msg_ok \"Stopped service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/mmdl/.env /opt/mmdl.env\n    rm -rf /opt/mmdl\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_gh_release \"mmdl\" \"intri-in/manage-my-damn-life-nextjs\" \"tarball\"\n    NODE_VERSION=\"22\" setup_nodejs\n\n    msg_info \"Configuring ${APP}\"\n    cd /opt/mmdl\n    export NEXT_TELEMETRY_DISABLED=1\n    $STD npm install\n    $STD npm run migrate\n    $STD npm run build\n    msg_ok \"Configured ${APP}\"\n\n    msg_info \"Starting service\"\n    systemctl start mmdl\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/manyfold.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01 | Co-Author: SunFlowerOwl\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/manyfold3d/manyfold\n\nAPP=\"Manyfold\"\nvar_tags=\"${var_tags:-3d}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-15}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/manyfold ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" NODE_MODULE=\"yarn\" setup_nodejs\n\n  if check_for_gh_release \"manyfold\" \"manyfold3d/manyfold\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop manyfold.target manyfold-rails.1 manyfold-default_worker.1 manyfold-performance_worker.1\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    CURRENT_VERSION=$(grep -oP 'APP_VERSION=\\K[^ ]+' /opt/manyfold/.env || echo \"unknown\")\n    cp -r /opt/manyfold/app/storage /opt/manyfold_storage_backup 2>/dev/null || true\n    cp -r /opt/manyfold/app/tmp /opt/manyfold_tmp_backup 2>/dev/null || true\n    cp /opt/manyfold/app/config/credentials.yml.enc /opt/manyfold_credentials.yml.enc 2>/dev/null || true\n    cp /opt/manyfold/app/config/master.key /opt/manyfold_master.key 2>/dev/null || true\n    $STD tar -czf \"/opt/manyfold_${CURRENT_VERSION}_backup.tar.gz\" -C /opt/manyfold app\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"manyfold\" \"manyfold3d/manyfold\" \"tarball\" \"latest\" \"/opt/manyfold/app\"\n\n    msg_info \"Configuring Manyfold\"\n    RUBY_INSTALL_VERSION=$(cat /opt/manyfold/app/.ruby-version)\n    YARN_VERSION=$(grep '\"packageManager\":' /opt/manyfold/app/package.json | sed -E 's/.*\"(yarn@[0-9\\.]+)\".*/\\1/')\n    RELEASE=$(get_latest_github_release \"manyfold3d/manyfold\")\n    sed -i \"s/^export APP_VERSION=.*/export APP_VERSION=$RELEASE/\" \"/opt/manyfold/.env\"\n    msg_ok \"Configured Manyfold\"\n\n    RUBY_VERSION=${RUBY_INSTALL_VERSION} RUBY_INSTALL_RAILS=\"true\" HOME=/home/manyfold setup_ruby\n\n    msg_info \"Installing Manyfold\"\n    chown -R manyfold:manyfold {/home/manyfold,/opt/manyfold}\n    chown -R manyfold:manyfold /opt/manyfold\n\n    sudo -u manyfold bash -c '\n            source /opt/manyfold/.env\n            export PATH=\"/home/manyfold/.rbenv/bin:$PATH\"\n            eval \"$(/home/manyfold/.rbenv/bin/rbenv init - bash)\"\n            cd /opt/manyfold/app\n            gem install bundler sidekiq foreman\n            bundle install\n            corepack enable yarn\n            corepack prepare '\"$YARN_VERSION\"' --activate\n            corepack use '\"$YARN_VERSION\"'\n            bin/rails db:migrate\n            bin/rails assets:precompile\n        '\n    msg_ok \"Installed Manyfold\"\n\n    msg_info \"Restoring Data\"\n    rm -rf /opt/manyfold/app/{storage,tmp,config/credentials.yml.enc,config/master.key}\n    cp -r /opt/manyfold_storage_backup /opt/manyfold/app/storage 2>/dev/null || true\n    cp -r /opt/manyfold_tmp_backup /opt/manyfold/app/tmp 2>/dev/null || true\n    cp /opt/manyfold_credentials.yml.enc /opt/manyfold/app/config/credentials.yml.enc 2>/dev/null || true\n    cp /opt/manyfold_master.key /opt/manyfold/app/config/master.key 2>/dev/null || true\n    chown -R manyfold:manyfold /opt/manyfold/app/storage /opt/manyfold/app/tmp /opt/manyfold/app/config\n    rm -rf /opt/manyfold_storage_backup /opt/manyfold_tmp_backup /opt/manyfold_credentials.yml.enc /opt/manyfold_master.key\n    msg_ok \"Restored Data\"\n\n    msg_info \"Restarting Services\"\n    source /opt/manyfold/.env\n    export PATH=\"/home/manyfold/.rbenv/shims:/home/manyfold/.rbenv/bin:$PATH\"\n    $STD foreman export systemd /etc/systemd/system -a manyfold -u manyfold -f /opt/manyfold/app/Procfile\n    for f in /etc/systemd/system/manyfold-*.service; do\n      sed -i \"s|/bin/bash -lc '|/bin/bash -lc 'source /opt/manyfold/.env \\&\\& |\" \"$f\"\n    done\n    systemctl daemon-reload\n    systemctl enable -q --now manyfold.target manyfold-rails.1 manyfold-default_worker.1 manyfold-performance_worker.1\n    msg_ok \"Restarted Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/mariadb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mariadb.org/\n\nAPP=\"MariaDB\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/mysql/mariadb.conf.d ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:3306${CL}\"\n"
  },
  {
    "path": "ct/matomo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://matomo.org/\n\nAPP=\"Matomo\"\nvar_tags=\"${var_tags:-analytics;tracking;privacy}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/matomo ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"matomo\" \"matomo-org/matomo\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop caddy\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    [[ -f /opt/matomo/config/config.ini.php ]] && cp /opt/matomo/config/config.ini.php /opt/matomo_config.bak\n    [[ -d /opt/matomo/misc/user ]] && cp -r /opt/matomo/misc/user /opt/matomo_user_backup\n    [[ -f /root/matomo.creds ]] && cp /root/matomo.creds /opt/matomo_db_creds.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"matomo\" \"matomo-org/matomo\" \"prebuild\" \"latest\" \"/opt/matomo\" \"matomo-*.zip\"\n\n    msg_info \"Restoring Data\"\n    if [[ -f /opt/matomo_config.bak ]]; then\n      mkdir -p /opt/matomo/config\n      cp /opt/matomo_config.bak /opt/matomo/config/config.ini.php\n    fi\n    if [[ -d /opt/matomo_user_backup ]]; then\n      mkdir -p /opt/matomo/misc/user\n      cp -r /opt/matomo_user_backup/. /opt/matomo/misc/user\n    fi\n    [[ -f /opt/matomo_db_creds.bak ]] && cp /opt/matomo_db_creds.bak /root/matomo.creds\n    rm -f /opt/matomo_config.bak /opt/matomo_db_creds.bak\n    rm -rf /opt/matomo_user_backup\n    chown -R www-data:www-data /opt/matomo\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Services\"\n    systemctl start caddy\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/matter-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/matter-js/python-matter-server\n\nAPP=\"Matter-Server\"\nvar_tags=\"${var_tags:-matter;iot;smart-home}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/matter-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"matter-server\" \"matter-js/python-matter-server\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop matter-server\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating Matter Server\"\n    MATTER_VERSION=$(get_latest_github_release \"matter-js/python-matter-server\")\n    $STD uv pip install --python /opt/matter-server/.venv/bin/python --upgrade \"python-matter-server[server]==${MATTER_VERSION}\"\n    echo \"${MATTER_VERSION}\" >~/.matter-server\n    msg_ok \"Updated Matter Server\"\n\n    fetch_and_deploy_gh_release \"chip-ota-provider-app\" \"home-assistant-libs/matter-linux-ota-provider\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"chip-ota-provider-app-x86-64\"\n\n    msg_info \"Starting Service\"\n    systemctl start matter-server\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Matter Server WebSocket API is running on port 5580.${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}ws://${IP}:5580/ws${CL}\"\n"
  },
  {
    "path": "ct/matterbridge.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Luligu/matterbridge\n\nAPP=\"Matterbridge\"\nvar_tags=\"${var_tags:-matter;smarthome}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /root/Matterbridge ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  $STD apt update\n  $STD apt upgrade -y\n  NODE_VERSION=\"24\" NODE_MODULE=\"matterbridge\" setup_nodejs\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8283${CL}\"\n"
  },
  {
    "path": "ct/mattermost.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kaedon Cleland-Host (dracentis)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mattermost.com/\n\nAPP=\"Mattermost\"\nvar_tags=\"${var_tags:-collaboration}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /etc/apt/sources.list.d/mattermost.list ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8065${CL}\"\n"
  },
  {
    "path": "ct/mealie.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mealie.io\n\nAPP=\"Mealie\"\nvar_tags=\"${var_tags:-recipes}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/mealie ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"mealie\" \"mealie-recipes/mealie\"; then\n    PYTHON_VERSION=\"3.12\" setup_uv\n    NODE_MODULE=\"yarn\" NODE_VERSION=\"24\" setup_nodejs\n\n    msg_info \"Stopping Service\"\n    systemctl stop mealie\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp -f /opt/mealie/mealie.env /opt/mealie.env\n    [[ -f /opt/mealie/start.sh ]] && cp -f /opt/mealie/start.sh /opt/mealie.start.sh\n    msg_ok \"Backup completed\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"mealie\" \"mealie-recipes/mealie\" \"tarball\"\n\n    msg_info \"Restoring Configuration\"\n    mv -f /opt/mealie.env /opt/mealie/mealie.env\n    if [[ -f /opt/mealie.start.sh ]]; then\n      mv -f /opt/mealie.start.sh /opt/mealie/start.sh\n    else\n      cat <<'STARTEOF' >/opt/mealie/start.sh\n#!/bin/bash\nset -a\nsource /opt/mealie/mealie.env\nset +a\nexec uv run mealie\nSTARTEOF\n    fi\n    chmod +x /opt/mealie/start.sh\n    msg_ok \"Configuration restored\"\n\n    msg_info \"Installing Python Dependencies with uv\"\n    cd /opt/mealie\n    $STD uv sync --frozen --extra pgsql\n    msg_ok \"Installed Python Dependencies\"\n\n    msg_info \"Building Frontend\"\n    MEALIE_VERSION=$(<$HOME/.mealie)\n    SITE_SETTINGS=$(find /opt/mealie/frontend -name \"site-settings.vue\" -path \"*/admin/*\" | head -1)\n    $STD sed -i \"s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g\" \"$SITE_SETTINGS\"\n    $STD sed -i \"s|value: data.buildId,|value: \\\"v${MEALIE_VERSION}\\\",|g\" \"$SITE_SETTINGS\"\n    $STD sed -i \"s|value: data.production ? i18n.t(\\\"about.production\\\") : i18n.t(\\\"about.development\\\"),|value: \\\"bare-metal\\\",|g\" \"$SITE_SETTINGS\"\n    export NUXT_TELEMETRY_DISABLED=1\n    cd /opt/mealie/frontend\n    $STD yarn install --prefer-offline --frozen-lockfile --non-interactive --production=false --network-timeout 1000000\n    $STD yarn generate\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Copying Built Frontend\"\n    mkdir -p /opt/mealie/mealie/frontend\n    cp -r /opt/mealie/frontend/dist/* /opt/mealie/mealie/frontend/\n    msg_ok \"Copied Frontend\"\n\n    setup_nltk \"averaged_perceptron_tagger_eng\" \"/nltk_data\"\n\n    msg_info \"Starting Service\"\n    systemctl start mealie\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/mediamanager.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/maxdorninger/MediaManager\n\nAPP=\"MediaManager\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/mediamanager ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_uv\n\n  if check_for_gh_release \"mediamanager\" \"maxdorninger/MediaManager\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop mediamanager\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"MediaManager\" \"maxdorninger/MediaManager\" \"tarball\" \"latest\" \"/opt/mediamanager\"\n    msg_info \"Updating MediaManager\"\n    MM_DIR=\"/opt/mm\"\n    export CONFIG_DIR=\"${MM_DIR}/config\"\n    export FRONTEND_FILES_DIR=\"${MM_DIR}/web/build\"\n    export PUBLIC_VERSION=\"\"\n    export PUBLIC_API_URL=\"\"\n    export BASE_PATH=\"/web\"\n    cd /opt/mediamanager/web \n    $STD npm install --no-fund --no-audit\n    $STD npm run build\n    rm -rf \"$FRONTEND_FILES_DIR\"/build\n    cp -r build \"$FRONTEND_FILES_DIR\"\n    export BASE_PATH=\"\"\n    export VIRTUAL_ENV=\"/opt/${MM_DIR}/venv\"\n    cd /opt/mediamanager \n    rm -rf \"$MM_DIR\"/{media_manager,alembic*}\n    cp -r {media_manager,alembic*} \"$MM_DIR\"\n    $STD /usr/local/bin/uv sync --locked --active -n -p cpython3.13 --managed-python\n    if ! grep -q \"LOG_FILE\" \"$MM_DIR\"/start.sh; then\n      sed -i \"\\|build\\\"$|a\\export LOG_FILE=\\\"$CONFIG_DIR/media_manager.log\\\"\" \"$MM_DIR\"/start.sh\n    fi\n\n    msg_ok \"Updated MediaManager\"\n\n    msg_info \"Starting Service\"\n    systemctl start mediamanager\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/mediamtx.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bluenviron/mediamtx\n\nAPP=\"MediaMTX\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/mediamtx/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"mediamtx\" \"bluenviron/mediamtx\"; then\n    msg_info \"Stopping service\"\n    systemctl stop mediamtx\n    msg_ok \"Service stopped\"\n\n    fetch_and_deploy_gh_release \"mediamtx\" \"bluenviron/mediamtx\" \"prebuild\" \"latest\" \"/opt/mediamtx\" \"mediamtx*linux_arm64.tar.gz\"\n\n    msg_info \"Starting service\"\n    systemctl start mediamtx\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/medusa.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pymedusa/Medusa.git\n\nAPP=\"Medusa\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/medusa ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Stopping Service\"\n  systemctl stop medusa\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating ${APP}\"\n  cd /opt/medusa\n  output=$(git pull --no-rebase)\n  if echo \"$output\" | grep -q \"Already up to date.\"; then\n    msg_ok \"$APP is already up to date.\"\n    exit\n  fi\n  msg_ok \"Updated successfully!\"\n\n  msg_info \"Starting Service\"\n  systemctl start medusa\n  msg_ok \"Started Service\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8081${CL}\"\n"
  },
  {
    "path": "ct/meilisearch.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.meilisearch.com/\n\nAPP=\"Meilisearch\"\nvar_tags=\"${var_tags:-full-text-search}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-7}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  setup_meilisearch\n\n  if [[ -d /opt/meilisearch-ui ]]; then\n    if check_for_gh_release \"meilisearch-ui\" \"riccox/meilisearch-ui\"; then\n      msg_info \"Stopping Meilisearch-UI\"\n      systemctl stop meilisearch-ui\n      msg_ok \"Stopped Meilisearch-UI\"\n\n      cp /opt/meilisearch-ui/.env.local /tmp/.env.local.bak\n      rm -rf /opt/meilisearch-ui\n      fetch_and_deploy_gh_release \"meilisearch-ui\" \"riccox/meilisearch-ui\" \"tarball\"\n\n      msg_info \"Configuring Meilisearch-UI\"\n      cd /opt/meilisearch-ui\n      sed -i 's|const hash = execSync(\"git rev-parse HEAD\").toString().trim();|const hash = \"unknown\";|' /opt/meilisearch-ui/vite.config.ts\n      mv /tmp/.env.local.bak /opt/meilisearch-ui/.env.local\n      $STD pnpm install\n      msg_ok \"Configured Meilisearch-UI\"\n\n      msg_info \"Starting Meilisearch-UI\"\n      systemctl start meilisearch-ui\n      msg_ok \"Started Meilisearch-UI\"\n    fi\n  fi\n\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}meilisearch: http://${IP}:7700$ | meilisearch-ui: http://${IP}:24900${CL}\"\n"
  },
  {
    "path": "ct/memos.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.usememos.com/\n\nAPP=\"Memos\"\nvar_tags=\"${var_tags:-notes}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/memos ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"memos\" \"usememos/memos\"; then\n    msg_info \"Stopping service\"\n    systemctl stop memos\n    msg_ok \"Service stopped\"\n\n    fetch_and_deploy_gh_release \"memos\" \"usememos/memos\" \"prebuild\" \"v0.25.3\" \"/opt/memos\" \"memos*linux_arm64.tar.gz\"\n\n    msg_info \"Starting service\"\n    systemctl start memos\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9030${CL}\"\n"
  },
  {
    "path": "ct/meshcentral.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://meshcentral.com/\n\nAPP=\"MeshCentral\"\nvar_tags=\"${var_tags:-remote-management}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/meshcentral ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/metabase.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.metabase.com/\n\nAPP=\"Metabase\"\nvar_tags=\"${var_tags:-analytics}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/metabase ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    if check_for_gh_release \"metabase\" \"metabase/metabase\"; then\n        msg_info \"Stopping Service\"\n        systemctl stop metabase\n        msg_info \"Stopped Service\"\n\n        msg_info \"Creating backup\"\n        mv /opt/metabase/.env /opt\n        msg_ok \"Created backup\"\n\n        msg_info \"Updating Metabase\"\n        RELEASE=$(get_latest_github_release \"metabase/metabase\")\n        curl -fsSL \"https://downloads.metabase.com/v${RELEASE}.x/metabase.jar\" -o /opt/metabase/metabase.jar\n        echo $RELEASE >~/.metabase\n        msg_ok \"Updated Metabase\"\n\n        msg_info \"Restoring backup\"\n        mv /opt/.env /opt/metabase\n        msg_ok \"Restored backup\"\n\n        msg_info \"Starting Service\"\n        systemctl start metabase\n        msg_ok \"Started Service\"\n        msg_ok \"Updated successfully!\"\n    fi\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/metube.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alexta69/metube\n\nAPP=\"MeTube\"\nvar_tags=\"${var_tags:-media;youtube}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/metube ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [[ $(echo \":$PATH:\" != *\":/usr/local/bin:\"*) ]]; then\n    echo -e \"\\nexport PATH=\\\"/usr/local/bin:\\$PATH\\\"\" >>~/.bashrc\n    source ~/.bashrc\n    if ! command -v deno &>/dev/null; then\n      export DENO_INSTALL=\"/usr/local\"\n      curl -fsSL https://deno.land/install.sh | $STD sh -s -- -y\n    else\n      $STD deno upgrade\n    fi\n  fi\n\n  NODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\n\n  if check_for_gh_release \"metube\" \"alexta69/metube\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop metube\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Old Installation\"\n    if [[ -d /opt/metube_bak ]]; then\n      rm -rf /opt/metube_bak\n    fi\n    mv /opt/metube /opt/metube_bak\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"metube\" \"alexta69/metube\" \"tarball\" \"latest\"\n\n    msg_info \"Building Frontend\"\n    cd /opt/metube/ui\n    if command -v corepack >/dev/null 2>&1; then\n      $STD corepack enable\n      $STD corepack prepare pnpm --activate || true\n    fi\n    echo 'onlyBuiltDependencies=*' >> .npmrc\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm run build\n    msg_ok \"Built Frontend\"\n\n    PYTHON_VERSION=\"3.13\" setup_uv\n\n    msg_info \"Installing Backend Requirements\"\n    cd /opt/metube\n    $STD uv sync\n    msg_ok \"Installed Backend\"\n\n    msg_info \"Restoring .env\"\n    if [[ -f /opt/metube_bak/.env ]]; then\n      cp /opt/metube_bak/.env /opt/metube/.env\n    fi\n    rm -rf /opt/metube_bak\n    msg_ok \"Restored .env\"\n\n    if grep -q 'pipenv' /etc/systemd/system/metube.service; then\n      msg_info \"Patching systemd Service\"\n      cat <<EOF >/etc/systemd/system/metube.service\n[Unit]\nDescription=Metube - YouTube Downloader\nAfter=network.target\n[Service]\nType=simple\nWorkingDirectory=/opt/metube\nEnvironmentFile=/opt/metube/.env\nExecStart=/opt/metube/.venv/bin/python3 app/main.py\nRestart=always\nUser=root\n[Install]\nWantedBy=multi-user.target\nEOF\n      msg_ok \"Patched systemd Service\"\n    fi\n    $STD systemctl daemon-reload\n    msg_ok \"Service Updated\"\n\n    msg_info \"Starting Service\"\n    systemctl start metube\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8081${CL}\"\n"
  },
  {
    "path": "ct/minarca.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://minarca.org/en_CA\n\nAPP=\"Minarca\"\nvar_tags=\"${var_tags:-backup}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_fuse=\"${var_fuse:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/minarca-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Stopping Service\"\n  systemctl stop minarca-server\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated ${APP} LXC\"\n\n  msg_info \"Starting Service\"\n  systemctl start minarca-server\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/mini-qr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: doge0420\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/lyqht/mini-qr\n\nAPP=\"Mini-QR\"\nvar_tags=\"${var_tags:-QRcode;}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/mini-qr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"mini-qr\" \"lyqht/mini-qr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop caddy\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"mini-qr\" \"lyqht/mini-qr\" \"tarball\"\n\n    msg_info \"Installing Dependencies\"\n    cd /opt/mini-qr\n    $STD npm install\n    msg_ok \"Installed Dependencies\"\n\n    msg_info \"Building MiniQR\"\n    $STD npm run build\n    msg_ok \"Built MiniQR\"\n\n    msg_info \"Starting Service\"\n    systemctl start caddy\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/miniflux.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: omernaveedxyz\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://miniflux.app/\n\nAPP=\"Miniflux\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if ! systemctl -q is-enabled miniflux 2>/dev/null; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  $STD miniflux -flush-sessions -config-file /etc/miniflux.conf\n  systemctl stop miniflux\n  msg_ok \"Service Stopped\"\n\n  fetch_and_deploy_gh_release \"miniflux\" \"miniflux/v2\" \"binary\" \"latest\"\n\n  msg_info \"Updating Miniflux\"\n  $STD miniflux -migrate -config-file /etc/miniflux.conf\n  msg_ok \"Updated Miniflux\"\n  msg_info \"Starting Service\"\n  $STD systemctl start miniflux\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/minio.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/minio/minio\n\nAPP=\"MinIO\"\nvar_tags=\"${var_tags:-object-storage}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/bin/minio ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  FEATURE_RICH_VERSION=\"2025-04-22T22-12-26Z\"\n  RELEASE=$(curl -fsSL https://api.github.com/repos/minio/minio/releases/latest | grep '\"tag_name\"' | awk -F '\"' '{print $4}')\n  CURRENT_VERSION=\"\"\n  [[ -f /opt/${APP}_version.txt ]] && CURRENT_VERSION=$(cat /opt/${APP}_version.txt)\n  RELEASE=$(curl -fsSL https://api.github.com/repos/minio/minio/releases/latest | grep '\"tag_name\"' | awk -F '\"' '{print $4}')\n\n  if [[ \"${CURRENT_VERSION}\" == \"${FEATURE_RICH_VERSION}\" && \"${RELEASE}\" != \"${FEATURE_RICH_VERSION}\" ]]; then\n    echo\n    echo \"You are currently running the last feature-rich community version: ${FEATURE_RICH_VERSION}\"\n    echo \"WARNING: Updating to the latest version will REMOVE most management features from the Console UI.\"\n    echo \"Do you still want to upgrade to the latest version? [y/N]: \"\n    read -n 1 -r\n    echo\n    if [[ ! $REPLY =~ ^[Yy]$ ]]; then\n      msg_ok \"No update performed. Staying on the feature-rich version.\"\n      exit\n    fi\n  fi\n\n  if [[ \"${CURRENT_VERSION}\" != \"${RELEASE}\" ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop minio\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP} to ${RELEASE}\"\n    mv /usr/local/bin/minio /usr/local/bin/minio_bak\n    curl -fsSL \"https://dl.min.io/server/minio/release/linux-arm64/minio\" -o /usr/local/bin/minio\n    chmod +x /usr/local/bin/minio\n    rm -f /usr/local/bin/minio_bak\n    echo \"${RELEASE}\" >/opt/${APP}_version.txt\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start minio\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/minthcm.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MintHCM\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/minthcm/minthcm\n\nAPP=\"MintHCM\"\nvar_tags=\"${var_tags:-hcm}\"\nvar_disk=\"${var_disk:-20}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/www/MintHCM ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_custom \"🚀\" \"${GN}\" \"The app offers a built-in updater. Please use it.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/mongodb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.mongodb.com/de-de\n\nAPP=\"MongoDB\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if ! command -v mongod &>/dev/null; then\n      msg_error \"No ${APP} Installation Found!\"\n      exit\n    fi\n    \n    msg_info \"Updating MongoDB LXC\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/monica.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.monicahq.com/\n\nAPP=\"Monica\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/monica ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_mariadb\n  NODE_VERSION=\"22\" NODE_MODULE=\"yarn@latest\" setup_nodejs\n\n  # Fix for previous versions not having cronjob\n  if ! grep -Fq 'php /opt/monica/artisan schedule:run' /etc/crontab; then\n    echo '* * * * * root php /opt/monica/artisan schedule:run >> /dev/null 2>&1' >>/etc/crontab\n  fi\n\n  if check_for_gh_release \"monica\" \"monicahq/monica\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mv /opt/monica/ /opt/monica-backup\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"monica\" \"monicahq/monica\" \"prebuild\" \"latest\" \"/opt/monica\" \"monica-v*.tar.bz2\"\n\n    msg_info \"Configuring monica\"\n    cd /opt/monica/\n    cp -r /opt/monica-backup/.env /opt/monica\n    cp -r /opt/monica-backup/storage/* /opt/monica/storage/\n    $STD composer install --no-interaction --no-dev\n    $STD yarn config set ignore-engines true\n    $STD yarn install\n    $STD yarn run production\n    $STD php artisan monica:update --force\n    chown -R www-data:www-data /opt/monica\n    chmod -R 775 /opt/monica/storage\n    rm -r /opt/monica-backup\n    msg_ok \"Configured monica\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/motioneye.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/motioneye-project/motioneye\n\nAPP=\"Motioneye\"\nvar_tags=\"${var_tags:-nvr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/motioneye.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD pip install motioneye --upgrade\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8765${CL}\"\n"
  },
  {
    "path": "ct/mqtt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mosquitto.org/\n\nAPP=\"MQTT\"\nvar_tags=\"${var_tags:-mqtt}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /etc/mosquitto/conf.d/default.conf ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:1883${CL}\"\n"
  },
  {
    "path": "ct/myip.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ipcheck.ing/\n\nAPP=\"MyIP\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/myip ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"myip\" \"jason5ng32/MyIP\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop myip\n    msg_ok \"Stopped Services\"\n\n    cp /opt/myip/.env /opt\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"myip\" \"jason5ng32/MyIP\" \"tarball\"\n    mv /opt/.env /opt/myip\n\n    msg_info \"Starting Services\"\n    systemctl start myip\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:18966${CL}\"\n"
  },
  {
    "path": "ct/mylar3.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: davalanche | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/mylar3/mylar3\n\nAPP=\"Mylar3\"\nvar_tags=\"${var_tags:-torrent;downloader;comic}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  if [[ ! -d /opt/mylar3 ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"mylar3\" \"mylar3/mylar3\"; then\n    fetch_and_deploy_gh_release \"mylar3\" \"mylar3/mylar3\" \"tarball\"\n    systemctl restart mylar3\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/myspeed.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://myspeed.dev/\n\nAPP=\"MySpeed\"\nvar_tags=\"${var_tags:-tracking}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/myspeed ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"myspeed\" \"gnmyt/myspeed\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop myspeed\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    cd /opt\n    rm -rf myspeed_bak\n    mv myspeed myspeed_bak\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"myspeed\" \"gnmyt/myspeed\" \"prebuild\" \"latest\" \"/opt/myspeed\" \"MySpeed-*.zip\"\n\n    msg_info \"Updating ${APP}\"\n    cd /opt/myspeed\n    $STD npm install\n    if [[ -d /opt/myspeed_bak/data ]]; then\n      mkdir -p /opt/myspeed/data/\n      cp -r /opt/myspeed_bak/data/* /opt/myspeed/data/\n    fi\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start myspeed\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5216${CL}\"\n"
  },
  {
    "path": "ct/mysql.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.mysql.com/\n\nAPP=\"MySQL\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/share/keyrings/mysql.gpg ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:3306${CL}\"\n"
  },
  {
    "path": "ct/n8n.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://n8n.io/\n\nAPP=\"n8n\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/n8n.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies build-essential python3-setuptools graphicsmagick\n  NODE_VERSION=\"24\" setup_nodejs\n\n  msg_info \"Updating n8n\"\n  if [ ! -f /opt/n8n.env ]; then\n    sed -i 's|^Environment=\"N8N_SECURE_COOKIE=false\"$|EnvironmentFile=/opt/n8n.env|' /etc/systemd/system/n8n.service\n    mkdir -p /opt\n    cat <<EOF >/opt/n8n.env\nN8N_SECURE_COOKIE=false\nN8N_PORT=5678\nN8N_PROTOCOL=http\nN8N_HOST=$LOCAL_IP\nEOF\n    systemctl daemon-reload\n  fi\n\n  $STD npm update -g n8n\n  systemctl restart n8n\n  msg_ok \"Updated n8n\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5678${CL}\"\n"
  },
  {
    "path": "ct/nagios.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CanbiZ (MickLesk)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/NagiosEnterprises/nagioscore\n\nAPP=\"Nagios\"\nvar_tags=\"${var_tags:-monitoring;alerts;infrastructure}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/nagios/etc/nagios.cfg ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Backing up Configuration\"\n  cp -a /usr/local/nagios/etc /opt/nagios-etc-backup\n  msg_ok \"Backed up Configuration\"\n\n  if check_for_gh_release \"nagios\" \"NagiosEnterprises/nagioscore\"; then\n    msg_info \"Stopping Nagios\"\n    systemctl stop nagios\n    msg_ok \"Stopped Nagios\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nagios\" \"NagiosEnterprises/nagioscore\" \"tarball\"\n\n    msg_info \"Building Nagios Core\"\n    cd /opt/nagios\n    $STD ./configure --with-httpd-conf=/etc/apache2/sites-enabled\n    $STD make all\n    $STD make install-groups-users\n    usermod -a -G nagios www-data\n    $STD make install\n    $STD make install-daemoninit\n    $STD make install-commandmode\n    $STD make install-webconf\n    $STD a2enmod rewrite\n    $STD a2enmod cgi\n    setcap cap_net_raw+p /bin/ping\n    msg_ok \"Built Nagios Core\"\n\n    msg_info \"Starting Nagios\"\n    systemctl restart apache2\n    systemctl start nagios\n    msg_ok \"Started Nagios\"\n  fi\n\n  if check_for_gh_release \"nagios-plugins\" \"nagios-plugins/nagios-plugins\"; then\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nagios-plugins\" \"nagios-plugins/nagios-plugins\" \"tarball\"\n    msg_info \"Building Nagios Plugins\"\n    cd /opt/nagios-plugins\n    $STD ./tools/setup\n    $STD ./configure\n    $STD make\n    $STD make install\n    msg_ok \"Built Nagios Plugins\"\n  fi\n\n  msg_info \"Restoring Configuration\"\n  rm -rf /usr/local/nagios/etc\n  cp -a /opt/nagios-etc-backup /usr/local/nagios/etc\n  rm -rf /opt/nagios-etc-backup\n  msg_ok \"Restored Configuration\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/nagios${CL}\"\n"
  },
  {
    "path": "ct/nametag.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/mattogodoy/nametag\n\nAPP=\"Nametag\"\nvar_tags=\"${var_tags:-contacts;crm}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/nametag ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"nametag\" \"mattogodoy/nametag\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nametag\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/nametag/.env /opt/nametag.env.bak\n    cp -r /opt/nametag/data /opt/nametag_data_bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nametag\" \"mattogodoy/nametag\" \"tarball\" \"latest\" \"/opt/nametag\"\n\n    msg_info \"Rebuilding Application\"\n    cd /opt/nametag\n    $STD npm ci\n    set -a\n    source /opt/nametag/.env\n    set +a\n    $STD npx prisma generate\n    $STD npm run build\n    cp -r /opt/nametag/.next/static /opt/nametag/.next/standalone/.next/static\n    cp -r /opt/nametag/public /opt/nametag/.next/standalone/public\n    msg_ok \"Rebuilt Application\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/nametag.env.bak /opt/nametag/.env\n    cp -r /opt/nametag_data_bak/. /opt/nametag/data/\n    rm -f /opt/nametag.env.bak\n    rm -rf /opt/nametag_data_bak\n    msg_ok \"Restored Data\"\n\n    msg_info \"Running Migrations\"\n    cd /opt/nametag\n    $STD npx prisma migrate deploy\n    msg_ok \"Ran Migrations\"\n\n    msg_info \"Starting Service\"\n    systemctl start nametag\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/navidrome.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/navidrome/navidrome\n\nAPP=\"Navidrome\"\nvar_tags=\"${var_tags:-music}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/navidrome ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"navidrome\" \"navidrome/navidrome\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop navidrome\n    msg_ok \"Services Stopped\"\n\n    fetch_and_deploy_gh_release \"navidrome\" \"navidrome/navidrome\" \"binary\"\n\n    msg_info \"Starting Services\"\n    systemctl start navidrome\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:4533${CL}\"\n"
  },
  {
    "path": "ct/neko.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CanbiZ (MickLesk)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://neko.m1k1o.net/\n\nAPP=\"Neko\"\nvar_tags=\"${var_tags:-virtual-browser;webrtc;streaming}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/neko ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"neko\" \"m1k1o/neko\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop neko\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /etc/neko/neko.yaml /opt/neko.yaml.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"neko\" \"m1k1o/neko\" \"tarball\"\n\n    msg_info \"Building Client\"\n    cd /opt/neko/client\n    $STD npm install\n    $STD npm run build\n    cp -r /opt/neko/client/dist/* /var/www/\n    msg_ok \"Built Client\"\n\n    msg_info \"Building Server\"\n    cd /opt/neko/server\n    $STD ./build\n    cp /opt/neko/server/bin/neko /usr/bin/neko\n    cp -r /opt/neko/server/bin/plugins/* /etc/neko/plugins/ 2>/dev/null || true\n    msg_ok \"Built Server\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/neko.yaml.bak /etc/neko/neko.yaml\n    rm -f /opt/neko.yaml.bak\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start neko\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/neo4j.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://neo4j.com/product/neo4j-graph-database/\n\nAPP=\"Neo4j\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/neo4j ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  JAVA_VERSION=\"21\" setup_java\n\n  msg_info \"Updating ${APP}\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7474${CL}\"\n"
  },
  {
    "path": "ct/netbird.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: TechHutTV\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://netbird.io/\n\nAPP=\"NetBird\"\nvar_tags=\"${var_tags:-network;vpn}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_tun=\"${var_tun:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /var/lib/netbird/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Netbird\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access NetBird by entering the container and running:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}netbird up${CL}\"\n"
  },
  {
    "path": "ct/netboot-xyz.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://netboot.xyz\n\nAPP=\"netboot.xyz\"\nvar_tags=\"${var_tags:-network;pxe;boot}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\nNSAPP=\"netboot-xyz\"\nvar_install=\"${NSAPP}-install\"\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f ~/.netboot-xyz ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"netboot-xyz\" \"netbootxyz/netboot.xyz\"; then\n    msg_info \"Backing up Configuration\"\n    cp /var/www/html/boot.cfg /opt/netboot-xyz-boot.cfg.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"netboot-xyz\" \"netbootxyz/netboot.xyz\" \"prebuild\" \"latest\" \"/var/www/html\" \"menus.tar.gz\"\n\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-efi\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-efi-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.efi.dsk\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-snp.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-snp-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-snp.efi.dsk\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-snponly.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal.efi.dsk\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-snp.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-snp-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-snp.efi.dsk\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-snponly.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-kpxe\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.kpxe\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-undionly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-undionly.kpxe\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-kpxe\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal.kpxe\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-lkrn\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.lkrn\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-linux-bin\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-linux.bin\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.dsk\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-pdsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.pdsk\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64-snp.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64-snponly.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-arm64\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-arm64.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-arm64-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-arm64-snp.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-arm64-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-arm64-snponly.efi\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-iso\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.iso\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-img\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.img\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-iso\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64.iso\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-img\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64.img\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-multiarch-iso\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-multiarch.iso\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-multiarch-img\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-multiarch.img\"\n    USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-checksums\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-sha256-checksums.txt\"\n\n    msg_info \"Restoring Configuration\"\n    cp /opt/netboot-xyz-boot.cfg.bak /var/www/html/boot.cfg\n    rm -f /opt/netboot-xyz-boot.cfg.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/netbox.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://netboxlabs.com/\n\nAPP=\"NetBox\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/netbox.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"netbox\" \"netbox-community/netbox\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop netbox netbox-rq\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up NetBox configurations\"\n    mv /opt/netbox/ /opt/netbox-backup\n    msg_ok \"Backed up NetBox configurations\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"netbox\" \"netbox-community/netbox\" \"tarball\"\n\n    cp -r /opt/netbox-backup/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/\n    cp -r /opt/netbox-backup/netbox/{media,scripts,reports}/ /opt/netbox/netbox/\n    cp -r /opt/netbox-backup/gunicorn.py /opt/netbox/\n    [[ -f /opt/netbox-backup/local_requirements.txt ]] && cp -r /opt/netbox-backup/local_requirements.txt /opt/netbox/\n    [[ -f /opt/netbox-backup/netbox/netbox/ldap_config.py ]] && cp -r /opt/netbox-backup/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/\n\n    $STD /opt/netbox/upgrade.sh\n    rm -r /opt/netbox-backup\n\n    msg_info \"Starting Services\"\n    systemctl start netbox netbox-rq\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/netvisor.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/scanopy/scanopy\n\nAPP=\"Scanopy\"\nvar_tags=\"${var_tags:-analytics}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/netvisor ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping services\"\n  systemctl -q disable --now netvisor-daemon netvisor-server\n  msg_ok \"Stopped services\"\n\n  NODE_VERSION=\"24\" setup_nodejs\n  CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"scanopy\" \"scanopy/scanopy\" \"tarball\" \"latest\" \"/opt/scanopy\"\n\n  ensure_dependencies pkg-config libssl-dev\n  TOOLCHAIN=\"$(grep \"channel\" /opt/scanopy/backend/rust-toolchain.toml | awk -F\\\" '{print $2}')\"\n  RUST_TOOLCHAIN=$TOOLCHAIN setup_rust\n\n  mv /opt/netvisor/.env /opt/scanopy/.env\n  if [[ -f /opt/netvisor/oidc.toml ]]; then\n    mv /opt/netvisor/oidc.toml /opt/scanopy/oidc.toml\n  fi\n  if ! grep -q \"PUBLIC_URL\" /opt/scanopy/.env; then\n    sed -i \"\\|_PATH=|a\\NETVISOR_PUBLIC_URL=http://${LOCAL_IP}:60072\" /opt/scanopy/.env\n  fi\n  sed -i 's|_TARGET=.*$|_URL=http://127.0.0.1:60072|' /opt/scanopy/.env\n  sed -i 's/NETVISOR/SCANOPY/g; s|netvisor/|scanopy/|' /opt/scanopy/.env\n\n  msg_info \"Creating frontend UI\"\n  export PUBLIC_SERVER_HOSTNAME=default\n  export PUBLIC_SERVER_PORT=\"\"\n  cd /opt/scanopy/ui\n  $STD npm ci --no-fund --no-audit\n  $STD npm run build\n  msg_ok \"Created frontend UI\"\n\n  msg_info \"Building Scanopy-server (patience)\"\n  cd /opt/scanopy/backend\n  $STD cargo build --release --bin server\n  mv ./target/release/server /usr/bin/scanopy-server\n  msg_ok \"Built Scanopy-server\"\n\n  msg_info \"Building Scanopy-daemon\"\n  $STD cargo build --release --bin daemon\n  cp ./target/release/daemon /usr/bin/scanopy-daemon\n  msg_ok \"Built Scanopy-daemon\"\n\n  sed -i '/^  \\\"server_target.*$/d' /root/.config/daemon/config.json\n  sed -i -e 's|-target|-url|' \\\n    -e 's| --server-port |:|' \\\n    -e 's/NetVisor/Scanopy/' \\\n    -e 's/netvisor/scanopy/' \\\n    /etc/systemd/system/netvisor-daemon.service\n  mv /etc/systemd/system/netvisor-daemon.service /etc/systemd/system/scanopy-daemon.service\n  sed -i -e 's/NetVisor/Scanopy/' \\\n    -e 's/netvisor/scanopy/g' \\\n    /etc/systemd/system/netvisor-server.service\n  mv /etc/systemd/system/netvisor-server.service /etc/systemd/system/scanopy-server.service\n  systemctl daemon-reload\n\n  msg_info \"Starting services\"\n  systemctl -q enable --now scanopy-server scanopy-daemon\n  msg_ok \"Updated successfully!\"\n\n  sed -i 's/netvisor/scanopy/' /usr/bin/update\n  mv ~/NetVisor.creds ~/scanopy.creds\n  rm ~/.netvisor\n  rm -rf /opt/netvisor\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:60072${CL}\"\necho -e \"${INFO}${YW} Then create your account, and run the 'configure_daemon.sh' script to setup the daemon.${CL}\"\n"
  },
  {
    "path": "ct/nextcloudpi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.turnkeylinux.org/nextcloud\n\nAPP=\"NextCloudPi\"\nvar_tags=\"${var_tags:-cloud}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /lib/systemd/system/nextcloud-domain.service ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/nextexplorer.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/nxzai/nextExplorer\n\nAPP=\"nextExplorer\"\nvar_tags=\"${var_tags:-files;documents}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/nextExplorer ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"nextExplorer\" \"nxzai/nextExplorer\"; then\n    msg_info \"Stopping nextExplorer\"\n    $STD systemctl stop nextexplorer\n    msg_ok \"Stopped nextExplorer\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nextExplorer\" \"nxzai/nextExplorer\" \"tarball\" \"latest\" \"/opt/nextExplorer\"\n\n    msg_info \"Updating nextExplorer\"\n    APP_DIR=\"/opt/nextExplorer/app\"\n    mkdir -p \"$APP_DIR\"\n    cd /opt/nextExplorer\n    export NODE_ENV=production\n    $STD npm ci --omit=dev --workspace backend\n    mv node_modules \"$APP_DIR\"\n    mv backend/{src,package.json} \"$APP_DIR\"\n    unset NODE_ENV\n    export NODE_ENV=development\n    $STD npm ci --workspace frontend\n    $STD npm run -w frontend build -- --sourcemap false\n    unset NODE_ENV\n    mv frontend/dist/ \"$APP_DIR\"/src/public\n    chown -R explorer:explorer \"$APP_DIR\" /etc/nextExplorer\n    sed -i \"\\|version|s|$(jq -cr '.version' ${APP_DIR}/package.json)|$(cat ~/.nextexplorer)|\" \"$APP_DIR\"/package.json\n    sed -i 's/app.js/server.js/' /etc/systemd/system/nextexplorer.service && systemctl daemon-reload\n    msg_ok \"Updated nextExplorer\"\n\n    msg_info \"Starting nextExplorer\"\n    $STD systemctl start nextexplorer\n    msg_ok \"Started nextExplorer\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/nextpvr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nextpvr.com/\n\nAPP=\"NextPVR\"\nvar_tags=\"${var_tags:-pvr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/nextpvr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Stopping Service\"\n  systemctl stop nextpvr-server\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating LXC packages\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated LXC packages\"\n\n  msg_info \"Updating ${APP}\"\n  cd /opt\n  curl -fsSL \"https://nextpvr.com/nextpvr-helper.deb\" -o $(basename \"https://nextpvr.com/nextpvr-helper.deb\")\n  $STD dpkg -i nextpvr-helper.deb\n  rm -rf /opt/nextpvr-helper.deb\n  msg_ok \"Updated ${APP}\"\n\n  msg_info \"Starting Service\"\n  systemctl start nextpvr-server\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8866${CL}\"\n"
  },
  {
    "path": "ct/nginx-ui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nginxui.com\n\nAPP=\"Nginx-UI\"\nvar_tags=\"${var_tags:-webserver;nginx;proxy}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/local/bin/nginx-ui ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"nginx-ui\" \"0xJacky/nginx-ui\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nginx-ui\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /usr/local/etc/nginx-ui/app.ini /tmp/nginx-ui-app.ini.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nginx-ui\" \"0xJacky/nginx-ui\" \"prebuild\" \"latest\" \"/opt/nginx-ui\" \"nginx-ui-linux-arm64.tar.gz\"\n\n    msg_info \"Updating Binary\"\n    cp /opt/nginx-ui/nginx-ui /usr/local/bin/nginx-ui\n    chmod +x /usr/local/bin/nginx-ui\n    rm -rf /opt/nginx-ui\n    msg_ok \"Updated Binary\"\n\n    msg_info \"Restoring Configuration\"\n    mv /tmp/nginx-ui-app.ini.bak /usr/local/etc/nginx-ui/app.ini\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start nginx-ui\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/nginxproxymanager.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nginxproxymanager.com/\n\nAPP=\"Nginx Proxy Manager\"\nvar_tags=\"${var_tags:-proxy}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/npm.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if command -v node &>/dev/null; then\n    CURRENT_NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1)\n    if [[ \"$CURRENT_NODE_VERSION\" != \"22\" ]]; then\n      systemctl stop openresty\n      $STD apt purge -y nodejs npm\n      $STD apt autoremove -y\n      rm -rf /usr/local/bin/node /usr/local/bin/npm\n      rm -rf /usr/local/lib/node_modules\n      rm -rf ~/.npm\n      rm -rf /root/.npm\n    fi\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n\n  if dpkg -s openresty &>/dev/null 2>&1; then\n    msg_info \"Migrating from packaged OpenResty to source\"\n    rm -f /etc/apt/trusted.gpg.d/openresty-archive-keyring.gpg /etc/apt/trusted.gpg.d/openresty.gpg\n    rm -f /etc/apt/sources.list.d/openresty.list /etc/apt/sources.list.d/openresty.sources\n    $STD apt purge -y openresty\n    $STD apt autoremove -y\n    rm -f ~/.openresty\n    msg_ok \"Migrated from packaged OpenResty to source\"\n  fi\n\n  local pcre_pkg=\"libpcre3-dev\"\n  if grep -qE 'VERSION_ID=\"1[3-9]\"' /etc/os-release 2>/dev/null; then\n    pcre_pkg=\"libpcre2-dev\"\n  fi\n  $STD apt install -y build-essential \"$pcre_pkg\" libssl-dev zlib1g-dev\n\n  if check_for_gh_release \"openresty\" \"openresty/openresty\"; then\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"openresty\" \"openresty/openresty\" \"prebuild\" \"${CHECK_UPDATE_RELEASE}\" \"/opt/openresty\" \"openresty-*.tar.gz\"\n\n    msg_info \"Building OpenResty\"\n    cd /opt/openresty\n    $STD ./configure \\\n      --with-http_v2_module \\\n      --with-http_realip_module \\\n      --with-http_stub_status_module \\\n      --with-http_ssl_module \\\n      --with-http_sub_module \\\n      --with-http_auth_request_module \\\n      --with-pcre-jit \\\n      --with-stream \\\n      --with-stream_ssl_module\n    $STD make -j\"$(nproc)\"\n    $STD make install\n    rm -rf /opt/openresty\n    cat <<'EOF' >/lib/systemd/system/openresty.service\n[Unit]\nDescription=The OpenResty Application Platform\nAfter=syslog.target network-online.target remote-fs.target nss-lookup.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStartPre=-/bin/mkdir -p /tmp/nginx/body /run/nginx\nExecStartPre=/usr/local/openresty/nginx/sbin/nginx -t\nExecStart=/usr/local/openresty/nginx/sbin/nginx -g 'daemon off;'\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    if [ -f /opt/nginxproxymanager/docker/rootfs/etc/nginx/nginx.conf ]; then\n      cp /opt/nginxproxymanager/docker/rootfs/etc/nginx/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf\n      sed -i 's+^daemon+#daemon+g' /usr/local/openresty/nginx/conf/nginx.conf\n      sed -i 's+include conf.d+include /etc/nginx/conf.d+g' /usr/local/openresty/nginx/conf/nginx.conf\n    fi\n    sed -i 's/user npm/user root/g; s/^pid/#pid/g' /usr/local/openresty/nginx/conf/nginx.conf\n    systemctl daemon-reload\n    systemctl unmask openresty 2>/dev/null || true\n    systemctl restart openresty\n    msg_ok \"Built OpenResty\"\n  fi\n\n  cd /root\n  if [ -d /opt/certbot ]; then\n    msg_info \"Updating Certbot\"\n    $STD /opt/certbot/bin/pip install --upgrade pip setuptools wheel\n    $STD /opt/certbot/bin/pip install --upgrade certbot certbot-dns-cloudflare\n    msg_ok \"Updated Certbot\"\n  fi\n\n  if check_for_gh_release \"nginxproxymanager\" \"NginxProxyManager/nginx-proxy-manager\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop openresty\n    systemctl stop npm\n    msg_ok \"Stopped Services\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nginxproxymanager\" \"NginxProxyManager/nginx-proxy-manager\" \"tarball\" \"${CHECK_UPDATE_RELEASE}\" \"/opt/nginxproxymanager\"\n\n    msg_info \"Cleaning old files\"\n    $STD rm -rf /app \\\n      /var/www/html \\\n      /etc/nginx \\\n      /var/log/nginx \\\n      /var/lib/nginx \\\n      /var/cache/nginx\n    msg_ok \"Cleaned old files\"\n\n    local RELEASE=\"${CHECK_UPDATE_RELEASE#v}\"\n    msg_info \"Setting up Environment\"\n    ln -sf /usr/bin/python3 /usr/bin/python\n    ln -sf /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx\n    ln -sf /usr/local/openresty/nginx/ /etc/nginx\n    sed -i \"0,/\\\"version\\\": \\\"[^\\\"]*\\\"/s|\\\"version\\\": \\\"[^\\\"]*\\\"|\\\"version\\\": \\\"$RELEASE\\\"|\" /opt/nginxproxymanager/backend/package.json\n    sed -i \"0,/\\\"version\\\": \\\"[^\\\"]*\\\"/s|\\\"version\\\": \\\"[^\\\"]*\\\"|\\\"version\\\": \\\"$RELEASE\\\"|\" /opt/nginxproxymanager/frontend/package.json\n    sed -i 's+^daemon+#daemon+g' /opt/nginxproxymanager/docker/rootfs/etc/nginx/nginx.conf\n    NGINX_CONFS=$(find /opt/nginxproxymanager -type f -name \"*.conf\")\n    for NGINX_CONF in $NGINX_CONFS; do\n      sed -i 's+include conf.d+include /etc/nginx/conf.d+g' \"$NGINX_CONF\"\n    done\n\n    mkdir -p /var/www/html /etc/nginx/logs\n    cp -r /opt/nginxproxymanager/docker/rootfs/var/www/html/* /var/www/html/\n    cp -r /opt/nginxproxymanager/docker/rootfs/etc/nginx/* /etc/nginx/\n    cp /opt/nginxproxymanager/docker/rootfs/etc/letsencrypt.ini /etc/letsencrypt.ini\n    cp /opt/nginxproxymanager/docker/rootfs/etc/logrotate.d/nginx-proxy-manager /etc/logrotate.d/nginx-proxy-manager\n    ln -sf /etc/nginx/nginx.conf /etc/nginx/conf/nginx.conf\n    rm -f /etc/nginx/conf.d/dev.conf\n\n    mkdir -p /tmp/nginx/body \\\n      /run/nginx \\\n      /data/nginx \\\n      /data/custom_ssl \\\n      /data/logs \\\n      /data/access \\\n      /data/nginx/default_host \\\n      /data/nginx/default_www \\\n      /data/nginx/proxy_host \\\n      /data/nginx/redirection_host \\\n      /data/nginx/stream \\\n      /data/nginx/dead_host \\\n      /data/nginx/temp \\\n      /var/lib/nginx/cache/public \\\n      /var/lib/nginx/cache/private \\\n      /var/cache/nginx/proxy_temp\n\n    chmod -R 777 /var/cache/nginx\n    chown root /tmp/nginx\n\n    echo resolver \"$(awk 'BEGIN{ORS=\" \"} $1==\"nameserver\" {print ($2 ~ \":\")? \"[\"$2\"]\": $2}' /etc/resolv.conf);\" >/etc/nginx/conf.d/include/resolvers.conf\n\n    if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]; then\n      $STD openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj \"/O=Nginx Proxy Manager/OU=Dummy Certificate/CN=localhost\" -keyout /data/nginx/dummykey.pem -out /data/nginx/dummycert.pem\n    fi\n\n    mkdir -p /app/frontend/images\n    cp -r /opt/nginxproxymanager/backend/* /app\n    msg_ok \"Set up Environment\"\n\n    msg_info \"Building Frontend\"\n    export NODE_OPTIONS=\"--max_old_space_size=2048 --openssl-legacy-provider\"\n    cd /opt/nginxproxymanager/frontend\n    sed -E -i 's/\"node-sass\" *: *\"([^\"]*)\"/\"sass\": \"\\1\"/g' package.json\n    $STD yarn install --network-timeout 600000\n    $STD yarn locale-compile\n    $STD yarn build\n    cp -r /opt/nginxproxymanager/frontend/dist/* /app/frontend\n    cp -r /opt/nginxproxymanager/frontend/public/images/* /app/frontend/images\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Initializing Backend\"\n    rm -rf /app/config/default.json\n    if [ ! -f /app/config/production.json ]; then\n      cat <<'EOF' >/app/config/production.json\n{\n  \"database\": {\n    \"engine\": \"knex-native\",\n    \"knex\": {\n      \"client\": \"better-sqlite3\",\n      \"connection\": {\n        \"filename\": \"/data/database.sqlite\"\n      },\n      \"useNullAsDefault\": true\n    }\n  }\n}\nEOF\n    fi\n    sed -i 's/\"client\": \"sqlite3\"/\"client\": \"better-sqlite3\"/' /app/config/production.json\n    cd /app\n    $STD yarn install --network-timeout 600000\n    msg_ok \"Initialized Backend\"\n\n    msg_info \"Starting Services\"\n    sed -i 's/user npm/user root/g; s/^pid/#pid/g' /usr/local/openresty/nginx/conf/nginx.conf\n    sed -r -i 's/^([[:space:]]*)su npm npm/\\1#su npm npm/g;' /etc/logrotate.d/nginx-proxy-manager\n    systemctl daemon-reload\n    systemctl enable -q --now openresty\n    systemctl enable -q --now npm\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:81${CL}\"\n"
  },
  {
    "path": "ct/nightscout.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: aendel\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/nightscout/cgm-remote-monitor\n\nAPP=\"Nightscout\"\nvar_tags=\"${var_tags:-health}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/nightscout ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"nightscout\" \"nightscout/cgm-remote-monitor\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nightscout\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"nightscout\" \"nightscout/cgm-remote-monitor\" \"tarball\"\n\n    msg_info \"Updating Nightscout\"\n    cd /opt/nightscout\n    $STD npm install\n    msg_ok \"Updated Nightscout\"\n\n    msg_info \"Starting Service\"\n    systemctl start nightscout\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1337${CL}\"\n"
  },
  {
    "path": "ct/nocodb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.nocodb.com/\n\nAPP=\"NocoDB\"\nvar_tags=\"${var_tags:-noCode}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  #RELEASE=\"0.301.1\"\n  if [[ ! -f /etc/systemd/system/nocodb.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  #if check_for_gh_release \"nocodb\" \"nocodb/nocodb\" \"${RELEASE}\"; then\n  if check_for_gh_release \"nocodb\" \"nocodb/nocodb\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nocodb\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"nocodb\" \"nocodb/nocodb\" \"singlefile\" \"latest\" \"/opt/nocodb/\" \"Noco-linux-x64\"\n\n    msg_info \"Starting Service\"\n    systemctl start nocodb\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/dashboard${CL}\"\n"
  },
  {
    "path": "ct/node-red.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nodered.org/\n\nAPP=\"Node-Red\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /root/.node-red ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  UPD=$(msg_menu \"Node-Red Update Options\" \\\n    \"1\" \"Update ${APP}\" \\\n    \"2\" \"Install Themes\")\n  if [ \"$UPD\" == \"1\" ]; then\n    NODE_VERSION=\"22\" setup_nodejs\n\n    msg_info \"Stopping Service\"\n    systemctl stop nodered\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating Node-Red\"\n    $STD npm install -g --unsafe-perm node-red\n    msg_ok \"Updated Node-Red\"\n\n    msg_info \"Starting Service\"\n    systemctl start nodered\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n  if [ \"$UPD\" == \"2\" ]; then\n    THEME=$(msg_menu \"Node-Red Themes\" \\\n      \"midnight-red\" \"Midnight Red (default)\" \\\n      \"aurora\" \"Aurora\" \\\n      \"cobalt2\" \"Cobalt2\" \\\n      \"dark\" \"Dark\" \\\n      \"dracula\" \"Dracula\" \\\n      \"espresso-libre\" \"Espresso Libre\" \\\n      \"github-dark\" \"GitHub Dark\" \\\n      \"github-dark-default\" \"GitHub Dark Default\" \\\n      \"github-dark-dimmed\" \"GitHub Dark Dimmed\" \\\n      \"monoindustrial\" \"Monoindustrial\" \\\n      \"monokai\" \"Monokai\" \\\n      \"monokai-dimmed\" \"Monokai Dimmed\" \\\n      \"noctis\" \"Noctis\" \\\n      \"oceanic-next\" \"Oceanic Next\" \\\n      \"oled\" \"OLED\" \\\n      \"one-dark-pro\" \"One Dark Pro\" \\\n      \"one-dark-pro-darker\" \"One Dark Pro Darker\" \\\n      \"solarized-dark\" \"Solarized Dark\" \\\n      \"solarized-light\" \"Solarized Light\" \\\n      \"tokyo-night\" \"Tokyo Night\" \\\n      \"tokyo-night-light\" \"Tokyo Night Light\" \\\n      \"tokyo-night-storm\" \"Tokyo Night Storm\" \\\n      \"totallyinformation\" \"TotallyInformation\" \\\n      \"zenburn\" \"Zenburn\")\n    header_info\n    msg_info \"Installing ${THEME} Theme\"\n    cd /root/.node-red\n    sed -i 's|// theme: \".*\",|theme: \"\",|g' /root/.node-red/settings.js\n    $STD npm install @node-red-contrib-themes/theme-collection\n    sed -i \"{s/theme: \".*\"/theme: '${THEME}',/g}\" /root/.node-red/settings.js\n    systemctl restart nodered\n    msg_ok \"Installed ${THEME} Theme\"\n    exit\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1880${CL}\"\n"
  },
  {
    "path": "ct/nodebb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nodebb.org/\n\nAPP=\"NodeBB\"\nvar_tags=\"${var_tags:-forum}\"\nvar_disk=\"${var_disk:-10}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\n# App Output & Base Settings\nheader_info \"$APP\"\n\n# Core\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/nodebb ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"nodebb\" \"NodeBB/NodeBB\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nodebb\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP}\"\n    cd /opt/nodebb\n    $STD ./nodebb upgrade\n    echo \"${CHECK_UPDATE_RELEASE}\" >~/.nodebb\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start nodebb\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:4567${CL}\"\n"
  },
  {
    "path": "ct/nodecast-tv.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/technomancer702/nodecast-tv\n\nAPP=\"nodecast-tv\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/nodecast-tv ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"nodecast-tv\" \"technomancer702/nodecast-tv\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nodecast-tv\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"nodecast-tv\" \"technomancer702/nodecast-tv\" \"tarball\"\n\n    msg_info \"Updating Modules\"\n    cd /opt/nodecast-tv\n    $STD npm install\n    msg_ok \"Updated Modules\"\n\n    msg_info \"Starting Service\"\n    systemctl start nodecast-tv\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n\n"
  },
  {
    "path": "ct/notifiarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://notifiarr.com/\n\nAPP=\"Notifiarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/lib/systemd/system/notifiarr.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Notifiarr\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated Notifiarr\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5454${CL}\"\n"
  },
  {
    "path": "ct/npmplus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ZoeyVid/NPMplus\n\nAPP=\"NPMplus\"\nvar_tags=\"${var_tags:-proxy;nginx}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-alpine}\"\nvar_version=\"${var_version:-3.23}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info \"$APP\"\n\n  msg_info \"Updating Alpine OS\"\n  $STD apk -U upgrade\n  msg_ok \"System updated\"\n\n  msg_info \"Pulling latest NPMplus container image\"\n  cd /opt\n  $STD docker compose pull\n  msg_info \"Recreating container\"\n  $STD docker compose up -d\n  msg_ok \"Updated NPMplus container\"\n\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:81${CL}\"\n"
  },
  {
    "path": "ct/ntfy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ntfy.sh/\n\nAPP=\"ntfy\"\nvar_tags=\"${var_tags:-notification}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/ntfy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [ -f /etc/apt/keyrings/archive.heckel.io.gpg ]; then\n    msg_info \"Correcting old Ntfy Repository\"\n    rm -f /etc/apt/keyrings/archive.heckel.io.gpg\n    rm -f /etc/apt/sources.list.d/archive.heckel.io.list\n    rm -f /etc/apt/sources.list.d/archive.heckel.io.list.bak\n    rm -f /etc/apt/sources.list.d/archive.heckel.io.sources\n    setup_deb822_repo \\\n      \"ntfy\" \\\n      \"https://archive.ntfy.sh/apt/keyring.gpg\" \\\n      \"https://archive.ntfy.sh/apt/\" \\\n      \"stable\"\n    msg_ok \"Corrected old Ntfy Repository\"\n  fi\n\n  msg_info \"Updating ntfy\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated ntfy\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/nxwitness.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nxvms.com/download/releases/linux\n\nAPP=\"NxWitness\"\nvar_tags=\"${var_tags:-nvr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/networkoptix-mediaserver.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  BASE_URL=\"https://updates.networkoptix.com/default/index.html\"\n  RELEASE=$(curl -fsSL \"$BASE_URL\" | grep -oP '(?<=<b>)[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+(?=</b>)' | head -n 1)\n  DETAIL_PAGE=$(curl -fsSL \"$BASE_URL#note_$RELEASE\")\n  DOWNLOAD_URL=$(echo \"$DETAIL_PAGE\" | grep -oP \"https://updates.networkoptix.com/default/$RELEASE/linux/nxwitness-server-[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+-linux_arm64\\.deb\" | head -n 1)\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop networkoptix-root-tool networkoptix-mediaserver\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP} to ${RELEASE}\"\n    cd /tmp\n    curl -fsSL \"$DOWNLOAD_URL\" -o \"\"nxwitness-server-$RELEASE-linux_arm64.deb\"\"\n    export DEBIAN_FRONTEND=noninteractive\n    export DEBCONF_NOWARNINGS=yes\n    $STD dpkg -i nxwitness-server-$RELEASE-linux_arm64.deb\n    rm -f /tmp/nxwitness-server-$RELEASE-linux_arm64.deb\n    echo \"${RELEASE}\" >/opt/${APP}_version.txt\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start networkoptix-root-tool networkoptix-mediaserver\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7001/${CL}\"\n"
  },
  {
    "path": "ct/nzbget.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nzbget.com/\n\nAPP=\"NZBGet\"\nvar_tags=\"${var_tags:-usenet;downloader}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/nzbget.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if ! command -v unrar &>/dev/null; then\n    setup_nonfree\n    $STD apt install -y unrar\n\n    if grep -q \"UnrarCmd=unrar-free\" /var/lib/nzbget/nzbget.conf; then\n      sed -i \"s|UnrarCmd=unrar-free|UnrarCmd=unrar|g\" /var/lib/nzbget/nzbget.conf\n    fi\n  fi\n\n  msg_info \"Updating NZBGet\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated NZBGet\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6789${CL}\"\n"
  },
  {
    "path": "ct/oauth2-proxy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/oauth2-proxy/oauth2-proxy/\n\nAPP=\"oauth2-proxy\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/oauth2-proxy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"oauth2-proxy\" \"oauth2-proxy/oauth2-proxy\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop oauth2-proxy\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"oauth2-proxy\" \"oauth2-proxy/oauth2-proxy\" \"prebuild\" \"latest\" \"/opt/oauth2-proxy\" \"oauth2-proxy*linux-arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start oauth2-proxy\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Now you can modify /opt/oauth2-proxy/config.toml with your needed config.${CL}\"\n"
  },
  {
    "path": "ct/octoprint.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://octoprint.org/\n\nAPP=\"OctoPrint\"\nvar_tags=\"${var_tags:-3d-printing}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/octoprint ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Stopping OctoPrint\"\n    systemctl stop octoprint\n    msg_ok \"Stopped OctoPrint\"\n\n    msg_info \"Updating OctoPrint\"\n    source /opt/octoprint/bin/activate\n    $STD pip3 install octoprint --upgrade\n    msg_ok \"Updated OctoPrint\"\n\n    msg_info \"Starting OctoPrint\"\n    systemctl start octoprint\n    msg_ok \"Started OctoPrint\"\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/odoo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/odoo/odoo\n\nAPP=\"Odoo\"\nvar_tags=\"${var_tags:-erp}\"\nvar_disk=\"${var_disk:-6}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/odoo/odoo.conf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  ensure_dependencies python3-lxml\n  if ! [[ $(dpkg -s python3-lxml-html-clean 2>/dev/null) ]]; then\n    curl -fsSL \"http://archive.ubuntu.com/ubuntu/pool/universe/l/lxml-html-clean/python3-lxml-html-clean_0.1.1-1_all.deb\" -o /opt/python3-lxml-html-clean.deb\n    $STD dpkg -i /opt/python3-lxml-html-clean.deb\n    rm -f /opt/python3-lxml-html-clean.deb\n  fi\n\n  RELEASE=$(curl -fsSL https://nightly.odoo.com/ | grep -oE 'href=\"[0-9]+\\.[0-9]+/nightly\"' | head -n1 | cut -d'\"' -f2 | cut -d/ -f1)\n  LATEST_VERSION=$(curl -fsSL \"https://nightly.odoo.com/${RELEASE}/nightly/deb/\" |\n    grep -oP \"odoo_${RELEASE}\\.\\d+_all\\.deb\" |\n    sed -E \"s/odoo_(${RELEASE}\\.[0-9]+)_all\\.deb/\\1/\" |\n    sort -V |\n    tail -n1)\n\n  if [[ \"${LATEST_VERSION}\" != \"$(cat /opt/${APP}_version.txt)\" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then\n    msg_info \"Stopping ${APP} service\"\n    systemctl stop odoo\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP} to ${LATEST_VERSION}\"\n    curl -fsSL https://nightly.odoo.com/${RELEASE}/nightly/deb/odoo_${RELEASE}.latest_all.deb -o /opt/odoo.deb\n    $STD apt install -y /opt/odoo.deb\n    rm -f /opt/odoo.deb\n    echo \"$LATEST_VERSION\" >/opt/${APP}_version.txt\n    msg_ok \"Updated ${APP} to ${LATEST_VERSION}\"\n\n    msg_info \"Starting Service\"\n    systemctl start odoo\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at ${LATEST_VERSION}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8069${CL}\"\n"
  },
  {
    "path": "ct/ollama.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: havardthom | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ollama.com/\n\nAPP=\"Ollama\"\nvar_tags=\"${var_tags:-ai}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-40}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /usr/local/lib/ollama ]]; then\n    msg_error \"No Ollama Installation Found!\"\n    exit\n  fi\n  RELEASE=$(curl -fsSL https://api.github.com/repos/ollama/ollama/releases/latest | grep \"tag_name\" | awk -F '\"' '{print $4}')\n  if [[ ! -f /opt/Ollama_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/Ollama_version.txt)\" ]]; then\n    if [[ ! -f /opt/Ollama_version.txt ]]; then\n      touch /opt/Ollama_version.txt\n    fi\n    ensure_dependencies zstd\n    msg_info \"Stopping Services\"\n    systemctl stop ollama\n    msg_ok \"Services Stopped\"\n\n    TMP_TAR=$(mktemp --suffix=.tar.zst)\n    curl -fL# -C - -o \"${TMP_TAR}\" \"https://github.com/ollama/ollama/releases/download/${RELEASE}/ollama-linux-arm64.tar.zst\"\n    msg_info \"Updating Ollama to ${RELEASE}\"\n    rm -rf /usr/local/lib/ollama\n    rm -rf /usr/local/bin/ollama\n    mkdir -p /usr/local/lib/ollama\n    tar --zstd -xf \"${TMP_TAR}\" -C /usr/local/lib/ollama\n    ln -sf /usr/local/lib/ollama/bin/ollama /usr/local/bin/ollama\n    rm -f \"${TMP_TAR}\"\n    echo \"${RELEASE}\" >/opt/Ollama_version.txt\n    msg_ok \"Updated Ollama to ${RELEASE}\"\n\n    msg_info \"Starting Services\"\n    systemctl start ollama\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. Ollama is already at ${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:11434${CL}\"\n"
  },
  {
    "path": "ct/omada.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.tp-link.com/us/support/download/omada-software-controller/\n\nAPP=\"Omada\"\nvar_tags=\"${var_tags:-tp-link;controller}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tplink ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating MongoDB\"\n  if lscpu | grep -q 'avx'; then\n    MONGO_VERSION=\"8.0\"\n  else\n    msg_error \"No AVX detected (CPU-Flag)! We have discontinued support for this. You are welcome to try it manually with a Debian LXC, but due to the many issues with Omada, we currently only support AVX CPUs.\"\n    exit 10\n  fi\n\n  JAVA_VERSION=\"21\" setup_java\n\n  OMADA_URL=$(curl -fsSL \"https://support.omadanetworks.com/en/download/software/omada-controller/\" |\n    grep -o 'https://static\\.tp-link\\.com/upload/software/[^\"]*linux_x64[^\"]*\\.deb' |\n    head -n1)\n  OMADA_PKG=$(basename \"${OMADA_URL}\")\n  VERSION=$(sed -n 's/.*_v\\([0-9.]*\\)_.*_\\([0-9]\\{14\\}\\)\\.deb$/\\1-\\2/p' <<<\"${OMADA_PKG}\")\n\n  CURRENT_VERSION=$(cat $HOME/.omada 2>/dev/null || echo \"0\")\n\n  if dpkg --compare-versions \"${VERSION}\" gt \"${CURRENT_VERSION}\"; then\n\n    msg_info \"Updating Omada Controller\"\n\n    if [ -z \"${OMADA_PKG}\" ]; then\n      msg_error \"Could not retrieve Omada package – server may be down.\"\n      exit\n    fi\n    curl -fsSL \"${OMADA_URL}\" -o \"${OMADA_PKG}\"\n    export DEBIAN_FRONTEND=noninteractive\n    $STD dpkg -i \"${OMADA_PKG}\"\n    rm -f \"${OMADA_PKG}\"\n    echo \"${VERSION}\" >$HOME/.omada\n    msg_ok \"Updated Omada Controller to ${VERSION}\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update available: ${APP} (${CURRENT_VERSION})\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8043${CL}\"\n"
  },
  {
    "path": "ct/ombi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ombi.io/\n\nAPP=\"Ombi\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/ombi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"ombi\" \"Ombi-app/Ombi\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop ombi\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    [[ -f /opt/ombi/Ombi.db ]] && mv /opt/ombi/Ombi.db /opt\n    [[ -f /opt/ombi/OmbiExternal.db ]] && mv /opt/ombi/OmbiExternal.db /opt\n    [[ -f /opt/ombi/OmbiSettings.db ]] && mv /opt/ombi/OmbiSettings.db /opt\n    [[ -f /opt/ombi/database.json ]] && mv /opt/ombi/database.json /opt\n    msg_ok \"Backup created\"\n\n    rm -rf /opt/ombi\n    fetch_and_deploy_gh_release \"ombi\" \"Ombi-app/Ombi\" \"prebuild\" \"latest\" \"/opt/ombi\" \"linux-x64.tar.gz\"\n    [[ -f /opt/Ombi.db ]] && mv /opt/Ombi.db /opt/ombi\n    [[ -f /opt/OmbiExternal.db ]] && mv /opt/OmbiExternal.db /opt/ombi\n    [[ -f /opt/OmbiSettings.db ]] && mv /opt/OmbiSettings.db /opt/ombi\n    [[ -f /opt/database.json ]] && mv /opt/database.json /opt/ombi\n\n    msg_info \"Starting Service\"\n    systemctl start ombi\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/omv.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.openmediavault.org/\n\nAPP=\"OMV\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apt/sources.list.d/openmediavault.list ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/onedev.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://onedev.io/\n\nAPP=\"OneDev\"\nvar_tags=\"${var_tags:-git}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/onedev.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"onedev\" \"theonedev/onedev\"; then\n    JAVA_VERSION=\"21\" setup_java\n\n    msg_info \"Stopping Service\"\n    systemctl stop onedev\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating OneDev\"\n    cd /opt\n    curl -fsSL \"https://code.onedev.io/onedev/server/~site/onedev-latest.tar.gz\" -o onedev-latest.tar.gz\n    tar -xzf onedev-latest.tar.gz\n    $STD /opt/onedev-latest/bin/upgrade.sh /opt/onedev\n    rm -rf /opt/onedev-latest\n    rm -rf /opt/onedev-latest.tar.gz\n    echo \"${CHECK_UPDATE_RELEASE}\" >~/.onedev\n    msg_ok \"Updated OneDev\"\n\n    msg_info \"Starting Service\"\n    systemctl start onedev\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6610${CL}\"\n"
  },
  {
    "path": "ct/onlyoffice.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.onlyoffice.com/\n\nAPP=\"ONLYOFFICE\"\nvar_tags=\"${var_tags:-word;excel;powerpoint;pdf}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /var/www/onlyoffice ]]; then\n    msg_error \"No valid ${APP} installation found!\"\n    exit\n  fi\n\n  msg_info \"Updating OnlyOffice Document Server\"\n  $STD apt update\n  $STD apt -y --only-upgrade install onlyoffice-documentserver\n  msg_ok \"Updated OnlyOffice Document Server\"\n\n  if systemctl is-enabled --quiet onlyoffice-documentserver; then\n    msg_info \"Restarting OnlyOffice Document Server\"\n    $STD systemctl restart onlyoffice-documentserver\n    msg_ok \"OnlyOffice Document Server restarted\"\n  fi\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/open-archiver.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://openarchiver.com/\n\nAPP=\"Open-Archiver\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/openarchiver ]]; then\n    msg_error \"No Open Archiver Installation Found!\"\n    exit\n  fi\n\n  setup_meilisearch\n\n  if check_for_gh_release \"openarchiver\" \"LogicLabs-OU/OpenArchiver\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop openarchiver\n    msg_ok \"Stopped Services\"\n\n    cp /opt/openarchiver/.env /opt/openarchiver.env\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"openarchiver\" \"LogicLabs-OU/OpenArchiver\" \"tarball\"\n    mv /opt/openarchiver.env /opt/openarchiver/.env\n\n    msg_info \"Updating Open Archiver\"\n    cd /opt/openarchiver\n    $STD pnpm install --shamefully-hoist --frozen-lockfile --prod=false\n    $STD pnpm run build:oss\n    $STD pnpm db:migrate\n    msg_ok \"Updated Open Archiver\"\n\n    if grep -q '^ExecStart=/usr/bin/pnpm docker-start$' /etc/systemd/system/openarchiver.service; then\n      sed -i 's|^ExecStart=/usr/bin/pnpm docker-start$|ExecStart=/usr/bin/pnpm docker-start:oss|' /etc/systemd/system/openarchiver.service\n      systemctl daemon-reload\n    fi\n\n    msg_info \"Starting Services\"\n    systemctl start openarchiver\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/opencloud.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://opencloud.eu\n\nAPP=\"OpenCloud\"\nvar_tags=\"${var_tags:-files;cloud}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /etc/opencloud ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=\"v6.1.0\"\n  if check_for_gh_release \"OpenCloud\" \"opencloud-eu/opencloud\" \"${RELEASE}\" \"each release is tested individually before the version is updated. Please do not open issues for this\"; then\n    msg_info \"Stopping services\"\n    systemctl stop opencloud opencloud-wopi\n    msg_ok \"Stopped services\"\n\n    msg_info \"Updating packages\"\n    $STD apt-get update\n    $STD apt-get dist-upgrade -y\n    ensure_dependencies \"inotify-tools\"\n    msg_ok \"Updated packages\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"OpenCloud\" \"opencloud-eu/opencloud\" \"singlefile\" \"${RELEASE}\" \"/usr/bin\" \"opencloud-*-linux-arm64\"\n\n    if ! grep -q 'POSIX_WATCH' /etc/opencloud/opencloud.env; then\n      sed -i '/^## External/i ## Uncomment below to enable PosixFS Collaborative Mode\\\n## Increase inotify watch/instance limits on your PVE host:\\\n### sysctl -w fs.inotify.max_user_watches=1048576\\\n### sysctl -w fs.inotify.max_user_instances=1024\\\n# STORAGE_USERS_POSIX_ENABLE_COLLABORATION=true\\\n# STORAGE_USERS_POSIX_WATCH_TYPE=inotifywait\\\n# STORAGE_USERS_POSIX_WATCH_FS=true\\\n# STORAGE_USERS_POSIX_WATCH_PATH=<path-to-storage-or-bind-mount>' /etc/opencloud/opencloud.env\n    fi\n\n    msg_info \"Starting services\"\n    systemctl start opencloud opencloud-wopi\n    msg_ok \"Started services\"\n    msg_ok \"Updated successfully\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://<your-OpenCloud-FQDN>${CL}\"\n"
  },
  {
    "path": "ct/opengist.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Jonathan (jd-apprentice)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://opengist.io/\n\nAPP=\"Opengist\"\nvar_tags=\"${var_tags:-development}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/opengist ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"opengist\" \"thomiceli/opengist\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop opengist\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mv /opt/opengist /opt/opengist-backup\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"opengist\" \"thomiceli/opengist\" \"prebuild\" \"latest\" \"/opt/opengist\" \"opengist*linux-arm64.tar.gz\"\n\n    msg_info \"Restoring Configuration\"\n    mv /opt/opengist-backup/config.yml /opt/opengist/config.yml\n    msg_ok \"Configuration Restored\"\n\n    msg_info \"Starting Service\"\n    systemctl start opengist\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6157${CL}\"\n"
  },
  {
    "path": "ct/openhab.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.openhab.org/\n\nAPP=\"openHAB\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/lib/systemd/system/openhab.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using one of the following URLs:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/openobserve.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://openobserve.ai/\n\nAPP=\"OpenObserve\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/openobserve/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"openobserve\" \"openobserve/openobserve\"; then\n    msg_info \"Updating OpenObserve\"\n    systemctl stop openobserve\n    RELEASE=$(get_latest_github_release \"openobserve/openobserve\")\n    tar zxf <(curl -fsSL https://downloads.openobserve.ai/releases/openobserve/v$RELEASE/openobserve-v$RELEASE-linux-arm64.tar.gz) -C /opt/openobserve\n    systemctl start openobserve\n    msg_ok \"Updated OpenObserve\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5080${CL}\"\n"
  },
  {
    "path": "ct/openproject.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/opf/openproject\n\nAPP=\"OpenProject\"\nvar_tags=\"${var_tags:-project-management;erp}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/openproject/installer.dat ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating OpenProject\"\n  $STD apt update\n  $STD apt install --only-upgrade -y openproject\n  msg_ok \"Updated OpenProject\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/openproject${CL}\"\n"
  },
  {
    "path": "ct/openthread-br.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://openthread.io/guides/border-router\n\nAPP=\"OpenThread-BR\"\nvar_tags=\"${var_tags:-thread;iot;border-router;matter}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_tun=\"${var_tun:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/ot-br-posix ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  cd /opt/ot-br-posix\n  LOCAL_COMMIT=$(git rev-parse HEAD)\n  $STD git fetch --depth 1 origin main\n  REMOTE_COMMIT=$(git rev-parse origin/main)\n\n  if [[ \"${LOCAL_COMMIT}\" == \"${REMOTE_COMMIT}\" ]]; then\n    msg_ok \"Already up to date (${LOCAL_COMMIT:0:7})\"\n    exit\n  fi\n\n  msg_info \"Stopping Services\"\n  systemctl stop otbr-web\n  systemctl stop otbr-agent\n  msg_ok \"Stopped Services\"\n\n  msg_info \"Updating Source\"\n  $STD git reset --hard origin/main\n  $STD git submodule update --depth 1 --init --recursive\n  msg_ok \"Updated Source\"\n\n  msg_info \"Rebuilding OpenThread Border Router (Patience)\"\n  cd /opt/ot-br-posix/build\n  $STD cmake -GNinja \\\n    -DBUILD_TESTING=OFF \\\n    -DCMAKE_INSTALL_PREFIX=/usr \\\n    -DOTBR_DBUS=ON \\\n    -DOTBR_MDNS=openthread \\\n    -DOTBR_REST=ON \\\n    -DOTBR_WEB=ON \\\n    -DOTBR_BORDER_ROUTING=ON \\\n    -DOTBR_BACKBONE_ROUTER=ON \\\n    -DOT_FIREWALL=ON \\\n    -DOT_POSIX_NAT64_CIDR=\"192.168.255.0/24\" \\\n    ..\n  $STD ninja\n  $STD ninja install\n  msg_ok \"Rebuilt OpenThread Border Router\"\n\n  msg_info \"Starting Services\"\n  systemctl start otbr-agent\n  systemctl start otbr-web\n  msg_ok \"Started Services\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/openwebui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://openwebui.com/\n\nAPP=\"Open WebUI\"\nvar_tags=\"${var_tags:-ai;interface}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-50}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  ensure_dependencies zstd build-essential libmariadb-dev\n\n  if [[ -d /opt/open-webui ]]; then\n    msg_warn \"Legacy installation detected — migrating to uv based install...\"\n    msg_info \"Stopping Service\"\n    systemctl stop open-webui\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /opt/open-webui-backup\n    cp -a /opt/open-webui/backend/data /opt/open-webui-backup/data || true\n    cp -a /opt/open-webui/.env /opt/open-webui-backup/.env || true\n    msg_ok \"Created Backup\"\n\n    msg_info \"Removing legacy installation\"\n    rm -rf /opt/open-webui\n    rm -rf /root/.open-webui || true\n    msg_ok \"Removed legacy installation\"\n\n    msg_info \"Installing uv-based Open-WebUI\"\n    PYTHON_VERSION=\"3.12\" setup_uv\n    $STD uv tool install --python 3.12 --constraint <(echo \"numba>=0.60\") open-webui[all]\n    msg_ok \"Installed uv-based Open-WebUI\"\n\n    msg_info \"Restoring data\"\n    mkdir -p /root/.open-webui\n    cp -a /opt/open-webui-backup/data/* /root/.open-webui/ || true\n    cp -a /opt/open-webui-backup/.env /root/.env || true\n    rm -rf /opt/open-webui-backup || true\n    msg_ok \"Restored data\"\n\n    msg_info \"Recreating Service\"\n    cat <<EOF >/etc/systemd/system/open-webui.service\n[Unit]\nDescription=Open WebUI Service\nAfter=network.target\n\n[Service]\nType=simple\nEnvironment=DATA_DIR=/root/.open-webui\nEnvironmentFile=-/root/.env\nExecStart=/root/.local/bin/open-webui serve\nWorkingDirectory=/root\nRestart=on-failure\nRestartSec=5\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n    $STD systemctl daemon-reload\n    systemctl enable -q --now open-webui\n    msg_ok \"Recreated Service\"\n\n    msg_ok \"Migration completed\"\n    exit 0\n  fi\n\n  if [[ ! -d /root/.open-webui ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [ -x \"/usr/bin/ollama\" ]; then\n    msg_info \"Checking for Ollama Update\"\n    OLLAMA_VERSION=$(ollama -v | awk '{print $NF}')\n    RELEASE=$(curl -s https://api.github.com/repos/ollama/ollama/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4)}')\n    if [ \"$OLLAMA_VERSION\" != \"$RELEASE\" ]; then\n      msg_info \"Ollama update available: v$OLLAMA_VERSION -> v$RELEASE\"\n      msg_info \"Downloading Ollama v$RELEASE \\n\"\n      curl -fS#LO https://github.com/ollama/ollama/releases/download/v${RELEASE}/ollama-linux-arm64.tar.zst\n      msg_ok \"Download Complete\"\n\n      if [ -f \"ollama-linux-arm64.tar.zst\" ]; then\n\n        msg_info \"Stopping Ollama Service\"\n        systemctl stop ollama\n        msg_ok \"Stopped Service\"\n\n        msg_info \"Installing Ollama\"\n        rm -rf /usr/lib/ollama\n        rm -rf /usr/bin/ollama\n        tar --zstd -C /usr -xf ollama-linux-arm64.tar.zst\n        rm -rf ollama-linux-arm64.tar.zst\n        msg_ok \"Installed Ollama\"\n\n        msg_info \"Starting Ollama Service\"\n        systemctl start ollama\n        msg_ok \"Started Service\"\n\n        msg_ok \"Ollama updated to version $RELEASE\"\n      else\n        msg_error \"Ollama download failed. Aborting update.\"\n      fi\n    else\n      msg_ok \"Ollama is already up to date.\"\n    fi\n  fi\n\n  msg_info \"Updating Open WebUI via uv\"\n  PYTHON_VERSION=\"3.12\" setup_uv\n  $STD uv tool install --force --python 3.12 --constraint <(echo \"numba>=0.60\") open-webui[all]\n  systemctl restart open-webui\n  msg_ok \"Updated Open WebUI\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/openziti-controller.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: emoscardini\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/openziti/ziti\n\nAPP=\"openziti-controller\"\nvar_tags=\"${var_tags:-network;openziti-controller}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/openziti ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:<port>/zac${CL}\"\n"
  },
  {
    "path": "ct/openziti-tunnel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: emoscardini\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/openziti/ziti\n\nAPP=\"openziti-tunnel\"\nvar_tags=\"${var_tags:-network;openziti-tunnel}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/openziti ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Application was assigned the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}Address: ${IP}${CL}\"\n"
  },
  {
    "path": "ct/ots.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Luzifer/ots\n\nAPP=\"OTS\"\nvar_tags=\"${var_tags:-secrets-sharer}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/ots ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"ots\" \"Luzifer/ots\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop ots\n    systemctl stop nginx\n    msg_ok \"Stopped Services\"\n\n    fetch_and_deploy_gh_release \"ots\" \"Luzifer/ots\" \"prebuild\" \"latest\" \"/opt/ots\" \"ots_linux_arm64.tgz\"\n\n    msg_info \"Starting Services\"\n    systemctl start ots\n    systemctl start nginx\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/outline.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/outline/outline\n\nAPP=\"Outline\"\nvar_tags=\"${var_tags:-documentation}\"\nvar_disk=\"${var_disk:-8}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/outline ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"outline\" \"outline/outline\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop outline\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Creating backup\"\n    cp /opt/outline/.env /opt\n    msg_ok \"Backup created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"outline\" \"outline/outline\" \"tarball\"\n\n    msg_info \"Updating Outline\"\n    cd /opt/outline\n    mv /opt/.env /opt/outline\n    export NODE_ENV=development\n    export NODE_OPTIONS=\"--max-old-space-size=3584\"\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    $STD corepack enable\n    $STD yarn install --immutable\n    export NODE_ENV=production\n    $STD yarn build\n    msg_ok \"Updated Outline\"\n\n    msg_info \"Starting Services\"\n    systemctl start outline\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/overseerr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://overseerr.dev/\n\nAPP=\"Overseerr\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/overseerr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [[ -f \"$HOME/.overseerr\" ]] && [[ \"$(printf '%s\\n' \"1.35.0\" \"$(cat \"$HOME/.overseerr\")\" | sort -V | head -n1)\" == \"1.35.0\" ]]; then\n    echo\n    echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n    echo \"Overseerr v1.34.0 detected.\"\n    echo\n    echo \"Seerr is the new unified Jellyseerr and Overseerr.\"\n    echo \"More info: https://docs.seerr.dev/blog/seerr-release\"\n    echo\n    read -rp \"Do you want to migrate to Seerr now? (y/N): \" MIGRATE\n    echo\n    if [[ ! \"$MIGRATE\" =~ ^[Yy]$ ]]; then\n      msg_info \"Migration cancelled. Exiting.\"\n      exit 0\n    fi\n\n    msg_info \"Switching update script to Seerr\"\n    TMP_UPDATE=$(mktemp)\n    cat <<'EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/seerr.sh)\"\nEOF\n    mv \"$TMP_UPDATE\" /usr/bin/update\n    chmod +x /usr/bin/update\n    msg_ok \"Switched update script to Seerr\"\n    msg_warn \"Please type 'update' again to complete the migration\"\n    exit 0\n  fi\n\n  if check_for_gh_release \"overseerr\" \"sct/overseerr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop overseerr\n    msg_ok \"Service stopped\"\n\n    msg_info \"Creating backup\"\n    mv /opt/overseerr/config /opt/config_backup\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"overseerr\" \"sct/overseerr\" \"tarball\"\n    rm -rf /opt/overseerr/config\n\n    msg_info \"Configuring ${APP} (Patience)\"\n    cd /opt/overseerr\n    $STD yarn install\n    $STD yarn build\n    mv /opt/config_backup /opt/overseerr/config\n    msg_ok \"Configured ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start overseerr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5055${CL}\"\n"
  },
  {
    "path": "ct/owncast.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://owncast.online/\n\nAPP=\"Owncast\"\nvar_tags=\"${var_tags:-broadcasting}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/owncast ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"owncast\" \"owncast/owncast\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop owncast\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"owncast\" \"owncast/owncast\" \"prebuild\" \"latest\" \"/opt/owncast\" \"owncast*linux-64bit.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl start owncast\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/admin${CL}\"\n"
  },
  {
    "path": "ct/ownfoil.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pajjski\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/a1ex4/ownfoil\n\nAPP=\"ownfoil\"\nvar_tags=\"${var_tags:-gaming}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/ownfoil ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"ownfoil\" \"a1ex4/ownfoil\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop ownfoil\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/ownfoil/app/config /opt/ownfoil_data_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"ownfoil\" \"a1ex4/ownfoil\" \"tarball\"\n\n    msg_info \"Installing Dependencies\"\n    cd /opt/ownfoil\n    $STD source .venv/bin/activate\n    $STD uv pip install -r requirements.txt\n    msg_ok \"Installed Dependencies\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/ownfoil_data_backup /opt/ownfoil/app/config\n    rm -rf /opt/ownfoil_data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start ownfoil\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8465${CL}\"\n"
  },
  {
    "path": "ct/pairdrop.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pairdrop.net/\n\nAPP=\"PairDrop\"\nvar_tags=\"${var_tags:-sharing}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/pairdrop ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"pairdrop\" \"schlagmichdoch/PairDrop\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop pairdrop\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"pairdrop\" \"schlagmichdoch/PairDrop\" \"tarball\"\n\n    msg_info \"Configuring PairDrop\"\n    cd /opt/pairdrop\n    $STD npm install\n    msg_ok \"Configured PairDrop\"\n\n    msg_info \"Starting Service\"\n    systemctl start pairdrop\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/pangolin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pangolin.net/\n\nAPP=\"Pangolin\"\nPANGOLIN_VERSION=\"${PANGOLIN_VERSION:-1.18.3}\"\nvar_tags=\"${var_tags:-proxy}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_tun=\"${var_tun:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/pangolin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies build-essential python3\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"pangolin\" \"fosrl/pangolin\" \"$PANGOLIN_VERSION\" \"Pinned to a tested release because Pangolin's schema changes have repeatedly broken unattended updates. To try a newer version at your own risk, run: 'export PANGOLIN_VERSION=<tag>' and re-run update. If it breaks, please open an issue at https://github.com/community-scripts/ProxmoxVE/issues with the error log.\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop pangolin\n    systemctl stop gerbil\n    msg_info \"Service stopped\"\n\n    msg_info \"Creating backup\"\n    tar -czf /opt/pangolin_config_backup.tar.gz -C /opt/pangolin config\n    if [[ -f /opt/pangolin/config/db/db.sqlite ]]; then\n      cp -a /opt/pangolin/config/db/db.sqlite \\\n        \"/opt/pangolin/config/db/db.sqlite.pre-${PANGOLIN_VERSION}-$(date +%Y%m%d-%H%M%S).bak\"\n    fi\n    msg_ok \"Created backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"pangolin\" \"fosrl/pangolin\" \"tarball\"\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"gerbil\" \"fosrl/gerbil\" \"singlefile\" \"latest\" \"/usr/bin\" \"gerbil_linux_arm64\"\n\n    msg_info \"Updating Pangolin\"\n    cd /opt/pangolin\n    $STD npm ci\n    $STD npm run set:sqlite\n    $STD npm run set:oss\n    rm -rf server/private\n    $STD npm run db:generate\n    $STD npm run build\n    $STD npm run build:cli\n    cp -R .next/standalone ./\n    chmod +x ./dist/cli.mjs\n    cp server/db/names.json ./dist/names.json\n    cp server/db/ios_models.json ./dist/ios_models.json\n    cp server/db/mac_models.json ./dist/mac_models.json\n    msg_ok \"Updated Pangolin\"\n\n    msg_info \"Restoring config\"\n    tar -xzf /opt/pangolin_config_backup.tar.gz -C /opt/pangolin --overwrite\n    rm -f /opt/pangolin_config_backup.tar.gz\n    msg_ok \"Restored config\"\n\n    if ! grep -q '^ExecStartPre=/usr/bin/node dist/migrations.mjs' /etc/systemd/system/pangolin.service 2>/dev/null; then\n      msg_info \"Adding migration step to pangolin.service\"\n      sed -i '/^ExecStart=\\/usr\\/bin\\/node --enable-source-maps dist\\/server.mjs/i ExecStartPre=/usr/bin/node dist/migrations.mjs' /etc/systemd/system/pangolin.service\n      systemctl daemon-reload\n      msg_ok \"Updated pangolin.service\"\n    fi\n\n    msg_info \"Running database migrations\"\n    cd /opt/pangolin\n    ENVIRONMENT=prod $STD node dist/migrations.mjs\n    msg_ok \"Ran database migrations\"\n\n    msg_info \"Updating Badger plugin version\"\n    BADGER_VERSION=$(get_latest_github_release \"fosrl/badger\" \"false\")\n    sed -i \"s/version: \\\"v[0-9.]*\\\"/version: \\\"$BADGER_VERSION\\\"/g\" /opt/pangolin/config/traefik/traefik_config.yml\n    msg_ok \"Updated Badger plugin version\"\n\n    msg_info \"Starting Services\"\n    systemctl start pangolin\n    systemctl start gerbil\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://<YOUR_PANGOLIN_URL>${CL}\"\n"
  },
  {
    "path": "ct/paperless-ai.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/clusterzx/paperless-ai\n\nAPP=\"Paperless-AI\"\nvar_tags=\"${var_tags:-ai;document}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/paperless-ai ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"paperless-ai\" \"clusterzx/paperless-ai\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop paperless-ai paperless-rag\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    cp -r /opt/paperless-ai/data /opt/paperless-ai-data-backup\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"paperless-ai\" \"clusterzx/paperless-ai\" \"tarball\"\n\n    msg_info \"Restoring data\"\n    cp -r /opt/paperless-ai-data-backup/* /opt/paperless-ai/data/\n    rm -rf /opt/paperless-ai-data-backup\n    msg_ok \"Restored data\"\n\n    msg_info \"Updating Paperless-AI\"\n    cd /opt/paperless-ai\n    if [[ ! -d /opt/paperless-ai/venv ]]; then\n      msg_info \"Recreating Python venv\"\n      $STD python3 -m venv /opt/paperless-ai/venv\n    fi\n    source /opt/paperless-ai/venv/bin/activate\n    $STD pip install --upgrade pip\n    $STD pip install --no-cache-dir -r requirements.txt\n    mkdir -p data/chromadb\n    $STD npm ci --only=production\n    msg_ok \"Updated Paperless-AI\"\n\n    msg_info \"Starting Service\"\n    systemctl start paperless-rag\n    sleep 3\n    systemctl start paperless-ai\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/paperless-gpt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/icereed/paperless-gpt\n\nAPP=\"Paperless-GPT\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-3}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-7}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/paperless-gpt ]]; then\n    msg_error \"No Paperless-GPT installation found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"paperless-gpt\" \"icereed/paperless-gpt\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop paperless-gpt\n    msg_ok \"Service Stopped\"\n\n    if should_update_tool \"node\" \"24\"; then\n      NODE_VERSION=\"24\" setup_nodejs\n    fi\n\n    fetch_and_deploy_gh_release \"paperless-gpt\" \"icereed/paperless-gpt\" \"tarball\"\n\n    msg_info \"Updating Paperless-GPT\"\n    cd /opt/paperless-gpt/web-app\n    $STD npm install\n    $STD npm run build\n    cd /opt/paperless-gpt\n    go mod download\n    export CC=musl-gcc\n    CGO_ENABLED=1 go build -tags musl -o /dev/null github.com/mattn/go-sqlite3\n    CGO_ENABLED=1 go build -tags musl -o paperless-gpt .\n    msg_ok \"Updated Paperless-GPT\"\n\n    msg_info \"Starting Service\"\n    systemctl start paperless-gpt\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/paperless-ngx.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.paperless-ngx.com/\n\nAPP=\"Paperless-ngx\"\nvar_tags=\"${var_tags:-document;management}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/paperless ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Check for old data structure and prompt migration (exclude symlinks)\n  if [[ -f /opt/paperless/paperless.conf ]]; then\n    local OLD_DIRS=()\n    [[ -d /opt/paperless/consume && ! -L /opt/paperless/consume ]] && OLD_DIRS+=(\"consume\")\n    [[ -d /opt/paperless/data && ! -L /opt/paperless/data ]] && OLD_DIRS+=(\"data\")\n    [[ -d /opt/paperless/media && ! -L /opt/paperless/media ]] && OLD_DIRS+=(\"media\")\n\n    if [[ ${#OLD_DIRS[@]} -gt 0 ]]; then\n      msg_error \"Old data structure detected in /opt/paperless/\"\n      msg_custom \"📂\" \"Found directories: ${OLD_DIRS[*]}\"\n      echo -e \"\"\n      msg_custom \"🔄\" \"Migration required to new data structure (/opt/paperless_data/)\"\n      msg_custom \"📖\" \"Please follow the migration guide:\"\n      echo -e \"${TAB}${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/9223${CL}\"\n      echo -e \"\"\n      msg_custom \"⚠️\" \"Update aborted. Please migrate your data first.\"\n      exit 253\n    fi\n  fi\n\n  if check_for_gh_release \"paperless\" \"paperless-ngx/paperless-ngx\"; then\n    msg_info \"Stopping all Paperless-ngx Services\"\n    systemctl stop paperless-consumer paperless-webserver paperless-scheduler paperless-task-queue\n    msg_ok \"Stopped all Paperless-ngx Services\"\n\n    if grep -q \"uv run\" /etc/systemd/system/paperless-webserver.service; then\n\n      msg_info \"Backing up configuration\"\n      local BACKUP_DIR=\"/opt/paperless_backup_$$\"\n      mkdir -p \"$BACKUP_DIR\"\n      [[ -f /opt/paperless/paperless.conf ]] && cp /opt/paperless/paperless.conf \"$BACKUP_DIR/\"\n      msg_ok \"Backup completed to $BACKUP_DIR\"\n\n      PYTHON_VERSION=\"3.13\" setup_uv\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"paperless\" \"paperless-ngx/paperless-ngx\" \"prebuild\" \"latest\" \"/opt/paperless\" \"paperless*tar.xz\"\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"jbig2enc\" \"ie13/jbig2enc\" \"tarball\" \"latest\" \"/opt/jbig2enc\"\n\n      . /etc/os-release\n      if [ \"$VERSION_CODENAME\" = \"bookworm\" ]; then\n        setup_gs\n      else\n        ensure_dependencies ghostscript\n      fi\n\n      msg_info \"Updating Paperless-ngx\"\n      cp -r \"$BACKUP_DIR\"/* /opt/paperless/\n      cd /opt/paperless\n      $STD uv sync --all-extras\n      cd /opt/paperless/src\n      $STD uv run -- python manage.py migrate\n      msg_ok \"Updated Paperless-ngx\"\n\n      rm -rf \"$BACKUP_DIR\"\n\n    else\n      msg_warn \"You are about to migrate your Paperless-ngx installation to uv!\"\n      msg_custom \"🔒\" \"It is strongly recommended to take a Proxmox snapshot first:\"\n      echo -e \"   1. Stop the container:  pct stop <CTID>\"\n      echo -e \"   2. Create a snapshot:  pct snapshot <CTID> pre-paperless-uv-migration\"\n      echo -e \"   3. Start the container again\\n\"\n\n      read -rp \"Have you created a snapshot? [y/N]: \" confirm\n      if [[ ! \"$confirm\" =~ ^([yY]|[yY][eE][sS])$ ]]; then\n        msg_error \"Migration aborted. Please create a snapshot first.\"\n        exit\n      fi\n      msg_info \"Migrating old Paperless-ngx installation to uv\"\n      rm -rf /opt/paperless/venv\n      find /opt/paperless -name \"__pycache__\" -type d -exec rm -rf {} +\n\n      msg_info \"Backing up configuration\"\n      local BACKUP_DIR=\"/opt/paperless_backup_$$\"\n      mkdir -p \"$BACKUP_DIR\"\n      [[ -f /opt/paperless/paperless.conf ]] && cp /opt/paperless/paperless.conf \"$BACKUP_DIR/\"\n      msg_ok \"Backup completed to $BACKUP_DIR\"\n\n      declare -A PATCHES=(\n        [\"paperless-consumer.service\"]=\"ExecStart=uv run -- python manage.py document_consumer\"\n        [\"paperless-scheduler.service\"]=\"ExecStart=uv run -- celery --app paperless beat --loglevel INFO\"\n        [\"paperless-task-queue.service\"]=\"ExecStart=uv run -- celery --app paperless worker --loglevel INFO\"\n        [\"paperless-webserver.service\"]=\"ExecStart=uv run -- granian --interface asgi --ws \\\"paperless.asgi:application\\\"\"\n      )\n\n      for svc in \"${!PATCHES[@]}\"; do\n        path=$(systemctl show -p FragmentPath \"$svc\" | cut -d= -f2)\n        if [[ -n \"$path\" && -f \"$path\" ]]; then\n          sed -i \"s|^ExecStart=.*|${PATCHES[$svc]}|\" \"$path\"\n          if [[ \"$svc\" == \"paperless-webserver.service\" ]]; then\n            grep -q \"^Environment=GRANIAN_HOST=\" \"$path\" ||\n              sed -i '/^\\[Service\\]/a Environment=GRANIAN_HOST=::' \"$path\"\n            grep -q \"^Environment=GRANIAN_PORT=\" \"$path\" ||\n              sed -i '/^\\[Service\\]/a Environment=GRANIAN_PORT=8000' \"$path\"\n            grep -q \"^Environment=GRANIAN_WORKERS=\" \"$path\" ||\n              sed -i '/^\\[Service\\]/a Environment=GRANIAN_WORKERS=1' \"$path\"\n          fi\n          msg_ok \"Patched $svc\"\n        else\n          msg_error \"Service file for $svc not found!\"\n        fi\n      done\n\n      $STD systemctl daemon-reload\n      msg_info \"Backing up configuration\"\n      BACKUP_DIR=\"/opt/paperless_backup_$$\"\n      mkdir -p \"$BACKUP_DIR\"\n      [[ -f /opt/paperless/paperless.conf ]] && cp /opt/paperless/paperless.conf \"$BACKUP_DIR/\"\n      msg_ok \"Backup completed to $BACKUP_DIR\"\n\n      PYTHON_VERSION=\"3.13\" setup_uv\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"paperless\" \"paperless-ngx/paperless-ngx\" \"prebuild\" \"latest\" \"/opt/paperless\" \"paperless*tar.xz\"\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"jbig2enc\" \"ie13/jbig2enc\" \"tarball\" \"latest\" \"/opt/jbig2enc\"\n\n      . /etc/os-release\n      if [ \"$VERSION_CODENAME\" = \"bookworm\" ]; then\n        setup_gs\n      else\n        msg_info \"Installing Ghostscript\"\n        ensure_dependencies ghostscript\n        msg_ok \"Installed Ghostscript\"\n      fi\n\n      msg_info \"Updating Paperless-ngx\"\n      cp -r \"$BACKUP_DIR\"/* /opt/paperless/\n      cd /opt/paperless\n      $STD uv sync --all-extras\n      cd /opt/paperless/src\n      $STD uv run -- python manage.py migrate\n      msg_ok \"Paperless-ngx migration and update completed\"\n\n      rm -rf \"$BACKUP_DIR\"\n      if [[ -d /opt/paperless/backup ]]; then\n        rm -rf /opt/paperless/backup\n        msg_ok \"Removed old backup directory\"\n      fi\n    fi\n\n    setup_nltk \"snowball_data stopwords punkt_tab\" \"/usr/share/nltk_data\"\n\n    msg_info \"Starting all Paperless-ngx Services\"\n    systemctl start paperless-consumer paperless-webserver paperless-scheduler paperless-task-queue\n    sleep 1\n    msg_ok \"Started all Paperless-ngx Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/papra.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/papra-hq/papra\n\nAPP=\"Papra\"\nvar_tags=\"${var_tags:-document-management}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/papra ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"papra\" \"papra-hq/papra\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop papra\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    if [[ -f /opt/papra/apps/papra-server/.env ]]; then\n      cp /opt/papra/apps/papra-server/.env /opt/papra_env.bak\n    fi\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"papra\" \"papra-hq/papra\" \"tarball\"\n\n    msg_info \"Building Application\"\n    cd /opt/papra\n    if [[ -f /opt/papra_env.bak ]]; then\n      cp /opt/papra_env.bak /opt/papra/apps/papra-server/.env\n    else\n      msg_warn \".env missing, regenerating from defaults\"\n      LOCAL_IP=$(hostname -I | awk '{print $1}')\n      cat <<EOF >/opt/papra/apps/papra-server/.env\nNODE_ENV=production\nSERVER_SERVE_PUBLIC_DIR=true\nPORT=1221\nDATABASE_URL=file:/opt/papra_data/db/db.sqlite\nDOCUMENT_STORAGE_FILESYSTEM_ROOT=/opt/papra_data/documents\nPAPRA_CONFIG_DIR=/opt/papra_data\nAUTH_SECRET=$(cat /opt/papra_data/.secret)\nBETTER_AUTH_SECRET=$(cat /opt/papra_data/.secret)\nBETTER_AUTH_TELEMETRY=0\nCLIENT_BASE_URL=http://${LOCAL_IP}:1221\nSERVER_BASE_URL=http://${LOCAL_IP}:1221\nEMAILS_DRY_RUN=true\nINGESTION_FOLDER_IS_ENABLED=true\nINGESTION_FOLDER_ROOT_PATH=/opt/papra_data/ingestion\nEOF\n    fi\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm --filter \"@papra/app-client...\" run build\n    $STD pnpm --filter \"@papra/app-server...\" run build\n    ln -sf /opt/papra/apps/papra-client/dist /opt/papra/apps/papra-server/public\n    rm -f /opt/papra_env.bak\n    msg_ok \"Built Application\"\n\n    msg_info \"Starting Service\"\n    systemctl start papra\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1221${CL}\"\n"
  },
  {
    "path": "ct/part-db.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.part-db.de/\n\nAPP=\"Part-DB\"\nvar_tags=\"${var_tags:-inventory;parts}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/partdb ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"partdb\" \"Part-DB/Part-DB-server\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    mv /opt/partdb/ /opt/partdb-backup\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"partdb\" \"Part-DB/Part-DB-server\" \"prebuild\" \"latest\" \"/opt/partdb\" \"partdb_with_assets.zip\"\n\n    msg_info \"Updating Part-DB\"\n    cd /opt/partdb/\n    cp -r /opt/partdb-backup/.env.local /opt/partdb/\n    cp -r /opt/partdb-backup/public/media /opt/partdb/public/\n    cp -r /opt/partdb-backup/config/banner.md /opt/partdb/config/\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-dev -o --no-interaction\n    $STD php bin/console cache:clear\n    $STD php bin/console doctrine:migrations:migrate -n\n    chown -R www-data:www-data /opt/partdb\n    rm -r /opt/partdb-backup\n    msg_ok \"Updated Part-DB\"\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/passbolt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.passbolt.com/\n\nAPP=\"Passbolt\"\nvar_tags=\"${var_tags:-auth}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated $APP LXC\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/patchmon.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/PatchMon/PatchMon\n\nAPP=\"PatchMon\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/patchmon\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"PatchMon\" \"PatchMon/PatchMon\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop patchmon-server\n    msg_ok \"Stopped Service\"\n\n    if [[ -d /opt/patchmon/backend ]]; then\n      msg_info \"Legacy install detected - creating full backup, please wait...\"\n      $STD tar czf ~/patchmon_legacy.tar.gz /opt/patchmon\n      cp /opt/patchmon/backend/.env /opt/legacy.env\n      msg_ok \"Full backup saved in /root\"\n      msg_info \"Starting migration to PatchMon v2.x.x\"\n      systemctl disable -q --now nginx\n      $STD npm cache clean --force\n      $STD apt autoremove --purge -y {nginx,nodejs}\n      if [[ -f /etc/apt/sources.list.d/nodesource.sources ]]; then\n        cp /etc/apt/sources.list.d/nodesource.sources /etc/apt/sources.list.d/nodesource.sources.bak\n        rm -f /etc/apt/sources.list.d/nodesource.sources\n      elif [[ -f /etc/apt/sources.list.d/nodesource.list ]]; then\n        cp /etc/apt/sources.list.d/nodesource.list /etc/apt/sources.list.d/nodesource.list.bak\n        rm -f /etc/apt/sources.list.d/nodesource.list\n      fi\n      rm -rf /opt/patchmon\n      mkdir -p /opt/patchmon/agents\n      cp /opt/legacy.env /opt/patchmon/.env\n      sed -i -e 's/^PORT=.*/PORT=3000/' \\\n        -e 's/^NODE_/APP_/' \\\n        -e '/^SERVER_*/d' \\\n        -e '/^# API*/,+2d' /opt/patchmon/.env\n      {\n        echo \"\"\n        echo \"SESSION_SECRET=$(openssl rand -hex 64)\"\n        echo \"AI_ENCRYPTION_KEY=$(openssl rand -hex 64)\"\n        echo \"AGENT_BINARIES_DIR=/opt/patchmon/agents\"\n      } >>/opt/patchmon/.env\n      sed -i -e '\\|Directory|s|/backend||' \\\n        -e 's|^ExecStart=.*|ExecStart=/opt/patchmon/patchmon-server|' \\\n        -e 's|^Environment=NODE_.*|EnvironmentFile=/opt/patchmon/.env|' \\\n        /etc/systemd/system/patchmon-server.service\n      systemctl daemon-reload\n      rm /opt/legacy.env\n      msg_ok \"Migration complete!\"\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"PatchMon\" \"PatchMon/PatchMon\" \"singlefile\" \"latest\" \"/opt/patchmon\" \"patchmon-server-linux-amd64\"\n    mv /opt/patchmon/PatchMon /opt/patchmon/patchmon-server\n\n    msg_info \"Fetching PatchMon agent binaries\"\n    RELEASE=$(get_latest_github_release \"PatchMon/PatchMon\")\n    [[ ! -d /opt/patchmon/agents ]] && mkdir -p /opt/patchmon/agents\n    FILE_URL=\"https://github.com/PatchMon/PatchMon/releases/download/v${RELEASE}/patchmon-agent-\"\n    AGENT_NAME=(\n      \"linux-amd64\"\n      \"linux-arm64\"\n      \"linux-arm\"\n      \"linux-386\"\n      \"freebsd-amd64\"\n      \"freebsd-arm64\"\n      \"freebsd-arm\"\n      \"freebsd-386\"\n      \"windows-amd64.exe\"\n      \"windows-arm64.exe\"\n    )\n    for arch in \"${AGENT_NAME[@]}\"; do\n      curl_with_retry \"${FILE_URL}${arch}\" \"/opt/patchmon/agents/patchmon-agent-${arch}\"\n      [[ \"${arch}\" != *.exe ]] && chmod 755 \"/opt/patchmon/agents/patchmon-agent-${arch}\"\n    done\n    msg_ok \"Fetched PatchMon agent binaries\"\n\n    msg_info \"Starting Service\"\n    systemctl start patchmon-server\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/paymenter.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.paymenter.org\n\nAPP=\"Paymenter\"\nvar_tags=\"${var_tags:-hosting;ecommerce;marketplace;}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/paymenter ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n\n  CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)\n  if [[ \"$CURRENT_PHP\" != \"8.3\" ]]; then\n    PHP_VERSION=\"8.3\" PHP_FPM=\"YES\" setup_php\n    setup_composer\n    sed -i 's|php8\\.2-fpm\\.sock|php8.3-fpm.sock|g' /etc/nginx/sites-available/paymenter.conf\n    $STD systemctl reload nginx\n  fi\n\n  if check_for_gh_release \"paymenter\" \"paymenter/paymenter\"; then\n    msg_info \"Updating ${APP}\"\n    cd /opt/paymenter\n    $STD php artisan app:upgrade --no-interaction\n    echo \"${CHECK_UPDATE_RELEASE}\" >~/.paymenter\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}\"\n"
  },
  {
    "path": "ct/peanut.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Brandawg93/PeaNUT/\n\nAPP=\"PeaNUT\"\nvar_tags=\"${var_tags:-network;ups}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-7}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/peanut.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\n\n  if check_for_gh_release \"PeaNUT\" \"Brandawg93/PeaNUT\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop peanut\n    msg_info \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"PeaNUT\" \"Brandawg93/PeaNUT\" \"tarball\" \"latest\" \"/opt/peanut\"\n\n    if ! grep -q '/opt/peanut/entrypoint.mjs' /etc/systemd/system/peanut.service; then\n      msg_info \"Fixing entrypoint\"\n      cd /opt/peanut\n      sed -i 's|/opt/peanut/.next/standalone/server.js|/opt/peanut/entrypoint.mjs|' /etc/systemd/system/peanut.service\n      systemctl daemon-reload\n      msg_ok \"Fixed entrypoint\"\n    fi\n\n    if [[ ! -f /etc/peanut/peanut.env ]]; then\n      msg_info \"Migrating service to EnvironmentFile\"\n      mkdir -p /etc/peanut\n      cat <<EOF >/etc/peanut/peanut.env\nNODE_ENV=production\n\n#WEB_HOST=0.0.0.0\n#WEB_PORT=8080\n#NUT_HOST=localhost\n#NUT_PORT=3493\n\n# Disable auth entirely:\n#AUTH_DISABLED=true\n\n# Bootstrap initial account on first start (ignored afterwards):\n#WEB_USERNAME=admin\n#WEB_PASSWORD=changeme\nEOF\n      chmod 600 /etc/peanut/peanut.env\n      sed -i '/^Environment=/d' /etc/systemd/system/peanut.service\n      if ! grep -q '^EnvironmentFile=/etc/peanut/peanut.env' /etc/systemd/system/peanut.service; then\n        sed -i '/^Type=simple/a EnvironmentFile=/etc/peanut/peanut.env' /etc/systemd/system/peanut.service\n      fi\n      systemctl daemon-reload\n      msg_ok \"Migrated to /etc/peanut/peanut.env\"\n    fi\n\n    msg_info \"Updating PeaNUT\"\n    cd /opt/peanut\n    $STD pnpm i\n    $STD pnpm run build:local\n    cp -r .next/static .next/standalone/.next/\n    mkdir -p /opt/peanut/.next/standalone/config\n    ln -sf /etc/peanut/settings.yml /opt/peanut/.next/standalone/config/settings.yml\n    ln -sf .next/standalone/server.js server.js\n    msg_ok \"Updated PeaNUT\"\n\n    msg_info \"Starting Service\"\n    systemctl start peanut\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/pelican-panel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pelican-dev/panel\n\nAPP=\"Pelican-Panel\"\nvar_tags=\"${var_tags:-Gaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/pelican-panel ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_mariadb\n  CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)\n  setup_composer\n\n  if [[ \"$CURRENT_PHP\" != \"8.4\" ]]; then\n    msg_info \"Migrating PHP $CURRENT_PHP to 8.4\"\n    $STD apt remove -y php\"${CURRENT_PHP//./}\"*\n    PHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" setup_php\n    msg_ok \"Migrated PHP $CURRENT_PHP to 8.4\"\n  fi\n\n  if check_for_gh_release \"pelican-panel\" \"pelican-dev/panel\"; then\n    msg_info \"Stopping Service\"\n    cd /opt/pelican-panel\n    $STD php artisan down\n    msg_ok \"Stopped Service\"\n\n    mkdir -p /opt/backup\n    cp -a /opt/pelican-panel/.env /opt/backup\n    mkdir -p /opt/backup/storage/app/\n    cp -a /opt/pelican-panel/storage/app/public /opt/backup/storage/app/\n    \n    SQLITE_INSTALL=$(ls /opt/pelican-panel/database/*.sqlite 1>/dev/null 2>&1 && echo \"true\" || echo \"false\")\n    $SQLITE_INSTALL && cp -r /opt/pelican-panel/database/*.sqlite /opt/backup\n    \n    find /opt/pelican-panel -mindepth 1 -maxdepth 1 ! -name 'backup' ! -name 'plugins' -exec rm -rf {} +\n    \n    fetch_and_deploy_gh_release \"pelican-panel\" \"pelican-dev/panel\" \"prebuild\" \"latest\" \"/opt/pelican-panel\" \"panel.tar.gz\"\n\n    msg_info \"Updating Pelican Panel\"\n    cp -a /opt/backup/.env /opt/pelican-panel/\n    $SQLITE_INSTALL && mv /opt/backup/*.sqlite /opt/pelican-panel/database/\n    cp -a /opt/backup/storage/app/public /opt/pelican-panel/storage/app/\n\n    $STD composer install --no-dev --optimize-autoloader --no-interaction\n    $STD php artisan p:environment:setup\n    $STD php artisan view:clear\n    $STD php artisan config:clear\n    $STD php artisan filament:optimize\n    $STD php artisan migrate --seed --force\n    chown -R www-data:www-data /opt/pelican-panel\n    chmod -R 755 /opt/pelican-panel/storage /opt/pelican-panel/bootstrap/cache/\n    msg_ok \"Updated Pelican Panel\"\n\n    msg_info \"Starting Service\"\n    $STD php artisan queue:restart\n    $STD php artisan up\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/installer${CL}\"\n"
  },
  {
    "path": "ct/pelican-wings.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pelican-dev/wings\n\nAPP=\"Pelican-Wings\"\nvar_tags=\"${var_tags:-Gaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/bin/wings ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"wings\" \"pelican-dev/wings\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop wings\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"wings\" \"pelican-dev/wings\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"wings_linux_arm64\"\n\n    msg_info \"Starting Service\"\n    systemctl start wings\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/photoprism.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.photoprism.app/\n\nAPP=\"PhotoPrism\"\nvar_tags=\"${var_tags:-media;photo}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/photoprism ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"photoprism\" \"photoprism/photoprism\"; then\n    msg_info \"Stopping PhotoPrism\"\n    systemctl stop photoprism\n    msg_ok \"Stopped PhotoPrism\"\n\n    if ! grep -q \"photoprism/config/.env\" ~/.bashrc 2>/dev/null; then\n      msg_info \"Adding environment export for CLI tools\"\n      echo '# Load PhotoPrism environment variables for CLI tools' >>~/.bashrc\n      echo 'export $(grep -v \"^#\" /opt/photoprism/config/.env | xargs)' >>~/.bashrc\n      msg_ok \"Added environment export\"\n    fi\n\n    fetch_and_deploy_gh_release \"photoprism\" \"photoprism/photoprism\" \"prebuild\" \"latest\" \"/opt/photoprism\" \"*linux-arm64.tar.gz\"\n\n    LIBHEIF_URL=$(curl -fsSL \"https://dl.photoprism.app/dist/libheif/\" | grep -oP \"libheif-bookworm-arm64-v[0-9\\.]+\\.tar\\.gz\" | sort -V | tail -n 1)\n    if [[ \"${LIBHEIF_URL}\" != \"$(cat ~/.photoprism_libheif 2>/dev/null)\" ]] || [[ ! -f ~/.photoprism_libheif ]]; then\n      msg_info \"Updating PhotoPrism LibHeif\"\n      ensure_dependencies libvips42\n      curl -fsSL \"https://dl.photoprism.app/dist/libheif/$LIBHEIF_URL\" -o /tmp/libheif.tar.gz\n      tar -xzf /tmp/libheif.tar.gz -C /usr/local\n      ldconfig\n      echo \"${LIBHEIF_URL}\" >~/.photoprism_libheif\n      msg_ok \"Updated PhotoPrism LibHeif\"\n    fi\n\n    msg_info \"Starting PhotoPrism\"\n    systemctl start photoprism\n    msg_ok \"Started PhotoPrism\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:2342${CL}\"\n"
  },
  {
    "path": "ct/pialert.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/leiweibau/Pi.Alert/\n\nAPP=\"PiAlert\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/pialert ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating PiAlert\"\n  bash -c \"$(curl -fsSL https://github.com/leiweibau/Pi.Alert/raw/main/install/pialert_update.sh)\" -s --lxc\n  msg_ok \"Updated PiAlert\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/pialert${CL}\"\n"
  },
  {
    "path": "ct/pihole.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pi-hole.net/\n\nAPP=\"Pihole\"\nvar_tags=\"${var_tags:-adblock}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /etc/pihole ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating PiHole\"\n    set +e\n    $STD apt update\n    $STD apt upgrade -y\n    /usr/local/bin/pihole -up\n    msg_ok \"Updated PiHole\"\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/admin${CL}\"\n"
  },
  {
    "path": "ct/planka.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/plankanban/planka\n\nAPP=\"PLANKA\"\nvar_tags=\"${var_tags:-Todo;kanban}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/planka.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"planka\" \"plankanban/planka\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop planka\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    BK=\"/opt/planka-backup\"\n    mkdir -p \"$BK\"/{favicons,user-avatars,background-images,attachments}\n    [ -f /opt/planka/.env ] && mv /opt/planka/.env \"$BK\"/\n    # Support both old (pre-v2) and new (v2) directory layouts\n    if [ -d /opt/planka/data/protected ]; then\n      [ -d /opt/planka/data/protected/favicons ] && cp -a /opt/planka/data/protected/favicons/. \"$BK/favicons/\"\n      [ -d /opt/planka/data/protected/user-avatars ] && cp -a /opt/planka/data/protected/user-avatars/. \"$BK/user-avatars/\"\n      [ -d /opt/planka/data/protected/background-images ] && cp -a /opt/planka/data/protected/background-images/. \"$BK/background-images/\"\n      [ -d /opt/planka/data/private/attachments ] && cp -a /opt/planka/data/private/attachments/. \"$BK/attachments/\"\n    else\n      [ -d /opt/planka/public/favicons ] && cp -a /opt/planka/public/favicons/. \"$BK/favicons/\"\n      [ -d /opt/planka/public/user-avatars ] && cp -a /opt/planka/public/user-avatars/. \"$BK/user-avatars/\"\n      [ -d /opt/planka/public/background-images ] && cp -a /opt/planka/public/background-images/. \"$BK/background-images/\"\n      [ -d /opt/planka/private/attachments ] && cp -a /opt/planka/private/attachments/. \"$BK/attachments/\"\n    fi\n    rm -rf /opt/planka\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"planka\" \"plankanban/planka\" \"prebuild\" \"latest\" \"/opt/planka\" \"planka-prebuild.zip\"\n\n    msg_info \"Update Frontend\"\n    cd /opt/planka\n    $STD npm install\n    msg_ok \"Updated Frontend\"\n\n    msg_info \"Restoring data\"\n    [ -f \"$BK/.env\" ] && mv \"$BK/.env\" /opt/planka/.env\n    # Planka v2 uses unified data directory structure\n    mkdir -p /opt/planka/data/protected/{favicons,user-avatars,background-images} /opt/planka/data/private/attachments\n    [ -d \"$BK/favicons\" ] && cp -a \"$BK/favicons/.\" /opt/planka/data/protected/favicons/\n    [ -d \"$BK/user-avatars\" ] && cp -a \"$BK/user-avatars/.\" /opt/planka/data/protected/user-avatars/\n    [ -d \"$BK/background-images\" ] && cp -a \"$BK/background-images/.\" /opt/planka/data/protected/background-images/\n    [ -d \"$BK/attachments\" ] && cp -a \"$BK/attachments/.\" /opt/planka/data/private/attachments/\n    rm -rf \"$BK\"\n    msg_ok \"Restored data\"\n\n    msg_info \"Migrate Database\"\n    cd /opt/planka\n    $STD npm run db:upgrade\n    $STD npm run db:migrate\n    msg_ok \"Migrated Database\"\n\n    msg_info \"Starting Service\"\n    systemctl start planka\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1337${CL}\"\n"
  },
  {
    "path": "ct/plant-it.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://plant-it.org/\n\nAPP=\"Plant-it\"\nvar_tags=\"${var_tags:-plants;garden}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  RELEASE=\"0.10.0\"\n  if [[ ! -d /opt/plant-it ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"plant-it\" \"MDeLuise/plant-it\" \"${RELEASE}\" \"last version that includes the web frontend\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop plant-it\n    msg_info \"Stopped Service\"\n\n    USE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"plant-it\" \"MDeLuise/plant-it\" \"singlefile\" \"${RELEASE}\" \"/opt/plant-it/backend\" \"server.jar\"\n    fetch_and_deploy_gh_release \"plant-it-front\" \"MDeLuise/plant-it\" \"prebuild\" \"${RELEASE}\" \"/opt/plant-it/frontend\" \"client.tar.gz\"\n    msg_warn \"Application is updated to latest Web version (v0.10.0). There will be no more updates available.\"\n    msg_warn \"Please read: https://github.com/MDeLuise/plant-it/releases/tag/1.0.0\"\n\n    msg_info \"Starting Service\"\n    systemctl start plant-it\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/plex.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.plex.tv/\n\nAPP=\"Plex\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if ! dpkg -l plexmediaserver &>/dev/null; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Migrate from old repository to new one if needed\n  if [[ -f /etc/apt/sources.list.d/plexmediaserver.sources ]]; then\n    local current_uri\n    current_uri=$(grep -oP '(?<=URIs: ).*' /etc/apt/sources.list.d/plexmediaserver.sources 2>/dev/null || true)\n    if [[ \"$current_uri\" == *\"downloads.plex.tv/repo/deb\"* ]]; then\n      msg_info \"Migrating to new Plex repository\"\n      rm -f /etc/apt/sources.list.d/plexmediaserver.sources\n      rm -f /usr/share/keyrings/PlexSign.asc\n      setup_deb822_repo \\\n        \"plexmediaserver\" \\\n        \"https://downloads.plex.tv/plex-keys/PlexSign.v2.key\" \\\n        \"https://repo.plex.tv/deb/\" \\\n        \"public\" \\\n        \"main\"\n      msg_ok \"Migrated to new Plex repository\"\n    fi\n  elif compgen -G \"/etc/apt/sources.list.d/plex*.list\" >/dev/null; then\n    msg_info \"Migrating to new Plex repository (deb822)\"\n    rm -f /etc/apt/sources.list.d/plex*.list\n    rm -f /usr/share/keyrings/PlexSign.asc\n    rm -f /usr/share/keyrings/plexmediaserver.v2.gpg\n    setup_deb822_repo \\\n      \"plexmediaserver\" \\\n      \"https://downloads.plex.tv/plex-keys/PlexSign.v2.key\" \\\n      \"https://repo.plex.tv/deb/\" \\\n      \"public\" \\\n      \"main\"\n    msg_ok \"Migrated to new Plex repository (deb822)\"\n  elif [[ ! -f /etc/apt/sources.list.d/plexmediaserver.sources ]]; then\n    msg_info \"Setting up Plex repository\"\n    setup_deb822_repo \\\n      \"plexmediaserver\" \\\n      \"https://downloads.plex.tv/plex-keys/PlexSign.v2.key\" \\\n      \"https://repo.plex.tv/deb/\" \\\n      \"public\" \\\n      \"main\"\n    msg_ok \"Set up Plex repository\"\n  fi\n  if [[ -f /usr/local/bin/plexupdate ]] || [[ -d /opt/plexupdate ]]; then\n    msg_info \"Removing legacy plexupdate\"\n    rm -rf /opt/plexupdate /usr/local/bin/plexupdate\n    crontab -l 2>/dev/null | grep -v plexupdate | crontab - 2>/dev/null || true\n    msg_ok \"Removed legacy plexupdate\"\n  fi\n\n  msg_info \"Updating Plex Media Server\"\n  $STD apt update\n  $STD apt install -y plexmediaserver\n  msg_ok \"Updated Plex Media Server\"\n\n  msg_info \"Restarting Plex Media Server\"\n  systemctl restart plexmediaserver\n  msg_ok \"Restarted Plex Media Server\"\n\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:32400/web${CL}\"\n"
  },
  {
    "path": "ct/pocketbase.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pocketbase.io/\n\nAPP=\"Pocketbase\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/pocketbase.service || ! -x /opt/pocketbase/pocketbase ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"pocketbase\" \"pocketbase/pocketbase\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop pocketbase\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP}\"\n    /opt/pocketbase/pocketbase update\n    echo \"${CHECK_UPDATE_RELEASE}\" >~/.pocketbase\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start pocketbase\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/_/${CL}\"\n"
  },
  {
    "path": "ct/pocketid.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Snarkenfaugister\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pocket-id/pocket-id\n\nAPP=\"PocketID\"\nvar_tags=\"${var_tags:-identity-provider}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/pocket-id ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Mandatory as of v2.x.x\n  ENCRYPTION_KEY=$(openssl rand -base64 32)\n  if ! grep -q '^ENCRYPTION_KEY=' /opt/pocket-id/.env; then\n    echo \"ENCRYPTION_KEY=$ENCRYPTION_KEY\" >> /opt/pocket-id/.env\n  fi\n\n  if check_for_gh_release \"pocket-id\" \"pocket-id/pocket-id\"; then\n    if [ \"$(printf '%s\\n%s' \"$(cat ~/.pocket-id 2>/dev/null || echo 0.0.0)\" \"1.0.0\" | sort -V | head -n1)\" = \"$(cat ~/.pocket-id 2>/dev/null || echo 0.0.0)\" ] &&\n      [ \"$(cat ~/.pocket-id 2>/dev/null || echo 0.0.0)\" != \"1.0.0\" ]; then\n      msg_info \"Migrating ${APP}\"\n      systemctl -q disable --now pocketid-backend pocketid-frontend caddy\n      mv /etc/caddy/Caddyfile ~/Caddyfile.bak\n      $STD apt remove --purge caddy nodejs -y\n      $STD apt autoremove -y\n      rm /etc/apt/{keyrings/nodesource.gpg,sources.list.d/nodesource.list}\n      rm -r /usr/local/go\n      cp -r /opt/pocket-id/backend/data /opt/data\n      cp /opt/pocket-id/backend/.env /opt/env\n      sed -i -e 's/PUBLIC_//g' \\\n        -e '/^SQLITE_DB_PATH/d' \\\n        -e '/^POSTGRES/s/^/# /' \\\n        -e '/^UPLOAD_PATH/d' \\\n        -e 's/8080/1411/' /opt/env\n      rm -r /opt/pocket-id\n      rm /etc/systemd/system/pocketid-frontend.service\n      BACKEND=\"/etc/systemd/system/pocketid-backend.service\"\n      sed -i -e 's/Backend/Service/' \\\n        -e 's/\\/backend\\|-backend//g' \"$BACKEND\"\n      mv \"$BACKEND\" ${BACKEND//-backend/}\n      systemctl daemon-reload\n      systemctl -q enable pocketid\n      mkdir /opt/pocket-id\n      mv /opt/data /opt/pocket-id\n      msg_ok \"Migration complete. The reverse proxy port has been changed to 1411.\"\n    else\n      msg_info \"Stopping Service\"\n      systemctl stop pocketid\n      msg_ok \"Stopped Service\"\n      cp /opt/pocket-id/.env /opt/env\n    fi\n\n    fetch_and_deploy_gh_release \"pocket-id\" \"pocket-id/pocket-id\" \"singlefile\" \"latest\" \"/opt/pocket-id/\" \"pocket-id-linux-arm64\"\n    mv /opt/env /opt/pocket-id/.env\n\n    msg_info \"Starting Service\"\n    systemctl start pocketid\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Configure your reverse proxy to point to:${BGN} ${IP}:1411${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://{PUBLIC_URL}/setup${CL}\"\n"
  },
  {
    "path": "ct/podman-homeassistant.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.home-assistant.io/\n\nAPP=\"Podman-Home Assistant\"\nvar_tags=\"${var_tags:-podman;smarthome}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/containers/systemd/homeassistant.container ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  UPD=$(msg_menu \"Home Assistant Update Options\" \\\n    \"1\" \"Update system and containers\" \\\n    \"2\" \"Install HACS\" \\\n    \"3\" \"Install FileBrowser\" \\\n    \"4\" \"Remove ALL Unused Images\")\n\n  if [ \"$UPD\" == \"1\" ]; then\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated successfully!\"\n\n    msg_info \"Updating All Containers\\n\"\n    CONTAINER_LIST=\"${1:-$(podman ps -q)}\"\n    for container in ${CONTAINER_LIST}; do\n      CONTAINER_IMAGE=\"$(podman inspect --format \"{{.Config.Image}}\" --type container ${container})\"\n      RUNNING_IMAGE=\"$(podman inspect --format \"{{.Image}}\" --type container \"${container}\")\"\n      podman pull \"${CONTAINER_IMAGE}\"\n      LATEST_IMAGE=\"$(podman inspect --format \"{{.Id}}\" --type image \"${CONTAINER_IMAGE}\")\"\n      if [[ \"${RUNNING_IMAGE}\" != \"${LATEST_IMAGE}\" ]]; then\n        echo \"Updating ${container} image ${CONTAINER_IMAGE}\"\n        systemctl restart homeassistant\n      fi\n    done\n    msg_ok \"All containers updated.\"\n    exit\n  fi\n  if [ \"$UPD\" == \"2\" ]; then\n    msg_info \"Installing Home Assistant Community Store (HACS)\"\n    $STD apt update\n    cd /var/lib/containers/storage/volumes/hass_config/_data\n    $STD bash <(curl -fsSL https://get.hacs.xyz)\n    msg_ok \"Installed Home Assistant Community Store (HACS)\"\n    echo -e \"\\n Reboot Home Assistant and clear browser cache then Add HACS integration.\\n\"\n    exit\n  fi\n  if [ \"$UPD\" == \"3\" ]; then\n    msg_info \"Installing FileBrowser\"\n    $STD curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash\n    $STD filebrowser config init -a '0.0.0.0'\n    $STD filebrowser config set -a '0.0.0.0'\n    $STD filebrowser users add admin community-scripts.org --perm.admin\n    msg_ok \"Installed FileBrowser\"\n\n    msg_info \"Creating Service\"\n    cat <<EOF >/etc/systemd/system/filebrowser.service\n[Unit]\nDescription=Filebrowser\nAfter=network-online.target\n\n[Service]\nUser=root\nWorkingDirectory=/root/\nExecStart=/usr/local/bin/filebrowser -r /\n\n[Install]\nWantedBy=default.target\nEOF\n    systemctl enable -q --now filebrowser\n    msg_ok \"Created Service\"\n\n    msg_ok \"Completed successfully!\\n\"\n    echo -e \"FileBrowser should be reachable by going to the following URL.\n         ${BL}http://$LOCAL_IP:8080${CL}   admin|community-scripts.org\\n\"\n    exit\n  fi\n  if [ \"$UPD\" == \"4\" ]; then\n    msg_info \"Removing ALL Unused Images\"\n    podman image prune -a -f\n    msg_ok \"Removed ALL Unused Images\"\n    exit\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8123${CL}\"\n"
  },
  {
    "path": "ct/podman.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://podman.io/\n\nAPP=\"Podman\"\nvar_tags=\"${var_tags:-container;kubernetes}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /etc/containers/registries.conf ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt upgrade -y\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/postgresql.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.postgresql.org/\n\nAPP=\"PostgreSQL\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if ! command -v psql >/dev/null 2>&1; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating ${APP} LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:5432${CL}\"\n"
  },
  {
    "path": "ct/powerdns.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.powerdns.com/\n\nAPP=\"PowerDNS\"\nvar_tags=\"${var_tags:-dns}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/poweradmin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating PowerDNS\"\n  $STD apt update\n  $STD apt install -y --only-upgrade pdns-server pdns-backend-sqlite3\n  msg_ok \"Updated PowerDNS\"\n\n  if check_for_gh_release \"poweradmin\" \"poweradmin/poweradmin\"; then\n    msg_info \"Backing up Configuration\"\n    cp /opt/poweradmin/config/settings.php /opt/poweradmin_settings.php.bak\n    cp /opt/poweradmin/powerdns.db /opt/poweradmin_powerdns.db.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"poweradmin\" \"poweradmin/poweradmin\" \"tarball\"\n\n    msg_info \"Updating Poweradmin\"\n    cp /opt/poweradmin_settings.php.bak /opt/poweradmin/config/settings.php\n    cp /opt/poweradmin_powerdns.db.bak /opt/poweradmin/powerdns.db\n    rm -rf /opt/poweradmin/install\n    rm -f /opt/poweradmin_settings.php.bak /opt/poweradmin_powerdns.db.bak\n    chown -R www-data:pdns /opt/poweradmin\n    chmod 775 /opt/poweradmin\n    chown pdns:pdns /opt/poweradmin/powerdns.db\n    chmod 664 /opt/poweradmin/powerdns.db\n    msg_ok \"Updated Poweradmin\"\n\n    msg_info \"Restarting Services\"\n    systemctl restart pdns apache2\n    msg_ok \"Restarted Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/privatebin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://privatebin.info/\n\nAPP=\"PrivateBin\"\nvar_tags=\"${var_tags:-paste;secure}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/privatebin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"privatebin\" \"PrivateBin/PrivateBin\"; then\n    msg_info \"Creating backup\"\n    cp -f /opt/privatebin/cfg/conf.php /tmp/privatebin_conf.bak\n    msg_ok \"Backup created\"\n\n    rm -rf /opt/privatebin/*\n    fetch_and_deploy_gh_release \"privatebin\" \"PrivateBin/PrivateBin\" \"tarball\"\n\n    msg_info \"Configuring ${APP}\"\n    mkdir -p /opt/privatebin/data\n    mv /tmp/privatebin_conf.bak /opt/privatebin/cfg/conf.php\n    chown -R www-data:www-data /opt/privatebin\n    chmod -R 0755 /opt/privatebin/data\n    systemctl reload nginx php8.2-fpm\n    msg_ok \"Configured ${APP}\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}${CL}\"\n"
  },
  {
    "path": "ct/profilarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dictionarry-Hub/profilarr\n\nAPP=\"Profilarr\"\nvar_tags=\"${var_tags:-arr;radarr;sonarr;config}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/profilarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"profilarr\" \"Dictionarry-Hub/profilarr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop profilarr\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    if [[ -d /config ]]; then\n      cp -r /config /opt/profilarr_config_backup\n    fi\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"profilarr\" \"Dictionarry-Hub/profilarr\" \"tarball\"\n\n    msg_info \"Installing Python Dependencies\"\n    cd /opt/profilarr/backend\n    $STD uv venv --clear /opt/profilarr/backend/.venv\n    sed 's/==/>=/g' requirements.txt >requirements-relaxed.txt\n    $STD uv pip install --python /opt/profilarr/backend/.venv/bin/python -r requirements-relaxed.txt\n    rm -f requirements-relaxed.txt\n    msg_ok \"Installed Python Dependencies\"\n\n    msg_info \"Building Frontend\"\n    if [[ -d /opt/profilarr/frontend ]]; then\n      cd /opt/profilarr/frontend\n      $STD npm install\n      $STD npm run build\n      cp -r dist /opt/profilarr/backend/app/static\n    fi\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Restoring Data\"\n    if [[ -d /opt/profilarr_config_backup ]]; then\n      mkdir -p /config\n      cp -r /opt/profilarr_config_backup/. /config/\n      rm -rf /opt/profilarr_config_backup\n    fi\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start profilarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6868${CL}\"\n"
  },
  {
    "path": "ct/projectsend.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.projectsend.org/\n\nAPP=\"ProjectSend\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/projectsend ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n\n  if check_for_gh_release \"projectsend\" \"projectsend/projectsend\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop apache2\n    msg_ok \"Stopped Service\"\n\n    php_ver=$(php -v | head -n 1 | awk '{print $2}')\n    if [[ ! $php_ver == \"8.4\"* ]]; then\n      PHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\n    fi\n\n    mv /opt/projectsend/includes/sys.config.php /opt/sys.config.php\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"projectsend\" \"projectsend/projectsend\" \"prebuild\" \"latest\" \"/opt/projectsend\" \"projectsend-r*.zip\"\n    mv /opt/sys.config.php /opt/projectsend/includes/sys.config.php\n    chown -R www-data:www-data /opt/projectsend\n    chmod -R 775 /opt/projectsend\n\n    msg_info \"Starting Service\"\n    systemctl start apache2\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/install for the initial setup${CL}\"\n"
  },
  {
    "path": "ct/prometheus-alertmanager.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prometheus.io/\n\nAPP=\"Prometheus-Alertmanager\"\nvar_tags=\"${var_tags:-monitoring;alerting}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/prometheus-alertmanager.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"alertmanager\" \"prometheus/alertmanager\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop prometheus-alertmanager\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"alertmanager\" \"prometheus/alertmanager\" \"prebuild\" \"latest\" \"/usr/local/bin/\" \"alertmanager*linux-arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start prometheus-alertmanager\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9093${CL}\"\n"
  },
  {
    "path": "ct/prometheus-blackbox-exporter.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Marfnl\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/prometheus/blackbox_exporter\n\nAPP=\"Prometheus-Blackbox-Exporter\"\nvar_tags=\"${var_tags:-monitoring;prometheus}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/blackbox-exporter ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"blackbox-exporter\" \"prometheus/blackbox_exporter\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop blackbox-exporter\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mv /opt/blackbox-exporter/blackbox.yml /opt\n    msg_ok \"Backup created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"blackbox-exporter\" \"prometheus/blackbox_exporter\" \"prebuild\" \"latest\" \"/opt/blackbox-exporter\" \"blackbox_exporter-*.linux-arm64.tar.gz\"\n\n    msg_info \"Restoring backup\"\n    cp -r /opt/blackbox.yml /opt/blackbox-exporter\n    rm -f /opt/blackbox.yml\n    msg_ok \"Backup restored\"\n\n    msg_info \"Starting Service\"\n    systemctl start blackbox-exporter\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9115${CL}\"\n"
  },
  {
    "path": "ct/prometheus-pve-exporter.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/prometheus-pve/prometheus-pve-exporter\n\nAPP=\"Prometheus-PVE-Exporter\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/prometheus-pve-exporter.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop prometheus-pve-exporter\n  msg_ok \"Stopped Service\"\n\n  export PVE_VENV_PATH=\"/opt/prometheus-pve-exporter/.venv\"\n  export PVE_EXPORTER_BIN=\"${PVE_VENV_PATH}/bin/pve_exporter\"\n\n  if [[ ! -d \"$PVE_VENV_PATH\" || ! -x \"$PVE_EXPORTER_BIN\" ]]; then\n    PYTHON_VERSION=\"3.12\" setup_uv\n    msg_info \"Migrating to uv/venv\"\n    rm -rf \"$PVE_VENV_PATH\"\n    mkdir -p /opt/prometheus-pve-exporter\n    cd /opt/prometheus-pve-exporter\n    $STD uv venv --clear \"$PVE_VENV_PATH\"\n    $STD \"$PVE_VENV_PATH/bin/python\" -m ensurepip --upgrade\n    $STD \"$PVE_VENV_PATH/bin/python\" -m pip install --upgrade pip\n    $STD \"$PVE_VENV_PATH/bin/python\" -m pip install prometheus-pve-exporter\n    msg_ok \"Migrated to uv/venv\"\n  else\n    msg_info \"Updating Prometheus Proxmox VE Exporter\"\n    PYTHON_VERSION=\"3.12\" setup_uv\n    $STD \"$PVE_VENV_PATH/bin/python\" -m pip install --upgrade prometheus-pve-exporter\n    msg_ok \"Updated Prometheus Proxmox VE Exporter\"\n  fi\n  local service_file=\"/etc/systemd/system/prometheus-pve-exporter.service\"\n  if ! grep -q \"${PVE_VENV_PATH}/bin/pve_exporter\" \"$service_file\"; then\n    msg_info \"Updating systemd service\"\n    cat <<EOF >\"$service_file\"\n[Unit]\nDescription=Prometheus Proxmox VE Exporter\nDocumentation=https://github.com/znerol/prometheus-pve-exporter\nAfter=syslog.target network.target\n\n[Service]\nUser=root\nRestart=always\nType=simple\nExecStart=${PVE_VENV_PATH}/bin/pve_exporter \\\\\n    --config.file=/opt/prometheus-pve-exporter/pve.yml \\\\\n    --web.listen-address=0.0.0.0:9221\nExecReload=/bin/kill -HUP \\$MAINPID\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    $STD systemctl daemon-reload\n    msg_ok \"Updated systemd service\"\n  fi\n\n  msg_info \"Starting Service\"\n  systemctl start prometheus-pve-exporter\n  msg_ok \"Started Service\"\n\n  msg_ok \"Updated successfully!\"\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9221${CL}\"\n"
  },
  {
    "path": "ct/prometheus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prometheus.io/\n\nAPP=\"Prometheus\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/prometheus.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"prometheus\" \"prometheus/prometheus\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop prometheus\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"prometheus\" \"prometheus/prometheus\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"*linux-arm64.tar.gz\"\n    rm -f /usr/local/bin/prometheus.yml\n\n    msg_info \"Starting Service\"\n    systemctl start prometheus\n    msg_ok \"Started Service\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9090${CL}\"\n"
  },
  {
    "path": "ct/protonmail-bridge.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Stephen Chin (steveonjava)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ProtonMail/proton-bridge\n\nAPP=\"ProtonMail-Bridge\"\nvar_tags=\"${var_tags:-mail;proton}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -x /usr/bin/protonmail-bridge ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit 1\n  fi\n\n  if check_for_gh_release \"protonmail-bridge\" \"ProtonMail/proton-bridge\"; then\n    local -a bridge_units=(\n      protonmail-bridge\n      protonmail-bridge-imap.socket\n      protonmail-bridge-smtp.socket\n      protonmail-bridge-imap-proxy\n      protonmail-bridge-smtp-proxy\n    )\n    local unit\n    declare -A was_active\n    for unit in \"${bridge_units[@]}\"; do\n      if systemctl is-active --quiet \"$unit\" 2>/dev/null; then\n        was_active[\"$unit\"]=1\n      else\n        was_active[\"$unit\"]=0\n      fi\n    done\n\n    msg_info \"Stopping Services\"\n    systemctl stop protonmail-bridge-imap.socket protonmail-bridge-smtp.socket protonmail-bridge-imap-proxy protonmail-bridge-smtp-proxy protonmail-bridge\n    msg_ok \"Stopped Services\"\n\n    fetch_and_deploy_gh_release \"protonmail-bridge\" \"ProtonMail/proton-bridge\" \"binary\"\n\n    if [[ -f /home/protonbridge/.protonmailbridge-initialized ]]; then\n      msg_info \"Starting Services\"\n      for unit in \"${bridge_units[@]}\"; do\n        if [[ \"${was_active[$unit]:-0}\" == \"1\" ]]; then\n          systemctl start \"$unit\"\n        fi\n      done\n      msg_ok \"Started Services\"\n    else\n      msg_ok \"Initialization not completed. Services remain disabled.\"\n    fi\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW}One-time configuration is required before Bridge services are enabled.${CL}\"\necho -e \"${INFO}${YW}Run this command in the container: protonmailbridge-configure${CL}\"\n"
  },
  {
    "path": "ct/prowlarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prowlarr.com/\n\nAPP=\"Prowlarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/prowlarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"prowlarr\" \"Prowlarr/Prowlarr\"; then\n    apt-get install -y libicu76 &>/dev/null\n    msg_info \"Stopping Service\"\n    systemctl stop prowlarr\n    msg_ok \"Stopped Service\"\n\n    rm -rf /opt/Prowlarr\n    fetch_and_deploy_gh_release \"prowlarr\" \"Prowlarr/Prowlarr\" \"prebuild\" \"latest\" \"/opt/Prowlarr\" \"Prowlarr.master*linux-core-arm64.tar.gz\"\n    chmod 775 /opt/Prowlarr\n\n    msg_info \"Starting Service\"\n    systemctl start prowlarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9696${CL}\"\n"
  },
  {
    "path": "ct/proxmox-backup-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.proxmox.com/en/proxmox-backup-server\n\nAPP=\"Proxmox-Backup-Server\"\nvar_tags=\"${var_tags:-backup}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -e /usr/sbin/proxmox-backup-manager ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8007${CL}\"\n"
  },
  {
    "path": "ct/proxmox-datacenter-manager.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: Proxmox Server Solution GmbH\n\nAPP=\"Proxmox-Datacenter-Manager\"\nvar_tags=\"${var_tags:-datacenter}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -e /usr/sbin/proxmox-datacenter-manager-admin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if grep -q 'Debian GNU/Linux 12' /etc/os-release && [ -f /etc/apt/sources.list.d/proxmox-release-bookworm.list ] && [ -f /etc/apt/sources.list.d/pdm-test.list ]; then\n    msg_info \"Updating outdated outdated source formats\"\n    echo \"deb [signed-by=/usr/share/keyrings/proxmox-archive-keyring.gpg] http://download.proxmox.com/debian/pdm bookworm pdm-test\" >/etc/apt/sources.list.d/pdm-test.list\n    curl -fsSL https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg -o /usr/share/keyrings/proxmox-archive-keyring.gpg\n    rm -f /etc/apt/keyrings/proxmox-release-bookworm.gpg /etc/apt/sources.list.d/proxmox-release-bookworm.list\n    $STD apt update\n    msg_ok \"Updated old sources\"\n  fi\n\n  if grep -q 'Debian GNU/Linux 13' /etc/os-release; then\n    if [ -f \"/etc/apt/sources.list.d/pdm-test.sources\" ]; then\n      if ! grep -qx \"Enabled: false\" \"/etc/apt/sources.list.d/pdm-test.sources\"; then\n          echo \"Enabled: false\" >> \"/etc/apt/sources.list.d/pdm-test.sources\"\n          setup_deb822_repo \\\n            \"pdm\" \\\n            \"https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg\" \\\n            \"http://download.proxmox.com/debian/pdm\" \\\n            \"trixie\" \\\n            \"pdm-no-subscription\"\n      fi\n    fi\n  fi\n\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}\"\n"
  },
  {
    "path": "ct/proxmox-mail-gateway.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.proxmox.com/en/products/proxmox-mail-gateway\n\nAPP=\"Proxmox-Mail-Gateway\"\nvar_tags=\"${var_tags:-mail}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -e /usr/bin/pmgproxy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Proxmox-Mail-Gateway\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated Proxmox-Mail-Gateway\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8006/${CL}\"\n"
  },
  {
    "path": "ct/ps5-mqtt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: liecno\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/FunkeyFlo/ps5-mqtt/\n\nAPP=\"PS5-MQTT\"\nvar_tags=\"${var_tags:-smarthome;automation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/ps5-mqtt ]]; then\n    msg_error \"No ${APP} installation found!\"\n    exit\n  fi\n  if check_for_gh_release \"ps5-mqtt\" \"FunkeyFlo/ps5-mqtt\"; then\n    msg_info \"Stopping service\"\n    systemctl stop ps5-mqtt\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"ps5-mqtt\" \"FunkeyFlo/ps5-mqtt\" \"tarball\"\n\n    msg_info \"Configuring ${APP}\"\n    cd /opt/ps5-mqtt/ps5-mqtt/\n    $STD npm install\n    $STD npm run build\n    msg_ok \"Configured ${APP}\"\n\n    msg_info \"Starting service\"\n    systemctl start ps5-mqtt\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8645${CL}\"\n"
  },
  {
    "path": "ct/pterodactyl-panel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pterodactyl/panel\n\nAPP=\"Pterodactyl-Panel\"\nvar_tags=\"${var_tags:-gaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/pterodactyl-panel ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)\n\n  if [[ \"$CURRENT_PHP\" != \"8.4\" ]]; then\n    msg_info \"Migrating PHP $CURRENT_PHP to 8.4\"\n    $STD curl -fsSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb\n    $STD dpkg -i /tmp/debsuryorg-archive-keyring.deb\n    $STD sh -c 'echo \"deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main\" > /etc/apt/sources.list.d/php.list'\n    cat <<EOF >/etc/apt/sources.list.d/php.sources\nTypes: deb\nURIs: https://packages.sury.org/php/\nSuites: $(lsb_release -sc)\nComponents: main\nSigned-By: /usr/share/keyrings/deb.sury.org-php.gpg\nEOF\n    $STD apt update\n    $STD apt remove -y php\"${CURRENT_PHP//./}\"*\n    $STD apt install -y \\\n      php8.4 \\\n      php8.4-{gd,mysql,mbstring,bcmath,xml,curl,zip,intl,fpm} \\\n      libapache2-mod-php8.4\n\n    msg_ok \"Migrated PHP $CURRENT_PHP to 8.4\"\n  fi\n\n  RELEASE=$(curl -fsSL https://api.github.com/repos/pterodactyl/panel/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_info \"Stopping Service\"\n    cd /opt/pterodactyl-panel\n    $STD php artisan down\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating ${APP} to v${RELEASE}\"\n    cp -r /opt/pterodactyl-panel/.env /opt/\n    rm -rf * .*\n    curl -fsSL \"https://github.com/pterodactyl/panel/releases/download/v${RELEASE}/panel.tar.gz\" -o $(basename \"https://github.com/pterodactyl/panel/releases/download/v${RELEASE}/panel.tar.gz\")\n    tar -xzf \"panel.tar.gz\"\n    mv /opt/.env /opt/pterodactyl-panel/\n    $STD composer install --no-dev --optimize-autoloader --no-interaction\n    $STD php artisan view:clear\n    $STD php artisan config:clear\n    $STD php artisan migrate --seed --force --no-interaction\n    chown -R www-data:www-data /opt/pterodactyl-panel/*\n    chmod -R 755 /opt/pterodactyl-panel/storage /opt/pterodactyl-panel/bootstrap/cache/\n    ln -s /opt/pterodactyl-panel /var/www/pterodactyl\n    rm -rf \"/opt/pterodactyl-panel/panel.tar.gz\"\n    echo \"${RELEASE}\" >/opt/${APP}_version.txt\n    msg_ok \"Updated $APP to v${RELEASE}\"\n\n    msg_info \"Starting Service\"\n    $STD php artisan queue:restart\n    $STD php artisan up\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/pterodactyl-wings.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pterodactyl/wings\n\nAPP=\"Pterodactyl-Wings\"\nvar_tags=\"${var_tags:-gaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -x /usr/local/bin/wings ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"wings\" \"pterodactyl/wings\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop wings\n    msg_ok \"Stopped Service\"\n\n    rm /usr/local/bin/wings\n    fetch_and_deploy_gh_release \"wings\" \"pterodactyl/wings\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"wings_linux_arm64\"\n\n    msg_info \"Starting Service\"\n    systemctl start wings\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/pulse.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rcourtman & vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rcourtman/Pulse\n\nAPP=\"Pulse\"\nvar_tags=\"${var_tags:-monitoring;proxmox}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/pulse ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"pulse\" \"rcourtman/Pulse\"; then\n    SERVICE_PATH=\"/etc/systemd/system\"\n    msg_info \"Stopping Services\"\n    systemctl stop pulse*.service\n    msg_ok \"Stopped Services\"\n\n    if [[ -f /opt/pulse/pulse ]]; then\n      rm -f /opt/pulse/pulse\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"pulse\" \"rcourtman/Pulse\" \"prebuild\" \"latest\" \"/opt/pulse\" \"pulse-v*-linux-arm64.tar.gz\"\n    ln -sf /opt/pulse/bin/pulse /usr/local/bin/pulse\n    mkdir -p /etc/pulse\n    chown pulse:pulse /etc/pulse\n    chown -R pulse:pulse /opt/pulse\n    chmod 700 /etc/pulse\n    if [[ -f \"$SERVICE_PATH\"/pulse-backend.service ]]; then\n      mv \"$SERVICE_PATH\"/pulse-backend.service \"$SERVICE_PATH\"/pulse.service\n    fi\n    sed -i -e 's|pulse/pulse|pulse/bin/pulse|' \\\n      -e 's/^Environment=\"API.*$//' \"$SERVICE_PATH\"/pulse.service\n    systemctl daemon-reload\n    if grep -q 'pulse-home:/bin/bash' /etc/passwd; then\n      usermod -s /usr/sbin/nologin pulse\n    fi\n\n    msg_info \"Starting Services\"\n    systemctl start pulse\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7655${CL}\"\n"
  },
  {
    "path": "ct/pve-scripts-local.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.debian.org/\n\nAPP=\"PVE-Scripts-Local\"\nvar_tags=\"${var_tags:-pve-scripts-local}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/ProxmoxVE-Local ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_custom \"🚀\" \"${GN}\" \"The app offers a built-in updater. Please use it.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/qbittorrent.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.qbittorrent.org/\n\nAPP=\"qBittorrent\"\nvar_tags=\"${var_tags:-torrent}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/qbittorrent-nox.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if [[ ! -f ~/.qbittorrent ]]; then\n    msg_error \"Please create new qBittorrent LXC. Updating from v4.x to v5.x is not supported!\"\n    exit\n  fi\n  if check_for_gh_release \"qbittorrent\" \"userdocs/qbittorrent-nox-static\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop qbittorrent-nox\n    msg_ok \"Stopped Service\"\n\n    rm -f /opt/qbittorrent/qbittorrent-nox\n    fetch_and_deploy_gh_release \"qbittorrent\" \"userdocs/qbittorrent-nox-static\" \"singlefile\" \"latest\" \"/opt/qbittorrent\" \"x86_64-qbittorrent-nox\"\n    mv /opt/qbittorrent/qbittorrent /opt/qbittorrent/qbittorrent-nox\n\n    msg_info \"Starting Service\"\n    systemctl start qbittorrent-nox\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/qdrant.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/qdrant/qdrant\n\nAPP=\"Qdrant\"\nvar_tags=\"${var_tags:-database;vector}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/qdrant ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"qdrant\" \"qdrant/qdrant\"; then\n    fetch_and_deploy_gh_release \"qdrant\" \"qdrant/qdrant\" \"prebuild\" \"latest\" \"/opt/qdrant\" \"qdrant-aarch64-unknown-linux-musl.tar.gz\"\n    chown -R root:root /var/lib/qdrant\n    chmod -R 755 /var/lib/qdrant\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6333/dashboard${CL}\"\n"
  },
  {
    "path": "ct/qui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/autobrr/qui\n\nAPP=\"Qui\"\nvar_tags=\"${var_tags:-torrent}\"\nvar_disk=\"${var_disk:-10}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/bin/qui ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"Qui\" \"autobrr/qui\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop qui\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"qui\" \"autobrr/qui\" \"prebuild\" \"latest\" \"/tmp/qui\" \"qui_*_linux_aarch64.tar.gz\"\n\n    msg_info \"Updating qui\"\n    mv /tmp/qui/qui /usr/local/bin/qui\n    chmod +x /usr/local/bin/qui\n    rm -rf /tmp/qui\n    msg_ok \"Updated qui\"\n\n    msg_info \"Starting Service\"\n    systemctl start qui\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7476${CL}\"\n"
  },
  {
    "path": "ct/rabbitmq.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.rabbitmq.com/\n\nAPP=\"RabbitMQ\"\nvar_tags=\"${var_tags:-mqtt}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/rabbitmq ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if grep -q \"dl.cloudsmith.io\" /etc/apt/sources.list.d/rabbitmq.list; then\n    rm -f /etc/apt/sources.list.d/rabbitmq.list\n    cat <<EOF >/etc/apt/sources.list.d/rabbitmq.list\n## Modern Erlang/OTP releases\ndeb [arch=arm64 signed-by=/usr/share/keyrings/com.rabbitmq.team.gpg] https://deb1.rabbitmq.com/rabbitmq-erlang/debian/trixie trixie main\ndeb [arch=arm64 signed-by=/usr/share/keyrings/com.rabbitmq.team.gpg] https://deb2.rabbitmq.com/rabbitmq-erlang/debian/trixie trixie main\n\n## Provides modern RabbitMQ releases\ndeb [arch=arm64 signed-by=/usr/share/keyrings/com.rabbitmq.team.gpg] https://deb1.rabbitmq.com/rabbitmq-server/debian/trixie trixie main\ndeb [arch=arm64 signed-by=/usr/share/keyrings/com.rabbitmq.team.gpg] https://deb2.rabbitmq.com/rabbitmq-server/debian/trixie trixie main\nEOF\n    $STD apt update\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop rabbitmq-server\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating...\"\n  $STD apt install --only-upgrade rabbitmq-server\n  msg_ok \"Updated successfully!\"\n\n  msg_info \"Starting Service\"\n  systemctl start rabbitmq-server\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:15672${CL}\"\n"
  },
  {
    "path": "ct/radarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://radarr.video/\n\nAPP=\"Radarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /var/lib/radarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Radarr\" \"Radarr/Radarr\"; then\n    apt-get install -y libicu76 &>/dev/null\n    msg_info \"Stopping Service\"\n    systemctl stop radarr\n    msg_ok \"Stopped Service\"\n\n    rm -rf /opt/Radarr\n    fetch_and_deploy_gh_release \"Radarr\" \"Radarr/Radarr\" \"prebuild\" \"latest\" \"/opt/Radarr\" \"Radarr.master*linux-core-arm64.tar.gz\"\n    chmod 775 /opt/Radarr\n\n    msg_info \"Starting Service\"\n    systemctl start radarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7878${CL}\"\n"
  },
  {
    "path": "ct/radicale.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://radicale.org/\n\nAPP=\"Radicale\"\nvar_tags=\"${var_tags:-calendar}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/radicale ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Radicale\" \"Kozea/Radicale\"; then\n    msg_info \"Stopping service\"\n    systemctl stop radicale\n    msg_ok \"Stopped service\"\n\n    msg_info \"Backing up users file\"\n    cp /opt/radicale/users /opt/radicale_users_backup\n    msg_ok \"Backed up users file\"\n\n    PYTHON_VERSION=\"3.13\" setup_uv\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Radicale\" \"Kozea/Radicale\" \"tarball\" \"latest\" \"/opt/radicale\"\n\n    msg_info \"Restoring users file\"\n    rm -f /opt/radicale/users\n    mv /opt/radicale_users_backup /opt/radicale/users\n    msg_ok \"Restored users file\"\n\n    if grep -q 'start.sh' /etc/systemd/system/radicale.service; then\n      sed -i -e '/^Description/i[Unit]' \\\n        -e '\\|^ExecStart|iWorkingDirectory=/opt/radicale' \\\n        -e 's|^ExecStart=.*|ExecStart=/usr/local/bin/uv run -m radicale --config /etc/radicale/config|' /etc/systemd/system/radicale.service\n      systemctl daemon-reload\n    fi\n    if [[ ! -f /etc/radicale/config ]]; then\n      msg_info \"Migrating to config file (/etc/radicale/config)\"\n      mkdir -p /etc/radicale\n      cat <<EOF >/etc/radicale/config\n[server]\nhosts = 0.0.0.0:5232\n\n[auth]\ntype = htpasswd\nhtpasswd_filename = /opt/radicale/users\nhtpasswd_encryption = sha512\n\n[storage]\ntype = multifilesystem\nfilesystem_folder = /var/lib/radicale/collections\n\n[web]\ntype = internal\nEOF\n      msg_ok \"Migrated to config (/etc/radicale/config)\"\n    fi\n    msg_info \"Starting service\"\n    systemctl start radicale\n    msg_ok \"Started service\"\n    msg_ok \"Updated Successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5232${CL}\"\n"
  },
  {
    "path": "ct/rclone.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rclone/rclone\n\nAPP=\"Rclone\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_fuse=\"${var_fuse:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/rclone ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"rclone\" \"rclone/rclone\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop rclone-web\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"rclone\" \"rclone/rclone\" \"prebuild\" \"latest\" \"/opt/rclone\" \"rclone*linux-arm64.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl start rclone-web\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/rdtclient.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rogerfar/rdt-client\n\nAPP=\"RDTClient\"\nvar_tags=\"${var_tags:-torrent}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/rdtc/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"rdt-client\" \"rogerfar/rdt-client\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop rdtc\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mkdir -p /opt/rdtc-backup\n    cp -R /opt/rdtc/appsettings.json /opt/rdtc-backup/\n    msg_ok \"Backup created\"\n\n    fetch_and_deploy_gh_release \"rdt-client\" \"rogerfar/rdt-client\" \"prebuild\" \"latest\" \"/opt/rdtc\" \"RealDebridClient.zip\"\n    cp -R /opt/rdtc-backup/appsettings.json /opt/rdtc/\n    if dpkg-query -W aspnetcore-runtime-9.0 >/dev/null 2>&1; then\n      $STD apt remove --purge -y aspnetcore-runtime-9.0\n      ensure_dependencies aspnetcore-runtime-10.0\n    fi\n    rm -rf /opt/rdtc-backup\n\n    msg_info \"Starting Service\"\n    systemctl start rdtc\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6500${CL}\"\n"
  },
  {
    "path": "ct/reactive-resume.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://rxresume.org\n\nAPP=\"Reactive-Resume\"\nvar_tags=\"${var_tags:-documents}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/reactive-resume.service ]]; then\n    msg_error \"No $APP Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"reactive-resume\" \"amruthpillai/reactive-resume\"; then\n    msg_info \"Stopping services\"\n    systemctl stop reactive-resume\n    msg_ok \"Stopped services\"\n\n    ensure_dependencies git\n\n    cp /opt/reactive-resume/.env /opt/reactive-resume.env.bak\n    NODE_VERSION=\"24\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"reactive-resume\" \"amruthpillai/reactive-resume\" \"tarball\" \"latest\" \"/opt/reactive-resume\"\n\n    msg_info \"Updating Reactive Resume (Patience)\"\n    cd /opt/reactive-resume\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    corepack enable\n    corepack prepare --activate\n    export CI=\"true\"\n    export NODE_ENV=\"production\"\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm run build\n    $STD pnpm run prisma:generate\n    mv /opt/rxresume.env /opt/Reactive-Resume/.env\n    msg_ok \"Updated Reactive-Resume\"\n\n    msg_info \"Updating Minio\"\n    systemctl stop minio\n    cd /tmp\n    curl -fsSL https://dl.min.io/server/minio/release/linux-arm64/minio.deb -o minio.deb\n    $STD dpkg -i minio.deb\n    rm -f /tmp/minio.deb\n    msg_ok \"Updated Minio\"\n\n    msg_info \"Updating Browserless (Patience)\"\n    systemctl stop browserless\n    cp /opt/browserless/.env /opt/browserless.env\n    rm -rf /opt/browserless\n    brwsr_tmp=$(mktemp)\n    TAG=$(curl -fsSL https://api.github.com/repos/browserless/browserless/tags?per_page=1 | grep \"name\" | awk '{print substr($2, 3, length($2)-4) }')\n    curl -fsSL https://github.com/browserless/browserless/archive/refs/tags/v\"$TAG\".zip -o \"$brwsr_tmp\"\n    $STD unzip \"$brwsr_tmp\"\n    mv browserless-\"$TAG\"/ /opt/browserless\n    cd /opt/browserless\n    $STD npm install typescript\n    $STD npm install esbuild\n    $STD npm install\n    rm -rf src/routes/{chrome,edge,firefox,webkit}\n    $STD node_modules/playwright-core/cli.js install --with-deps chromium\n    $STD npm run build\n    $STD npm run build:function\n    $STD npm prune production\n    mv /opt/browserless.env /opt/browserless/.env\n    rm -f \"$brwsr_tmp\"\n    msg_ok \"Updated Browserless\"\n\n    msg_info \"Restarting services\"\n    systemctl start chromium-printer reactive-resume\n    msg_ok \"Restarted services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/readarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://readarr.com/\n\nAPP=\"Readarr\"\nvar_tags=\"${var_tags:-media;comic;eBook}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/readarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt -y upgrade\n  apt-get install -y libicu76 &>/dev/null\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8787${CL}\"\n"
  },
  {
    "path": "ct/readeck.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://readeck.org/en/\n\nAPP=\"Readeck\"\nvar_tags=\"${var_tags:-bookmark}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/readeck ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_codeberg_release \"readeck\" \"readeck/readeck\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop readeck\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_codeberg_release \"readeck\" \"readeck/readeck\" \"singlefile\" \"latest\" \"/opt/readeck\" \"readeck-*-linux-arm64\"\n\n    msg_info \"Starting Service\"\n    systemctl start readeck\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at the latest version.\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/recyclarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MrYadro\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://recyclarr.dev/wiki/\n\nAPP=\"Recyclarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /root/.config/recyclarr/recyclarr.yml ]] && [[ ! -d /root/.config/recyclarr/configs ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"recyclarr\" \"recyclarr/recyclarr\"; then\n\t\tapt-get install -y libicu76 &>/dev/null\n    msg_info \"Updating ${APP}\"\n\n    fetch_and_deploy_gh_release \"recyclarr\" \"recyclarr/recyclarr\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"recyclarr-linux-arm64.tar.xz\"\n\n    # Migrate includes from configs/ to includes/ (recyclarr v8)\n    RECYCLARR_DIR=\"/root/.config/recyclarr\"\n    mkdir -p \"$RECYCLARR_DIR/includes\"\n    if [[ -d \"$RECYCLARR_DIR/configs\" ]]; then\n      for item in \"$RECYCLARR_DIR/configs\"/*/; do\n        [[ -d \"$item\" ]] || continue\n        dir_name=$(basename \"$item\")\n        # Only move subdirs that look like include dirs (not the configs themselves)\n        if [[ \"$dir_name\" != \"configs\" ]] && [[ ! -d \"$RECYCLARR_DIR/includes/$dir_name\" ]]; then\n          mv \"$item\" \"$RECYCLARR_DIR/includes/\"\n        fi\n      done\n    fi\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}${CL}\"\n"
  },
  {
    "path": "ct/redis.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://redis.io/\n\nAPP=\"Redis\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/redis-server.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating $APP LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:6379${CL}\"\n"
  },
  {
    "path": "ct/reitti.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dedicatedcode/reitti\n\nAPP=\"Reitti\"\nvar_tags=\"${var_tags:-location-tracker}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-15}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /opt/reitti/reitti.jar ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Enable PostGIS extension if not already enabled\n  if systemctl is-active --quiet postgresql; then\n    if ! sudo -u postgres psql -d reitti_db -tAc \"SELECT 1 FROM pg_extension WHERE extname='postgis'\" 2>/dev/null | grep -q 1; then\n      msg_info \"Enabling PostGIS extension\"\n      sudo -u postgres psql -d reitti_db -c \"CREATE EXTENSION IF NOT EXISTS postgis;\" &>/dev/null\n      msg_ok \"Enabled PostGIS extension\"\n    fi\n  fi\n\n  # Migrate v3 -> v4: Remove RabbitMQ (no longer required) / Photon / Spring Settings\n  if systemctl is-enabled --quiet rabbitmq-server 2>/dev/null; then\n    msg_info \"Migrating to v4: Removing RabbitMQ\"\n    systemctl stop rabbitmq-server\n    systemctl disable rabbitmq-server\n    $STD apt-get purge -y rabbitmq-server erlang-base\n    $STD apt-get autoremove -y\n    msg_ok \"Removed RabbitMQ\"\n  fi\n\n  if systemctl is-enabled --quiet photon 2>/dev/null; then\n    msg_info \"Migrating to v4: Removing Photon service\"\n    systemctl stop photon\n    systemctl disable photon\n    rm -f /etc/systemd/system/photon.service\n    systemctl daemon-reload\n    msg_ok \"Removed Photon service\"\n  fi\n\n  if grep -q \"spring.rabbitmq\\|PHOTON_BASE_URL\\|PROCESSING_WAIT_TIME\\|DANGEROUS_LIFE\" /opt/reitti/application.properties 2>/dev/null; then\n    msg_info \"Migrating to v4: Rewriting application.properties\"\n    local DB_URL DB_USER DB_PASS\n    DB_URL=$(grep '^spring.datasource.url=' /opt/reitti/application.properties | cut -d'=' -f2-)\n    DB_USER=$(grep '^spring.datasource.username=' /opt/reitti/application.properties | cut -d'=' -f2-)\n    DB_PASS=$(grep '^spring.datasource.password=' /opt/reitti/application.properties | cut -d'=' -f2-)\n    cp /opt/reitti/application.properties /opt/reitti/application.properties.bak\n    cat <<PROPEOF >/opt/reitti/application.properties\n# Server configuration\nserver.port=8080\nserver.servlet.context-path=/\nserver.forward-headers-strategy=framework\nserver.compression.enabled=true\nserver.compression.min-response-size=1024\nserver.compression.mime-types=text/plain,application/json\n\n# Logging configuration\nlogging.level.root=INFO\nlogging.level.org.hibernate.engine.jdbc.spi.SqlExceptionHelper=FATAL\nlogging.level.com.dedicatedcode.reitti=INFO\n\n# Internationalization\nspring.messages.basename=messages\nspring.messages.encoding=UTF-8\nspring.messages.cache-duration=3600\nspring.messages.fallback-to-system-locale=false\n\n# PostgreSQL configuration\nspring.datasource.url=${DB_URL}\nspring.datasource.username=${DB_USER}\nspring.datasource.password=${DB_PASS}\nspring.datasource.hikari.maximum-pool-size=20\n\n# Redis configuration\nspring.data.redis.host=127.0.0.1\nspring.data.redis.port=6379\nspring.data.redis.username=\nspring.data.redis.password=\nspring.data.redis.database=0\nspring.cache.redis.key-prefix=\n\nspring.cache.cache-names=processed-visits,significant-places,users,magic-links,configurations,transport-mode-configs,avatarThumbnails,avatarData,user-settings\nspring.cache.redis.time-to-live=1d\n\n# Upload configuration\nspring.servlet.multipart.max-file-size=5GB\nspring.servlet.multipart.max-request-size=5GB\nserver.tomcat.max-part-count=100\n\n# Rqueue configuration\nrqueue.web.enable=false\nrqueue.job.enabled=false\nrqueue.message.durability.in-terminal-state=0\nrqueue.key.prefix=\\${spring.cache.redis.key-prefix}\nrqueue.message.converter.provider.class=com.dedicatedcode.reitti.config.RQueueCustomMessageConverter\n\n# Application-specific settings\nreitti.server.advertise-uri=\n\nreitti.security.local-login.disable=false\n\n# OIDC / Security Settings\nreitti.security.oidc.enabled=false\nreitti.security.oidc.registration.enabled=false\n\nreitti.import.batch-size=10000\nreitti.import.processing-idle-start-time=10\n\nreitti.geo-point-filter.max-speed-kmh=1000\nreitti.geo-point-filter.max-accuracy-meters=100\nreitti.geo-point-filter.history-lookback-hours=24\nreitti.geo-point-filter.window-size=50\n\nreitti.process-data.schedule=0 */10 * * * *\nreitti.process-data.refresh-views.schedule=0 0 4 * * *\nreitti.imports.schedule=0 5/10 * * * *\nreitti.imports.owntracks-recorder.schedule=\\${reitti.imports.schedule}\n\n# Geocoding service configuration\nreitti.geocoding.max-errors=10\nreitti.geocoding.photon.base-url=\n\n# Tiles Configuration\nreitti.ui.tiles.cache.url=http://127.0.0.1\nreitti.ui.tiles.default.service=https://tile.openstreetmap.org/{z}/{x}/{y}.png\nreitti.ui.tiles.default.attribution=&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors\n\n# Data management configuration\nreitti.data-management.enabled=false\nreitti.data-management.preview-cleanup.cron=0 0 4 * * *\n\nreitti.storage.path=data/\nreitti.storage.cleanup.cron=0 0 4 * * *\n\n# Location data density normalization\nreitti.location.density.target-points-per-minute=4\n\n# Logging buffer\nreitti.logging.buffer-size=1000\nreitti.logging.max-buffer-size=10000\n\nspring.config.import=optional:oidc.properties\nPROPEOF\n    # Update reitti.service dependencies\n    if [[ -f /etc/systemd/system/reitti.service ]]; then\n      sed -i 's/ rabbitmq-server\\.service//g; s/ photon\\.service//g' /etc/systemd/system/reitti.service\n      systemctl daemon-reload\n    fi\n    msg_ok \"Rewrote application.properties (backup: application.properties.bak)\"\n  fi\n\n  if check_for_gh_release \"reitti\" \"dedicatedcode/reitti\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop reitti\n    msg_ok \"Stopped Service\"\n\n    JAVA_VERSION=\"25\" setup_java\n\n    rm -f /opt/reitti/reitti.jar\n    USE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"reitti\" \"dedicatedcode/reitti\" \"singlefile\" \"latest\" \"/opt/reitti\" \"reitti-app.jar\"\n    mv /opt/reitti/reitti-*.jar /opt/reitti/reitti.jar\n\n    msg_info \"Starting Service\"\n    systemctl start reitti\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/resiliosync.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: David Bennett (dbinit)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.resilio.com/sync\n\nAPP=\"Resilio Sync\"\nvar_tags=\"${var_tags:-sync}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/resilio-sync ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Resilio Sync\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8888${CL}\"\n"
  },
  {
    "path": "ct/revealjs.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/hakimel/reveal.js\n\nAPP=\"RevealJS\"\nvar_tags=\"${var_tags:-presentation}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d \"/opt/revealjs\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"revealjs\" \"hakimel/reveal.js\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop revealjs\n    msg_info \"Stopped Service\"\n\n    cp /opt/revealjs/index.html /opt\n    fetch_and_deploy_gh_release \"revealjs\" \"hakimel/reveal.js\" \"tarball\"\n\n    msg_info \"Updating RevealJS\"\n    cd /opt/revealjs\n    $STD npm install\n    cp -f /opt/index.html /opt/revealjs\n    sed -i 's/\"vite\"/\"vite --host\"/g' package.json\n    rm -f /opt/index.html\n    msg_ok \"Updated RevealJS\"\n\n    msg_info \"Starting Service\"\n    systemctl start revealjs\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/romm.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -s https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ) | DevelopmentCats | AlphaLawless\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://romm.app\n\nAPP=\"RomM\"\nvar_tags=\"${var_tags:-emulation}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/romm ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"romm\" \"rommapp/romm\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop romm-backend romm-worker romm-scheduler romm-watcher\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up configuration\"\n    cp /opt/romm/.env /opt/romm/.env.backup\n    msg_ok \"Backed up configuration\"\n\n    fetch_and_deploy_gh_release \"romm\" \"rommapp/romm\" \"tarball\" \"latest\" \"/opt/romm\"\n\n    msg_info \"Updating ROMM\"\n    cp /opt/romm/.env.backup /opt/romm/.env\n    cd /opt/romm\n    $STD uv sync --all-extras\n    cd /opt/romm/backend\n    $STD uv run alembic upgrade head\n    cd /opt/romm/frontend\n    $STD npm install\n    $STD npm run build\n    # Merge static assets into dist folder\n    cp -rf /opt/romm/frontend/assets/* /opt/romm/frontend/dist/assets/\n    mkdir -p /opt/romm/frontend/dist/assets/romm\n    ROMM_BASE=$(grep '^ROMM_BASE_PATH=' /opt/romm/.env | cut -d'=' -f2)\n    ROMM_BASE=${ROMM_BASE:-/var/lib/romm}\n    ln -sfn \"$ROMM_BASE\"/resources /opt/romm/frontend/dist/assets/romm/resources\n    ln -sfn \"$ROMM_BASE\"/assets /opt/romm/frontend/dist/assets/romm/assets\n    sed -i \"s|alias .*/library/;|alias ${ROMM_BASE}/library/;|\" /etc/nginx/sites-available/romm\n    systemctl reload nginx\n    msg_ok \"Updated ROMM\"\n\n    msg_info \"Starting Services\"\n    systemctl start romm-backend romm-worker romm-scheduler romm-watcher\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/runtipi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://runtipi.io/\n\nAPP=\"Runtipi\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nADDON_SCRIPT=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/runtipi.sh\"\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/runtipi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_warn \"⚠️  ${APP} has been migrated to an addon script.\"\n  echo \"\"\n  msg_info \"This is a one-time migration. After this, you can update ${APP} anytime with:\"\n  echo -e \"${TAB}${TAB}${GN}update_runtipi${CL}  or  ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}\"\n  echo \"\"\n  read -r -p \"${TAB}Migrate update function now? [y/N]: \" CONFIRM\n  if [[ ! \"${CONFIRM,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Migration skipped. The old update will continue to work for now.\"\n    msg_info \"Updating ${APP} (legacy)\"\n    cd /opt/runtipi && ./runtipi-cli update latest\n    msg_ok \"Updated ${APP}\"\n    exit\n  fi\n\n  msg_info \"Migrating update function\"\n  TMP_UPDATE=$(mktemp)\n  cat <<'MIGRATION_EOF' >\"$TMP_UPDATE\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/runtipi.sh)\"\nMIGRATION_EOF\n  mv \"$TMP_UPDATE\" /usr/bin/update\n  chmod +x /usr/bin/update\n\n  ln -sf /usr/bin/update /usr/bin/update_runtipi 2>/dev/null || true\n  msg_ok \"Migration complete\"\n\n  msg_info \"Running addon update\"\n  type=update bash <(curl -fsSL \"${ADDON_SCRIPT}\")\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/rustdeskserver.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rustdesk/rustdesk-server\n\nAPP=\"RustDesk Server\"\nvar_tags=\"${var_tags:-remote-desktop}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -x /usr/bin/hbbr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"rustdesk-hbbs\" \"lejianwen/rustdesk-server\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop rustdesk-hbbr\n    systemctl stop rustdesk-hbbs\n    if [[ -f /lib/systemd/system/rustdesk-api.service ]]; then\n      systemctl stop rustdesk-api\n    fi\n    msg_info \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"rustdesk-hbbr\" \"rustdesk/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbr*arm64.deb\"\n    fetch_and_deploy_gh_release \"rustdesk-hbbs\" \"rustdesk/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbs*arm64.deb\"\n    fetch_and_deploy_gh_release \"rustdesk-utils\" \"rustdesk/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-utils*arm64.deb\"\n    fetch_and_deploy_gh_release \"rustdesk-api\" \"lejianwen/rustdesk-api\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-api-server*arm64.deb\"\n\n    msg_info \"Starting services\"\n    systemctl start -q rustdesk-*\n    msg_ok \"Services started\"\n\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:21114${CL}\"\n"
  },
  {
    "path": "ct/rustypaste.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -s https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: GoldenSpringness\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/orhun/rustypaste\n\nAPP=\"rustypaste\"\nvar_tags=\"${var_tags:-pastebin;storage}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/rustypaste/rustypaste ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"rustypaste\" \"orhun/rustypaste\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop rustypaste\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Creating Backup\"\n    tar -czf \"/opt/rustypaste_backup_$(date +%F).tar.gz\" /opt/rustypaste/upload 2>/dev/null || true\n    cp /opt/rustypaste/config.toml /tmp/rustypaste_config.toml.bak\n    msg_ok \"Backup Created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"rustypaste\" \"orhun/rustypaste\" \"prebuild\" \"latest\" \"/opt/rustypaste\" \"*aarch64-unknown-linux-gnu.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    mv /tmp/rustypaste_config.toml.bak /opt/rustypaste/config.toml\n    tar -xzf \"/opt/rustypaste_backup_$(date +%F).tar.gz\" -C /opt/rustypaste/upload 2>/dev/null || true\n    rm -rf /opt/rustypaste_backup_$(date +%F).tar.gz\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Services\"\n    systemctl start rustypaste\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n\n  if check_for_gh_release \"rustypaste-cli\" \"orhun/rustypaste-cli\"; then\n    fetch_and_deploy_gh_release \"rustypaste-cli\" \"orhun/rustypaste-cli\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"*aarch64-unknown-linux-gnu.tar.gz\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}rustypaste setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/rwmarkable.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/fccview/rwMarkable\n\nAPP=\"rwMarkable\"\nvar_tags=\"${var_tags:-tasks;notes}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-3072}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/rwmarkable ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping service\"\n  systemctl -q disable --now rwmarkable\n  msg_ok \"Stopped Service\"\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n  CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"jotty\" \"fccview/jotty\" \"tarball\" \"latest\" \"/opt/jotty\"\n\n  msg_info \"Updating app\"\n  cd /opt/jotty\n  $STD yarn --frozen-lockfile\n  $STD yarn next telemetry disable\n  $STD yarn build\n  msg_ok \"Updated app\"\n\n  msg_info \"Migrating configuration & data\"\n  cp /opt/rwmarkable/.env /opt/jotty/.env\n  mkdir -p /opt/jotty/data\n  cp -r /opt/rwmarkable/data/* /opt/jotty/data\n  cp -r /opt/rwmarkable/config/* /opt/jotty/config\n  msg_ok \"Migrated configuration & data\"\n\n  msg_info \"Patching systemd service file\"\n  sed -i 's/rw[M|m]arkable/jotty/g' /etc/systemd/system/rwmarkable.service\n  mv /etc/systemd/system/rwmarkable.service /etc/systemd/system/jotty.service\n  systemctl daemon-reload\n  msg_ok \"Patched systemd service file\"\n\n  msg_info \"Patching update script\"\n  sed -i 's/rwmarkable/jotty/g' /usr/bin/update\n  msg_ok \"Patched update script\"\n\n  msg_info \"Starting jotty service\"\n  systemctl -q enable --now jotty\n  msg_ok \"Started jotty service\"\n  msg_ok \"Migrated Successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/sabnzbd.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sabnzbd.org/\n\nAPP=\"SABnzbd\"\nvar_tags=\"${var_tags:-downloader}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n\n    if par2 --version | grep -q \"par2cmdline-turbo\"; then\n        fetch_and_deploy_gh_release \"par2cmdline-turbo\" \"animetosho/par2cmdline-turbo\" \"prebuild\" \"latest\" \"/usr/bin/\" \"*-linux-arm64.zip\"\n    fi\n\n    if [[ ! -d /opt/sabnzbd ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    if check_for_gh_release \"sabnzbd-org\" \"sabnzbd/sabnzbd\"; then\n        PYTHON_VERSION=\"3.13\" setup_uv\n        systemctl stop sabnzbd\n        cp -r /opt/sabnzbd /opt/sabnzbd_backup_$(date +%s)\n        fetch_and_deploy_gh_release \"sabnzbd-org\" \"sabnzbd/sabnzbd\" \"prebuild\" \"latest\" \"/opt/sabnzbd\" \"SABnzbd-*-src.tar.gz\"\n\n        # Always ensure venv exists\n        if [[ ! -d /opt/sabnzbd/venv ]]; then\n            msg_info \"Migrating SABnzbd to uv virtual environment\"\n            $STD uv venv --clear /opt/sabnzbd/venv\n            msg_ok \"Created uv venv at /opt/sabnzbd/venv\"\n        fi\n\n        # Always check and fix service file if needed\n        if [[ -f /etc/systemd/system/sabnzbd.service ]] && grep -q \"ExecStart=python3 SABnzbd.py\" /etc/systemd/system/sabnzbd.service; then\n            sed -i \"s|ExecStart=python3 SABnzbd.py|ExecStart=/opt/sabnzbd/venv/bin/python SABnzbd.py|\" /etc/systemd/system/sabnzbd.service\n            systemctl daemon-reload\n            msg_ok \"Updated SABnzbd service to use uv venv\"\n        fi\n        $STD uv pip install --upgrade pip --python=/opt/sabnzbd/venv/bin/python\n        $STD uv pip install -r /opt/sabnzbd/requirements.txt --python=/opt/sabnzbd/venv/bin/python\n\n        systemctl start sabnzbd\n        msg_ok \"Updated successfully!\"\n    fi\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7777${CL}\"\n"
  },
  {
    "path": "ct/salt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/saltstack/salt\n\nAPP=\"Salt\"\nvar_tags=\"${var_tags:-automations}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /etc/salt ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(get_latest_github_release \"saltstack/salt\")\n  if check_for_gh_release \"salt\" \"saltstack/salt\"; then\n    msg_info \"Updating Salt\"\n    sed -i \"s/^\\(Pin: version \\).*/\\1${RELEASE}/\" /etc/apt/preferences.d/salt-pin-1001\n    $STD apt update\n    $STD apt upgrade -y\n    echo \"${RELEASE}\" >/~.salt\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/scanopy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/scanopy/scanopy\n\nAPP=\"Scanopy\"\nvar_tags=\"${var_tags:-analytics}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/scanopy ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Scanopy\" \"scanopy/scanopy\"; then\n    msg_info \"Stopping services\"\n    systemctl stop scanopy-server\n    [[ -f /etc/systemd/system/scanopy-daemon.service ]] && systemctl stop scanopy-daemon\n    msg_ok \"Stopped services\"\n\n    msg_info \"Backing up configurations\"\n    cp /opt/scanopy/.env /opt/scanopy.env\n    [[ -f /opt/scanopy/oidc.toml ]] && cp /opt/scanopy/oidc.toml /opt/scanopy.oidc.toml\n    msg_ok \"Backed up configurations\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Scanopy\" \"scanopy/scanopy\" \"tarball\" \"latest\" \"/opt/scanopy\"\n\n    ensure_dependencies pkg-config libssl-dev\n    TOOLCHAIN=\"$(grep \"channel\" /opt/scanopy/backend/rust-toolchain.toml | awk -F\\\" '{print $2}')\"\n    RUST_TOOLCHAIN=$TOOLCHAIN setup_rust\n\n    [[ -f /opt/scanopy.env ]] && mv /opt/scanopy.env /opt/scanopy/.env\n    [[ -f /opt/scanopy.oidc.toml ]] && mv /opt/scanopy.oidc.toml /opt/scanopy/oidc.toml\n    if ! grep -q \"PUBLIC_URL\" /opt/scanopy/.env; then\n      sed -i \"\\|_PATH=|a\\\\scanopy_PUBLIC_URL=http://${LOCAL_IP}:60072\" /opt/scanopy/.env\n    fi\n    sed -i 's|_TARGET=.*$|_URL=http://127.0.0.1:60072|' /opt/scanopy/.env\n\n    msg_info \"Building Scanopy Server (patience)\"\n    cd /opt/scanopy/backend\n    $STD cargo build --release --bin server --bin generate-fixtures\n    $STD ./target/release/generate-fixtures --output-dir /opt/scanopy/ui/src/lib/data\n    mv ./target/release/server /usr/bin/scanopy-server\n    msg_ok \"Built Scanopy Server\"\n\n    msg_info \"Creating frontend UI\"\n    export PUBLIC_SERVER_HOSTNAME=default\n    export PUBLIC_SERVER_PORT=\"\"\n    cd /opt/scanopy/ui\n    $STD npm ci --no-fund --no-audit\n    $STD npm run build\n    msg_ok \"Created frontend UI\"\n\n    if [[ -f /etc/systemd/system/scanopy-daemon.service ]]; then\n      fetch_and_deploy_gh_release \"Scanopy Daemon\" \"scanopy/scanopy\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"scanopy-daemon-linux-amd64\"\n      mv \"/usr/local/bin/Scanopy Daemon\" /usr/local/bin/scanopy-daemon\n      rm -f /usr/bin/scanopy-daemon ~/configure_daemon.sh\n      sed -i -e 's|usr/bin|usr/local/bin|' \\\n        -e 's/push/daemon_poll/' \\\n        -e 's/pull/server_poll/' /etc/systemd/system/scanopy-daemon.service\n      systemctl daemon-reload\n      msg_ok \"Updated Scanopy Daemon\"\n    fi\n\n    msg_info \"Starting services\"\n    systemctl start scanopy-server\n    [[ -f /etc/systemd/system/scanopy-daemon.service ]] && systemctl start scanopy-daemon\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:60072${CL}\"\necho -e \"${INFO}${YW} Then create your account, and create a daemon in the UI.${CL}\"\n"
  },
  {
    "path": "ct/scraparr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: JasonGreenC\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/thecfu/scraparr\n\nAPP=\"Scraparr\"\nvar_tags=\"${var_tags:-arr;monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/scraparr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"scraparr\" \"thecfu/scraparr\"; then\n\t  apt-get install -y libicu76 &>/dev/null\n    msg_info \"Stopping Services\"\n    systemctl stop scraparr\n    msg_ok \"Services Stopped\"\n\n    PYTHON_VERSION=\"3.12\" setup_uv\n    fetch_and_deploy_gh_release \"scrappar\" \"thecfu/scraparr\" \"tarball\" \"latest\" \"/opt/scraparr\"\n\n    msg_info \"Updating Scraparr\"\n    cd /opt/scraparr\n    $STD uv venv --clear /opt/scraparr/.venv\n    $STD /opt/scraparr/.venv/bin/python -m ensurepip --upgrade\n    $STD /opt/scraparr/.venv/bin/python -m pip install --upgrade pip\n    $STD /opt/scraparr/.venv/bin/python -m pip install -r /opt/scraparr/src/scraparr/requirements.txt\n    chmod -R 755 /opt/scraparr\n    msg_ok \"Updated Scraparr\"\n\n    msg_info \"Starting Services\"\n    systemctl start scraparr\n    msg_ok \"Services Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7100${CL}\"\n"
  },
  {
    "path": "ct/searxng.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/searxng/searxng\n\nAPP=\"SearXNG\"\nvar_tags=\"${var_tags:-search}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-7}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /usr/local/searxng/searxng-src ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  chown -R searxng:searxng /usr/local/searxng/searxng-src\n  if su -s /bin/bash -c \"git -C /usr/local/searxng/searxng-src pull\" searxng | grep -q 'Already up to date'; then\n     msg_ok \"There is currently no update available.\"\n     exit\n  fi\n\n  msg_info \"Updating SearXNG installation\"\n  msg_info \"Stopping Service\"\n  systemctl stop searxng\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating SearXNG\"\n  $STD su -s /bin/bash searxng -c '\n    python3 -m venv /usr/local/searxng/searx-pyenv &&\n    . /usr/local/searxng/searx-pyenv/bin/activate &&\n    pip install -U pip setuptools wheel pyyaml lxml msgspec typing_extensions &&\n    pip install --use-pep517 --no-build-isolation -e /usr/local/searxng/searxng-src\n    '\n  msg_ok \"Updated SearXNG\"\n  \n  msg_info \"Starting Services\"\n  systemctl start searxng\n  msg_ok \"Started Services\"\n  msg_ok \"Updated successfully!\"\n exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8888${CL}\"\n"
  },
  {
    "path": "ct/seaweedfs.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/seaweedfs/seaweedfs\n\nAPP=\"SeaweedFS\"\nvar_tags=\"${var_tags:-storage;s3;filesystem}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_fuse=\"${var_fuse:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/seaweedfs/weed ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"seaweedfs\" \"seaweedfs/seaweedfs\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop seaweedfs\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"seaweedfs\" \"seaweedfs/seaweedfs\" \"prebuild\" \"latest\" \"/opt/seaweedfs\" \"linux_amd64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start seaweedfs\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9333${CL}\"\n"
  },
  {
    "path": "ct/seelf.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/YuukanOO/seelf\n\nAPP=\"seelf\"\nvar_tags=\"${var_tags:-server;docker}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/seelf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"seelf\" \"YuukanOO/seelf\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop seelf\n    msg_info \"Stopped Service\"\n\n    msg_info \"Updating seelf\"\n    cd /opt/seelf \n    $STD make build\n    msg_ok \"Updated seelf\"\n\n    msg_info \"Starting Service\"\n    systemctl start seelf\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/seerr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.seerr.dev/\n\nAPP=\"Seerr\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/seerr && ! -d /opt/jellyseerr && ! -d /opt/overseerr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Start Migration from Jellyseerr\n  if [[ -f /etc/systemd/system/jellyseerr.service ]]; then\n    msg_info \"Stopping Jellyseerr\"\n    $STD systemctl stop jellyseerr || true\n    $STD systemctl disable jellyseerr || true\n    [ -f /etc/systemd/system/jellyseerr.service ] && rm -f /etc/systemd/system/jellyseerr.service\n    msg_ok \"Stopped Jellyseerr\"\n\n    msg_info \"Creating Backup (Patience)\"\n    tar -czf /opt/jellyseerr_backup_$(date +%Y%m%d_%H%M%S).tar.gz -C /opt jellyseerr\n    msg_ok \"Created Backup\"\n\n    msg_info \"Migrating Jellyseerr to seerr\"\n    [ -d /opt/jellyseerr ] && mv /opt/jellyseerr /opt/seerr\n    [ -d /etc/jellyseerr ] && mv /etc/jellyseerr /etc/seerr\n    [ -f /etc/seerr/jellyseerr.conf ] && mv /etc/seerr/jellyseerr.conf /etc/seerr/seerr.conf\n    cat <<EOF >/etc/systemd/system/seerr.service\n[Unit]\nDescription=Seerr Service\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nEnvironmentFile=/etc/seerr/seerr.conf\nEnvironment=NODE_ENV=production\nType=exec\nRestart=on-failure\nWorkingDirectory=/opt/seerr\nExecStart=/usr/bin/node dist/index.js\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    systemctl enable -q --now seerr\n    msg_ok \"Migrated Jellyserr to Seerr\"\n  fi\n  # END Jellyseerr Migration\n\n  # Start Migration from Overseerr\n  if [[ -f /etc/systemd/system/overseerr.service ]]; then\n    msg_info \"Stopping Overseerr\"\n    $STD systemctl stop overseerr || true\n    $STD systemctl disable overseerr || true\n    [ -f /etc/systemd/system/overseerr.service ] && rm -f /etc/systemd/system/overseerr.service\n    msg_ok \"Stopped Overseerr\"\n\n    msg_info \"Creating Backup (Patience)\"\n    tar -czf /opt/overseerr_backup_$(date +%Y%m%d_%H%M%S).tar.gz -C /opt overseerr\n    msg_ok \"Created Backup\"\n\n    msg_info \"Migrating Overseerr to seerr\"\n    [ -d /opt/overseerr ] && mv /opt/overseerr /opt/seerr\n    mkdir -p /etc/seerr\n    cat <<EOF >/etc/seerr/seerr.conf\n## Seerr's default port is 5055, if you want to use both, change this.\n## specify on which port to listen\nPORT=5055\n\n## specify on which interface to listen, by default seerr listens on all interfaces\n#HOST=127.0.0.1\n\n## Uncomment if you want to force Node.js to resolve IPv4 before IPv6 (advanced users only)\n# FORCE_IPV4_FIRST=true\nEOF\n    cat <<EOF >/etc/systemd/system/seerr.service\n[Unit]\nDescription=Seerr Service\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nEnvironmentFile=/etc/seerr/seerr.conf\nEnvironment=NODE_ENV=production\nType=exec\nRestart=on-failure\nWorkingDirectory=/opt/seerr\nExecStart=/usr/bin/node dist/index.js\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    systemctl enable -q --now seerr\n    msg_ok \"Migrated Overseerr to Seerr\"\n  fi\n  # END Overseerr Migration\n\n  if check_for_gh_release \"seerr\" \"seerr-team/seerr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop seerr\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    cp -a /opt/seerr/config /opt/seerr_backup\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"seerr\" \"seerr-team/seerr\" \"tarball\"\n\n    ensure_dependencies build-essential python3-setuptools\n\n    msg_info \"Updating PNPM Version\"\n    pnpm_desired=$(grep -Po '\"pnpm\":\\s*\"\\K[^\"]+' /opt/seerr/package.json)\n    NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$pnpm_desired\" setup_nodejs\n    msg_ok \"Updated PNPM Version\"\n\n    msg_info \"Updating Seerr\"\n    cd /opt/seerr\n    rm -rf dist .next node_modules\n    export CYPRESS_INSTALL_BINARY=0\n    $STD pnpm install --frozen-lockfile\n    export NODE_OPTIONS=\"--max-old-space-size=3072\"\n    $STD pnpm build\n    msg_ok \"Updated Seerr\"\n\n    msg_info \"Restoring Backup\"\n    rm -rf /opt/seerr/config\n    mv /opt/seerr_backup /opt/seerr/config\n    msg_ok \"Restored Backup\"\n\n    msg_info \"Starting Service\"\n    systemctl start seerr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5055${CL}\"\n"
  },
  {
    "path": "ct/semaphore.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://semaphoreui.com/\n\nAPP=\"Semaphore\"\nvar_tags=\"${var_tags:-dev_ops}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/systemd/system/semaphore.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"semaphore\" \"semaphoreui/semaphore\"; then\n    if [[ -f /opt/semaphore/semaphore_db.bolt ]]; then\n      msg_warn \"WARNING: Due to bugs with BoltDB database, update script will move your application\"\n      msg_warn \"to use SQLite database instead. Make sure you have a backup of your data!\"\n      echo \"\"\n      read -r -p \"${TAB3}Do you want to continue? (y/N): \" CONFIRM\n      if [[ ! \"$CONFIRM\" =~ ^[Yy]$ ]]; then\n        exit 0\n      else\n        msg_info \"Moving from BoltDB to SQLite\"\n        sed -i \\\n          -e 's|\"bolt\": {|\"sqlite\": {|' \\\n          -e 's|/semaphore_db.bolt\"|/database.sqlite\"|' \\\n          -e '/semaphore_db.bolt/d' \\\n          -e '/\"dialect\"/d' \\\n          -e '/^  },$/a\\  \"dialect\": \"sqlite\",' \\\n          /opt/semaphore/config.json\n        msg_ok \"Moved from BoltDB to SQLite\"\n      fi\n    fi\n\n    msg_info \"Stopping Service\"\n    systemctl stop semaphore\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"semaphore\" \"semaphoreui/semaphore\" \"binary\" \"latest\" \"/opt/semaphore\" \"semaphore_*_linux_arm64.deb\"\n\n    if [[ -f /opt/semaphore/semaphore_db.bolt ]]; then\n      $STD semaphore migrate --from-boltdb /opt/semaphore/semaphore_db.bolt --config /opt/semaphore/config.json\n      rm -f /opt/semaphore/semaphore_db.bolt\n    fi\n\n    msg_info \"Starting Service\"\n    systemctl start semaphore\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/sftpgo.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sftpgo.com/\n\nAPP=\"SFTPGo\"\nvar_tags=\"${var_tags:-ftp;sftp}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  \n  msg_info \"Updating SFTPGo\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated SFTPGo\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/web/admin${CL}\"\n"
  },
  {
    "path": "ct/shelfmark.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/calibrain/shelfmark\n\nAPP=\"shelfmark\"\nvar_tags=\"${var_tags:-ebooks}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/shelfmark ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n  PYTHON_VERSION=\"3.14\" setup_uv\n\n  if check_for_gh_release \"shelfmark\" \"calibrain/shelfmark\"; then\n    msg_info \"Stopping Service(s)\"\n    systemctl stop shelfmark\n    [[ -f /etc/systemd/system/chromium.service ]] && systemctl stop chromium\n    msg_ok \"Stopped Service(s)\"\n\n    [[ -f /etc/systemd/system/flaresolverr.service ]] && if check_for_gh_release \"flaresolverr\" \"Flaresolverr/Flaresolverr\"; then\n      msg_info \"Stopping FlareSolverr service\"\n      systemctl stop flaresolverr\n      msg_ok \"Stopped FlareSolverr service\"\n\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\" \"prebuild\" \"latest\" \"/opt/flaresolverr\" \"flaresolverr_linux_x64.tar.gz\"\n\n      msg_info \"Starting FlareSolverr Service\"\n      systemctl start flaresolverr\n      msg_ok \"Started FlareSolverr Service\"\n      msg_ok \"Updated FlareSolverr\"\n    fi\n\n    cp /opt/shelfmark/start.sh /opt/start.sh.bak\n    if command -v chromedriver &>/dev/null; then\n      $STD apt remove -y chromium-driver\n    fi\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"shelfmark\" \"calibrain/shelfmark\" \"tarball\" \"latest\" \"/opt/shelfmark\"\n    RELEASE_VERSION=$(cat \"$HOME/.shelfmark\")\n\n    msg_info \"Updating Shelfmark\"\n    export VIRTUAL_ENV=/opt/shelfmark/venv\n    sed -i \"s/^RELEASE_VERSION=.*/RELEASE_VERSION=$RELEASE_VERSION/\" /etc/shelfmark/.env\n    cd /opt/shelfmark/src/frontend\n    $STD npm ci\n    $STD npm run build\n    mv /opt/shelfmark/src/frontend/dist /opt/shelfmark/frontend-dist\n    cd /opt/shelfmark\n    $STD uv venv -c ./venv\n    $STD source ./venv/bin/activate\n    if [[ $(sed -n '/_BYPASS=/s/[^=]*=//p' /etc/shelfmark/.env) == \"true\" ]] && [[ $(sed -n '/BYPASSER=/s/[^=]*=//p' /etc/shelfmark/.env) == \"false\" ]]; then\n      $STD uv sync --active --locked --no-default-groups --extra browser\n    else\n      $STD uv sync --active --locked --no-default-groups\n    fi\n    mv /opt/start.sh.bak /opt/shelfmark/start.sh\n    msg_ok \"Updated Shelfmark\"\n\n    msg_info \"Starting Service(s)\"\n    systemctl start shelfmark\n    [[ -f /etc/systemd/system/chromium.service ]] && systemctl start chromium\n    msg_ok \"Started Service(s)\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8084${CL}\"\n"
  },
  {
    "path": "ct/shinobi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://shinobi.video/\n\nAPP=\"Shinobi\"\nvar_tags=\"${var_tags:-nvr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/Shinobi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  msg_info \"Updating Shinobi\"\n  cd /opt/Shinobi\n  $STD sh UPDATE.sh\n  $STD pm2 flush\n  $STD pm2 restart camera\n  $STD pm2 restart cron\n  msg_ok \"Updated Shinobi\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/super${CL}\"\n"
  },
  {
    "path": "ct/shlink.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://shlink.io/\n\nAPP=\"Shlink\"\nvar_tags=\"${var_tags:-url-shortener;analytics;php}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/shlink ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"shlink\" \"shlinkio/shlink\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop shlink\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/shlink/.env /opt/shlink.env.bak\n    cp -r /opt/shlink/data /opt/shlink_data_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"shlink\" \"shlinkio/shlink\" \"prebuild\" \"latest\" \"/opt/shlink\" \"shlink*_php8.5_dist.zip\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/shlink.env.bak /opt/shlink/.env\n    rm -f /opt/shlink.env.bak\n    cp -r /opt/shlink_data_backup/. /opt/shlink/data\n    rm -rf /opt/shlink_data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating Application\"\n    cd /opt/shlink\n    $STD php ./vendor/bin/rr get --no-interaction --location bin/\n    chmod +x bin/rr\n    set -a\n    source /opt/shlink/.env\n    set +a\n    $STD php vendor/bin/shlink-installer init --no-interaction --clear-db-cache --skip-download-geolite\n    msg_ok \"Updated Application\"\n\n    msg_info \"Starting Service\"\n    systemctl start shlink\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n\n  if [[ -d /opt/shlink-web-client ]]; then\n    if check_for_gh_release \"shlink-web-client\" \"shlinkio/shlink-web-client\"; then\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"shlink-web-client\" \"shlinkio/shlink-web-client\" \"prebuild\" \"latest\" \"/opt/shlink-web-client\" \"shlink-web-client_*_dist.zip\"\n      msg_ok \"Updated Web Client\"\n    fi\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access Shlink Web Client using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\necho -e \"${INFO}${YW} Shlink HTTP API:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/signoz.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://signoz.io/\n\nAPP=\"SigNoz\"\nvar_tags=\"${var_tags:-notes}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/signoz ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"signoz\" \"SigNoz/signoz\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop signoz\n    systemctl stop signoz-otel-collector\n    msg_ok \"Stopped Services\"\n\n    fetch_and_deploy_gh_release \"signoz\" \"SigNoz/signoz\" \"prebuild\" \"latest\" \"/opt/signoz\" \"signoz-community_linux_arm64.tar.gz\"\n    fetch_and_deploy_gh_release \"signoz-otel-collector\" \"SigNoz/signoz-otel-collector\" \"prebuild\" \"latest\" \"/opt/signoz-otel-collector\" \"signoz-otel-collector_linux_arm64.tar.gz\"\n    fetch_and_deploy_gh_release \"signoz-schema-migrator\" \"SigNoz/signoz-otel-collector\" \"prebuild\" \"latest\" \"/opt/signoz-schema-migrator\" \"signoz-schema-migrator_linux_arm64.tar.gz\"\n\n    msg_info \"Updating SigNoz\"\n    cd /opt/signoz-schema-migrator/bin \n    $STD ./signoz-schema-migrator sync --dsn=\"tcp://localhost:9000?password=\" --replication=true --up=\n    $STD ./signoz-schema-migrator async --dsn=\"tcp://localhost:9000?password=\" --replication=true --up=\n    msg_ok \"Updated SigNoz\"\n\n    msg_info \"Starting Services\"\n    systemctl start signoz-otel-collector\n    systemctl start signoz\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/silverbullet.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dominik Siebel (dsiebel)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://silverbullet.md\n\nAPP=\"Silverbullet\"\nvar_tags=\"${var_tags:-notes}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_disk=\"${var_disk:-2}\"\nvar_ram=\"${var_ram:-512}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\n\nheader_info \"${APP}\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/silverbullet ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"silverbullet\" \"silverbulletmd/silverbullet\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop silverbullet\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"silverbullet\" \"silverbulletmd/silverbullet\" \"prebuild\" \"latest\" \"/opt/silverbullet/bin\" \"silverbullet-server-linux-aarch64.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl start silverbullet\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/slskd.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/slskd/slskd, https://github.com/mrusse/soularr\n\nAPP=\"slskd\"\nvar_tags=\"${var_tags:-arr;p2p}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/slskd ]]; then\n    msg_error \"No Slskd Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Slskd\" \"slskd/slskd\"; then\n    msg_info \"Stopping Service(s)\"\n    systemctl stop slskd\n    [[ -f /etc/systemd/system/soularr.service ]] && systemctl stop soularr.timer soularr.service\n    msg_ok \"Stopped Service(s)\"\n\n    msg_info \"Backing up config\"\n    cp /opt/slskd/config/slskd.yml /opt/slskd.yml.bak\n    msg_ok \"Backed up config\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Slskd\" \"slskd/slskd\" \"prebuild\" \"latest\" \"/opt/slskd\" \"slskd-*-linux-arm64.zip\"\n\n    msg_info \"Restoring config\"\n    mv /opt/slskd.yml.bak /opt/slskd/config/slskd.yml\n\n    # Migrate 0.25.0 breaking config key renames\n    sed -i 's/^global:/transfers:/' /opt/slskd/config/slskd.yml\n    sed -i 's/^integration:/integrations:/' /opt/slskd/config/slskd.yml\n    msg_ok \"Restored config\"\n\n    msg_info \"Starting Service(s)\"\n    systemctl start slskd\n    [[ -f /etc/systemd/system/soularr.service ]] && systemctl start soularr.timer\n    msg_ok \"Started Service(s)\"\n    msg_ok \"Updated Slskd successfully!\"\n  fi\n  [[ -d /opt/soularr ]] && if check_for_gh_release \"Soularr\" \"mrusse/soularr\"; then\n    if systemctl is-active soularr.timer >/dev/null; then\n      msg_info \"Stopping Timer and Service\"\n      systemctl stop soularr.timer soularr.service\n      msg_ok \"Stopped Timer and Service\"\n    fi\n\n    msg_info \"Backing up Soularr config\"\n    cp /opt/soularr/config.ini /opt/soularr_config.ini.bak\n    cp /opt/soularr/run.sh /opt/soularr_run.sh.bak\n    msg_ok \"Backed up Soularr config\"\n\n    PYTHON_VERSION=\"3.11\" setup_uv\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Soularr\" \"mrusse/soularr\" \"tarball\" \"latest\" \"/opt/soularr\"\n    msg_info \"Updating Soularr\"\n    cd /opt/soularr\n    $STD uv venv -c venv\n    $STD source venv/bin/activate\n    $STD uv pip install -r requirements.txt\n    deactivate\n    msg_ok \"Updated Soularr\"\n\n    msg_info \"Restoring Soularr config\"\n    mv /opt/soularr_config.ini.bak /opt/soularr/config.ini\n    mv /opt/soularr_run.sh.bak /opt/soularr/run.sh\n    msg_ok \"Restored Soularr config\"\n\n    msg_info \"Starting Soularr Timer\"\n    systemctl restart soularr.timer\n    msg_ok \"Started Soularr Timer\"\n    msg_ok \"Updated Soularr successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5030${CL}\"\n"
  },
  {
    "path": "ct/smokeping.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://oss.oetiker.ch/smokeping/\n\nAPP=\"SmokePing\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if ! command -v smokeping &>/dev/null; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    msg_info \"Updating ${APP}\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/smokeping${CL}\"\n"
  },
  {
    "path": "ct/snipeit.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://snipeitapp.com/\n\nAPP=\"SnipeIT\"\nvar_tags=\"${var_tags:-asset-management;foss}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/snipe-it ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if ! grep -q \"client_max_body_size[[:space:]]\\+100M;\" /etc/nginx/conf.d/snipeit.conf; then\n    sed -i '/index index.php;/i \\        client_max_body_size 100M;' /etc/nginx/conf.d/snipeit.conf\n  fi\n\n  if check_for_gh_release \"snipe-it\" \"grokability/snipe-it\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop nginx\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Creating Backup\"\n    mv /opt/snipe-it /opt/snipe-it-backup\n    msg_ok \"Created Backup\"\n\n    fetch_and_deploy_gh_release \"snipe-it\" \"grokability/snipe-it\" \"tarball\"\n    [[ \"$(php -v 2>/dev/null)\" == PHP\\ 8.2* ]] && PHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULE=\"ldap,soap,xsl\" setup_php\n    sed -i 's/php8.2/php8.3/g' /etc/nginx/conf.d/snipeit.conf\n    setup_composer\n\n    msg_info \"Updating Snipe-IT\"\n    $STD apt update\n    $STD apt -y upgrade\n    cp /opt/snipe-it-backup/.env /opt/snipe-it/.env\n    cp -r /opt/snipe-it-backup/public/uploads/. /opt/snipe-it/public/uploads/\n    cp -r /opt/snipe-it-backup/storage/private_uploads/. /opt/snipe-it/storage/private_uploads/\n    cd /opt/snipe-it/\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-dev --optimize-autoloader --no-interaction\n    $STD composer dump-autoload\n    $STD php artisan migrate --force\n    $STD php artisan config:clear\n    $STD php artisan route:clear\n    $STD php artisan cache:clear\n    $STD php artisan view:clear\n    chown -R www-data: /opt/snipe-it\n    chmod -R 755 /opt/snipe-it\n    rm -rf /opt/snipe-it-backup\n    msg_ok \"Updated Snipe-IT\"\n\n    msg_info \"Starting Service\"\n    systemctl start nginx\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/snowshare.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: TuroYT\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TuroYT/snowshare\n\nAPP=\"SnowShare\"\nvar_tags=\"${var_tags:-file-sharing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/snowshare ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  if check_for_gh_release \"snowshare\" \"TuroYT/snowshare\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop snowshare\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up uploads\"\n    [ -d /opt/snowshare/uploads ] && cp -a /opt/snowshare/uploads /opt/.snowshare_uploads_backup\n    msg_ok \"Uploads backed up\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"snowshare\" \"TuroYT/snowshare\" \"tarball\"\n\n    msg_info \"Restoring uploads\"\n    [ -d /opt/.snowshare_uploads_backup ] && rm -rf /opt/snowshare/uploads && cp -a /opt/.snowshare_uploads_backup /opt/snowshare/uploads\n    msg_ok \"Uploads restored\"\n\n    msg_info \"Updating Snowshare\"\n    cd /opt/snowshare\n    $STD npm ci\n    $STD npx prisma generate\n    $STD npm run build\n    msg_ok \"Updated Snowshare\"\n\n    msg_info \"Starting Service\"\n    systemctl start snowshare\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/solidtime.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.solidtime.io/\n\nAPP=\"SolidTime\"\nvar_tags=\"${var_tags:-time-tracking;productivity;business}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/solidtime ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"solidtime\" \"solidtime-io/solidtime\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop caddy\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/solidtime/.env /opt/solidtime.env.bak\n    cp -r /opt/solidtime/storage /opt/solidtime_storage_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"solidtime\" \"solidtime-io/solidtime\" \"tarball\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/solidtime.env.bak /opt/solidtime/.env\n    rm -f /opt/solidtime.env.bak\n    cp -r /opt/solidtime_storage_backup/. /opt/solidtime/storage\n    rm -rf /opt/solidtime_storage_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating Application\"\n    cd /opt/solidtime\n    $STD composer install --no-dev --optimize-autoloader\n    $STD npm install\n    $STD npm run build\n    $STD php artisan migrate --force\n    $STD php artisan optimize:clear\n    chown -R www-data:www-data /opt/solidtime\n    msg_ok \"Updated Application\"\n\n    msg_info \"Starting Services\"\n    systemctl start caddy\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\necho -e \"${INFO}${YW}HTTPS is not enabled by default (use domain + reverse proxy/TLS if needed).${CL}\"\n"
  },
  {
    "path": "ct/sonarqube.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: prop4n\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.sonarsource.com/sonarqube-server\n\nAPP=\"SonarQube\"\nvar_tags=\"${var_tags:-automation}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-25}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/sonarqube ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"sonarqube\" \"SonarSource/sonarqube\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop sonarqube\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    BACKUP_DIR=\"/opt/sonarqube-backup\"\n    mv /opt/sonarqube ${BACKUP_DIR}\n    msg_ok \"Created Backup\"\n\n    msg_info \"Updating SonarQube\"\n    temp_file=$(mktemp)\n    RELEASE=$(get_latest_github_release \"SonarSource/sonarqube\")\n    curl -fsSL \"https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${RELEASE}.zip\" -o $temp_file\n    unzip -q \"$temp_file\" -d /opt\n    rm -f \"$temp_file\"\n    mv /opt/sonarqube-${RELEASE} /opt/sonarqube\n    echo \"${RELEASE}\" > ~/.sonarqube\n    msg_ok \"Updated SonarQube\"\n\n    msg_info \"Restoring Backup\"\n    cp -rp ${BACKUP_DIR}/data/ /opt/sonarqube/data/\n    cp -rp ${BACKUP_DIR}/extensions/ /opt/sonarqube/extensions/\n    cp -p ${BACKUP_DIR}/conf/sonar.properties /opt/sonarqube/conf/sonar.properties\n    rm -rf ${BACKUP_DIR}\n    chown -R sonarqube:sonarqube /opt/sonarqube\n    msg_ok \"Restored Backup\"\n\n    msg_info \"Starting Service\"\n    systemctl start sonarqube\n    msg_ok \"Service Started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/sonarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sonarr.tv/\n\nAPP=\"Sonarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /var/lib/sonarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  apt-get install -y libicu76 &>/dev/null\n  msg_info \"Stopping Service\"\n  systemctl stop sonarr\n  msg_ok \"Stopped Service\"\n\n  CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Sonarr\" \"Sonarr/Sonarr\" \"prebuild\" \"latest\" \"/opt/Sonarr\" \"Sonarr.main.*.linux-arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start sonarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8989${CL}\"\n"
  },
  {
    "path": "ct/sonobarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: GoldenSpringness\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dodelidoo-Labs/sonobarr\n\nAPP=\"sonobarr\"\nvar_tags=\"${var_tags:-music;discovery}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d \"/opt/sonobarr\" ]]; then\n    msg_error \"No sonobarr Installation Found!\"\n    exit\n  fi\n\n  PYTHON_VERSION=\"3.12\" setup_uv\n\n  if check_for_gh_release \"sonobarr\" \"Dodelidoo-Labs/sonobarr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop sonobarr\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"sonobarr\" \"Dodelidoo-Labs/sonobarr\" \"tarball\"\n\n    msg_info \"Updating sonobarr\"\n    $STD uv venv -c /opt/sonobarr/venv\n    $STD source /opt/sonobarr/venv/bin/activate\n    $STD uv pip install --no-cache-dir -r /opt/sonobarr/requirements.txt\n    sed -i \"/release_version/s/=.*/=$(cat ~/.sonobarr)/\" /etc/sonobarr/.env\n    msg_ok \"Updated sonobarr\"\n\n    msg_info \"Starting Service\"\n    systemctl start sonobarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}sonobarr setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}\"\n"
  },
  {
    "path": "ct/soulsync.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/Nezreka/SoulSync\n\nAPP=\"SoulSync\"\nvar_tags=\"${var_tags:-music;automation;media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f ~/.soulsync ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"soulsync\" \"Nezreka/SoulSync\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop soulsync\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    mv /opt/soulsync/config /opt/soulsync-config.bak\n    mv /opt/soulsync/data /opt/soulsync-data.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"soulsync\" \"Nezreka/SoulSync\" \"tarball\"\n\n    msg_info \"Updating Python Dependencies\"\n    cd /opt/soulsync\n    $STD uv venv --clear /opt/soulsync/.venv --python 3.11\n    $STD /opt/soulsync/.venv/bin/pip install -r requirements.txt\n    msg_ok \"Updated Python Dependencies\"\n\n    mv /opt/soulsync-config.bak /opt/soulsync/config\n    mv /opt/soulsync-data.bak /opt/soulsync/data\n\n    msg_info \"Starting Service\"\n    systemctl start soulsync\n    msg_ok \"Started Service\"\n    msg_ok \"Updated ${APP}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8008${CL}\"\n"
  },
  {
    "path": "ct/sparkyfitness.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Tom Frenzel (tomfrenzel)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/CodeWithCJ/SparkyFitness\n\nAPP=\"SparkyFitness\"\nvar_tags=\"${var_tags:-health;fitness}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/sparkyfitness ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"sparkyfitness\" \"CodeWithCJ/SparkyFitness\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop sparkyfitness-server nginx\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up data\"\n    mkdir -p /opt/sparkyfitness_backup\n    if [[ -d /opt/sparkyfitness/SparkyFitnessServer/uploads ]]; then\n      cp -r /opt/sparkyfitness/SparkyFitnessServer/uploads /opt/sparkyfitness_backup/\n    fi\n    if [[ -d /opt/sparkyfitness/SparkyFitnessServer/backup ]]; then\n      cp -r /opt/sparkyfitness/SparkyFitnessServer/backup /opt/sparkyfitness_backup/\n    fi\n    msg_ok \"Backed up data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"sparkyfitness\" \"CodeWithCJ/SparkyFitness\" \"tarball\"\n\n    PNPM_VERSION=\"$(jq -r '.packageManager | split(\"@\")[1]' /opt/sparkyfitness/package.json)\"\n    NODE_VERSION=\"25\" NODE_MODULE=\"pnpm@${PNPM_VERSION}\" setup_nodejs\n\n    msg_info \"Updating Sparky Fitness Backend\"\n    cd /opt/sparkyfitness/SparkyFitnessServer\n    $STD pnpm install\n    msg_ok \"Updated Sparky Fitness Backend\"\n\n    msg_info \"Updating Sparky Fitness Frontend (Patience)\"\n    cd /opt/sparkyfitness\n    $STD pnpm install\n    cd /opt/sparkyfitness/SparkyFitnessFrontend\n    $STD pnpm run build\n    cp -a /opt/sparkyfitness/SparkyFitnessFrontend/dist/. /var/www/sparkyfitness/\n    msg_ok \"Updated Sparky Fitness Frontend\"\n\n    msg_info \"Refreshing SparkyFitness Service\"\n    cat <<EOF >/etc/systemd/system/sparkyfitness-server.service\n  [Unit]\n  Description=SparkyFitness Backend Service\n  After=network.target postgresql.service\n  Requires=postgresql.service\n\n  [Service]\n  Type=simple\n  WorkingDirectory=/opt/sparkyfitness/SparkyFitnessServer\n  EnvironmentFile=/etc/sparkyfitness/.env\n  ExecStart=/opt/sparkyfitness/SparkyFitnessServer/node_modules/.bin/tsx SparkyFitnessServer.js\n  Restart=always\n  RestartSec=5\n\n  [Install]\n  WantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    msg_ok \"Refreshed SparkyFitness Service\"\n\n    msg_info \"Restoring data\"\n    cp -r /opt/sparkyfitness_backup/. /opt/sparkyfitness/SparkyFitnessServer/\n    rm -rf /opt/sparkyfitness_backup\n    msg_ok \"Restored data\"\n\n    msg_info \"Starting Services\"\n    $STD systemctl start sparkyfitness-server nginx\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/speedtest-tracker.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: AlphaLawless\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alexjustesen/speedtest-tracker\n\nAPP=\"Speedtest-Tracker\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/speedtest-tracker ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"speedtest-tracker\" \"alexjustesen/speedtest-tracker\"; then\n    PHP_VERSION=\"8.4\" PHP_FPM=\"YES\" setup_php\n    setup_composer\n    NODE_VERSION=\"22\" setup_nodejs\n    setcap cap_net_raw+ep /bin/ping\n\n    msg_info \"Stopping Service\"\n    systemctl stop speedtest-tracker\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating Speedtest CLI\"\n    $STD apt update\n    $STD apt --only-upgrade install -y speedtest\n    msg_ok \"Updated Speedtest CLI\"\n\n    msg_info \"Creating Backup\"\n    cp -r /opt/speedtest-tracker /opt/speedtest-tracker-backup\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_gh_release \"speedtest-tracker\" \"alexjustesen/speedtest-tracker\" \"tarball\" \"latest\" \"/opt/speedtest-tracker\"\n\n    msg_info \"Updating Speedtest Tracker\"\n    cp -r /opt/speedtest-tracker-backup/.env /opt/speedtest-tracker/.env\n    cd /opt/speedtest-tracker\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --optimize-autoloader --no-dev\n    $STD npm ci\n    $STD npm run build\n    $STD php artisan migrate --force\n    $STD php artisan config:clear\n    $STD php artisan cache:clear\n    $STD php artisan view:clear\n    chown -R www-data:www-data /opt/speedtest-tracker\n    chmod -R 755 /opt/speedtest-tracker/storage\n    chmod -R 755 /opt/speedtest-tracker/bootstrap/cache\n    msg_ok \"Updated Speedtest Tracker\"\n\n    msg_info \"Starting Service\"\n    systemctl start speedtest-tracker\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/split-pro.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: johanngrobe\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/oss-apps/split-pro\n\nAPP=\"Split-Pro\"\nvar_tags=\"${var_tags:-finance;expense-sharing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/split-pro ]]; then\n    msg_error \"No Split Pro Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"split-pro\" \"oss-apps/split-pro\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop split-pro\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/split-pro/.env /opt/split-pro.env\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"split-pro\" \"oss-apps/split-pro\" \"tarball\"\n\n    msg_info \"Building Application\"\n    cd /opt/split-pro\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm build\n    cp /opt/split-pro.env /opt/split-pro/.env\n    rm -f /opt/split-pro.env\n    ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads\n    $STD pnpm exec prisma migrate deploy\n    msg_ok \"Built Application\"\n\n    msg_info \"Starting Service\"\n    systemctl start split-pro\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/splunk-enterprise.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rcastley\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.splunk.com/en_us/download.html\n\nAPP=\"Splunk-Enterprise\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-40}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/splunk ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_error \"Currently we don't provide an update function for this ${APP}.\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW}Access the Splunk Enterprise Web interface using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/spoolman.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Donkie/Spoolman\n\nAPP=\"Spoolman\"\nvar_tags=\"${var_tags:-3d-printing}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/spoolman ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  PYTHON_VERSION=\"3.14\" setup_uv\n\n  if check_for_gh_release \"spoolman\" \"Donkie/Spoolman\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop spoolman\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    [ -d /opt/spoolman_bak ] && rm -rf /opt/spoolman_bak\n    mv /opt/spoolman /opt/spoolman_bak\n    msg_ok \"Created Backup\"\n\n    fetch_and_deploy_gh_release \"spoolman\" \"Donkie/Spoolman\" \"prebuild\" \"latest\" \"/opt/spoolman\" \"spoolman.zip\"\n\n    msg_info \"Updating Spoolman\"\n    cd /opt/spoolman\n    $STD uv sync --locked --no-install-project\n    $STD uv sync --locked\n    cp /opt/spoolman_bak/.env /opt/spoolman\n    sed -i 's|^ExecStart=.*|ExecStart=/usr/bin/bash /opt/spoolman/scripts/start.sh|' /etc/systemd/system/spoolman.service\n    msg_ok \"Updated Spoolman\"\n\n    msg_info \"Starting Service\"\n    systemctl start spoolman\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7912${CL}\"\n"
  },
  {
    "path": "ct/sportarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Sportarr/Sportarr\n\nAPP=\"Sportarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/sportarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"sportarr\" \"Sportarr/Sportarr\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop sportarr\n    msg_ok \"Stopped Services\"\n\n    fetch_and_deploy_gh_release \"sportarr\" \"Sportarr/Sportarr\" \"prebuild\" \"latest\" \"/opt/sportarr\" \"Sportarr-linux-arm64-*.tar.gz\"\n\n    msg_info \"Starting Services\"\n    systemctl start sportarr\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1867${CL}\"\n"
  },
  {
    "path": "ct/sqlserver2022.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.microsoft.com/en-us/sql-server/sql-server-2022\n\nAPP=\"SQL Server 2022\"\nvar_tags=\"${var_tags:-sql}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-22.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/mssql ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating SQL Server 2022\"\n    rm -f /etc/profile.d/debuginfod.sh /etc/profile.d/debuginfod.csh\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:1433${CL}\"\n"
  },
  {
    "path": "ct/sqlserver2025.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.microsoft.com/en-us/sql-server/sql-server-2025\n\nAPP=\"SQL Server 2025\"\nvar_tags=\"${var_tags:-sql;database}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/mssql ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating SQL Server 2025\"\n    rm -f /etc/profile.d/debuginfod.sh /etc/profile.d/debuginfod.csh\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:1433${CL}\"\n"
  },
  {
    "path": "ct/step-ca.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joerg Heinemann (heinemannj)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/smallstep/certificates\n\nAPP=\"step-ca\"\nvar_tags=\"${var_tags:-certificate-authority;pki;acme-server}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apt/sources.list.d/smallstep.sources ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating step-ca and step-cli\"\n  $STD apt update\n  $STD apt upgrade -y step-ca step-cli\n\n  # Patch for making $STD happy (/usr/bin/step is a symlink to /usr/bin/step-cli)\n  STEPBIN=\"$(which step)\"\n  rm -f \"$STEPBIN\"\n  cp -f \"$(which step-cli)\" \"$STEPBIN\"\n\n  $STD systemctl restart step-ca\n  msg_ok \"Updated step-ca and step-cli\"\n\n  if check_for_gh_release \"step-badger\" \"lukasz-lobocki/step-badger\"; then\n    fetch_and_deploy_gh_release \"step-badger\" \"lukasz-lobocki/step-badger\" \"prebuild\" \"latest\" \"/opt/step-badger\" \"step-badger_Linux_x86_64.tar.gz\"\n    msg_ok \"Updated step-badger\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}/provisioners${CL}\"\n"
  },
  {
    "path": "ct/stirling-pdf.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.stirlingpdf.com/\n\nAPP=\"Stirling-PDF\"\nvar_tags=\"${var_tags:-pdf-editor}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/Stirling-PDF ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"stirling-pdf\" \"Stirling-Tools/Stirling-PDF\"; then\n    if [[ ! -f /etc/systemd/system/unoserver.service ]]; then\n      msg_custom \"⚠️ \" \"\\e[33m\" \"Legacy installation detected – please recreate the container using the latest install script.\"\n      exit 0\n    fi\n\n    PYTHON_VERSION=\"3.12\" setup_uv\n    JAVA_VERSION=\"25\" setup_java\n\n    msg_info \"Stopping Services\"\n    systemctl stop stirlingpdf libreoffice-listener unoserver\n    msg_ok \"Stopped Services\"\n\n    if [[ -f ~/.Stirling-PDF-login ]]; then\n      USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"stirling-pdf\" \"Stirling-Tools/Stirling-PDF\" \"singlefile\" \"latest\" \"/opt/Stirling-PDF\" \"Stirling-PDF-with-login.jar\"\n      mv /opt/Stirling-PDF/Stirling-PDF-with-login.jar /opt/Stirling-PDF/Stirling-PDF.jar\n    else\n      USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"stirling-pdf\" \"Stirling-Tools/Stirling-PDF\" \"singlefile\" \"latest\" \"/opt/Stirling-PDF\" \"Stirling-PDF.jar\"\n    fi\n\n    msg_info \"Refreshing Font Cache\"\n    $STD fc-cache -fv\n    msg_ok \"Font Cache Updated\"\n\n    msg_info \"Starting Services\"\n    systemctl start stirlingpdf libreoffice-listener unoserver\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/storybook.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/storybookjs/storybook\n\nAPP=\"Storybook\"\nvar_tags=\"${var_tags:-dev-tools;frontend;ui}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/storybook/.projectpath ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  PROJECT_PATH=$(cat /opt/storybook/.projectpath)\n\n  if [[ ! -d \"$PROJECT_PATH\" ]]; then\n    msg_error \"Project directory not found: $PROJECT_PATH\"\n    exit\n  fi\n\n  msg_info \"Updating Storybook\"\n  cd \"$PROJECT_PATH\"\n  $STD npx storybook@latest upgrade --yes\n  msg_ok \"Updated Storybook\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6006${CL}\"\n"
  },
  {
    "path": "ct/storyteller.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gitlab.com/storyteller-platform/storyteller\n\nAPP=\"Storyteller\"\nvar_tags=\"${var_tags:-media;ebook;audiobook}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-10240}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/storyteller ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gl_release \"storyteller\" \"storyteller-platform/storyteller\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop storyteller\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/storyteller/.env /opt/storyteller_env.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gl_release \"storyteller\" \"storyteller-platform/storyteller\" \"tarball\" \"latest\" \"/opt/storyteller\"\n\n    msg_info \"Restoring Configuration\"\n    mv /opt/storyteller_env.bak /opt/storyteller/.env\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Rebuilding Storyteller\"\n    cd /opt/storyteller\n    export NODE_OPTIONS=\"--max-old-space-size=4096\"\n    $STD yarn install --network-timeout 600000\n    $STD gcc -g -fPIC -rdynamic -shared web/sqlite/uuid.c -o web/sqlite/uuid.c.so\n    export CI=1\n    export NODE_ENV=production\n    export NEXT_TELEMETRY_DISABLED=1\n    export SQLITE_NATIVE_BINDING=/opt/storyteller/node_modules/better-sqlite3/build/Release/better_sqlite3.node\n    $STD yarn workspaces foreach -Rpt --from @storyteller-platform/web --exclude @storyteller-platform/eslint run build\n    mkdir -p /opt/storyteller/web/.next/standalone/web/.next/static\n    cp -rT /opt/storyteller/web/.next/static /opt/storyteller/web/.next/standalone/web/.next/static\n    if [[ -d /opt/storyteller/web/public ]]; then\n      mkdir -p /opt/storyteller/web/.next/standalone/web/public\n      cp -rT /opt/storyteller/web/public /opt/storyteller/web/.next/standalone/web/public\n    fi\n    mkdir -p /opt/storyteller/web/.next/standalone/web/migrations\n    cp -rT /opt/storyteller/web/migrations /opt/storyteller/web/.next/standalone/web/migrations\n    mkdir -p /opt/storyteller/web/.next/standalone/web/sqlite\n    cp -rT /opt/storyteller/web/sqlite /opt/storyteller/web/.next/standalone/web/sqlite\n    ln -sf /opt/storyteller/.env /opt/storyteller/web/.next/standalone/web/.env\n    msg_ok \"Rebuilt Storyteller\"\n\n    msg_info \"Starting Service\"\n    systemctl start storyteller\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8001${CL}\"\n"
  },
  {
    "path": "ct/strapi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pespinel\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://strapi.io/\n\nAPP=\"Strapi\"\nvar_tags=\"${var_tags:-cms}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/strapi.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  msg_info \"Stopping Strapi\"\n  systemctl stop strapi\n  msg_ok \"Stopped Strapi\"\n\n  msg_info \"Updating Strapi\"\n  cd /opt/strapi\n  $STD npx @strapi/upgrade minor --yes\n  msg_ok \"Updated Strapi\"\n\n  msg_info \"Building Strapi\"\n  export NODE_OPTIONS=\"--max-old-space-size=3072\"\n  $STD npm run build\n  msg_ok \"Built Strapi\"\n\n  msg_info \"Starting Strapi\"\n  systemctl start strapi\n  msg_ok \"Started Strapi\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:1337${CL}\"\n"
  },
  {
    "path": "ct/streamlink-webui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/CrazyWolf13/streamlink-webui\n\nAPP=\"streamlink-webui\"\nvar_tags=\"${var_tags:-download;streaming}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/streamlink-webui ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"streamlink-webui\" \"CrazyWolf13/streamlink-webui\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop streamlink-webui\n    msg_info \"Stopped Service\"\n\n    NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n    setup_uv\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"streamlink-webui\" \"CrazyWolf13/streamlink-webui\" \"tarball\"\n\n    msg_info \"Updating streamlink-webui\"\n    $STD uv venv --clear /opt/streamlink-webui/backend/src/.venv\n    source /opt/streamlink-webui/backend/src/.venv/bin/activate\n    $STD uv pip install -r /opt/streamlink-webui/backend/src/requirements.txt --python=/opt/streamlink-webui/backend/src/.venv\n    cd /opt/streamlink-webui/frontend/src\n    $STD yarn install\n    $STD yarn build\n    chmod +x /opt/streamlink-webui/start.sh\n    msg_ok \"Updated streamlink-webui\"\n\n    msg_info \"Starting Service\"\n    systemctl start streamlink-webui\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/stylus.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/mmastrac/stylus\n\nAPP=\"Stylus\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_fuse=\"${var_fuse:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/stylus ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"stylus\" \"mmastrac/stylus\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop stylus\n    msg_info \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"stylus\" \"mmastrac/stylus\" \"singlefile\" \"latest\" \"/usr/bin/\" \"*_linux_arm64\"\n\n    msg_info \"Starting Service\"\n    systemctl start stylus\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/sure.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sure.am\n\nAPP=\"Sure\"\nvar_tags=\"${var_tags:-finance}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/sure ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Sure\" \"we-promise/sure\"; then\n    if [[ ! -f /etc/systemd/system/sure-worker.service ]]; then\n      cat <<EOF >/etc/systemd/system/sure-worker.service\n[Unit]\nDescription=Sure Background Worker (Sidekiq)\nAfter=network.target redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/sure\nEnvironment=RAILS_ENV=production\nEnvironment=BUNDLE_DEPLOYMENT=1\nEnvironment=BUNDLE_WITHOUT=development\nEnvironment=PATH=/root/.rbenv/shims:/root/.rbenv/bin:/usr/bin:/usr/local/bin:/sbin:/bin\nEnvironmentFile=/etc/sure/.env\nExecStart=/opt/sure/bin/bundle exec sidekiq -e production\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n      systemctl enable -q sure-worker\n      msg_info \"Stopping Service\"\n      $STD systemctl stop sure\n      msg_ok \"Stopped Service\"\n    else\n      msg_info \"Stopping services\"\n      $STD systemctl stop sure-worker sure\n      msg_ok \"Stopped services\"\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Sure\" \"we-promise/sure\" \"tarball\" \"latest\" \"/opt/sure\"\n    RUBY_VERSION=\"$(cat /opt/sure/.ruby-version)\" RUBY_INSTALL_RAILS=false setup_ruby\n\n    msg_info \"Updating Sure\"\n    source ~/.profile\n    cd /opt/sure\n    export RAILS_ENV=production\n    export BUNDLE_DEPLOYMENT=1\n    export BUNDLE_WITHOUT=development\n    $STD ./bin/bundle install\n    $STD ./bin/bundle exec bootsnap precompile --gemfile -j 0\n    $STD ./bin/bundle exec bootsnap precompile -j 0 app/ lib/\n    export SECRET_KEY_BASE_DUMMY=1 && $STD ./bin/rails assets:precompile\n    unset SECRET_KEY_BASE_DUMMY\n    msg_ok \"Updated Sure\"\n\n    msg_info \"Starting Services\"\n    $STD systemctl start sure sure-worker\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/swizzin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: EEJoshua\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://swizzin.ltd/\n\nAPP=\"Swizzin\"\nvar_tags=\"${var_tags:-seedbox}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if ! command -v sudo box >/dev/null 2>&1; then\n    msg_error \"No ${APP} installation found!\"\n    exit\n  fi\n  msg_info \"Running 'sudo box update' inside the container\"\n  $STD sudo box update\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW}If installed panel, access through the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/syncthing.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://syncthing.net/\n\nAPP=\"Syncthing\"\nvar_tags=\"${var_tags:-sync}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apt/sources.list.d/syncthing.list && ! -f /etc/apt/sources.list.d/syncthing.sources ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Syncthing\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8384${CL}\"\n"
  },
  {
    "path": "ct/tandoor.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tandoor.dev/\n\nAPP=\"Tandoor\"\nvar_tags=\"${var_tags:-recipes}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tandoor ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if [[ ! -f ~/.tandoor ]]; then\n    msg_error \"v1 Installation found, please export your data and create an new LXC.\"\n    exit\n  fi\n\n  if ! grep -q \"^ALLOWED_HOSTS=\" /opt/tandoor/.env; then\n    echo \"ALLOWED_HOSTS=${LOCAL_IP}\" >>/opt/tandoor/.env\n  fi\n\n  if check_for_gh_release \"tandoor\" \"TandoorRecipes/recipes\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop tandoor\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    mv /opt/tandoor /opt/tandoor.bak\n    msg_ok \"Backup Created\"\n\n    NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n    PYTHON_VERSION=\"3.13\" setup_uv\n    fetch_and_deploy_gh_release \"tandoor\" \"TandoorRecipes/recipes\" \"tarball\" \"latest\" \"/opt/tandoor\"\n\n    msg_info \"Updating Tandoor\"\n    cp -r /opt/tandoor.bak/{config,api,mediafiles,staticfiles} /opt/tandoor/\n    mv /opt/tandoor.bak/.env /opt/tandoor/.env\n    cd /opt/tandoor\n    $STD uv venv --clear .venv --python=python3\n    $STD uv pip install -r requirements.txt --python .venv/bin/python\n    cd /opt/tandoor/vue3\n    $STD yarn install\n    $STD yarn build\n    TANDOOR_VERSION=$(get_latest_github_release \"TandoorRecipes/recipes\")\n    cat <<EOF >/opt/tandoor/cookbook/version_info.py\nTANDOOR_VERSION = \"$TANDOOR_VERSION\"\nTANDOOR_REF = \"bare-metal\"\nVERSION_INFO = []\nEOF\n    cd /opt/tandoor\n    $STD /opt/tandoor/.venv/bin/python manage.py migrate\n    $STD /opt/tandoor/.venv/bin/python manage.py collectstatic --no-input\n    rm -rf /opt/tandoor.bak\n    msg_ok \"Updated Tandoor\"\n\n    msg_info \"Starting Service\"\n    systemctl start tandoor\n    systemctl reload nginx\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8002${CL}\"\n"
  },
  {
    "path": "ct/tasmoadmin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TasmoAdmin/TasmoAdmin\n\nAPP=\"TasmoAdmin\"\nvar_tags=\"${var_tags:-tasmota;smarthome}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating TasmoAdmin\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated TasmoAdmin\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9999${CL}\"\n"
  },
  {
    "path": "ct/tasmocompiler.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/benzino77/tasmocompiler\n\nAPP=\"TasmoCompiler\"\nvar_tags=\"${var_tags:-compiler}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tasmocompiler ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  RELEASE=$(curl -fsSL https://api.github.com/repos/benzino77/tasmocompiler/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop tasmocompiler\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating TasmoCompiler\"\n    cd /opt\n    rm -rf /opt/tasmocompiler\n    RELEASE=$(curl -fsSL https://api.github.com/repos/benzino77/tasmocompiler/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n    curl -fsSL \"https://github.com/benzino77/tasmocompiler/archive/refs/tags/v${RELEASE}.tar.gz\" -o $(basename \"https://github.com/benzino77/tasmocompiler/archive/refs/tags/v${RELEASE}.tar.gz\")\n    tar xzf v${RELEASE}.tar.gz\n    mv tasmocompiler-${RELEASE}/ /opt/tasmocompiler/\n    cd /opt/tasmocompiler\n    $STD yarn install\n    export NODE_OPTIONS=--openssl-legacy-provider\n    $STD npm i\n    $STD yarn build\n    rm -r \"/opt/v${RELEASE}.tar.gz\"\n    echo \"${RELEASE}\" >/opt/${APP}_version.txt\n    msg_ok \"Updated TasmoCompiler\"\n\n    msg_info \"Starting Service\"\n    systemctl start tasmocompiler\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/tautulli.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tautulli.com/\n\nAPP=\"Tautulli\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/Tautulli/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Tautulli\" \"Tautulli/Tautulli\"; then\n    PYTHON_VERSION=\"3.13\" setup_uv\n\n    msg_info \"Stopping Service\"\n    systemctl stop tautulli\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up config and database\"\n    cp /opt/Tautulli/config.ini /opt/tautulli_config.ini.backup\n    cp /opt/Tautulli/tautulli.db /opt/tautulli.db.backup\n    msg_ok \"Backed up config and database\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Tautulli\" \"Tautulli/Tautulli\" \"tarball\"\n\n    msg_info \"Updating Tautulli\"\n    cd /opt/Tautulli\n    TAUTULLI_VERSION=$(get_latest_github_release \"Tautulli/Tautulli\" \"false\")\n    echo \"${TAUTULLI_VERSION}\" >/opt/Tautulli/version.txt\n    echo \"master\" >/opt/Tautulli/branch.txt\n    $STD uv venv -c\n    $STD source /opt/Tautulli/.venv/bin/activate\n    $STD uv pip install -r requirements.txt\n    $STD uv pip install pyopenssl\n    $STD uv pip install \"setuptools<81\"\n    msg_ok \"Updated Tautulli\"\n\n    msg_info \"Restoring config and database\"\n    cp /opt/tautulli_config.ini.backup /opt/Tautulli/config.ini\n    cp /opt/tautulli.db.backup /opt/Tautulli/tautulli.db\n    rm -f /opt/{tautulli_config.ini.backup,tautulli.db.backup}\n    msg_ok \"Restored config and database\"\n\n    msg_info \"Starting Service\"\n    systemctl start tautulli\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8181${CL}\"\n"
  },
  {
    "path": "ct/tdarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://home.tdarr.io/\n\nAPP=\"Tdarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tdarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Tdarr\"\n  $STD apt update\n  $STD apt upgrade -y\n  rm -rf /opt/tdarr/Tdarr_Updater\n  cd /opt/tdarr\n  RELEASE=$(curl_with_retry \"https://f000.backblazeb2.com/file/tdarrs/versions.json\" \"-\" | grep -oP '(?<=\"Tdarr_Updater\": \")[^\"]+' | grep linux_x64 | head -n 1)\n  curl_with_retry \"$RELEASE\" \"Tdarr_Updater.zip\"\n  $STD unzip Tdarr_Updater.zip\n  chmod +x Tdarr_Updater\n  $STD ./Tdarr_Updater\n  rm -rf /opt/tdarr/Tdarr_Updater.zip\n  [[ -f /opt/tdarr/Tdarr_Server/Tdarr_Server ]] || {\n    msg_error \"Tdarr_Updater failed — tdarr.io may be blocked by local DNS\"\n    exit 250\n  }\n  msg_ok \"Updated Tdarr\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8265${CL}\"\n"
  },
  {
    "path": "ct/teable.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/teableio/teable\n\nAPP=\"Teable\"\nvar_tags=\"${var_tags:-database;no-code;spreadsheet}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-10240}\"\nvar_disk=\"${var_disk:-25}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/teable ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"teable\" \"teableio/teable\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop teable\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /opt/teable/.env /opt/teable.env.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"teable\" \"teableio/teable\" \"tarball\"\n\n    msg_info \"Restoring Configuration\"\n    mv /opt/teable.env.bak /opt/teable/.env\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Rebuilding Teable\"\n    cd /opt/teable\n    TEABLE_VERSION=$(cat ~/.teable)\n    echo \"NEXT_PUBLIC_BUILD_VERSION=\\\"${TEABLE_VERSION}\\\"\" >>apps/nextjs-app/.env\n    export HUSKY=0\n    export NODE_OPTIONS=\"--max-old-space-size=8192\"\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm -F @teable/db-main-prisma prisma-generate --schema ./prisma/postgres/schema.prisma\n    NODE_ENV=production NEXT_BUILD_ENV_TYPECHECK=false \\\n      $STD pnpm -r --filter '!playground' run build\n    msg_ok \"Rebuilt Teable\"\n\n    msg_info \"Running Database Migrations\"\n    source /opt/teable/.env\n    $STD pnpm -F @teable/db-main-prisma prisma-migrate deploy --schema ./prisma/postgres/schema.prisma\n    msg_ok \"Ran Database Migrations\"\n\n    msg_info \"Starting Service\"\n    systemctl start teable\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update available.\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/teamspeak-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021 (Slaviša Arežina)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://teamspeak.com/en/\n\nAPP=\"Teamspeak-Server\"\nvar_tags=\"${var_tags:-voice;communication}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/teamspeak-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(curl -fsSL https://teamspeak.com/en/downloads/#server | grep -oP 'teamspeak3-server_linux_arm64-\\K[0-9]+\\.[0-9]+\\.[0-9]+' | head -1)\n  if [[ \"${RELEASE}\" != \"$(cat ~/.teamspeak-server 2>/dev/null)\" ]] || [[ ! -f ~/.teamspeak-server ]]; then\n    msg_info \"Stopping Service\"\n    systemctl stop teamspeak-server\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating Teamspeak Server\"\n    curl -fsSL \"https://files.teamspeak-services.com/releases/server/${RELEASE}/teamspeak3-server_linux_arm64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\n    tar -xf ./ts3server.tar.bz2\n    cp -ru teamspeak3-server_linux_arm64/* /opt/teamspeak-server/\n    rm -f ~/ts3server.tar.bz*\n    echo \"${RELEASE}\" >~/.teamspeak-server\n    msg_ok \"Updated Teamspeak Server\"\n\n    msg_info \"Starting Service\"\n    systemctl start teamspeak-server\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"Already up to date\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:9987${CL}\"\n"
  },
  {
    "path": "ct/technitiumdns.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://technitium.com/dns/\n\nAPP=\"Technitium DNS\"\nvar_tags=\"${var_tags:-dns}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/dns ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if [[ -f /etc/systemd/system/dns.service ]]; then\n    mv /etc/systemd/system/dns.service /etc/systemd/system/technitium.service\n    systemctl daemon-reload\n    systemctl enable -q --now technitium\n  fi\n  if ! is_package_installed \"aspnetcore-runtime-10.0\"; then\n    $STD apt remove -y aspnetcore-runtime-8.0 aspnetcore-runtime-9.0 2>/dev/null || true\n    [ -f /etc/apt/sources.list.d/microsoft-prod.list ] && rm -f /etc/apt/sources.list.d/microsoft-prod.list\n    [ -f /usr/share/keyrings/microsoft-prod.gpg ] && rm -f /usr/share/keyrings/microsoft-prod.gpg\n    setup_deb822_repo \\\n      \"microsoft\" \\\n      \"https://packages.microsoft.com/keys/microsoft-2025.asc\" \\\n      \"https://packages.microsoft.com/debian/13/prod/\" \\\n      \"trixie\" \\\n      \"main\"\n    $STD apt install -y aspnetcore-runtime-10.0\n  fi\n\n  RELEASE=$(curl -fsSL https://technitium.com/dns/ | grep -oP 'Version \\K[\\d.]+')\n  if [[ ! -f ~/.technitium || ${RELEASE} != \"$(cat ~/.technitium 2>/dev/null)\" ]]; then\n    systemctl stop technitium\n    fetch_and_deploy_from_url \"https://download.technitium.com/dns/DnsServerPortable.tar.gz\" /opt/technitium/dns\n    echo \"${RELEASE}\" >~/.technitium\n    systemctl start technitium\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required.  Technitium DNS is already at v${RELEASE}.\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5380${CL}\"\n"
  },
  {
    "path": "ct/teddycloud.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dominik Siebel (dsiebel)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/toniebox-reverse-engineering/teddycloud\n\nAPP=\"TeddyCloud\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_disk=\"${var_disk:-8}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"${APP}\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/teddycloud ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"teddycloud\" \"toniebox-reverse-engineering/teddycloud\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop teddycloud\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mv /opt/teddycloud /opt/teddycloud_bak\n    msg_ok \"Backup created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"teddycloud\" \"toniebox-reverse-engineering/teddycloud\" \"prebuild\" \"latest\" \"/opt/teddycloud\" \"teddycloud.arm64.release*.zip\"\n\n    msg_info \"Restoring data\"\n    cp -R /opt/teddycloud_bak/certs /opt/teddycloud_bak/config /opt/teddycloud_bak/data /opt/teddycloud\n    rm -rf /opt/teddycloud_bak\n    msg_ok \"Data restored\"\n\n    msg_info \"Starting Service\"\n    systemctl start teddycloud\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/telegraf.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/influxdata/telegraf\n\nAPP=\"telegraf\"\nvar_tags=\"${var_tags:-collector;metrics}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/telegraf/telegraf.conf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop telegraf\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating Telegraf\"\n  $STD apt update\n  $STD apt upgrade -y telegraf\n  msg_ok \"Updated Telegraf\"\n\n  msg_info \"Starting Service\"\n  systemctl start telegraf\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/teleport.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://goteleport.com/\n\nAPP=\"Teleport\"\nvar_tags=\"${var_tags:-zero-trust}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/teleport.yaml ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Teleport\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:3080${CL}\"\n"
  },
  {
    "path": "ct/termix.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Termix-SSH/Termix\n\nAPP=\"Termix\"\nvar_tags=\"${var_tags:-ssh;terminal;management}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/termix ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_tag \"guacd\" \"apache/guacamole-server\"; then\n    msg_info \"Stopping guacd\"\n    systemctl stop guacd 2>/dev/null || true\n    msg_ok \"Stopped guacd\"\n\n    ensure_dependencies \\\n      libcairo2-dev \\\n      libjpeg62-turbo-dev \\\n      libpng-dev \\\n      libtool-bin \\\n      uuid-dev \\\n      libvncserver-dev \\\n      freerdp3-dev \\\n      libssh2-1-dev \\\n      libtelnet-dev \\\n      libwebsockets-dev \\\n      libpulse-dev \\\n      libvorbis-dev \\\n      libwebp-dev \\\n      libssl-dev \\\n      libpango1.0-dev \\\n      libswscale-dev \\\n      libavcodec-dev \\\n      libavutil-dev \\\n      libavformat-dev\n\n    msg_info \"Updating Guacamole Server (guacd)\"\n    fetch_and_deploy_gh_tag \"guacd\" \"apache/guacamole-server\" \"${CHECK_UPDATE_RELEASE}\" \"/opt/guacamole-server\"\n    cd /opt/guacamole-server\n    export CPPFLAGS=\"-Wno-error=deprecated-declarations\"\n    $STD autoreconf -fi\n    $STD ./configure --with-init-dir=/etc/init.d --enable-allow-freerdp-snapshots\n    $STD make\n    $STD make install\n    $STD ldconfig\n    cd /opt\n    rm -rf /opt/guacamole-server\n    msg_ok \"Updated Guacamole Server (guacd) to ${CHECK_UPDATE_RELEASE}\"\n\n    if [[ ! -f /etc/guacamole/guacd.conf ]]; then\n      mkdir -p /etc/guacamole\n      cat <<EOF >/etc/guacamole/guacd.conf\n[server]\nbind_host = 127.0.0.1\nbind_port = 4822\nEOF\n    fi\n\n    if [[ ! -f /etc/systemd/system/guacd.service ]] || grep -q \"Type=forking\" /etc/systemd/system/guacd.service 2>/dev/null; then\n      cat <<EOF >/etc/systemd/system/guacd.service\n[Unit]\nDescription=Guacamole Proxy Daemon (guacd)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/sbin/guacd -f -b 127.0.0.1 -l 4822\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    fi\n\n    if ! grep -q \"guacd.service\" /etc/systemd/system/termix.service 2>/dev/null; then\n      sed -i '/^After=network.target/s/$/ guacd.service/' /etc/systemd/system/termix.service\n      sed -i '/^\\[Unit\\]/a Wants=guacd.service' /etc/systemd/system/termix.service\n    fi\n\n    systemctl daemon-reload\n    systemctl enable -q --now guacd\n  fi\n\n  if check_for_gh_release \"termix\" \"Termix-SSH/Termix\"; then\n    msg_info \"Stopping Termix\"\n    systemctl stop termix\n    msg_ok \"Stopped Termix\"\n\n    msg_info \"Migrating Configuration\"\n    if [[ ! -f /opt/termix/.env ]]; then\n      cat <<EOF >/opt/termix/.env\nNODE_ENV=production\nDATA_DIR=/opt/termix/data\nGUACD_HOST=127.0.0.1\nGUACD_PORT=4822\nEOF\n    fi\n    if ! grep -q \"EnvironmentFile\" /etc/systemd/system/termix.service 2>/dev/null; then\n      cat <<EOF >/etc/systemd/system/termix.service\n[Unit]\nDescription=Termix Backend\nAfter=network.target guacd.service\nWants=guacd.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/termix\nEnvironmentFile=/opt/termix/.env\nExecStart=/usr/bin/node /opt/termix/dist/backend/backend/starter.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n      systemctl daemon-reload\n    fi\n    msg_ok \"Migrated Configuration\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/termix/data /opt/termix_data_backup\n    cp -r /opt/termix/uploads /opt/termix_uploads_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"termix\" \"Termix-SSH/Termix\" \"tarball\"\n\n    msg_info \"Recreating Directories\"\n    mkdir -p /opt/termix/html \\\n      /opt/termix/nginx \\\n      /opt/termix/nginx/logs \\\n      /opt/termix/nginx/cache \\\n      /opt/termix/nginx/client_body\n    msg_ok \"Recreated Directories\"\n\n    NODE_VERSION=\"24\" setup_nodejs\n\n    msg_info \"Building Frontend\"\n    cd /opt/termix\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    find public/fonts -name \"*.ttf\" ! -name \"*Regular.ttf\" ! -name \"*Bold.ttf\" ! -name \"*Italic.ttf\" -delete 2>/dev/null || true\n    $STD npm install --ignore-scripts --force\n    $STD npm run build\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Building Backend\"\n    $STD npm rebuild better-sqlite3 --force\n    $STD npm run build:backend\n    msg_ok \"Built Backend\"\n\n    msg_info \"Setting up Production Dependencies\"\n    $STD npm ci --only=production --ignore-scripts --force\n    $STD npm rebuild better-sqlite3 bcryptjs --force\n    $STD npm cache clean --force\n    msg_ok \"Set up Production Dependencies\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/termix_data_backup /opt/termix/data\n    cp -r /opt/termix_uploads_backup /opt/termix/uploads\n    rm -rf /opt/termix_data_backup /opt/termix_uploads_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating Frontend Files\"\n    rm -rf /opt/termix/html/*\n    cp -r /opt/termix/dist/* /opt/termix/html/ 2>/dev/null || true\n    cp -r /opt/termix/src/locales /opt/termix/html/locales 2>/dev/null || true\n    cp -r /opt/termix/public/fonts /opt/termix/html/fonts 2>/dev/null || true\n    msg_ok \"Updated Frontend Files\"\n\n    msg_warn \"The Nginx configuration may need to be updated for new features to work.\"\n    msg_custom \"💾\" \"Your current config will be backed up to nginx.conf.bak\"\n    msg_custom \"⚠️ \" \"Note: Custom modifications (reverse proxy, SSL) will be overwritten!\"\n    echo \"\"\n    read -rp \"${TAB3}Update Nginx configuration? [Y/n]: \" REPLY\n    if [[ \"${REPLY,,}\" =~ ^(y|yes|)$ ]]; then\n      msg_info \"Updating Nginx Configuration\"\n      cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak\n      curl -fsSL \"https://raw.githubusercontent.com/Termix-SSH/Termix/main/docker/nginx.conf\" -o /etc/nginx/nginx.conf\n      sed -i '/^master_process/d' /etc/nginx/nginx.conf\n      sed -i 's|pid /tmp/nginx/nginx.pid;|pid /run/nginx.pid;|' /etc/nginx/nginx.conf\n      sed -i 's|error_log /tmp/nginx/error.log|error_log /var/log/nginx/error.log|' /etc/nginx/nginx.conf\n      sed -i 's|access_log /tmp/nginx/access.log|access_log /var/log/nginx/access.log|' /etc/nginx/nginx.conf\n      sed -i 's|/app/html|/opt/termix/html|g' /etc/nginx/nginx.conf\n      sed -i 's|/app/nginx|/opt/termix/nginx|g' /etc/nginx/nginx.conf\n      sed -i 's|listen ${PORT};|listen 80;|g' /etc/nginx/nginx.conf\n\n      nginx -t && systemctl reload nginx\n      msg_ok \"Updated Nginx Configuration\"\n    else\n      msg_warn \"Nginx configuration not updated. If Termix doesn't work, restore from backup or update manually.\"\n    fi\n\n    msg_info \"Starting Termix\"\n    systemctl start termix\n    msg_ok \"Started Termix\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/the-lounge.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://thelounge.chat/\n\nAPP=\"The-Lounge\"\nvar_tags=\"${var_tags:-irc}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/lib/systemd/system/thelounge.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"thelounge\" \"thelounge/thelounge-deb\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop thelounge\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"thelounge\" \"thelounge/thelounge-deb\" \"binary\"\n\n    msg_info \"Starting Service\"\n    systemctl start thelounge\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}\"\n"
  },
  {
    "path": "ct/thingsboard.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/thingsboard/thingsboard\n\nAPP=\"ThingsBoard\"\nvar_tags=\"${var_tags:-iot;platform}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /usr/share/thingsboard ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"thingsboard\" \"thingsboard/thingsboard\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop thingsboard\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"thingsboard\" \"thingsboard/thingsboard\" \"binary\" \"latest\" \"/tmp\" \"thingsboard-*.deb\"\n\n    msg_info \"Running Database Upgrade\"\n    $STD /usr/share/thingsboard/bin/install/upgrade.sh\n    msg_ok \"Ran Database Upgrade\"\n\n    msg_info \"Starting Service\"\n    systemctl start thingsboard\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/threadfin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Threadfin/Threadfin\n\nAPP=\"Threadfin\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/threadfin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"threadfin-app\" \"threadfin/threadfin\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop threadfin\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"threadfin\" \"threadfin/threadfin\" \"singlefile\" \"latest\" \"/opt/threadfin\" \"Threadfin_linux_arm64\"\n\n    msg_info \"Starting Service\"\n    systemctl start threadfin\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:34400/web${CL}\"\n"
  },
  {
    "path": "ct/tianji.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tianji.msgbyte.com/\n\nAPP=\"Tianji\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tianji ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_uv\n  if check_for_gh_release \"tianji\" \"msgbyte/tianji\"; then\n    NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$(curl -s https://raw.githubusercontent.com/msgbyte/tianji/master/package.json | jq -r '.packageManager | split(\"@\")[1]')\" setup_nodejs\n\n    msg_info \"Stopping Service\"\n    systemctl stop tianji\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up data\"\n    cp /opt/tianji/src/server/.env /opt/.env\n    mv /opt/tianji /opt/tianji_bak\n    msg_ok \"Backed up data\"\n\n    fetch_and_deploy_gh_release \"tianji\" \"msgbyte/tianji\" \"tarball\"\n\n    msg_info \"Updating Tianji\"\n    cd /opt/tianji\n    export NODE_OPTIONS=\"--max_old_space_size=4096\"\n    $STD pnpm install --filter @tianji/client... --config.dedupe-peer-dependents=false --frozen-lockfile\n    $STD pnpm build:static\n    $STD pnpm install --filter @tianji/server... --config.dedupe-peer-dependents=false\n    mkdir -p ./src/server/public\n    cp -r ./geo ./src/server/public\n    $STD pnpm build:server\n    mv /opt/.env /opt/tianji/src/server/.env\n    cd src/server\n    $STD pnpm db:migrate:apply\n    rm -rf /opt/tianji_bak\n    rm -rf /opt/tianji/src/client\n    rm -rf /opt/tianji/website\n    rm -rf /opt/tianji/reporter\n    msg_ok \"Updated Tianji\"\n\n    msg_info \"Updating AppRise\"\n    $STD uv pip install apprise cryptography --system\n    msg_ok \"Updated AppRise\"\n\n    msg_info \"Starting Service\"\n    systemctl start tianji\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:12345${CL}\"\n"
  },
  {
    "path": "ct/tinyauth.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: BiluliB\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/plexguide/Huntarr.io\n\nAPP=\"Tinyauth\"\nvar_tags=\"${var_tags:-auth}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tinyauth ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"tinyauth\" \"steveiliop56/tinyauth\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop tinyauth\n    msg_ok \"Stopped Service\"\n\n    local arch asset\n    arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n    asset=\"tinyauth-amd64\"\n    [[ \"$arch\" == \"arm64\" || \"$arch\" == \"aarch64\" ]] && asset=\"tinyauth-arm64\"\n    fetch_and_deploy_gh_release \"tinyauth\" \"steveiliop56/tinyauth\" \"singlefile\" \"latest\" \"/opt/tinyauth\" \"$asset\"\n\n    msg_info \"Stopping Service\"\n    systemctl stop tinyauth\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"tinyauth\" \"steveiliop56/tinyauth\" \"singlefile\" \"latest\" \"/opt/tinyauth\" \"tinyauth-amd64\"\n\n    msg_info \"Starting Service\"\n    systemctl start tinyauth\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/traccar.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.traccar.org/\n\nAPP=\"Traccar\"\nvar_tags=\"${var_tags:-gps;tracker}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/traccar ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"traccar\" \"traccar/traccar\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop traccar\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mv /opt/traccar/conf/traccar.xml /opt\n    [[ -d /opt/traccar/data ]] && mv /opt/traccar/data /opt\n    [[ -d /opt/traccar/media ]] && mv /opt/traccar/media /opt\n    msg_ok \"Backup created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"traccar\" \"traccar/traccar\" \"prebuild\" \"latest\" \"/opt/traccar\" \"traccar-linux-64*.zip\"\n\n    msg_info \"Perform Update\"\n    cd /opt/traccar\n    $STD ./traccar.run\n    msg_ok \"App-Update completed\"\n\n    msg_info \"Restoring data\"\n    mv /opt/traccar.xml /opt/traccar/conf\n    [[ -d /opt/data ]] && mv /opt/data /opt/traccar\n    [[ -d /opt/media ]] && mv /opt/media /opt/traccar\n    [ -f README.txt ] || [ -f traccar.run ] && rm -f README.txt traccar.run\n    msg_ok \"Data restored\"\n\n    msg_info \"Starting Service\"\n    systemctl start traccar\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8082${CL}\"\n"
  },
  {
    "path": "ct/tracearr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: durzo\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/connorgallopo/Tracearr\n\nAPP=\"Tracearr\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-8192}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/tracearr.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" setup_nodejs\n\n  msg_info \"Updating prestart script\"\n  cat <<EOF >/data/tracearr/prestart.sh\n#!/usr/bin/env bash\n# =============================================================================\n# Tune PostgreSQL for available resources (runs every startup)\n# =============================================================================\n# timescaledb-tune automatically optimizes PostgreSQL settings based on\n# available RAM and CPU. Safe to run repeatedly - recalculates if resources change.\nif command -v timescaledb-tune &> /dev/null; then\n    total_ram_kb=\\$(grep MemTotal /proc/meminfo | awk '{print \\$2}')\n    ram_for_tsdb=\\$((total_ram_kb / 1024 / 2))\n    timescaledb-tune -yes -memory \"\\$ram_for_tsdb\"MB --quiet 2>/dev/null \\\n        || echo \"Warning: timescaledb-tune failed (non-fatal)\"\nfi\n# =============================================================================\n# Ensure required PostgreSQL settings for Tracearr\n# =============================================================================\npg_config_file=\"/etc/postgresql/18/main/postgresql.conf\"\nif [ -f \\$pg_config_file ]; then\n    # Ensure max_tuples_decompressed_per_dml_transaction is set\n    if grep -q \"^timescaledb\\.max_tuples_decompressed_per_dml_transaction\" \\$pg_config_file; then\n        # Setting exists (uncommented) - update if not 0\n        current_value=\\$(grep \"^timescaledb\\.max_tuples_decompressed_per_dml_transaction\" \\$pg_config_file | grep -oE '[0-9]+' | head -1)\n        if [ -n \"\\$current_value\" ] && [ \"\\$current_value\" -ne 0 ]; then\n            sed -i \"s/^timescaledb\\.max_tuples_decompressed_per_dml_transaction.*/timescaledb.max_tuples_decompressed_per_dml_transaction = 0/\" \\$pg_config_file\n        fi\n    elif ! grep -q \"^timescaledb\\.max_tuples_decompressed_per_dml_transaction\" \\$pg_config_file; then\n        echo \"\" >> \\$pg_config_file\n        echo \"# Allow unlimited tuple decompression for migrations on compressed hypertables\" >> \\$pg_config_file\n        echo \"timescaledb.max_tuples_decompressed_per_dml_transaction = 0\" >> \\$pg_config_file\n    fi\n    # Ensure max_locks_per_transaction is set (for existing databases)\n    if grep -q \"^max_locks_per_transaction\" \\$pg_config_file; then\n        # Setting exists (uncommented) - update if below 4096\n        current_value=\\$(grep \"^max_locks_per_transaction\" \\$pg_config_file | grep -oE '[0-9]+' | head -1)\n        if [ -n \"\\$current_value\" ] && [ \"\\$current_value\" -lt 4096 ]; then\n            sed -i \"s/^max_locks_per_transaction.*/max_locks_per_transaction = 4096/\" \\$pg_config_file\n        fi\n    elif ! grep -q \"^max_locks_per_transaction\" \\$pg_config_file; then\n        echo \"\" >> \\$pg_config_file\n        echo \"# Increase lock table size for TimescaleDB hypertables with many chunks\" >> \\$pg_config_file\n        echo \"max_locks_per_transaction = 4096\" >> \\$pg_config_file\n    fi\nfi\nsystemctl restart postgresql\nsudo -u postgres psql -c \"ALTER USER tracearr WITH SUPERUSER;\"\nEOF\n  chmod +x /data/tracearr/prestart.sh\n  msg_ok \"Updated prestart script\"\n\n  # check if tailscale is installed\n  if command -v tailscale >/dev/null 2>&1; then\n    # Tracearr runs tailscaled in user mode, disable the service.\n    $STD systemctl disable --now tailscaled\n    $STD systemctl stop tailscaled\n    msg_ok \"Tailscale already installed\"\n  else\n    msg_info \"Installing tailscale\"\n    setup_deb822_repo \\\n      \"tailscale\" \\\n      \"https://pkgs.tailscale.com/stable/$(get_os_info id)/$(get_os_info codename).noarmor.gpg\" \\\n      \"https://pkgs.tailscale.com/stable/$(get_os_info id)/\" \\\n      \"$(get_os_info codename)\"\n    $STD apt install -y tailscale\n    # Tracearr runs tailscaled in user mode, disable the service.\n    $STD systemctl disable --now tailscaled\n    $STD systemctl stop tailscaled\n    msg_ok \"Installed tailscale\"\n  fi\n\n  if check_for_gh_release \"tracearr\" \"connorgallopo/Tracearr\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop tracearr postgresql redis-server\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Updating pnpm\"\n    PNPM_VERSION=\"$(curl -fsSL \"https://raw.githubusercontent.com/connorgallopo/Tracearr/refs/heads/main/package.json\" | jq -r '.packageManager | split(\"@\")[1]' | cut -d'+' -f1)\"\n    export COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n    $STD corepack prepare pnpm@${PNPM_VERSION} --activate\n    msg_ok \"Updated pnpm\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"tracearr\" \"connorgallopo/Tracearr\" \"tarball\" \"latest\" \"/opt/tracearr.build\"\n\n    msg_info \"Building Tracearr\"\n    export TZ=$(cat /etc/timezone)\n    export NODE_OPTIONS=\"--max-old-space-size=4096\"\n    cd /opt/tracearr.build\n    $STD pnpm install --frozen-lockfile --force\n    $STD pnpm turbo telemetry disable\n    $STD pnpm turbo run build --no-daemon --filter=@tracearr/shared --filter=@tracearr/server --filter=@tracearr/web\n    rm -rf /opt/tracearr\n    mkdir -p /opt/tracearr/{packages/shared,apps/server,apps/web,apps/server/src/db}\n    cp -rf package.json /opt/tracearr/\n    cp -rf pnpm-workspace.yaml /opt/tracearr/\n    cp -rf pnpm-lock.yaml /opt/tracearr/\n    cp -rf apps/server/package.json /opt/tracearr/apps/server/\n    cp -rf apps/server/dist /opt/tracearr/apps/server/dist\n    cp -rf apps/server/scripts /opt/tracearr/apps/server/scripts\n    cp -rf apps/web/dist /opt/tracearr/apps/web/dist\n    cp -rf packages/shared/package.json /opt/tracearr/packages/shared/\n    cp -rf packages/shared/dist /opt/tracearr/packages/shared/dist\n    cp -rf apps/server/src/db/migrations /opt/tracearr/apps/server/src/db/migrations\n    cp -rf data /opt/tracearr/data\n    mkdir -p /opt/tracearr/data/image-cache\n    rm -rf /opt/tracearr.build\n    cd /opt/tracearr\n    $STD pnpm install --prod --frozen-lockfile --ignore-scripts\n    $STD chown -R tracearr:tracearr /opt/tracearr\n    msg_ok \"Built Tracearr\"\n\n    msg_info \"Configuring Tracearr\"\n    sed -i \"s|^APP_VERSION=.*|APP_VERSION=${CHECK_UPDATE_RELEASE#v}|\" /data/tracearr/.env\n    chmod 600 /data/tracearr/.env\n    chown -R tracearr:tracearr /data/tracearr\n    mkdir -p /data/backup\n    chown -R tracearr:tracearr /data/backup\n    msg_ok \"Configured Tracearr\"\n\n    msg_info \"Starting services\"\n    systemctl start postgresql redis-server tracearr\n    msg_ok \"Started services\"\n    msg_ok \"Updated successfully!\"\n  else\n    # no new release, just restart service to apply prestart changes\n    msg_info \"Restarting service\"\n    systemctl restart tracearr\n    msg_ok \"Restarted service\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/tracktor.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tracktor.bytedge.in/\n\nAPP=\"tracktor\"\nvar_tags=\"${var_tags:-car;monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tracktor ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"tracktor\" \"javedh-dev/tracktor\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop tracktor\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Correcting Services\"\n    if [ ! -d \"/opt/tracktor-data/uploads\" ]; then\n      mkdir -p /opt/tracktor-data/{uploads,logs}\n    fi\n    if ! grep -qxF 'BODY_SIZE_LIMIT=Infinity' /opt/tracktor.env; then\n      rm /opt/tracktor.env\n    cat <<EOF >/opt/tracktor.env\ncat <<EOF >/opt/tracktor.env\nNODE_ENV=production\n# Set this to the path of the database file. Default - ./tracktor.db\nDB_PATH=/opt/tracktor-data/tracktor.db\n# Set this to the path of the uploads directory. Default - ./uploads\nUPLOADS_DIR=\"/opt/tracktor-data/uploads\"\n# Set this to the path of the logs directory. Default - ./logs\nLOG_DIR=\"/opt/tracktor-data/logs\"\n# Hostname to bind the server to. Default - 0.0.0.0\n#HOST=\"0.0.0.0\"\n# Port to bind the server to. Default - 3000\n#PORT=3000\n# Set this to remove upload size limitations. Default - 512 Kb\nBODY_SIZE_LIMIT=Infinity\n# Enable request logging. Default - true\n#LOG_REQUESTS=true\n# Set the logging level. Options - error, warn, info, verbose, debug, silly. Default - info\n#LOG_LEVEL=\"info\"\n# Enable demo mode. Default - false\n#TRACKTOR_DEMO_MODE=false\n# Force reseeding of data on every startup. Default - false\n#FORCE_DATA_SEED=false\nEOF\n    fi\n    msg_ok \"Corrected Services\"\n\n    NODE_VERSION=\"24\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"tracktor\" \"javedh-dev/tracktor\" \"tarball\" \"latest\" \"/opt/tracktor\"\n\n    msg_info \"Updating tracktor\"\n    cd /opt/tracktor\n    $STD npm install\n    $STD npm run build\n    msg_ok \"Updated tracktor\"\n\n    msg_info \"Starting Service\"\n    systemctl start tracktor\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/traefik.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://traefik.io/\n\nAPP=\"Traefik\"\nvar_tags=\"${var_tags:-proxy}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/traefik.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"traefik\" \"traefik/traefik\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop traefik\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"traefik\" \"traefik/traefik\" \"prebuild\" \"latest\" \"/usr/bin\" \"traefik_v*_linux_arm64.tar.gz\"\n\n    msg_info \"Starting Service\"\n    systemctl start traefik\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/transmission.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://transmissionbt.com/\n\nAPP=\"Transmission\"\nvar_tags=\"${var_tags:-torrent}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/transmission-daemon/settings.json ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Transmission\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated Transmission\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9091/transmission${CL}\"\n"
  },
  {
    "path": "ct/transmute.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/transmute-app/transmute\n\nAPP=\"Transmute\"\nvar_tags=\"${var_tags:-files;converter}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/transmute ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  fetch_and_deploy_gh_release \"calibre\" \"kovidgoyal/calibre\" \"prebuild\" \"latest\" \"/opt/calibre\" \"calibre-*-x86_64.txz\"\n  ln -sf /opt/calibre/ebook-convert /usr/bin/ebook-convert\n  fetch_and_deploy_gh_release \"drawio\" \"jgraph/drawio-desktop\" \"binary\" \"latest\" \"\" \"drawio-amd64-*.deb\"\n  fetch_and_deploy_gh_release \"pandoc\" \"jgm/pandoc\" \"binary\" \"latest\" \"\" \"pandoc-*-amd64.deb\"\n\n  if check_for_gh_release \"transmute\" \"transmute-app/transmute\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop transmute\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/transmute/backend/.env /opt/transmute.env.bak\n    cp -r /opt/transmute/data /opt/transmute_data_bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"transmute\" \"transmute-app/transmute\" \"tarball\"\n\n    msg_info \"Updating Python Dependencies\"\n    cd /opt/transmute\n    $STD uv venv --clear /opt/transmute/.venv\n    $STD uv pip install --python /opt/transmute/.venv/bin/python -r requirements.txt\n    msg_ok \"Updated Python Dependencies\"\n\n    msg_info \"Rebuilding Frontend\"\n    cd /opt/transmute/frontend\n    $STD npm ci\n    $STD npm run build\n    msg_ok \"Rebuilt Frontend\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/transmute.env.bak /opt/transmute/backend/.env\n    cp -r /opt/transmute_data_bak/. /opt/transmute/data/\n    rm -f /opt/transmute.env.bak\n    rm -rf /opt/transmute_data_bak\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start transmute\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3313${CL}\"\n"
  },
  {
    "path": "ct/trek.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/mauriceboe/TREK\n\nAPP=\"TREK\"\nvar_tags=\"${var_tags:-travel;planning;collaboration}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/trek ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"trek\" \"mauriceboe/TREK\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop trek\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/trek/server/.env /opt/trek.env.bak\n    mv /opt/trek/data /opt/trek-data.bak\n    mv /opt/trek/uploads /opt/trek-uploads.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"trek\" \"mauriceboe/TREK\" \"tarball\"\n\n    msg_info \"Building Client\"\n    cd /opt/trek/client\n    $STD npm ci\n    $STD npm run build\n    mkdir -p /opt/trek/server/public\n    cp -r /opt/trek/client/dist/* /opt/trek/server/public/\n    cp -r /opt/trek/client/public/fonts /opt/trek/server/public/fonts 2>/dev/null || true\n    msg_ok \"Built Client\"\n\n    msg_info \"Installing Server Dependencies\"\n    cd /opt/trek/server\n    $STD npm ci\n    msg_ok \"Installed Server Dependencies\"\n\n    msg_info \"Restoring Data\"\n    mv /opt/trek-data.bak /opt/trek/data\n    mv /opt/trek-uploads.bak /opt/trek/uploads\n    rm -rf /opt/trek/server/data /opt/trek/server/uploads\n    ln -s /opt/trek/data /opt/trek/server/data\n    ln -s /opt/trek/uploads /opt/trek/server/uploads\n    cp /opt/trek.env.bak /opt/trek/server/.env\n    rm -f /opt/trek.env.bak\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start trek\n    msg_ok \"Started Service\"\n    msg_ok \"Updated Successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/trilium.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TriliumNext/Trilium\n\nAPP=\"Trilium\"\nvar_tags=\"${var_tags:-notes}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/trilium ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"Trilium\" \"TriliumNext/Trilium\"; then\n    if [[ -d /opt/trilium/db ]]; then\n      DB_PATH=\"/opt/trilium/db\"\n      DB_RESTORE_PATH=\"/opt/trilium/db\"\n    elif [[ -d /opt/trilium/assets/db ]]; then\n      DB_PATH=\"/opt/trilium/assets/db\"\n      DB_RESTORE_PATH=\"/opt/trilium/assets/db\"\n    else\n      msg_error \"Database not found in either /opt/trilium/db or /opt/trilium/assets/db\"\n      exit\n    fi\n\n    msg_info \"Stopping Service\"\n    systemctl stop trilium\n    sleep 1\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Database\"\n    mkdir -p /opt/trilium_backup\n    cp -r \"${DB_PATH}\" /opt/trilium_backup/\n    rm -rf /opt/trilium\n    msg_ok \"Backed up Database\"\n\n    fetch_and_deploy_gh_release \"Trilium\" \"TriliumNext/Trilium\" \"prebuild\" \"latest\" \"/opt/trilium\" \"TriliumNotes-Server-*linux-x64.tar.xz\"\n\n    msg_info \"Restoring Database\"\n    mkdir -p \"$(dirname \"${DB_RESTORE_PATH}\")\"\n    cp -r /opt/trilium_backup/$(basename \"${DB_PATH}\") \"${DB_RESTORE_PATH}\"\n    rm -rf /opt/trilium_backup\n    msg_ok \"Restored Database\"\n\n    msg_info \"Starting Service\"\n    systemctl start trilium\n    sleep 1\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/trip.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/itskovacs/TRIP\n\nAPP=\"TRIP\"\nvar_tags=\"${var_tags:-maps;travel}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/trip ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"trip\" \"itskovacs/TRIP\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop trip\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"trip\" \"itskovacs/TRIP\" \"tarball\"\n\n    msg_info \"Updating Frontend\"\n    cd /opt/trip/src\n    $STD npm install\n    $STD npm run build\n    mkdir -p /opt/trip/frontend\n    cp -r /opt/trip/src/dist/trip/browser/* /opt/trip/frontend/\n    msg_ok \"Updated Frontend\"\n\n    msg_info \"Updating Backend\"\n    cd /opt/trip/backend\n    $STD uv pip install --python /opt/trip/.venv/bin/python -r trip/requirements.txt\n    msg_ok \"Updated Backend\"\n\n    msg_info \"Starting Service\"\n    systemctl start trip\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/tubearchivist.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/tubearchivist/tubearchivist\n\nAPP=\"Tube Archivist\"\nvar_tags=\"${var_tags:-media;youtube;archiving}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-30}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/tubearchivist ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"tubearchivist\" \"tubearchivist/tubearchivist\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop tubearchivist tubearchivist-celery tubearchivist-beat\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/tubearchivist/.env /opt/tubearchivist_env.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"tubearchivist\" \"tubearchivist/tubearchivist\" \"tarball\"\n\n    msg_info \"Rebuilding Tube Archivist\"\n    cd /opt/tubearchivist/frontend\n    $STD npm install\n    $STD npm run build:deploy\n    mkdir -p /opt/tubearchivist/backend/static\n    cp -r /opt/tubearchivist/frontend/dist/* /opt/tubearchivist/backend/static/\n    cp /opt/tubearchivist/docker_assets/backend_start.py /opt/tubearchivist/backend/\n    $STD uv pip install --python /opt/tubearchivist/.venv/bin/python -r /opt/tubearchivist/backend/requirements.txt\n    if [[ -f /opt/tubearchivist/backend/requirements.plugins.txt ]]; then\n      mkdir -p /opt/yt_plugins/bgutil\n      $STD uv pip install --python /opt/tubearchivist/.venv/bin/python --target /opt/yt_plugins/bgutil -r /opt/tubearchivist/backend/requirements.plugins.txt\n    fi\n    msg_ok \"Rebuilt Tube Archivist\"\n\n    msg_info \"Restoring Configuration\"\n    mv /opt/tubearchivist_env.bak /opt/tubearchivist/.env\n    sed -i 's|^TA_APP_DIR=/opt/tubearchivist$|TA_APP_DIR=/opt/tubearchivist/backend|' /opt/tubearchivist/.env\n    sed -i 's|^TA_CACHE_DIR=/opt/tubearchivist/cache$|TA_CACHE_DIR=/cache|' /opt/tubearchivist/.env\n    sed -i 's|^TA_MEDIA_DIR=/opt/tubearchivist/media$|TA_MEDIA_DIR=/youtube|' /opt/tubearchivist/.env\n    ln -sf /opt/tubearchivist/cache /cache\n    ln -sf /opt/tubearchivist/media /youtube\n    ln -sf /opt/tubearchivist/.env /opt/tubearchivist/backend/.env\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Services\"\n    systemctl start tubearchivist tubearchivist-celery tubearchivist-beat\n    systemctl reload nginx\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/tududi.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tududi.com\n\nAPP=\"Tududi\"\nvar_tags=\"${var_tags:-todo-app}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tududi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" setup_nodejs\n\n  if check_for_gh_release \"tududi\" \"chrisvel/tududi\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop tududi\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up env file\"\n    if [[ -f /opt/tududi/backend/.env ]]; then\n      cp /opt/tududi/backend/.env /opt/tududi.env\n    else\n      cp /opt/tududi/.env /opt/tududi.env\n    fi\n    msg_ok \"Backed up env file\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"tududi\" \"chrisvel/tududi\" \"tarball\" \"latest\" \"/opt/tududi\"\n\n    msg_info \"Updating Tududi\"\n    cd /opt/tududi\n    $STD npm install\n    export NODE_ENV=production\n    $STD npm run frontend:build\n    mv ./dist ./backend\n    mv /opt/tududi.env /opt/tududi/backend/.env\n    DB=\"$(sed -n '/^DB_FILE/s/[^=]*=//p' /opt/tududi/backend/.env)\"\n    export DB_FILE=\"$DB\"\n    sed -i -e 's|/tududi$|/tududi/backend|' \\\n      -e 's|npm run start|bash /opt/tududi/backend/cmd/start.sh|' \\\n      /etc/systemd/system/tududi.service\n    systemctl daemon-reload\n    msg_ok \"Updated Tududi\"\n\n    msg_info \"Starting Service\"\n    systemctl start tududi\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3002${CL}\"\n"
  },
  {
    "path": "ct/tunarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: chrisbenincasa\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tunarr.com/\n\nAPP=\"Tunarr\"\nvar_tags=\"${var_tags:-iptv}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/tunarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"tunarr\" \"chrisbenincasa/tunarr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop tunarr\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    if [ -d \"/usr/local/share/tunarr\" ]; then\n      tar -czf \"/opt/${APP}_backup_$(date +%F).tar.gz\" /usr/local/share/tunarr $STD\n      msg_ok \"Backup Created\"\n    else\n      msg_error \"Backup failed: /usr/local/share/tunarr does not exist\"\n    fi\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"tunarr\" \"chrisbenincasa/tunarr\" \"prebuild\" \"latest\" \"/opt/tunarr\" \"*linux-arm64.tar.gz\"\n    cd /opt/tunarr\n    mv tunarr* tunarr\n\n    msg_info \"Starting Service\"\n    systemctl start tunarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n\n  if check_for_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop tunarr\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\" \"prebuild\" \"latest\" \"/opt/ErsatzTV-ffmpeg\" \"*-linuxarm64-gpl-7.1.tar.xz\"\n\n    msg_info \"Set ErsatzTV-ffmpeg links\"\n    chmod +x /opt/ErsatzTV-ffmpeg/bin/*\n    ln -sf /opt/ErsatzTV-ffmpeg/bin/ffmpeg /usr/local/bin/ffmpeg\n    ln -sf /opt/ErsatzTV-ffmpeg/bin/ffplay /usr/local/bin/ffplay\n    ln -sf /opt/ErsatzTV-ffmpeg/bin/ffprobe /usr/local/bin/ffprobe\n    msg_ok \"ffmpeg links set\"\n\n    msg_info \"Starting Service\"\n    systemctl start tunarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/twingate-connector.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: twingate-andrewb\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.twingate.com/docs/\n\nAPP=\"Twingate-Connector\"\nvar_tags=\"${var_tags:-network;connector;twingate}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-3}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /lib/systemd/system/twingate-connector.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating Twingate Connector\"\n  $STD apt update\n  $STD apt install -y --only-upgrade twingate-connector\n  $STD systemctl restart twingate-connector\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"All Finished! If you need to update your access or refresh tokens, they can be found in /etc/twingate/connector.conf\"\n"
  },
  {
    "path": "ct/ubuntu.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ubuntu.com/\n\nAPP=\"Ubuntu\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-ubuntu}\"\nvar_version=\"${var_version:-24.04}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt-get update\n  $STD apt-get -y upgrade\n  msg_ok \"Updated ${APP} LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\n"
  },
  {
    "path": "ct/uhf.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: zackwithak13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.uhfapp.com/server\n\nAPP=\"UHF\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/uhf-server ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"uhf-server\" \"swapplications/uhf-server-dist\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop uhf-server\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated LXC\"\n\n    fetch_and_deploy_gh_release \"comskip\" \"swapplications/comskip\" \"prebuild\" \"latest\" \"/opt/comskip\" \"comskip-arm64-*.zip\"\n    fetch_and_deploy_gh_release \"uhf-server\" \"swapplications/uhf-server-dist\" \"prebuild\" \"latest\" \"/opt/uhf-server\" \"UHF.Server-linux-arm64-*.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl start uhf-server\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7568${CL}\"\n"
  },
  {
    "path": "ct/umami.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://umami.is/\n\nAPP=\"Umami\"\nvar_tags=\"${var_tags:-analytics}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/umami ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"umami\" \"umami-software/umami\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop umami\n    msg_ok \"Stopped Service\"\n\n    mv /opt/umami/.env /opt/.env.bak\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"umami\" \"umami-software/umami\" \"tarball\"\n    mv /opt/.env.bak /opt/umami/.env\n\n    msg_info \"Updating Umami\"\n    cd /opt/umami\n    $STD pnpm install\n    $STD pnpm run build\n    msg_ok \"Updated Umami\"\n\n    msg_info \"Starting Service\"\n    systemctl start umami\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/umlautadaptarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: elvito\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/PCJones/UmlautAdaptarr\n\nAPP=\"UmlautAdaptarr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/UmlautAdaptarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"UmlautAdaptarr\" \"PCJones/Umlautadaptarr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop umlautadaptarr\n    msg_ok \"Stopped Service\"\n\n    cp /opt/UmlautAdaptarr/appsettings.json /opt/UmlautAdaptarr/appsettings.json.bak\n    fetch_and_deploy_gh_release \"UmlautAdaptarr\" \"PCJones/Umlautadaptarr\" \"prebuild\" \"latest\" \"/opt/UmlautAdaptarr\" \"linux-x64.zip\"\n    cp /opt/UmlautAdaptarr/appsettings.json.bak /opt/UmlautAdaptarr/appsettings.json\n\n    msg_info \"Starting Service\"\n    systemctl start umlautadaptarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5005${CL}\"\n"
  },
  {
    "path": "ct/unbound.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: wimb0\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/NLnetLabs/unbound\n\nAPP=\"Unbound\"\nvar_tags=\"${var_tags:-dns}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/unbound ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Unbound\"\n  $STD apt update\n  $STD apt -y upgrade\n  msg_ok \"Updated Unbound\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5335${CL}\"\n"
  },
  {
    "path": "ct/unifi-os-server.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ui.com/\n\nAPP=\"UniFi-OS-Server\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_tun=\"${var_tun:-yes}\"\nvar_nesting=\"${var_nesting:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/sbin/unifi-os-server.bin && ! -d /data/unifi ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_custom \"🚀\" \"${GN}\" \"The app offers a built-in updater. Please use it.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:11443${CL}\"\n"
  },
  {
    "path": "ct/unmanic.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.unmanic.app/\n\nAPP=\"Unmanic\"\nvar_tags=\"${var_tags:-file;media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\nvar_gpu=\"${var_gpu:-yes}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/unmanic.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating $APP LXC\"\n  $STD pip3 install -U unmanic\n  $STD apt -y upgrade\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8888${CL}\"\n"
  },
  {
    "path": "ct/upgopher.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Eduard González (wanetty)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wanetty/upgopher\n\nAPP=\"Upgopher\"\nvar_tags=\"${var_tags:-file-sharing}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/upgopher ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    if check_for_gh_release \"upgopher\" \"wanetty/upgopher\"; then\n        msg_info \"Stopping Service\"\n        systemctl stop upgopher\n        msg_ok \"Stopped Service\"\n\n        fetch_and_deploy_gh_release \"upgopher\" \"wanetty/upgopher\" \"prebuild\" \"latest\" \"/opt/upgopher\" \"upgopher_*_linux_arm64.tar.gz\"\n        chmod +x /opt/upgopher/upgopher\n\n        msg_info \"Starting Service\"\n        systemctl start upgopher\n        msg_ok \"Started Service\"\n        msg_ok \"Updated successfully!\"\n    fi\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9090${CL}\"\n"
  },
  {
    "path": "ct/upsnap.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/seriousm4x/UpSnap\n\nAPP=\"UpSnap\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/upsnap ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    if check_for_gh_release \"upsnap\" \"seriousm4x/UpSnap\"; then\n        msg_info \"Stopping Services\"\n        systemctl stop upsnap\n        msg_ok \"Stopped Services\"\n\n        fetch_and_deploy_gh_release \"upsnap\" \"seriousm4x/UpSnap\" \"prebuild\" \"latest\" \"/opt/upsnap\" \"UpSnap_*_linux_arm64.zip\"\n\n        msg_info \"Starting Services\"\n        systemctl start upsnap\n        msg_ok \"Started Services\"\n        msg_ok \"Updated successfully!\"\n    fi\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8090${CL}\"\n"
  },
  {
    "path": "ct/uptimekuma.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://uptime.kuma.pet/\n\nAPP=\"Uptime Kuma\"\nvar_tags=\"${var_tags:-analytics;monitoring}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/uptime-kuma ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" setup_nodejs\n\n  ensure_dependencies chromium\n  if [[ ! -L /opt/uptime-kuma/chromium ]]; then\n    ln -s /usr/bin/chromium /opt/uptime-kuma/chromium\n  fi\n\n  if check_for_gh_release \"uptime-kuma\" \"louislam/uptime-kuma\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop uptime-kuma\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"uptime-kuma\" \"louislam/uptime-kuma\" \"tarball\"\n\n    msg_info \"Updating Uptime Kuma\"\n    cd /opt/uptime-kuma\n    $STD npm install --omit dev\n    $STD npm run download-dist\n    msg_ok \"Updated Uptime Kuma\"\n\n    msg_info \"Starting Service\"\n    systemctl start uptime-kuma\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3001${CL}\"\n"
  },
  {
    "path": "ct/urbackupserver.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.urbackup.org/\n\nAPP=\"UrBackup Server\"\nvar_tags=\"${var_tags:-backup}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/urbackup ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating ${APP} LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\npct set \"$CTID\" -features fuse=1,nesting=1\npct reboot \"$CTID\"\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}:55414${CL}\"\n"
  },
  {
    "path": "ct/valkey.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pshankinclarke (lazarillo)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://valkey.io/\n\nAPP=\"Valkey\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -f /lib/systemd/system/valkey-server.service ]]; then\n        msg_error \"No Valkey Installation Found!\"\n        exit\n    fi\n    msg_info \"Updating Valkey LXC\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated Valkey LXC\"\n    msg_ok \"Updated successfully!\"\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6379${CL}\"\n"
  },
  {
    "path": "ct/vaultwarden.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dani-garcia/vaultwarden\n\nAPP=\"Vaultwarden\"\nvar_tags=\"${var_tags:-password-manager}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/vaultwarden.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  VAULT=$(get_latest_github_release \"dani-garcia/vaultwarden\")\n  WVRELEASE=$(get_latest_github_release \"dani-garcia/bw_web_builds\")\n\n  UPD=$(msg_menu \"Vaultwarden Update Options\" \\\n    \"1\" \"Update VaultWarden + Web-Vault\" \\\n    \"2\" \"Set Admin Token\")\n\n  if [ \"$UPD\" == \"1\" ]; then\n    if check_for_gh_release \"vaultwarden\" \"dani-garcia/vaultwarden\"; then\n      msg_info \"Stopping Service\"\n      systemctl stop vaultwarden\n      msg_ok \"Stopped Service\"\n\n      fetch_and_deploy_gh_release \"vaultwarden\" \"dani-garcia/vaultwarden\" \"tarball\" \"latest\" \"/tmp/vaultwarden-src\"\n\n      msg_info \"Updating VaultWarden to $VAULT (Patience)\"\n      cd /tmp/vaultwarden-src\n      VW_VERSION=\"$VAULT\"\n      export VW_VERSION\n      $STD cargo build --features \"sqlite,mysql,postgresql\" --release\n      if [[ -f /usr/bin/vaultwarden ]]; then\n        cp target/release/vaultwarden /usr/bin/\n      else\n        cp target/release/vaultwarden /opt/vaultwarden/bin/\n      fi\n      cd ~ && rm -rf /tmp/vaultwarden-src\n      msg_ok \"Updated VaultWarden to ${VAULT}\"\n\n      msg_info \"Starting Service\"\n      systemctl start vaultwarden\n      msg_ok \"Started Service\"\n    else\n      msg_ok \"VaultWarden is already up-to-date\"\n    fi\n\n    if check_for_gh_release \"vaultwarden_webvault\" \"dani-garcia/bw_web_builds\"; then\n      msg_info \"Stopping Service\"\n      systemctl stop vaultwarden\n      msg_ok \"Stopped Service\"\n\n      msg_info \"Updating Web-Vault to $WVRELEASE\"\n      rm -rf /opt/vaultwarden/web-vault\n      mkdir -p /opt/vaultwarden/web-vault\n\n      fetch_and_deploy_gh_release \"vaultwarden_webvault\" \"dani-garcia/bw_web_builds\" \"prebuild\" \"latest\" \"/opt/vaultwarden/web-vault\" \"bw_web_*.tar.gz\"\n\n      chown -R root:root /opt/vaultwarden/web-vault/\n      msg_ok \"Updated Web-Vault to ${WVRELEASE}\"\n\n      msg_info \"Starting Service\"\n      systemctl start vaultwarden\n      msg_ok \"Started Service\"\n    else\n      msg_ok \"Web-Vault is already up-to-date\"\n    fi\n\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n\n  if [ \"$UPD\" == \"2\" ]; then\n    if [[ \"${PHS_SILENT:-0}\" == \"1\" ]]; then\n      msg_warn \"Set Admin Token requires interactive mode, skipping.\"\n      exit\n    fi\n    read -r -s -p \"Set the ADMIN_TOKEN: \" NEWTOKEN\n    echo \"\"\n    if [[ -n \"$NEWTOKEN\" ]]; then\n      ensure_dependencies argon2\n      TOKEN=$(echo -n \"${NEWTOKEN}\" | argon2 \"$(openssl rand -base64 32)\" -t 2 -m 16 -p 4 -l 64 -e)\n      sed -i \"s|ADMIN_TOKEN=.*|ADMIN_TOKEN='${TOKEN}'|\" /opt/vaultwarden/.env\n      if [[ -f /opt/vaultwarden/data/config.json ]]; then\n        sed -i \"s|\\\"admin_token\\\":.*|\\\"admin_token\\\": \\\"${TOKEN}\\\"|\" /opt/vaultwarden/data/config.json\n      fi\n      systemctl restart vaultwarden\n      msg_ok \"Admin token updated\"\n    fi\n    exit\n  fi\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/versitygw.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/versity/versitygw\n\nAPP=\"VersityGW\"\nvar_tags=\"${var_tags:-s3;storage;gateway}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/bin/versitygw ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"versitygw\" \"versity/versitygw\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop versitygw@gateway\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"versitygw\" \"versity/versitygw\" \"binary\"\n\n    msg_info \"Starting Service\"\n    systemctl start versitygw@gateway\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:7070 (Gateway) or http://${IP}:7070 (WebUI)${CL}\""
  },
  {
    "path": "ct/victoriametrics.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/VictoriaMetrics/VictoriaMetrics\n\nAPP=\"VictoriaMetrics\"\nvar_tags=\"${var_tags:-database}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-16}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/victoriametrics ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"victoriametrics\" \"VictoriaMetrics/VictoriaMetrics\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop victoriametrics\n    [[ -f /etc/systemd/system/victoriametrics-logs.service ]] && systemctl stop victoriametrics-logs\n    [[ -f /etc/systemd/system/vmagent.service ]] && systemctl stop vmagent\n    [[ -f /etc/systemd/system/vmalert.service ]] && systemctl stop vmalert\n    msg_ok \"Stopped Service\"\n\n    victoriametrics_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaMetrics/releases/latest\" |\n      jq -r '.assets[].name' |\n      grep -E '^victoria-metrics-linux-arm64-v[0-9.]+\\.tar\\.gz$')\n    vmutils_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaMetrics/releases/latest\" |\n      jq -r '.assets[].name' |\n      grep -E '^vmutils-linux-arm64-v[0-9.]+\\.tar\\.gz$')\n\n    msg_debug \"Using release $victoriametrics_release\"\n\n    victoriametrics_filename=\"victoria-metrics-linux-amd64-${victoriametrics_release}.tar.gz\"\n    vmutils_filename=\"vmutils-linux-amd64-${victoriametrics_release}.tar.gz\"\n\n    fetch_and_deploy_gh_release \"victoriametrics\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"$victoriametrics_release\" \"/opt/victoriametrics\" \"$victoriametrics_filename\"\n    fetch_and_deploy_gh_release \"vmutils\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"$victoriametrics_release\" \"/opt/victoriametrics\" \"$vmutils_filename\"\n\n    if [[ -f /etc/systemd/system/victoriametrics-logs.service ]]; then\n      vmlogs_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaLogs/releases/latest\" |\n        jq -r '.assets[].name' |\n        grep -E '^victoria-logs-linux-arm64-v[0-9.]+\\.tar\\.gz$')  \n      vlutils_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaLogs/releases/latest\" |\n        jq -r '.assets[].name' |\n        grep -E '^vlutils-linux-arm64-v[0-9.]+\\.tar\\.gz$')\n      fetch_and_deploy_gh_release \"victorialogs\" \"VictoriaMetrics/VictoriaLogs\" \"prebuild\" \"latest\" \"/opt/victoriametrics\" \"$vmlogs_filename\"\n      fetch_and_deploy_gh_release \"vlutils\" \"VictoriaMetrics/VictoriaLogs\" \"prebuild\" \"latest\" \"/opt/victoriametrics\" \"$vlutils_filename\"\n    fi\n    chmod +x /opt/victoriametrics/*\n\n    msg_info \"Starting Service\"\n    systemctl start victoriametrics\n    [[ -f /etc/systemd/system/victoriametrics-logs.service ]] && systemctl start victoriametrics-logs\n    [[ -f /etc/systemd/system/vmagent.service ]] && systemctl start vmagent\n    [[ -f /etc/systemd/system/vmalert.service ]] && systemctl start vmalert\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8428/vmui${CL}\"\n"
  },
  {
    "path": "ct/vikunja.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://vikunja.io/\n\nAPP=\"Vikunja\"\nvar_tags=\"${var_tags:-todo-app}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/vikunja ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=\"$( [[ -f \"$HOME/.vikunja\" ]] && cat \"$HOME/.vikunja\" 2>/dev/null || [[ -f /opt/Vikunja_version ]] && cat /opt/Vikunja_version 2>/dev/null || true)\"\n  if [[ -z \"$RELEASE\" ]] || [[ \"$RELEASE\" == \"unstable\" ]] || dpkg --compare-versions \"${RELEASE:-0.0.0}\" lt \"1.0.0\"; then\n    msg_warn \"You are upgrading from Vikunja '$RELEASE'.\"\n    msg_warn \"This requires MANUAL config changes in /etc/vikunja/config.yml.\"\n    msg_warn \"See: https://vikunja.io/changelog/whats-new-in-vikunja-1.0.0/#config-changes\"\n\n    read -rp \"Continue with update? (y to proceed): \" -t 30 CONFIRM1 || exit 254\n    [[ \"$CONFIRM1\" =~ ^[yY]$ ]] || exit 0\n\n    echo\n    msg_warn \"Vikunja may not start after the update until you manually adjust the config.\"\n    msg_warn \"Details: https://vikunja.io/changelog/whats-new-in-vikunja-1.0.0/#config-changes\"\n\n    read -rp \"Acknowledge and continue? (y): \" -t 30 CONFIRM2 || exit 254\n    [[ \"$CONFIRM2\" =~ ^[yY]$ ]] || exit 0\n  fi\n\n  if check_for_gh_release \"vikunja\" \"go-vikunja/vikunja\"; then\n    echo\n    msg_warn \"The package update may include config file changes.\"\n    echo -e \"${TAB}${YW}How do you want to handle /etc/vikunja/config.yml?${CL}\"\n    echo -e \"${TAB}  1) Keep your current config\"\n    echo -e \"${TAB}  2) Install the new package maintainer's config\"\n    read -rp \"  Choose [1/2] (default: 1): \" -t 60 CONFIG_CHOICE || CONFIG_CHOICE=\"1\"\n    [[ -z \"$CONFIG_CHOICE\" ]] && CONFIG_CHOICE=\"1\"\n\n    if [[ \"$CONFIG_CHOICE\" == \"2\" ]]; then\n      export DPKG_FORCE_CONFNEW=\"1\"\n    else\n      export DPKG_FORCE_CONFOLD=\"1\"\n    fi\n\n    msg_info \"Stopping Service\"\n    systemctl stop vikunja\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"vikunja\" \"go-vikunja/vikunja\" \"binary\"\n    $STD systemctl daemon-reload\n\n    msg_info \"Starting Service\"\n    systemctl start vikunja\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit 0\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3456${CL}\"\n"
  },
  {
    "path": "ct/wallabag.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wallabag.org/\n\nAPP=\"Wallabag\"\nvar_tags=\"${var_tags:-productivity;read-it-later}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/wallabag ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"wallabag\" \"wallabag/wallabag\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop nginx php8.3-fpm\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/wallabag/app/config/parameters.yml /tmp/wallabag_parameters.yml.bak\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wallabag\" \"wallabag/wallabag\" \"prebuild\" \"latest\" \"/opt/wallabag\" \"wallabag-*.tar.gz\"\n\n    msg_info \"Restoring Configuration\"\n    cp /tmp/wallabag_parameters.yml.bak /opt/wallabag/app/config/parameters.yml\n    rm -f /tmp/wallabag_parameters.yml.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Running Migrations\"\n    cd /opt/wallabag\n    $STD php bin/console cache:clear --env=prod\n    $STD php bin/console doctrine:migrations:migrate --env=prod --no-interaction\n    chown -R www-data:www-data /opt/wallabag\n    chmod -R 755 /opt/wallabag/{var,web/assets}\n    msg_ok \"Ran Migrations\"\n\n    msg_info \"Starting Services\"\n    systemctl start php8.3-fpm nginx\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/wallos.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wallosapp.com/\n\nAPP=\"Wallos\"\nvar_tags=\"${var_tags:-finance}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/wallos ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"wallos\" \"ellite/Wallos\"; then\n    msg_info \"Creating backup\"\n    mkdir -p /opt/logos\n    mv /opt/wallos/db/wallos.db /opt/wallos.db\n    mv /opt/wallos/images/uploads/logos /opt/logos/\n    msg_ok \"Backup created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wallos\" \"ellite/Wallos\" \"tarball\"\n\n    msg_info \"Configuring Wallos\"\n    rm -rf /opt/wallos/db/wallos.empty.db\n    mv /opt/wallos.db /opt/wallos/db/wallos.db\n    mv /opt/logos/* /opt/wallos/images/uploads/logos\n    if ! grep -q \"storetotalyearlycost.php\" /opt/wallos.cron; then\n      echo \"30 1 * * 1 php /opt/wallos/endpoints/cronjobs/storetotalyearlycost.php >> /var/log/cron/storetotalyearlycost.log 2>&1\" >>/opt/wallos.cron\n    fi\n    chown -R www-data:www-data /opt/wallos\n    chmod -R 755 /opt/wallos\n    mkdir -p /var/log/cron\n    $STD curl http://localhost/endpoints/db/migrate.php\n    msg_ok \"Configured Wallos\"\n\n    msg_info \"Reload Apache2\"\n    systemctl reload apache2\n    msg_ok \"Apache2 Reloaded\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/wanderer.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rrole\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wanderer.to\n\nAPP=\"Wanderer\"\nvar_tags=\"${var_tags:-travelling;sport}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/wanderer/start.sh ]]; then\n    msg_error \"No wanderer Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"wanderer\" \"Flomp/wanderer\"; then\n    msg_info \"Stopping service\"\n    systemctl stop wanderer-web\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"wanderer\" \"open-wanderer/wanderer\" \"tarball\" \"latest\" \"/opt/wanderer/source\"\n\n    msg_info \"Updating wanderer\"\n    cd /opt/wanderer/source/db\n    $STD go mod tidy\n    $STD go build\n    cd /opt/wanderer/source/web\n    $STD npm ci --omit=dev\n    $STD npm run build\n    msg_ok \"Updated wanderer\"\n\n    msg_info \"Starting service\"\n    systemctl start wanderer-web\n    msg_ok \"Started service\"\n    msg_ok \"Update Successful\"\n  fi\n  if check_for_gh_release \"meilisearch\" \"meilisearch/meilisearch\"; then\n    msg_info \"Stopping service\"\n    systemctl stop wanderer-web\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"meilisearch\" \"meilisearch/meilisearch\" \"singlefile\" \"latest\" \"/opt/wanderer/source/search\" \"meilisearch-linux-aarch64\"\n    grep -q -- '--experimental-dumpless-upgrade' /opt/wanderer/start.sh || sed -i 's|meilisearch --master-key|meilisearch --experimental-dumpless-upgrade --master-key|' /opt/wanderer/start.sh\n\n    msg_info \"Starting service\"\n    systemctl start wanderer-web\n    msg_ok \"Started service\"\n    msg_ok \"Update Successful\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/warracker.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: BvdBerg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/sassanix/Warracker/\n\nAPP=\"Warracker\"\nvar_tags=\"${var_tags:-warranty}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/warracker ]]; then\n        msg_error \"No ${APP} Installation Found!\"\n        exit\n    fi\n\n    if check_for_gh_release \"warracker\" \"sassanix/Warracker\"; then\n        msg_info \"Stopping Services\"\n        systemctl stop warrackermigration\n        systemctl stop warracker\n        systemctl stop nginx\n        msg_ok \"Stopped Services\"\n\n        fetch_and_deploy_gh_release \"warracker\" \"sassanix/Warracker\" \"tarball\" \"latest\" \"/opt/warracker\"\n\n        msg_info \"Updating Warracker\"\n        cd /opt/warracker/backend\n        $STD uv venv --clear .venv\n        $STD source .venv/bin/activate\n        $STD uv pip install -r requirements.txt\n        msg_ok \"Updated Warracker\"\n\n        msg_info \"Starting Services\"\n        systemctl start warracker\n        systemctl start nginx\n        msg_ok \"Started Services\"\n        msg_ok \"Updated successfully!\"\n    fi\n    exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/wastebin.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/matze/wastebin\n\nAPP=\"Wastebin\"\nvar_tags=\"${var_tags:-file;code}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/wastebin ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  ensure_dependencies zstd\n  RELEASE=$(curl -fsSL https://api.github.com/repos/matze/wastebin/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\n  # Dirty-Fix 03/2025 for missing APP_version.txt on old installations, set to pre-latest release\n  msg_info \"Running Migration\"\n  if [[ ! -f /opt/${APP}_version.txt ]]; then\n    echo \"2.7.1\" >/opt/${APP}_version.txt\n    mkdir -p /opt/wastebin-data\n    cat <<EOF >/opt/wastebin-data/.env\nWASTEBIN_DATABASE_PATH=/opt/wastebin-data/wastebin.db\nWASTEBIN_CACHE_SIZE=1024\nWASTEBIN_HTTP_TIMEOUT=30\nWASTEBIN_SIGNING_KEY=$(openssl rand -hex 32)\nWASTEBIN_PASTE_EXPIRATIONS=0,600,3600=d,86400,604800,2419200,29030400\nEOF\n    systemctl stop wastebin\n    cat <<EOF >/etc/systemd/system/wastebin.service\n[Unit]\nDescription=Wastebin Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/wastebin\nExecStart=/opt/wastebin/wastebin\nEnvironmentFile=/opt/wastebin-data/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n  fi\n  msg_ok \"Migration Done\"\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_info \"Stopping Wastebin\"\n    systemctl stop wastebin\n    msg_ok \"Wastebin Stopped\"\n\n    msg_info \"Updating Wastebin\"\n    temp_file=$(mktemp)\n    curl -fsSL \"https://github.com/matze/wastebin/releases/download/${RELEASE}/wastebin_${RELEASE}_aarch64-unknown-linux-musl.tar.zst\" -o \"$temp_file\"\n    tar -xf \"$temp_file\"\n    cp -f wastebin* /opt/wastebin/\n    chmod +x /opt/wastebin/wastebin\n    chmod +x /opt/wastebin/wastebin-ctl\n    rm -f \"$temp_file\"\n    echo \"${RELEASE}\" >/opt/${APP}_version.txt\n    msg_ok \"Updated Wastebin\"\n\n    msg_info \"Starting Wastebin\"\n    systemctl start wastebin\n    msg_ok \"Started Wastebin\"\n    msg_ok \"Updated successfully!\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8088${CL}\"\n"
  },
  {
    "path": "ct/watcharr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/sbondCo/Watcharr\n\nAPP=\"Watcharr\"\nvar_tags=\"${var_tags:-media}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/watcharr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"watcharr\" \"sbondCo/Watcharr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop watcharr\n    msg_ok \"Stopped Service\"\n\n    rm -f /opt/watcharr/server/watcharr\n    rm -rf /opt/watcharr/server/ui\n    fetch_and_deploy_gh_release \"watcharr\" \"sbondCo/Watcharr\" \"tarball\"\n\n    msg_info \"Updating Watcharr\"\n    cd /opt/watcharr\n    export GOOS=linux\n    $STD npm i\n    $STD npm run build\n    mv ./build ./server/ui\n    cd server\n    $STD go mod download\n    $STD go build -o ./watcharr\n    msg_ok \"Updated Watcharr\"\n\n    msg_info \"Starting Service\"\n    systemctl start watcharr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3080${CL}\"\n"
  },
  {
    "path": "ct/watchyourlan.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/aceberg/WatchYourLAN\n\nAPP=\"WatchYourLAN\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/watchyourlan.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"watchyourlan\" \"aceberg/WatchYourLAN\"; then\n    msg_info \"Stopping service\"\n    systemctl stop watchyourlan\n    msg_ok \"Service stopped\"\n\n    cp -R /data/config.yaml ~/config.yaml\n    fetch_and_deploy_gh_release \"watchyourlan\" \"aceberg/WatchYourLAN\" \"binary\"\n    cp -R config.yaml /data/config.yaml\n    sed -i 's|/etc/watchyourlan/config.yaml|/data/config.yaml|' /lib/systemd/system/watchyourlan.service\n    rm ~/config.yaml\n\n    msg_info \"Starting service\"\n    systemctl enable -q --now watchyourlan\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8840${CL}\"\n"
  },
  {
    "path": "ct/wavelog.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Don Locke (DonLocke)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.wavelog.org/\n\nAPP=\"Wavelog\"\nvar_tags=\"${var_tags:-radio-logging}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/wavelog ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  if check_for_gh_release \"wavelog\" \"wavelog/wavelog\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop apache2\n    msg_ok \"Services Stopped\"\n\n    msg_info \"Creating backup\"\n    cp /opt/wavelog/application/config/config.php /opt/config.php\n    cp /opt/wavelog/application/config/database.php /opt/database.php\n    cp -r /opt/wavelog/userdata /opt/userdata\n    if [[ -f /opt/wavelog/assets/js/sections/custom.js ]]; then\n      cp /opt/wavelog/assets/js/sections/custom.js /opt/custom.js\n    fi\n    msg_ok \"Backup created\"\n\n    rm -rf /opt/wavelog\n    fetch_and_deploy_gh_release \"wavelog\" \"wavelog/wavelog\" \"tarball\"\n\n    msg_info \"Updating Wavelog\"\n    rm -rf /opt/wavelog/install\n    mv /opt/config.php /opt/wavelog/application/config/config.php\n    mv /opt/database.php /opt/wavelog/application/config/database.php\n    cp -r /opt/userdata/* /opt/wavelog/userdata\n    rm -rf /opt/userdata\n    if [[ -f /opt/custom.js ]]; then\n      mv /opt/custom.js /opt/wavelog/assets/js/sections/custom.js\n    fi\n    chown -R www-data:www-data /opt/wavelog/\n    find /opt/wavelog/ -type d -exec chmod 755 {} \\;\n    find /opt/wavelog/ -type f -exec chmod 664 {} \\;\n    msg_ok \"Updated Wavelog\"\n\n    msg_info \"Starting Services\"\n    systemctl start apache2\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/wazuh.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wazuh.com/\n\necho -e \"Wazuh not supported on ARM64.\\n\"\nexit\n\nAPP=\"Wazuh\"\nvar_tags=\"${var_tags:-security;monitoring}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-25}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /lib/systemd/system/wazuh-manager.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating Wazuh LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated Wazuh LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:443${CL}\"\n"
  },
  {
    "path": "ct/wealthfolio.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wealthfolio.app/\n\nAPP=\"Wealthfolio\"\nvar_tags=\"${var_tags:-finance;portfolio}\"\nvar_cpu=\"${var_cpu:-4}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/wealthfolio ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\n\n  if grep -q '^WF_CORS_ALLOW_ORIGINS=\\*$' /opt/wealthfolio/.env; then\n    sed -i \"s|^WF_CORS_ALLOW_ORIGINS=\\*$|WF_CORS_ALLOW_ORIGINS=http://${LOCAL_IP}:8080|\" /opt/wealthfolio/.env\n  fi\n\n  if check_for_gh_release \"wealthfolio\" \"afadil/wealthfolio\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop wealthfolio\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/wealthfolio_data /opt/wealthfolio_data_backup\n    cp /opt/wealthfolio/.env /opt/wealthfolio_env_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wealthfolio\" \"afadil/wealthfolio\" \"tarball\"\n\n    msg_info \"Building Frontend (patience)\"\n    cd /opt/wealthfolio\n    export BUILD_TARGET=web\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm --filter frontend... build\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Building Backend (patience)\"\n    source ~/.cargo/env\n    $STD cargo build --release --manifest-path apps/server/Cargo.toml\n    cp /opt/wealthfolio/target/release/wealthfolio-server /usr/local/bin/wealthfolio-server\n    chmod +x /usr/local/bin/wealthfolio-server\n    msg_ok \"Built Backend\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/wealthfolio_data_backup/. /opt/wealthfolio_data\n    cp /opt/wealthfolio_env_backup /opt/wealthfolio/.env\n    rm -rf /opt/wealthfolio_data_backup /opt/wealthfolio_env_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Cleaning Up\"\n    rm -rf /opt/wealthfolio/target\n    rm -rf /root/.cargo/registry\n    rm -rf /opt/wealthfolio/node_modules\n    msg_ok \"Cleaned Up\"\n\n    msg_info \"Starting Service\"\n    systemctl start wealthfolio\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/web-check.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Lissy93/web-check\n\nAPP=\"web-check\"\nvar_tags=\"${var_tags:-network;analysis}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-12}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/web-check ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"web-check\" \"Lissy93/web-check\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop web-check\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating backup\"\n    mv /opt/web-check/.env /opt\n    msg_ok \"Created backup\"\n\n    NODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"web-check\" \"Lissy93/web-check\" \"tarball\"\n\n    msg_info \"Restoring backup\"\n    mv /opt/.env /opt/web-check\n    msg_ok \"Restored backup\"\n\n    msg_info \"Building Web-Check\"\n    cd /opt/web-check \n    $STD yarn install --frozen-lockfile --network-timeout 100000\n    $STD yarn build --production\n    $STD npm cache clean --force\n    msg_ok \"Built Web-Check\"\n\n    msg_info \"Starting Service\"\n    systemctl start web-check\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/wger.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wger-project/wger\n\nAPP=\"wger\"\nvar_tags=\"${var_tags:-management;fitness}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/wger ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"wger\" \"wger-project/wger\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop redis-server nginx celery celery-beat wger\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/wger/media /opt/wger_media_backup\n    cp /opt/wger/.env /opt/wger_env_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wger\" \"wger-project/wger\" \"tarball\"\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/wger_media_backup/. /opt/wger/media\n    cp /opt/wger_env_backup /opt/wger/.env\n    rm -rf /opt/wger_media_backup /opt/wger_env_backup\n\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating wger\"\n    cd /opt/wger\n    set -a && source /opt/wger/.env && set +a\n    export DJANGO_SETTINGS_MODULE=settings.main\n    $STD uv pip install .\n    $STD npm install\n    $STD npm run build:css:sass\n    $STD uv run python manage.py migrate\n    $STD uv run python manage.py collectstatic --no-input\n    msg_ok \"Updated wger\"\n\n    msg_info \"Starting Services\"\n    systemctl start redis-server nginx celery celery-beat wger\n    msg_ok \"Started Services\"\n    msg_ok \"Updated Successfully\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/whisparr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Whisparr/Whisparr\n\nAPP=\"Whisparr\"\nvar_tags=\"${var_tags:-arr}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/lib/whisparr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  \n  msg_custom \"🚀\" \"${GN}\" \"The app offers a built-in updater. Please use it.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:6969${CL}\"\n"
  },
  {
    "path": "ct/whodb.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://whodb.com/\n\nAPP=\"WhoDB\"\nvar_tags=\"${var_tags:-database;management;gui}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-2}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/whodb/whodb ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"whodb\" \"clidey/whodb\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop whodb\n    msg_ok \"Stopped Service\"\n\n    fetch_and_deploy_gh_release \"whodb\" \"clidey/whodb\" \"singlefile\" \"latest\" \"/opt/whodb\" \"whodb-*-linux-amd64\"\n\n    msg_info \"Starting Service\"\n    systemctl start whodb\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/wikijs.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://js.wiki/\n\nAPP=\"Wikijs\"\nvar_tags=\"${var_tags:-wiki}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/wikijs ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"yarn,node-gyp\" setup_nodejs\n\n  if check_for_gh_release \"wikijs\" \"requarks/wiki\"; then\n    msg_info \"Verifying whether ${APP}' new release is v3.x+ and current install uses SQLite.\"\n    SQLITE_INSTALL=$([ -f /opt/wikijs/db.sqlite ] && echo \"true\" || echo \"false\")\n    if [[ \"${SQLITE_INSTALL}\" == \"true\" && \"${CHECK_UPDATE_RELEASE}\" =~ ^3.* ]]; then\n      echo \"SQLite is not supported in v3.x+, currently there is no update path availble.\"\n      exit\n    fi\n    msg_ok \"There is an update path available for ${APP}\"\n\n    msg_info \"Stopping Service\"\n    systemctl stop wikijs\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    mkdir /opt/wikijs-backup\n    $SQLITE_INSTALL && cp /opt/wikijs/db.sqlite /opt/wikijs-backup\n    cp -R /opt/wikijs/{config.yml,/data} /opt/wikijs-backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wikijs\" \"requarks/wiki\" \"prebuild\" \"latest\" \"/opt/wikijs\" \"wiki-js.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    cp -R /opt/wikijs-backup/* /opt/wikijs\n    $SQLITE_INSTALL && $STD npm rebuild sqlite3\n    rm -rf /opt/wikijs-backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start wikijs\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/wireguard.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.wireguard.com/\n\nAPP=\"Wireguard\"\nvar_tags=\"${var_tags:-network;vpn}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\nvar_tun=\"${var_tun:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /etc/wireguard ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  \n  ensure_dependencies git\n\n  msg_info \"Updating LXC\"\n  $STD apt update\n  $STD apt upgrade -y\n  if [[ -d /etc/wgdashboard ]]; then\n    sleep 2\n    cd /etc/wgdashboard/src\n    $STD ./wgd.sh update -y\n    $STD ./wgd.sh start\n  fi\n  msg_ok \"Updated LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW}Access WGDashboard (if installed) using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:10086${CL}\"\n"
  },
  {
    "path": "ct/wishlist.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dunky13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/cmintey/wishlist\n\nAPP=\"Wishlist\"\nvar_tags=\"${var_tags:-sharing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/wishlist ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"wishlist\" \"cmintey/wishlist\"; then\n    NODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\n\n    msg_info \"Stopping Service\"\n    systemctl stop wishlist\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /opt/wishlist-backup\n    cp /opt/wishlist/.env /opt/wishlist-backup/.env\n    cp -a /opt/wishlist/uploads /opt/wishlist-backup\n    cp -a /opt/wishlist/data /opt/wishlist-backup\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"wishlist\" \"cmintey/wishlist\" \"tarball\"\n    LATEST_APP_VERSION=$(get_latest_github_release \"cmintey/wishlist\")\n\n    msg_info \"Updating Wishlist\"\n    cd /opt/wishlist\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm svelte-kit sync\n    $STD pnpm prisma generate\n    sed -i 's|/usr/src/app/|/opt/wishlist/|g' $(grep -rl '/usr/src/app/' /opt/wishlist)\n    export VERSION=\"v${LATEST_APP_VERSION}\"\n    export SHA=\"v${LATEST_APP_VERSION}\"\n    $STD pnpm run build\n    $STD pnpm prune --prod\n    chmod +x /opt/wishlist/entrypoint.sh\n\n    msg_info \"Restoring Backup\"\n    cp /opt/wishlist-backup/.env /opt/wishlist/.env\n    cp -a /opt/wishlist-backup/uploads /opt/wishlist\n    cp -a /opt/wishlist-backup/data /opt/wishlist\n    rm -rf /opt/wishlist-backup\n    msg_ok \"Restored Backup\"\n    \n    msg_ok \"Updated Wishlist\"\n    msg_info \"Starting Service\"\n    systemctl start wishlist\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3280${CL}\"\n"
  },
  {
    "path": "ct/wizarr.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wizarrrr/wizarr\n\nAPP=\"Wizarr\"\nvar_tags=\"${var_tags:-media;arr}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/wizarr ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  setup_uv\n\n  if check_for_gh_release \"wizarr\" \"wizarrrr/wizarr\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop wizarr\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    BACKUP_FILE=\"/opt/wizarr_backup_$(date +%F).tar.gz\"\n    $STD tar -czf \"$BACKUP_FILE\" /opt/wizarr/{.env,start.sh} /opt/wizarr/database/ &>/dev/null\n    rm -rf /opt/wizarr/migrations/versions/*\n    msg_ok \"Backup Created\"\n\n    fetch_and_deploy_gh_release \"wizarr\" \"wizarrrr/wizarr\" \"tarball\"\n\n    msg_info \"Updating Wizarr\"\n    cd /opt/wizarr\n    $STD /usr/local/bin/uv sync --frozen\n    $STD /usr/local/bin/uv run --frozen pybabel compile -d app/translations\n    $STD npm --prefix app/static install\n    $STD npm --prefix app/static run build:css\n    mkdir -p ./.cache\n    $STD tar -xf \"$BACKUP_FILE\" --directory=/\n    if grep -q 'workers' /opt/wizarr/start.sh; then\n      sed -i 's/--workers 4//' /opt/wizarr/start.sh\n    fi\n    if ! grep -qE 'FLASK|WORKERS|VERSION' /opt/wizarr/.env; then\n      {\n        echo \"FLASK_ENV=production\"\n        echo \"GUNICORN_WORKERS=4\"\n        echo \"APP_VERSION=$(sed 's/^20/v&/' ~/.wizarr)\"\n      } >>/opt/wizarr/.env\n    else\n      sed -i \"s/_VERSION=v.*$/_VERSION=v$(cat ~/.wizarr)/\" /opt/wizarr/.env\n    fi\n    rm -rf \"$BACKUP_FILE\"\n    export FLASK_SKIP_SCHEDULER=true\n    $STD /usr/local/bin/uv run --frozen flask db upgrade\n    msg_ok \"Updated Wizarr\"\n\n    msg_info \"Starting Service\"\n    systemctl start wizarr\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:5690${CL}\"\n"
  },
  {
    "path": "ct/wordpress.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wordpress.org/\n\n## App Default Values\nAPP=\"Wordpress\"\nvar_tags=\"${var_tags:-blog;cms}\"\nvar_disk=\"${var_disk:-5}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /var/www/html/wordpress ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  setup_mariadb\n  msg_error \"Wordpress should be updated via the user interface.\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN} ${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/${CL}\"\n"
  },
  {
    "path": "ct/writefreely.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: StellaeAlis\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/writefreely/writefreely\n\nAPP=\"WriteFreely\"\nvar_tags=\"${var_tags:-writing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/writefreely ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"writefreely\" \"writefreely/writefreely\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop writefreely\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Creating Backup\"\n    mkdir -p /tmp/writefreely_backup\n    cp /opt/writefreely/keys /tmp/writefreely_backup/ 2>/dev/null\n    cp /opt/writefreely/config.ini /tmp/writefreely_backup/ 2>/dev/null\n    msg_ok \"Created Backup\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"writefreely\" \"writefreely/writefreely\" \"prebuild\" \"latest\" \"/opt/writefreely\" \"writefreely_*_linux_arm64.tar.gz\"\n\n    msg_info \"Restoring Data\"\n    cp /tmp/writefreely_backup/config.ini /opt/writefreely/ 2>/dev/null\n    cp /tmp/writefreely_backup/keys/* /opt/writefreely/keys/ 2>/dev/null\n    rm -rf /tmp/writefreely_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Running Post-Update Tasks\"\n    cd /opt/writefreely\n    $STD ./writefreely db migrate\n    ln -s /opt/writefreely/writefreely /usr/local/bin/writefreely\n    msg_ok \"Ran Post-Update Tasks\"\n\n    msg_info \"Starting Services\"\n    systemctl start writefreely\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/yamtrack.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/FuzzyGrim/Yamtrack\n\nAPP=\"Yamtrack\"\nvar_tags=\"${var_tags:-media;tracker;movies;anime}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/yamtrack ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"yamtrack\" \"FuzzyGrim/Yamtrack\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop yamtrack yamtrack-celery\n    msg_ok \"Stopped Services\"\n\n    msg_info \"Backing up Data\"\n    cp /opt/yamtrack/src/.env /opt/yamtrack_env.bak\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"yamtrack\" \"FuzzyGrim/Yamtrack\" \"tarball\"\n\n    msg_info \"Installing Python Dependencies\"\n    cd /opt/yamtrack\n    $STD uv venv --clear .venv\n    $STD uv pip install --no-cache-dir -r requirements.txt\n    msg_ok \"Installed Python Dependencies\"\n\n    msg_info \"Restoring Data\"\n    cp /opt/yamtrack_env.bak /opt/yamtrack/src/.env\n    rm -f /opt/yamtrack_env.bak\n    msg_ok \"Restored Data\"\n\n    msg_info \"Updating Yamtrack\"\n    cd /opt/yamtrack/src\n    $STD /opt/yamtrack/.venv/bin/python manage.py migrate\n    $STD /opt/yamtrack/.venv/bin/python manage.py collectstatic --noinput\n    msg_ok \"Updated Yamtrack\"\n\n    msg_info \"Updating Nginx Configuration\"\n    cp /opt/yamtrack/nginx.conf /etc/nginx/nginx.conf\n    sed -i 's|user abc;|user www-data;|' /etc/nginx/nginx.conf\n    sed -i 's|pid /tmp/nginx.pid;|pid /run/nginx.pid;|' /etc/nginx/nginx.conf\n    sed -i 's|/yamtrack/staticfiles/|/opt/yamtrack/src/staticfiles/|' /etc/nginx/nginx.conf\n    sed -i 's|error_log /dev/stderr|error_log /var/log/nginx/error.log|' /etc/nginx/nginx.conf\n    sed -i 's|access_log /dev/stdout|access_log /var/log/nginx/access.log|' /etc/nginx/nginx.conf\n    $STD systemctl reload nginx\n    msg_ok \"Updated Nginx Configuration\"\n\n    msg_info \"Starting Services\"\n    systemctl start yamtrack yamtrack-celery\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/yourls.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://yourls.org/\n\nAPP=\"YOURLS\"\nvar_tags=\"${var_tags:-url-shortener;php}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /opt/yourls/yourls-loader.php ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"yourls\" \"YOURLS/YOURLS\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop nginx\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp -r /opt/yourls/user /opt/yourls_user.bak\n    msg_ok \"Backed up Configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"yourls\" \"YOURLS/YOURLS\" \"tarball\"\n    chown -R www-data:www-data /opt/yourls\n\n    msg_info \"Restoring Configuration\"\n    cp -r /opt/yourls_user.bak/. /opt/yourls/user/\n    rm -rf /opt/yourls_user.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start nginx\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} First, complete the database setup at:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/admin/install.php${CL}\"\necho -e \"${INFO}${YW} Admin credentials are in the install log:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}grep -A2 'admin' /opt/yourls/user/config.php${CL}\"\n"
  },
  {
    "path": "ct/yt-dlp-webui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/marcopiovanello/yt-dlp-web-ui\n\nAPP=\"yt-dlp-webui\"\nvar_tags=\"${var_tags:-downloads;yt-dlp}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/local/bin/yt-dlp-webui ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"yt-dlp-webui\" \"marcopiovanello/yt-dlp-web-ui\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop yt-dlp-webui\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Updating yt-dlp\"\n    $STD yt-dlp -U\n    msg_ok \"Updated yt-dlp\"\n\n    rm -rf /usr/local/bin/yt-dlp-webui\n    fetch_and_deploy_gh_release \"yt-dlp-webui\" \"marcopiovanello/yt-dlp-web-ui\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"yt-dlp-webui_linux-arm64\"\n\n    msg_info \"Starting Service\"\n    systemctl start yt-dlp-webui\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3033${CL}\"\n"
  },
  {
    "path": "ct/yubal.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Crazywolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/guillevc/yubal\n\nAPP=\"Yubal\"\nvar_tags=\"${var_tags:-music;media}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-15}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/yubal ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  ensure_dependencies git\n\n  if check_for_gh_release \"yubal\" \"guillevc/yubal\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop yubal\n    msg_ok \"Stopped Services\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"yubal\" \"guillevc/yubal\" \"tarball\" \"latest\" \"/opt/yubal\"\n\n    msg_info \"Building Frontend\"\n    cd /opt/yubal/web\n    $STD bun install --frozen-lockfile\n    VERSION=$(get_latest_github_release \"guillevc/yubal\")\n    VITE_VERSION=$VERSION VITE_COMMIT_SHA=$VERSION VITE_IS_RELEASE=true $STD bun run build\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Installing Python Dependencies\"\n    cd /opt/yubal\n    $STD uv sync --package yubal-api --no-dev --frozen\n    msg_ok \"Installed Python Dependencies\"\n\n    msg_info \"Starting Services\"\n    systemctl start yubal\n    msg_ok \"Started Services\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/yunohost.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://yunohost.org/\n\nAPP=\"YunoHost\"\nvar_tags=\"${var_tags:-os}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-20}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/apt/trusted.gpg.d/php.gpg ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating OS\"\n  $STD apt-get update\n  $STD apt-get -y upgrade\n  msg_ok \"Updated OS\"\n\n  msg_info \"Updating $APP LXC\"\n  $STD yunohost tools update\n  $STD yunohost tools upgrade system\n  $STD yunohost tools upgrade apps\n  msg_ok \"Updated $APP LXC\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/zabbix.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zabbix.com/\n\nAPP=\"Zabbix\"\nvar_tags=\"${var_tags:-monitoring}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /etc/zabbix/zabbix_server.conf ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  . /etc/os-release\n  if [ \"$VERSION_CODENAME\" != \"trixie\" ]; then\n    msg_error \"Unsupported Debian version: $VERSION_CODENAME – please upgrade to Debian 13 (Trixie) before updating Zabbix.\"\n    exit\n  fi\n\n  if systemctl cat zabbix-agent2.service &>/dev/null; then\n    AGENT_SERVICE=\"zabbix-agent2\"\n  elif systemctl cat zabbix-agent.service &>/dev/null; then\n    AGENT_SERVICE=\"zabbix-agent\"\n  else\n    AGENT_SERVICE=\"\"\n    msg_warn \"No Zabbix Agent service found, skipping agent actions\"\n  fi\n\n  msg_info \"Stopping Services\"\n  systemctl stop zabbix-server\n  [[ -n \"$AGENT_SERVICE\" ]] && systemctl stop \"$AGENT_SERVICE\"\n  msg_ok \"Stopped Services\"\n\n  read -rp \"Choose Zabbix version [1] 7.0 LTS  [2] 7.4 (Latest Stable)  [3] Latest available (default: 2): \" ZABBIX_CHOICE\n  ZABBIX_CHOICE=${ZABBIX_CHOICE:-2}\n  case \"$ZABBIX_CHOICE\" in\n  1) ZABBIX_VERSION=\"7.0\" ;;\n  2) ZABBIX_VERSION=\"7.4\" ;;\n  3) ZABBIX_VERSION=$(curl -fsSL https://repo.zabbix.com/zabbix/ |\n    grep -oP '(?<=href=\")[0-9]+\\.[0-9]+(?=/\")' | sort -V | tail -n1) ;;\n  *)\n    ZABBIX_VERSION=\"7.4\"\n    echo \"Invalid choice. Defaulting to 7.4.\"\n    ;;\n  esac\n\n  msg_info \"Updating Zabbix to $ZABBIX_VERSION\"\n  mkdir -p /opt/zabbix-backup/\n  cp /etc/zabbix/zabbix_server.conf /opt/zabbix-backup/\n  cp /etc/apache2/conf-enabled/zabbix.conf /opt/zabbix-backup/\n  cp -R /usr/share/zabbix/ /opt/zabbix-backup/\n\n  rm -Rf /etc/apt/sources.list.d/zabbix.list\n  cd /tmp\n\n  if [[ \"$ZABBIX_VERSION\" == \"7.0\" ]]; then\n    ZABBIX_DEB_URL=\"https://repo.zabbix.com/zabbix/${ZABBIX_VERSION}/debian/pool/main/z/zabbix-release/zabbix-release_latest_${ZABBIX_VERSION}+debian13_all.deb\"\n    ZABBIX_DEB_FILE=\"zabbix-release_latest_${ZABBIX_VERSION}+debian13_all.deb\"\n  else\n    ZABBIX_DEB_URL=\"https://repo.zabbix.com/zabbix/${ZABBIX_VERSION}/release/debian/pool/main/z/zabbix-release/zabbix-release_latest+debian13_all.deb\"\n    ZABBIX_DEB_FILE=\"zabbix-release_latest+debian13_all.deb\"\n  fi\n\n  curl -fsSL \"$ZABBIX_DEB_URL\" -o /tmp/\"$ZABBIX_DEB_FILE\"\n  $STD dpkg -i /tmp/\"$ZABBIX_DEB_FILE\"\n  rm -rf /tmp/zabbix-release_*.deb\n  $STD apt update\n\n  $STD apt install --only-upgrade zabbix-server-pgsql zabbix-frontend-php php8.4-pgsql\n\n  if [[ \"$AGENT_SERVICE\" == \"zabbix-agent2\" ]]; then\n    $STD apt install --only-upgrade zabbix-agent2 zabbix-agent2-plugin-postgresql\n    if [ -f /etc/zabbix/zabbix_agent2.d/plugins.d/nvidia.conf ]; then\n      sed -i 's|^Plugins.NVIDIA.System.Path=.*|# Plugins.NVIDIA.System.Path=/usr/libexec/zabbix/zabbix-agent2-plugin-nvidia-gpu|' \\\n        /etc/zabbix/zabbix_agent2.d/plugins.d/nvidia.conf\n    fi\n  elif [[ \"$AGENT_SERVICE\" == \"zabbix-agent\" ]]; then\n    $STD apt install --only-upgrade zabbix-agent\n  fi\n\n  if command -v fping >/dev/null 2>&1; then\n    FPING_PATH=$(command -v fping)\n    sed -i \"s|^#\\?FpingLocation=.*|FpingLocation=$FPING_PATH|\" /etc/zabbix/zabbix_server.conf\n  fi\n  if command -v fping6 >/dev/null 2>&1; then\n    FPING6_PATH=$(command -v fping6)\n    sed -i \"s|^#\\?Fping6Location=.*|Fping6Location=$FPING6_PATH|\" /etc/zabbix/zabbix_server.conf\n  fi\n  msg_ok \"Updated Zabbix\"\n\n  msg_info \"Starting Services\"\n  systemctl start zabbix-server\n  [[ -n \"$AGENT_SERVICE\" ]] && systemctl start \"$AGENT_SERVICE\"\n  systemctl restart apache2\n  msg_ok \"Started Services\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}/zabbix${CL}\"\n"
  },
  {
    "path": "ct/zammad.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zammad.com\n\nAPP=\"Zammad\"\nvar_tags=\"${var_tags:-webserver;ticket-system}\"\nvar_disk=\"${var_disk:-8}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-12}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/zammad ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop zammad\n  msg_ok \"Stopped Service\"\n\n  msg_info \"Updating Zammad\"\n  $STD apt update\n  $STD apt-mark hold zammad\n  $STD apt upgrade -y\n  $STD apt-mark unhold zammad\n  $STD apt upgrade -y\n  msg_ok \"Updated Zammad\"\n\n  msg_info \"Starting Service\"\n  systemctl start zammad\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/zerobyte.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: community-scripts\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/nicotsx/zerobyte\n\nAPP=\"Zerobyte\"\nvar_tags=\"${var_tags:-backup;encryption;restic}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-6144}\"\nvar_disk=\"${var_disk:-10}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -d /opt/zerobyte ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"zerobyte\" \"nicotsx/zerobyte\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop zerobyte\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Configuration\"\n    cp /opt/zerobyte/.env /opt/zerobyte.env.bak\n    msg_ok \"Backed up Configuration\"\n    \n    ensure_dependencies git\n    NODE_VERSION=\"24\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"zerobyte\" \"nicotsx/zerobyte\" \"tarball\"\n\n    msg_info \"Building Zerobyte\"\n    export NODE_OPTIONS=\"--max-old-space-size=3072\"\n    cd /opt/zerobyte\n    $STD bun install\n    $STD node ./node_modules/vite/bin/vite.js build\n    msg_ok \"Built Zerobyte\"\n\n    msg_info \"Restoring Configuration\"\n    cp /opt/zerobyte.env.bak /opt/zerobyte/.env\n    rm -f /opt/zerobyte.env.bak\n    msg_ok \"Restored Configuration\"\n\n    msg_info \"Starting Service\"\n    systemctl start zerobyte\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed Successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:4096${CL}\"\n"
  },
  {
    "path": "ct/zerotier-one.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zerotier.com/\n\nAPP=\"Zerotier-One\"\nvar_tags=\"${var_tags:-networking}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-512}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  if [[ ! -f /usr/sbin/zerotier-one ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop zerotier-one\n  msg_ok \"Stopping Service\"\n\n  msg_info \"Updating Zerotier-One\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated Zerotier-One\"\n\n  msg_info \"Starting Service\"\n  systemctl start zerotier-one\n  msg_ok \"Started Service\"\n  msg_ok \"Updated successfully!\"\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following IP:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}https://${IP}:3443${CL}\"\n"
  },
  {
    "path": "ct/zigbee2mqtt.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zigbee2mqtt.io/\n\nAPP=\"Zigbee2MQTT\"\nvar_tags=\"${var_tags:-smarthome;zigbee;mqtt}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/zigbee2mqtt ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"Zigbee2MQTT\" \"Koenkk/zigbee2mqtt\"; then\n    NODE_VERSION=\"24\" NODE_MODULE=\"pnpm@$(curl -fsSL https://raw.githubusercontent.com/Koenkk/zigbee2mqtt/master/package.json | jq -r '.packageManager | split(\"@\")[1]')\" setup_nodejs\n    msg_info \"Stopping Service\"\n    systemctl stop zigbee2mqtt\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    ensure_dependencies zstd\n    mkdir -p /opt/{backups,z2m_backup}\n    BACKUP_VERSION=\"$(<\"$HOME/.zigbee2mqtt\")\"\n    BACKUP_FILE=\"/opt/backups/${APP}_backup_${BACKUP_VERSION}.tar.zst\"\n    $STD tar -cf - -C /opt zigbee2mqtt | zstd -q -o \"$BACKUP_FILE\"\n    ls -t /opt/backups/${APP}_backup_*.tar.zst 2>/dev/null | tail -n +6 | xargs -r rm -f\n    mv /opt/zigbee2mqtt/data /opt/z2m_backup/data\n    msg_ok \"Backup Created (${BACKUP_VERSION})\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Zigbee2MQTT\" \"Koenkk/zigbee2mqtt\" \"tarball\" \"latest\" \"/opt/zigbee2mqtt\"\n\n    msg_info \"Updating Zigbee2MQTT\"\n    rm -rf /opt/zigbee2mqtt/data\n    mv /opt/z2m_backup/data /opt/zigbee2mqtt\n    cd /opt/zigbee2mqtt\n    grep -q \"^packageImportMethod\" ./pnpm-workspace.yaml 2>/dev/null || echo \"packageImportMethod: hardlink\" >>./pnpm-workspace.yaml\n    $STD pnpm install --frozen-lockfile\n    $STD pnpm build\n    rm -rf /opt/z2m_backup\n    msg_ok \"Updated Zigbee2MQTT\"\n\n    msg_info \"Starting Service\"\n    systemctl start zigbee2mqtt\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:9442${CL}\"\n"
  },
  {
    "path": "ct/zipline.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zipline.diced.sh/\n\nAPP=\"Zipline\"\nvar_tags=\"${var_tags:-file;sharing}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/zipline ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  NODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\n\n  if check_for_gh_release \"zipline\" \"diced/zipline\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop zipline\n    msg_ok \"Service Stopped\"\n\n    mkdir -p /opt/zipline-uploads\n    if [ -d /opt/zipline/uploads ] && [ \"$(ls -A /opt/zipline/uploads)\" ]; then\n      cp -R /opt/zipline/uploads/* /opt/zipline-uploads/\n    fi\n    cp /opt/zipline/.env /opt/\n    rm -R /opt/zipline\n    fetch_and_deploy_gh_release \"zipline\" \"diced/zipline\" \"tarball\"\n\n    msg_info \"Updating ${APP}\"\n    cd /opt/zipline\n    mv /opt/.env /opt/zipline/.env\n    $STD pnpm install\n    $STD pnpm build\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting Service\"\n    systemctl start zipline\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}\"\n"
  },
  {
    "path": "ct/zitadel.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: dave-yap (dave-yap)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zitadel.com/\n\nAPP=\"Zitadel\"\nvar_tags=\"${var_tags:-identity-provider}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-8}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /etc/systemd/system/zitadel.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"zitadel\" \"zitadel/zitadel\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop zitadel\n    msg_ok \"Stopped Service\"\n\n    rm -f /usr/local/bin/zitadel\n    fetch_and_deploy_gh_release \"zitadel\" \"zitadel/zitadel\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"zitadel-linux-arm64.tar.gz\"\n\n    msg_info \"Updating Zitadel\"\n    $STD zitadel setup --masterkeyFile /opt/zitadel/.masterkey --config /opt/zitadel/config.yaml --init-projections=true\n    msg_ok \"Updated Zitadel\"\n\n    msg_info \"Starting Service\"\n    systemctl start zitadel\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080/ui/console${CL}\"\n"
  },
  {
    "path": "ct/zoraxy.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zoraxy.aroz.org/\n\nAPP=\"Zoraxy\"\nvar_tags=\"${var_tags:-network}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-2048}\"\nvar_disk=\"${var_disk:-6}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/zoraxy/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"zoraxy\" \"tobychui/zoraxy\"; then\n    msg_info \"Stopping service\"\n    systemctl stop zoraxy\n    msg_ok \"Service stopped\"\n\n    rm -rf /opt/zoraxy/zoraxy\n    fetch_and_deploy_gh_release \"zoraxy\" \"tobychui/zoraxy\" \"singlefile\" \"latest\" \"/opt/zoraxy\" \"zoraxy_linux_arm64\"\n\n    msg_info \"Starting service\"\n    systemctl start zoraxy\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}\"\n"
  },
  {
    "path": "ct/zot-registry.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zotregistry.dev/\n\nAPP=\"Zot-Registry\"\nvar_tags=\"${var_tags:-registry;oci}\"\nvar_cpu=\"${var_cpu:-1}\"\nvar_ram=\"${var_ram:-4096}\"\nvar_disk=\"${var_disk:-5}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-1}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -f /usr/bin/zot ]]; then\n    msg_error \"No ${APP} installation found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"zot\" \"project-zot/zot\"; then\n    msg_info \"Stopping Zot service\"\n    systemctl stop zot\n    msg_ok \"Stopped Zot service\"\n\n    rm -f /usr/bin/zot\n    fetch_and_deploy_gh_release \"zot\" \"project-zot/zot\" \"singlefile\" \"latest\" \"/usr/bin\" \"zot-linux-arm64\"\n\n    msg_info \"Configuring Zot Registry\"\n    chown root:root /usr/bin/zot\n    msg_ok \"Configured Zot Registry\"\n\n    msg_info \"Starting service\"\n    systemctl start zot\n    msg_ok \"Service started\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/zwave-js-ui.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zwave-js.github.io/zwave-js-ui/#/\n\nAPP=\"Zwave-JS-UI\"\nvar_tags=\"${var_tags:-smarthome;zwave}\"\nvar_cpu=\"${var_cpu:-2}\"\nvar_ram=\"${var_ram:-1024}\"\nvar_disk=\"${var_disk:-4}\"\nvar_os=\"${var_os:-debian}\"\nvar_version=\"${var_version:-13}\"\nvar_unprivileged=\"${var_unprivileged:-0}\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n  if [[ ! -d /opt/zwave-js-ui ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"zwave-js-ui\" \"zwave-js/zwave-js-ui\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop zwave-js-ui\n    msg_ok \"Stopped Service\"\n\n    rm -rf /opt/zwave-js-ui/*\n    fetch_and_deploy_gh_release \"zwave-js-ui\" \"zwave-js/zwave-js-ui\" \"prebuild\" \"latest\" \"/opt/zwave-js-ui\" \"zwave-js-ui*-linux.zip\"\n\n    msg_info \"Starting Service\"\n    systemctl start zwave-js-ui\n    msg_ok \"Started Service\"\n\n    msg_info \"Cleanup\"\n    rm -rf /opt/zwave-js-ui/store\n    msg_ok \"Cleaned\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8091${CL}\"\n"
  },
  {
    "path": "install/2fauth-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jkrgr0\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.2fauth.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y nginx\nmsg_ok \"Installed Dependencies\"\n\nexport PHP_VERSION=\"8.4\"\nPHP_FPM=\"YES\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"2fauth_db\" MARIADB_DB_USER=\"2fauth\" setup_mariadb_db\n\nfetch_and_deploy_gh_release \"2fauth\" \"Bubka/2FAuth\" \"tarball\"\n\nmsg_info \"Setup 2FAuth\"\ncd /opt/2fauth\ncp .env.example .env\nsed -i -e \"s|^APP_URL=.*|APP_URL=http://$LOCAL_IP|\" \\\n  -e \"s|^DB_CONNECTION=$|DB_CONNECTION=mysql|\" \\\n  -e \"s|^DB_DATABASE=$|DB_DATABASE=$MARIADB_DB_NAME|\" \\\n  -e \"s|^DB_HOST=$|DB_HOST=127.0.0.1|\" \\\n  -e \"s|^DB_PORT=$|DB_PORT=3306|\" \\\n  -e \"s|^DB_USERNAME=$|DB_USERNAME=$MARIADB_DB_USER|\" \\\n  -e \"s|^DB_PASSWORD=$|DB_PASSWORD=$MARIADB_DB_PASS|\" .env\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD composer update --no-plugins --no-scripts\n$STD composer install --no-dev --prefer-dist --no-plugins --no-scripts\n$STD php artisan key:generate --force\n$STD php artisan migrate:refresh\n$STD php artisan passport:install -q -n\n$STD php artisan storage:link\n$STD php artisan config:cache\nchown -R www-data: /opt/2fauth\nchmod -R 755 /opt/2fauth\nmsg_ok \"Setup 2fauth\"\n\nmsg_info \"Configure Service\"\ncat <<EOF >/etc/nginx/conf.d/2fauth.conf\nserver {\n        listen 80;\n        root /opt/2fauth/public;\n        server_name $LOCAL_IP;\n        index index.php;\n        charset utf-8;\n\n        location / {\n                try_files \\$uri \\$uri/ /index.php?\\$query_string;\n        }\n\n        location = /favicon.ico { access_log off; log_not_found off; }\n        location = /robots.txt { access_log off; log_not_found off; }\n\n        error_page 404 /index.php;\n\n        location ~ \\.php\\$ {\n                fastcgi_pass unix:/var/run/php/php${PHP_VERSION}-fpm.sock;\n                fastcgi_param SCRIPT_FILENAME \\$realpath_root\\$fastcgi_script_name;\n                include fastcgi_params;\n        }\n\n        location ~ /\\.(?!well-known).* {\n                deny all;\n        }\n}\nEOF\nsystemctl reload nginx\nmsg_ok \"Configured Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/actualbudget-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://actualbudget.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  make \\\n  g++\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\ncreate_self_signed_cert\n\nmsg_info \"Installing Actual Budget\"\ncd /opt\nRELEASE=$(get_latest_github_release \"actualbudget/actual\")\nmkdir -p /opt/actualbudget-data/{server-files,upload,migrate,user-files,migrations,config}\nchown -R root:root /opt/actualbudget-data\nchmod -R 755 /opt/actualbudget-data\n\ncat <<EOF >/opt/actualbudget-data/config.json\n{\n  \"port\": 5006,\n  \"hostname\": \"::\",\n  \"serverFiles\": \"/opt/actualbudget-data/server-files\",\n  \"userFiles\": \"/opt/actualbudget-data/user-files\",\n  \"trustedProxies\": [\n    \"10.0.0.0/8\",\n    \"172.16.0.0/12\",\n    \"192.168.0.0/16\",\n    \"127.0.0.0/8\",\n    \"::1/128\",\n    \"fc00::/7\"\n  ],\n  \"https\": {\n    \"key\": \"/etc/ssl/actualbudget/actualbudget.key\",\n    \"cert\": \"/etc/ssl/actualbudget/actualbudget.crt\"\n  }\n}\nEOF\nmkdir -p /opt/actualbudget\ncd /opt/actualbudget\n$STD npm install --location=global @actual-app/sync-server\necho \"${RELEASE}\" >~/.actualbudget\nmsg_ok \"Installed Actual Budget\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/actualbudget.service\n[Unit]\nDescription=Actual Budget Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nWorkingDirectory=/opt/actualbudget\nEnvironment=ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20\nEnvironment=ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50\nEnvironment=ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20\nExecStart=/usr/bin/actual-server --config /opt/actualbudget-data/config.json\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now actualbudget\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/adguard-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://adguard.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"AdGuardHome\" \"AdguardTeam/AdGuardHome\" \"prebuild\" \"latest\" \"/opt/AdGuardHome\" \"AdGuardHome_linux_arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/AdGuardHome.service\n[Unit]\nDescription=AdGuard Home: Network-level blocker\nConditionFileIsExecutable=/opt/AdGuardHome/AdGuardHome\nAfter=syslog.target network-online.target\n\n[Service]\nStartLimitInterval=5\nStartLimitBurst=10\nExecStart=/opt/AdGuardHome/AdGuardHome \"-s\" \"run\"\nWorkingDirectory=/opt/AdGuardHome\nStandardOutput=file:/var/log/AdGuardHome.out\nStandardError=file:/var/log/AdGuardHome.err\nRestart=always\nRestartSec=10\nEnvironmentFile=-/etc/sysconfig/AdGuardHome\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now AdGuardHome\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/adventurelog-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/seanmorley15/AdventureLog\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  gdal-bin \\\n  libgdal-dev \\\n  git \\\n  memcached \\\n  libmemcached-tools\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm@latest\" setup_nodejs\nPG_VERSION=\"17\" PG_MODULES=\"postgis\" setup_postgresql\nPG_DB_NAME=\"adventurelog_db\" PG_DB_USER=\"adventurelog_user\" PG_DB_EXTENSIONS=\"postgis\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"adventurelog\" \"seanmorley15/adventurelog\" \"tarball\"\n\nmsg_info \"Installing AdventureLog (Patience)\"\nSECRET_KEY=\"$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)\"\necho \"AdventureLog Secret: $SECRET_KEY\" >>~/adventurelog.creds\nDJANGO_ADMIN_USER=\"djangoadmin\"\nDJANGO_ADMIN_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\"\ncat <<EOF >/opt/adventurelog/backend/server/.env\nPGHOST='localhost'\nPGDATABASE='${PG_DB_NAME}'\nPGUSER='${PG_DB_USER}'\nPGPASSWORD='${PG_DB_PASS}'\nSECRET_KEY='${SECRET_KEY}'\nPUBLIC_URL='http://$LOCAL_IP:8000'\nDEBUG=True\nFRONTEND_URL='http://$LOCAL_IP:3000'\nCSRF_TRUSTED_ORIGINS='http://127.0.0.1:3000,http://localhost:3000,http://$LOCAL_IP:3000'\nDJANGO_ADMIN_USERNAME='${DJANGO_ADMIN_USER}'\nDJANGO_ADMIN_PASSWORD='${DJANGO_ADMIN_PASS}'\nDISABLE_REGISTRATION=False\n# EMAIL_BACKEND='email'\n# EMAIL_HOST='smtp.gmail.com'\n# EMAIL_USE_TLS=False\n# EMAIL_PORT=587\n# EMAIL_USE_SSL=True\n# EMAIL_HOST_USER='user'\n# EMAIL_HOST_PASSWORD='password'\n# DEFAULT_FROM_EMAIL='user@example.com'\nEOF\ncd /opt/adventurelog/backend/server\nmkdir -p /opt/adventurelog/backend/server/media\n$STD uv venv --clear /opt/adventurelog/backend/server/.venv\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m pip install -r requirements.txt\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m pip install 'djangorestframework<3.15'\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m manage collectstatic --noinput\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m manage migrate\n$STD /opt/adventurelog/backend/server/.venv/bin/python -m manage download-countries\ncat <<EOF >/opt/adventurelog/frontend/.env\nPUBLIC_SERVER_URL=http://$LOCAL_IP:8000\nBODY_SIZE_LIMIT=Infinity\nORIGIN='http://$LOCAL_IP:3000'\nEOF\ncd /opt/adventurelog/frontend\n$STD pnpm i\n$STD pnpm build\nmsg_ok \"Installed AdventureLog\"\n\nmsg_info \"Setting up Django Admin\"\ncd /opt/adventurelog/backend/server\n$STD .venv/bin/python -m manage shell <<EOF\nfrom django.contrib.auth import get_user_model\nUserModel = get_user_model()\nuser = UserModel.objects.create_user('$DJANGO_ADMIN_USER', password='$DJANGO_ADMIN_PASS')\nuser.is_superuser = True\nuser.is_staff = True\nuser.save()\nEOF\n{\n  echo \"\"\n  echo \"Django-Credentials\"\n  echo \"Django Admin User: $DJANGO_ADMIN_USER\"\n  echo \"Django Admin Password: $DJANGO_ADMIN_PASS\"\n} >>~/adventurelog.creds\nmsg_ok \"Setup Django Admin\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/adventurelog-backend.service\n[Unit]\nDescription=AdventureLog Backend Service\nAfter=network.target postgresql.service\n\n[Service]\nWorkingDirectory=/opt/adventurelog/backend/server\nExecStart=/opt/adventurelog/backend/server/.venv/bin/python -m manage runserver 0.0.0.0:8000\nRestart=always\nEnvironmentFile=/opt/adventurelog/backend/server/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/adventurelog-frontend.service\n[Unit]\nDescription=AdventureLog SvelteKit Frontend Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/adventurelog/frontend\nExecStart=/usr/bin/node build 127.0.0.1:3000\nRestart=always\nEnvironmentFile=/opt/adventurelog/frontend/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now adventurelog-backend\nsystemctl enable -q --now adventurelog-frontend\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/agentdvr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.ispyconnect.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  alsa-utils \\\n  libxext-dev \\\n  fontconfig \\\n  libva-drm2\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing AgentDVR\"\nmkdir -p /opt/agentdvr/agent\nRELEASE=$(curl -fsSL \"https://www.ispyconnect.com/api/Agent/DownloadLocation4?platform=LinuxARM64&fromVersion=0\" | tr -d '\\r' | grep -Eo 'https://[^\"[:space:]]+\\.zip' | head -n1)\ncd /opt/agentdvr/agent\n$STD curl -fsSL \"$RELEASE\" -o \"$(basename \"${RELEASE%%\\?*}\")\"\n$STD unzip -o \"$(basename \"${RELEASE%%\\?*}\")\"\nchmod +x ./Agent\necho \"$RELEASE\" >~/.agentdvr\nrm -f \"$(basename \"${RELEASE%%\\?*}\")\"\nmsg_ok \"Installed AgentDVR\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/AgentDVR.service\n[Unit]\nDescription=AgentDVR\n\n[Service]\nWorkingDirectory=/opt/agentdvr/agent\nExecStart=/opt/agentdvr/agent/./Agent\nEnvironment=\"MALLOC_TRIM_THRESHOLD_=100000\"\nSyslogIdentifier=AgentDVR\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now AgentDVR\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/alpine-adguard-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://adguardhome.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Downloading AdGuard Home\"\n$STD curl -fsSL -o /tmp/AdGuardHome_linux_arm64.tar.gz \\\n  \"https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_arm64.tar.gz\"\nmsg_ok \"Downloaded AdGuard Home\"\n\nmsg_info \"Installing AdGuard Home\"\n$STD tar -xzf /tmp/AdGuardHome_linux_arm64.tar.gz -C /opt\n$STD rm /tmp/AdGuardHome_linux_arm64.tar.gz\nmsg_ok \"Installed AdGuard Home\"\n\nmsg_info \"Creating AdGuard Home Service\"\ncat <<EOF >/etc/init.d/adguardhome\n#!/sbin/openrc-run\nname=\"AdGuardHome\"\ndescription=\"AdGuard Home Service\"\ncommand=\"/opt/AdGuardHome/AdGuardHome\"\ncommand_background=\"yes\"\npidfile=\"/run/adguardhome.pid\"\nEOF\n$STD chmod +x /etc/init.d/adguardhome\nmsg_ok \"Created AdGuard Home Service\"\n\nmsg_info \"Enabling AdGuard Home Service\"\n$STD rc-update add adguardhome default\nmsg_ok \"Enabled AdGuard Home Service\"\n\nmsg_info \"Starting AdGuard Home\"\n$STD rc-service adguardhome start\nmsg_ok \"Started AdGuard Home\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-bitmagnet-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bitmagnet-io/bitmagnet\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apk add --no-cache \\\n  gcc \\\n  musl-dev \\\n  git \\\n  iproute2-ss \\\n  sudo\n$STD apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community go\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Installing PostgreSQL\"\n$STD apk add --no-cache \\\n  postgresql16 \\\n  postgresql16-contrib \\\n  postgresql16-openrc\n$STD rc-update add postgresql\n$STD rc-service postgresql start\nmsg_ok \"Installed PostreSQL\"\n\nRELEASE=$(curl -fsSL https://api.github.com/repos/bitmagnet-io/bitmagnet/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\n\nmsg_info \"Installing bitmagnet v${RELEASE}\"\nmkdir -p /opt/bitmagnet\ntemp_file=$(mktemp)\ncurl -fsSL \"https://github.com/bitmagnet-io/bitmagnet/archive/refs/tags/v${RELEASE}.tar.gz\" -o \"$temp_file\"\ntar zxf \"$temp_file\" --strip-components=1 -C /opt/bitmagnet\ncd /opt/bitmagnet\nVREL=v$RELEASE\n$STD go build -ldflags \"-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=$VREL\"\nchmod +x bitmagnet\n$STD su - postgres -c \"psql -c 'CREATE DATABASE bitmagnet;'\"\necho \"${RELEASE}\" >/opt/bitmagnet_version.txt\nmsg_ok \"Installed bitmagnet v${RELEASE}\"\n\nread -rp \"${TAB3}Enter your TMDB API key if you have one: \" tmdbapikey\n\nmsg_info \"Enabling bitmagnet Service\"\ncat <<EOF >/etc/init.d/bitmagnet\n#!/sbin/openrc-run\ndescription=\"bitmagnet Service\"\ndirectory=\"/opt/bitmagnet\"\ncommand=\"/opt/bitmagnet/bitmagnet\"\ncommand_args=\"worker run --all\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/bitmagnet.pid\"\n\ndepend() {\n    use net\n}\n\nstart_pre() {\n    export TMDB_API_KEY=\"$tmdbapikey\"\n}\nEOF\nchmod +x /etc/init.d/bitmagnet\n$STD rc-update add bitmagnet default\nmsg_ok \"Enabled bitmagnet Service\"\n\nmsg_info \"Starting bitmagnet\"\n$STD service bitmagnet start\nmsg_ok \"Started bitmagnet\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nrm -f \"$temp_file\"\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-borgbackup-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Sander Koenders (sanderkoenders)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.borgbackup.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing BorgBackup\"\n$STD apk add --no-cache borgbackup openssh\n$STD rc-update add sshd\n$STD rc-service sshd start\nmsg_ok \"Installed BorgBackup\"\n\nmsg_info \"Creating backup user\"\n$STD adduser -D -s /bin/bash -h /home/backup backup\n$STD passwd -d backup\nmsg_ok \"Created backup user\"\n\nmsg_info \"Configure SSH, disabling password authentication and enabling public key authentication\"\n$STD sed -i -e 's/^#\\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config\n$STD rc-service sshd restart\nmsg_ok \"Configured SSH\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/alpine-caddy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: cobalt (cobaltgit)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://caddyserver.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Caddy\"\n$STD apk add --no-cache caddy caddy-openrc\ncat <<EOF >/etc/caddy/Caddyfile\n:80 {\n        # Set this path to your site's directory.\n        root * /var/www/html\n\n        # Enable the static file server.\n        file_server\n\n        # Another common task is to set up a reverse proxy:\n        # reverse_proxy localhost:8080\n\n        # Or serve a PHP site through php-fpm:\n        # php_fastcgi localhost:9000\n}\nEOF\nmkdir -p /var/www/html\ncat <<EOF >/var/www/html/index.html\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Caddy works!</title>\n  </head>\n  <body>\n    <h1>Hello Caddy!</h1>\n    <p>For more information, refer to the Caddy <a href=\"https://caddyserver.com/docs/\">documentation</a><p>\n  </body>\n</html>\nEOF\nmsg_ok \"Installed Caddy\"\n\nread -r -p \"${TAB3}Would you like to install xCaddy Addon? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  GO_VERSION=\"$(curl -fsSL https://go.dev/VERSION?m=text | head -1 | cut -c3-)\" setup_go\n  msg_info \"Setup xCaddy\"\n  cd /opt\n  RELEASE=$(curl -fsSL https://api.github.com/repos/caddyserver/xcaddy/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\n  curl -fsSL \"https://github.com/caddyserver/xcaddy/releases/download/${RELEASE}/xcaddy_${RELEASE:1}_linux_arm64.tar.gz\" -o \"xcaddy_${RELEASE:1}_linux_arm64.tar.gz\"\n  $STD tar xzf xcaddy_\"${RELEASE:1}\"_linux_arm64.tar.gz -C /usr/local/bin xcaddy\n  rm -rf /opt/xcaddy*\n  $STD xcaddy build\n  msg_ok \"Setup xCaddy\"\nfi\n\nmsg_info \"Enabling Caddy Service\"\n$STD rc-update add caddy default\nmsg_ok \"Enabled Caddy Service\"\n\nmsg_info \"Starting Caddy\"\n$STD service caddy start\nmsg_ok \"Started Caddy\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-docker-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.docker.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add tzdata openssl\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Docker\"\n$STD apk add docker\n$STD rc-service docker start\n$STD rc-update add docker default\nmsg_ok \"Installed Docker\"\n\nget_latest_release() {\n  curl -fsSL https://api.github.com/repos/\"$1\"/releases/latest | grep '\"tag_name\":' | cut -d'\"' -f4\n}\nPORTAINER_LATEST_VERSION=$(get_latest_release \"portainer/portainer\")\nDOCKER_COMPOSE_LATEST_VERSION=$(get_latest_release \"docker/compose\")\nPORTAINER_AGENT_LATEST_VERSION=$(get_latest_release \"portainer/agent\")\n\nread -r -p \"${TAB3}Would you like to add Portainer? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Portainer $PORTAINER_LATEST_VERSION\"\n  docker volume create portainer_data >/dev/null\n  $STD docker run -d \\\n    -p 8000:8000 \\\n    -p 9443:9443 \\\n    --name=portainer \\\n    --restart=always \\\n    -v /var/run/docker.sock:/var/run/docker.sock \\\n    -v portainer_data:/data \\\n    portainer/portainer-ce:latest\n  msg_ok \"Installed Portainer $PORTAINER_LATEST_VERSION\"\nelse\n  read -r -p \"${TAB3}Would you like to add the Portainer Agent? <y/N> \" prompt\n  if [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION\"\n    $STD docker run -d \\\n      -p 9001:9001 \\\n      --name portainer_agent \\\n      --restart=always \\\n      -v /var/run/docker.sock:/var/run/docker.sock \\\n      -v /var/lib/docker/volumes:/var/lib/docker/volumes \\\n      portainer/agent\n    msg_ok \"Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION\"\n  fi\nfi\nread -r -p \"${TAB3}Would you like to add Docker Compose? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Docker Compose $DOCKER_COMPOSE_LATEST_VERSION\"\n  DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}\n  mkdir -p \"$DOCKER_CONFIG\"/cli-plugins\n  curl -fsSL https://github.com/docker/compose/releases/download/\"$DOCKER_COMPOSE_LATEST_VERSION\"/docker-compose-linux-aarch64 -o ~/.docker/cli-plugins/docker-compose\n  chmod +x \"$DOCKER_CONFIG\"/cli-plugins/docker-compose\n  msg_ok \"Installed Docker Compose $DOCKER_COMPOSE_LATEST_VERSION\"\nfi\n\nread -r -p \"${TAB3}Would you like to expose the Docker TCP socket? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Exposing Docker TCP socket\"\n  $STD mkdir -p /etc/docker\n  $STD echo '{ \"hosts\": [\"unix:///var/run/docker.sock\", \"tcp://0.0.0.0:2375\"] }' > /etc/docker/daemon.json\n  $STD rc-service docker restart\n  msg_ok \"Exposed Docker TCP socket at tcp://+:2375\"\nfi\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-forgejo-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Johann3s-H (An!ma)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://forgejo.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Forgejo\"\n$STD apk add --no-cache forgejo\nmsg_ok \"Installed Forgejo\"\n\nmsg_info \"Enabling Forgejo Service\"\n$STD rc-update add forgejo default\nmsg_ok \"Enabled Forgejo Service\"\n\nmsg_info \"Starting Forgejo\"\n$STD service forgejo start\nmsg_ok \"Started Forgejo\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-garage-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://garagehq.deuxfleurs.fr/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add --no-cache openssl\nmsg_ok \"Installed Dependencies\"\n\nGITEA_RELEASE=$(curl -s https://api.github.com/repos/deuxfleurs-org/garage/tags | jq -r '.[0].name')\ncurl -fsSL \"https://garagehq.deuxfleurs.fr/_releases/${GITEA_RELEASE}/aarch64-unknown-linux-musl/garage\" -o /usr/local/bin/garage\nchmod +x /usr/local/bin/garage\nmkdir -p /var/lib/garage/{data,meta,snapshots}\nmkdir -p /etc/garage\nRPC_SECRET=$(openssl rand -hex 64 | cut -c1-64)\nADMIN_TOKEN=$(openssl rand -base64 32)\nMETRICS_TOKEN=$(openssl rand -base64 32)\n{\n  echo \"Garage Tokens and Secrets\"\n  echo \"RPC Secret: $RPC_SECRET\"\n  echo \"Admin Token: $ADMIN_TOKEN\"\n  echo \"Metrics Token: $METRICS_TOKEN\"\n} >~/garage.creds\necho $GITEA_RELEASE >>~/.garage\ncat <<EOF >/etc/garage.toml\nmetadata_dir = \"/var/lib/garage/meta\"\ndata_dir = \"/var/lib/garage/data\"\ndb_engine = \"sqlite\"\nreplication_factor = 1\n\nrpc_bind_addr = \"0.0.0.0:3901\"\nrpc_public_addr = \"127.0.0.1:3901\"\nrpc_secret = \"${RPC_SECRET}\"\n\n[s3_api]\ns3_region = \"garage\"\napi_bind_addr = \"0.0.0.0:3900\"\nroot_domain = \".s3.garage\"\n\n[s3_web]\nbind_addr = \"0.0.0.0:3902\"\nroot_domain = \".web.garage\"\nindex = \"index.html\"\n\n[k2v_api]\napi_bind_addr = \"0.0.0.0:3904\"\n\n[admin]\napi_bind_addr = \"0.0.0.0:3903\"\nadmin_token = \"${ADMIN_TOKEN}\"\nmetrics_token = \"${METRICS_TOKEN}\"\nEOF\nmsg_ok \"Configured Garage\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/etc/init.d/garage\n#!/sbin/openrc-run\nname=\"Garage Object Storage\"\ncommand=\"/usr/local/bin/garage\"\ncommand_args=\"server\"\ncommand_background=\"yes\"\npidfile=\"/run/garage.pid\"\ndepend() {\n    need net\n}\nEOF\n\nchmod +x /etc/init.d/garage\n$STD rc-update add garage default\n$STD rc-service garage restart || rc-service garage start\nmsg_ok \"Service active\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-gatus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TwiN/gatus\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apk add --no-cache \\\n  ca-certificates \\\n  libcap-setcap\n$STD apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community go\nmsg_ok \"Installed dependencies\"\n\nRELEASE=$(curl -s https://api.github.com/repos/TwiN/gatus/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\nmsg_info \"Installing gatus v${RELEASE}\"\ntemp_file=$(mktemp)\nmkdir -p /opt/gatus\ncurl -fsSL \"https://github.com/TwiN/gatus/archive/refs/tags/v${RELEASE}.tar.gz\" -o \"$temp_file\"\ntar zxf \"$temp_file\" --strip-components=1 -C /opt/gatus\ncd /opt/gatus\n$STD go mod tidy\nCGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus .\nsetcap CAP_NET_RAW+ep gatus\nmv config.yaml config\necho \"${RELEASE}\" >/opt/gatus_version.txt\nmsg_ok \"Installed gatus v${RELEASE}\"\n\nmsg_info \"Enabling gatus Service\"\ncat <<EOF >/etc/init.d/gatus\n#!/sbin/openrc-run\ndescription=\"gatus Service\"\ndirectory=\"/opt/gatus\"\ncommand=\"/opt/gatus/gatus\"\ncommand_args=\"\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/gatus.pid\"\n\nexport GATUS_CONFIG_PATH=\"\"\nexport GATUS_LOG_LEVEL=\"INFO\"\nexport PORT=\"8080\"\n\ndepend() {\n    use net\n}\nEOF\nchmod +x /etc/init.d/gatus\n$STD rc-update add gatus default\nmsg_ok \"Enabled gatus Service\"\n\nmsg_info \"Starting gatus\"\n$STD service gatus start\nmsg_ok \"Started gatus\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nrm -f \"$temp_file\"\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-gitea-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gitea.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Gitea\"\n$STD apk add --no-cache gitea\nmsg_ok \"Installed Gitea\"\n\nmsg_info \"Enabling Gitea Service\"\n$STD rc-update add gitea default\nmsg_ok \"Enabled Gitea Service\"\n\nmsg_info \"Starting Gitea\"\n$STD service gitea start\nmsg_ok \"Started Gitea\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-grafana-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://grafana.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Grafana\"\n$STD apk add grafana\n$STD sed -i '/http_addr/s/127.0.0.1/0.0.0.0/g' /etc/conf.d/grafana\n$STD rc-service grafana start\n$STD rc-update add grafana default\nmsg_ok \"Installed Grafana\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://alpinelinux.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add sudo\nmsg_ok \"Installed Dependencies\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-ironclaw-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/nearai/ironclaw\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add openssl dbus gnome-keyring\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing PostgreSQL\"\n$STD apk add postgresql17 postgresql17-openrc postgresql-pgvector postgresql-common\n$STD rc-service postgresql setup\n$STD rc-update add postgresql default\n$STD rc-service postgresql start\nmsg_ok \"Installed PostgreSQL\"\n\nmsg_info \"Setting up Database\"\nPG_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\n$STD su -s /bin/sh postgres -c \"psql -c \\\"CREATE ROLE ironclaw WITH LOGIN PASSWORD '${PG_PASS}';\\\"\"\n$STD su -s /bin/sh postgres -c \"psql -c \\\"CREATE DATABASE ironclaw WITH OWNER ironclaw;\\\"\"\n$STD su -s /bin/sh postgres -c \"psql -d ironclaw -c \\\"CREATE EXTENSION IF NOT EXISTS vector;\\\"\"\nmsg_ok \"Set up Database\"\n\nfetch_and_deploy_gh_release \"ironclaw-bin\" \"nearai/ironclaw\" \"prebuild\" \"latest\" \"/usr/local/bin\" \\\n  \"ironclaw-$(uname -m)-unknown-linux-musl.tar.gz\"\nchmod +x /usr/local/bin/ironclaw\n\nmsg_info \"Configuring IronClaw\"\nmkdir -p /root/.ironclaw\nGATEWAY_TOKEN=$(openssl rand -hex 32)\ncat <<EOF >/root/.ironclaw/.env\nDATABASE_URL=postgresql://ironclaw:${PG_PASS}@localhost:5432/ironclaw?sslmode=disable\nGATEWAY_ENABLED=true\nGATEWAY_HOST=0.0.0.0\nGATEWAY_PORT=3000\nGATEWAY_AUTH_TOKEN=${GATEWAY_TOKEN}\nCLI_ENABLED=false\nAGENT_NAME=ironclaw\nRUST_LOG=ironclaw=info,tower_http=info\nEOF\nchmod 600 /root/.ironclaw/.env\nmsg_ok \"Configured IronClaw\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/init.d/ironclaw\n#!/sbin/openrc-run\n\nname=\"IronClaw\"\ndescription=\"IronClaw AI Agent\"\ncommand=\"/usr/bin/dbus-run-session\"\ncommand_args=\"/usr/local/bin/ironclaw\"\ncommand_background=true\npidfile=\"/run/ironclaw.pid\"\ndirectory=\"/root\"\nsupervise_daemon_args=\"--env-file /root/.ironclaw/.env\"\n\ndepend() {\n  need net postgresql\n}\nEOF\nchmod +x /etc/init.d/ironclaw\n$STD rc-update add ironclaw default\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-it-tools-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: nicedevil007 (NiceDevil)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://it-tools.tech/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add --no-cache \\\n  nginx \\\n  python3\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing IT-Tools\"\nRELEASE=$(curl -fsSL https://api.github.com/repos/sharevb/it-tools/releases/latest | grep '\"tag_name\":' | cut -d '\"' -f4)\ncurl -fsSL \"https://github.com/sharevb/it-tools/releases/download/${RELEASE}/it-tools-${RELEASE#v}.zip\" -o it-tools.zip\nmkdir -p /usr/share/nginx/html\n$STD unzip it-tools.zip -d /tmp/\nmv /tmp/dist/* /usr/share/nginx/html\ncat <<'EOF' >/etc/nginx/http.d/default.conf\nserver {\n  listen 80;\n  server_name localhost;\n  root /usr/share/nginx/html;\n  index index.html;\n  \n  location / {\n      try_files $uri $uri/ /index.html;\n  }\n}\nEOF\n$STD rc-update add nginx default\n$STD rc-service nginx start\necho \"${RELEASE}\" >/opt/\"${APPLICATION}\"_version.txt\nmsg_ok \"Installed IT-Tools\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nrm -rf /tmp/dist\nrm -f it-tools.zip\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-loki-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: hoholms\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/grafana/loki\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Loki\"\n$STD apk add loki\n$STD sed -i '/http_addr/s/127.0.0.1/0.0.0.0/g' /etc/conf.d/loki\nmkdir -p /var/lib/loki/{chunks,boltdb-shipper-active,boltdb-shipper-cache}\nchown -R loki:grafana /var/lib/loki\nmkdir -p /var/log/loki\nchown -R loki:grafana /var/log/loki\ncat <<EOF >/etc/loki/loki-local-config.yaml\nauth_enabled: false\n\nserver:\n  http_listen_port: 3100\n  log_level: info\n\ncommon:\n  instance_addr: 127.0.0.1\n  path_prefix: /var/lib/loki\n  storage:\n    filesystem:\n      chunks_directory: /var/lib/loki/chunks\n      rules_directory: /var/lib/loki/rules\n  replication_factor: 1\n  ring:\n    kvstore:\n      store: inmemory\n\nschema_config:\n  configs:\n    - from: 2020-10-24\n      store: tsdb\n      object_store: filesystem\n      schema: v13\n      index:\n        prefix: index_\n        period: 24h\n\nquery_range:\n  results_cache:\n    cache:\n      embedded_cache:\n        enabled: true\n        max_size_mb: 100\n\nlimits_config:\n  metric_aggregation_enabled: true\n\nruler:\n  alertmanager_url: http://localhost:9093\nEOF\nchown loki:grafana /etc/loki/loki-local-config.yaml\nchmod 644 /etc/loki/loki-local-config.yaml\necho \"output_log=\\\"\\${output_log:-/var/log/loki/output.log}\\\"\" >> /etc/init.d/loki\necho \"error_log=\\\"\\${error_log:-/var/log/loki/error.log}\\\"\" >> /etc/init.d/loki\necho \"start_stop_daemon_args=\\\"\\${SSD_OPTS} -1 \\${output_log} -2 \\${error_log}\\\"\" >> /etc/init.d/loki\n$STD rc-update add loki default\n$STD rc-service loki start\nmsg_ok \"Installed Loki\"\n\nread -rp \"Would you like to install Promtail? (y/N): \" INSTALL_PROMTAIL\nif [[ \"${INSTALL_PROMTAIL,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Promtail\"\n  $STD apk add loki-promtail\n  $STD sed -i '/http_addr/s/127.0.0.1/0.0.0.0/g' /etc/conf.d/loki-promtail\n  $STD rc-update add loki-promtail default\n  $STD rc-service loki-promtail start\n  msg_ok \"Installed Promtail\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/alpine-mariadb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mariadb.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing MariaDB\"\n$STD apk add --no-cache mariadb mariadb-client\n$STD rc-update add mariadb default\nmsg_ok \"Installed MariaDB\"\n\nmsg_info \"Configuring MariaDB\"\nmysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql >/dev/null 2>&1\n$STD rc-service mariadb start\nmsg_ok \"MariaDB Configured\"\n\nread -r -p \"${TAB3}Would you like to install Adminer with lighttpd? <y/N>: \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Adminer and dependencies\"\n  $STD apk add --no-cache \\\n    lighttpd \\\n    lighttpd-openrc \\\n    php83 \\\n    php83-cgi \\\n    php83-common \\\n    php83-curl \\\n    php83-gd \\\n    php83-mbstring \\\n    php83-mysqli \\\n    php83-mysqlnd \\\n    php83-openssl \\\n    php83-zip \\\n    php83-session \\\n    jq\n\n  sed -i 's|# *include \"mod_fastcgi.conf\"|include \"mod_fastcgi.conf\"|' /etc/lighttpd/lighttpd.conf\n  sed -i 's|/usr/bin/php-cgi|/usr/bin/php-cgi83|g' /etc/lighttpd/mod_fastcgi.conf\n  mkdir -p /var/www/localhost/htdocs\n  ADMINER_VERSION=$(curl -fsSL https://api.github.com/repos/vrana/adminer/releases/latest | jq -r '.tag_name' | sed 's/^v//')\n  curl -fsSL \"https://github.com/vrana/adminer/releases/download/v${ADMINER_VERSION}/adminer-${ADMINER_VERSION}.php\" -o /var/www/localhost/htdocs/adminer.php\n  chown lighttpd:lighttpd /var/www/localhost/htdocs/adminer.php\n  chmod 755 /var/www/localhost/htdocs/adminer.php\n  msg_ok \"Adminer Installed\"\n\n  msg_info \"Starting Lighttpd\"\n  $STD rc-update add lighttpd default\n  $STD rc-service lighttpd restart\n  msg_ok \"Lighttpd Started\"\nfi\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-nextcloud-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nextcloud.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add openssl\n$STD apk add nginx\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing PHP/Redis\"\n$STD apk add php83-opcache\n$STD apk add php83-redis\n$STD apk add php83-apcu\n$STD apk add php83-fpm\n$STD apk add php83-sysvsem\n$STD apk add php83-ftp\n$STD apk add php83-pecl-smbclient\n$STD apk add php83-pecl-imagick\n$STD apk add php83-pecl-vips\n$STD apk add php83-exif\n$STD apk add php83-sodium\n$STD apk add php83-bz2\n$STD apk add redis\nmsg_ok \"Installed PHP/Redis\"\n\nmsg_info \"Installing MySQL Database\"\nDB_NAME=nextcloud\nDB_USER=nextcloud\nDB_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\nADMIN_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\necho \"\" >>~/nextcloud.creds\necho -e \"MySQL Admin Password: \\e[32m$ADMIN_PASS\\e[0m\" >>~/nextcloud.creds\necho -e \"Nextcloud Database Username: \\e[32m$DB_USER\\e[0m\" >>~/nextcloud.creds\necho -e \"Nextcloud Database Password: \\e[32m$DB_PASS\\e[0m\" >>~/nextcloud.creds\necho -e \"Nextcloud Database Name: \\e[32m$DB_NAME\\e[0m\" >>~/nextcloud.creds\n$STD apk add nextcloud-mysql mariadb mariadb-client\n$STD mariadb-install-db --user=mysql --datadir=/var/lib/mysql\n$STD service mariadb start\n$STD rc-update add mariadb\n$STD mariadb -uroot -p\"$ADMIN_PASS\" -e \"GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '$ADMIN_PASS' WITH GRANT OPTION; DELETE FROM mysql.user WHERE User=''; DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'); DROP DATABASE test; DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'; CREATE DATABASE $DB_NAME; GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS'; GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost.localdomain' IDENTIFIED BY '$DB_PASS'; FLUSH PRIVILEGES;\"\n$STD apk del mariadb-client\nmsg_ok \"Installed MySQL Database\"\n\nmsg_info \"Installing Nextcloud\"\nADMIN_USER=ncAdmin\necho \"\" >>~/nextcloud.creds\necho -e \"Nextcloud Admin Username: \\e[32m$ADMIN_USER\\e[0m\" >>~/nextcloud.creds\necho -e \"Nextcloud Admin Password: \\e[32m$ADMIN_PASS\\e[0m (Initially enter twice)\" >>~/nextcloud.creds\n$STD apk add nextcloud-initscript\n$STD openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout /etc/ssl/private/nextcloud-selfsigned.key -out /etc/ssl/certs/nextcloud-selfsigned.crt -subj \"/C=US/O=Nextcloud/OU=Domain Control Validated/CN=nextcloud.local\"\ncat <<'EOF' >/usr/share/webapps/nextcloud/config/config.php\n<?php\n$CONFIG = array (\n  'datadirectory' => '/var/lib/nextcloud/data',\n  'logfile' => '/var/log/nextcloud/nextcloud.log',\n  'logdateformat' => 'F d, Y H:i:s',\n  'log_rotate_size' => 104857600,\n  'apps_paths' => array (\n    0 => array (\n      'path' => '/usr/share/webapps/nextcloud/apps',\n      'url' => '/apps',\n      'writable' => false,\n    ),\n    1 => array (\n      'path' => '/var/lib/nextcloud/apps',\n      'url' => '/apps-appstore',\n      'writable' => true,\n    ),\n  ),\n  'updatechecker' => false,\n  'check_for_working_htaccess' => false,\n  'memcache.local' => '\\\\OC\\\\Memcache\\\\Redis',\n  'memcache.locking' => '\\\\OC\\\\Memcache\\\\Redis',\n  'redis' => array(\n    'host' => 'localhost',\n    'port' => 6379,\n    'dbindex' => 0,\n    'timeout' => 1.5,\n  ),\n  'installed' => false,\n);\nEOF\nrm -rf /etc/nginx/http.d/default.conf\ncat <<'EOF' >/etc/nginx/http.d/nextcloud.conf\nserver {\n        listen       [::]:80;\n        listen       80;\n        return 301 https://$host$request_uri;\n        server_name localhost;\n        client_max_body_size 16G;\n        fastcgi_read_timeout 120s;\n}\nserver {\n        listen       443 ssl;\n        listen       [::]:443 ssl;\n        http2        on;\n        server_name  localhost;\n        root /usr/share/webapps/nextcloud;\n        index  index.php index.html index.htm;\n        disable_symlinks off;\n        ssl_certificate      /etc/ssl/certs/nextcloud-selfsigned.crt;\n        ssl_certificate_key  /etc/ssl/private/nextcloud-selfsigned.key;\n        ssl_session_timeout  5m;\n        ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA;\n        ssl_prefer_server_ciphers  on;\n        location / {\n            try_files $uri $uri/ /index.html;\n        }\n        location ~ [^/]\\.php(/|$) {\n                fastcgi_split_path_info ^(.+?\\.php)(/.*)$;\n                if (!-f $document_root$fastcgi_script_name) {\n                        return 404;\n                }\n                fastcgi_pass unix:/run/nextcloud/fastcgi.sock; # From the nextcloud-initscript package\n                fastcgi_index index.php;\n                include fastcgi.conf;\n                fastcgi_read_timeout 120s;\n                client_max_body_size 16G;\n        }\n        location ^~ /.well-known/carddav { return 301 /remote.php/dav/; }\n        location ^~ /.well-known/caldav { return 301 /remote.php/dav/; }\n        location ^~ /.well-known/webfinger { return 301 /index.php/.well-known/webfinger; }\n        location ^~ /.well-known/nodeinfo { return 301 /index.php/.well-known/nodeinfo; }\n}\nEOF\nsed -i -e 's|memory_limit = 128M|memory_limit = 512M|; $aapc.enable_cli=1' /etc/php83/php.ini\nsed -i -e 's|upload_max_file_size = 2M|upload_max_file_size = 16G|' /etc/php83/php.ini\nsed -i -E '/^php_admin_(flag|value)\\[opcache/s/^/;/' /etc/php83/php-fpm.d/nextcloud.conf\nsed -i -e 's| js;| mjs js;|' /etc/nginx/mime.types\nmsg_ok \"Installed Nextcloud\"\n\nmsg_info \"Adding Additional Nextcloud Packages\"\n$STD apk add nextcloud-occ\n$STD apk add nextcloud-default-apps\n$STD apk add nextcloud-activity\n$STD apk add nextcloud-admin_audit\n$STD apk add nextcloud-comments\n$STD apk add nextcloud-dashboard\n$STD apk add nextcloud-doc\n$STD apk add nextcloud-encryption\n$STD apk add nextcloud-federation\n$STD apk add nextcloud-files_external\n$STD apk add nextcloud-files_sharing\n$STD apk add nextcloud-files_trashbin\n$STD apk add nextcloud-files_versions\n$STD apk add nextcloud-notifications\n$STD apk add nextcloud-sharebymail\n$STD apk add nextcloud-suspicious_login\n$STD apk add nextcloud-support\n$STD apk add nextcloud-systemtags\n$STD apk add nextcloud-user_status\n$STD apk add nextcloud-weather_status\nmsg_ok \"Added Additional Nextcloud Packages\"\n\nmsg_info \"Starting Services\"\n$STD rc-service redis start\n$STD rc-update add redis default\n$STD rc-service php-fpm83 start\nchown -R nextcloud:www-data /var/log/nextcloud/\nchown -R nextcloud:www-data /usr/share/webapps/nextcloud/\n$STD rc-service php-fpm83 restart\n$STD rc-service nginx start\n$STD rc-service nextcloud start\n$STD rc-update add nginx default\n$STD rc-update add nextcloud default\nmsg_ok \"Started Services\"\n\nmsg_info \"Start Nextcloud Setup-Wizard\"\necho -e \"export VISUAL=nano\\nexport EDITOR=nano\" >>/etc/profile\ncd /usr/share/webapps/nextcloud\n$STD su nextcloud -s /bin/sh -c \"php83 occ maintenance:install \\\n--database='mysql' --database-name $DB_NAME \\\n--database-user '$DB_USER' --database-pass '$DB_PASS' \\\n--admin-user '$ADMIN_USER' --admin-pass '$ADMIN_PASS' \\\n--data-dir '/var/lib/nextcloud/data'\"\n$STD su nextcloud -s /bin/sh -c 'php83 occ background:cron'\nrm -rf /usr/share/webapps/nextcloud/apps/serverinfo\nIP4=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)\nsed -i \"/0 => \\'localhost\\',/a \\    \\1 => '$IP4',\" /usr/share/webapps/nextcloud/config/config.php\nsu nextcloud -s /bin/sh -c 'php83 -f /usr/share/webapps/nextcloud/cron.php'\nmsg_ok \"Finished Nextcloud Setup-Wizard\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-node-red-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nodered.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add --no-cache \\\n  git \\\n  nodejs \\\n  npm\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Creating Node-RED User\"\nadduser -D -H -s /sbin/nologin -G users nodered\nmsg_ok \"Created Node-RED User\"\n\nmsg_info \"Installing Node-RED\"\n$STD npm install -g --unsafe-perm node-red\nmsg_ok \"Installed Node-RED\"\n\nmsg_info \"Creating /home/nodered\"\nmkdir -p /home/nodered\nchown -R nodered:users /home/nodered\nchmod 750 /home/nodered\nmsg_ok \"Created /home/nodered\"\n\nmsg_info \"Creating Node-RED Service\"\nservice_path=\"/etc/init.d/nodered\"\n\necho '#!/sbin/openrc-run\ndescription=\"Node-RED Service\"\n\ncommand=\"/usr/local/bin/node-red\"\ncommand_args=\"--max-old-space-size=128 -v\"\ncommand_user=\"nodered\"\npidfile=\"/var/run/nodered.pid\"\ncommand_background=\"yes\"\n\ndepend() {\n    use net\n}' >$service_path\n\nchmod +x $service_path\nmsg_ok \"Created Node-RED Service\"\n\nmsg_info \"Starting Node-RED\"\n$STD rc-update add nodered\n$STD rc-service nodered start\nmsg_ok \"Started Node-RED\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-ntfy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: cobalt (cobaltgit)\n# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE\n# Source: https://ntfy.sh/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing ntfy\"\n$STD apk add --no-cache ntfy ntfy-openrc libcap\nsed -i '/^listen-http/s/^\\(.*\\)$/#\\1\\n/' /etc/ntfy/server.yml\nsetcap 'cap_net_bind_service=+ep' /usr/bin/ntfy\n$STD rc-update add ntfy default\n$STD service ntfy start\nmsg_ok \"Installed ntfy\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-postgresql-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.postgresql.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nread -r -p \"${TAB3}Enter PostgreSQL version (15/16/17): \" ver\n[[ $ver =~ ^(15|16|17)$ ]] || { echo \"Invalid version\"; exit 64; }\n\nmsg_info \"Installing PostgreSQL ${ver}\"\n$STD apk add --no-cache postgresql${ver} postgresql${ver}-contrib postgresql${ver}-openrc sudo\nmsg_ok \"Installed PostgreSQL ${ver}\"\n\nmsg_info \"Enabling PostgreSQL Service\"\n$STD rc-update add postgresql default\nmsg_ok \"Enabled PostgreSQL Service\"\n\nmsg_info \"Starting PostgreSQL\"\n$STD rc-service postgresql start\nmsg_ok \"Started PostgreSQL\"\n\nmsg_info \"Configuring PostgreSQL for External Access\"\nconf_file=\"/etc/postgresql${ver}/postgresql.conf\"\nhba_file=\"/etc/postgresql${ver}/pg_hba.conf\"\nsed -i 's/^#listen_addresses =.*/listen_addresses = '\\''*'\\''/' \"$conf_file\"\nsed -i '/^host\\s\\+all\\s\\+all\\s\\+127.0.0.1\\/32\\s\\+md5/ s/.*/host all all 0.0.0.0\\/0 md5/' \"$hba_file\"\n$STD rc-service postgresql restart\nmsg_ok \"Configured and Restarted PostgreSQL\"\n\nread -r -p \"${TAB3}Would you like to install Adminer with lighttpd? <y/N>: \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Adminer and dependencies\"\n  $STD apk add --no-cache \\\n    lighttpd \\\n    lighttpd-openrc \\\n    php83 \\\n    php83-cgi \\\n    php83-common \\\n    php83-curl \\\n    php83-gd \\\n    php83-mbstring \\\n    php83-pdo \\\n    php83-pgsql \\\n    php83-openssl \\\n    php83-zip \\\n    php83-session \\\n    jq\n\n  sed -i 's|# *include \"mod_fastcgi.conf\"|include \"mod_fastcgi.conf\"|' /etc/lighttpd/lighttpd.conf\n  sed -i 's|/usr/bin/php-cgi|/usr/bin/php-cgi83|g' /etc/lighttpd/mod_fastcgi.conf\n  mkdir -p /var/www/localhost/htdocs\n  ADMINER_VERSION=$(curl -fsSL https://api.github.com/repos/vrana/adminer/releases/latest | jq -r '.tag_name' | sed 's/^v//')\n  curl -fsSL \"https://github.com/vrana/adminer/releases/download/v${ADMINER_VERSION}/adminer-${ADMINER_VERSION}.php\" -o /var/www/localhost/htdocs/adminer.php\n  chown lighttpd:lighttpd /var/www/localhost/htdocs/adminer.php\n  chmod 755 /var/www/localhost/htdocs/adminer.php\n  msg_ok \"Adminer Installed\"\n\n  msg_info \"Starting Lighttpd\"\n  $STD rc-update add lighttpd default\n  $STD rc-service lighttpd restart\n  msg_ok \"Lighttpd Started\"\nfi\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-prometheus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prometheus.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Prometheus\"\n$STD apk add --no-cache prometheus\nmsg_ok \"Installed Prometheus\"\n\nmsg_info \"Enabling Prometheus Service\"\n$STD rc-update add prometheus default\nmsg_ok \"Enabled Prometheus Service\"\n\nmsg_info \"Starting Prometheus\"\n$STD service prometheus start\nmsg_ok \"Started Prometheus\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-rclone-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rclone/rclone\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apk add --no-cache \\\n  apache2-utils fuse3\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Installing rclone\"\ntemp_file=$(mktemp)\nmkdir -p /opt/rclone\nRELEASE=$(curl -s https://api.github.com/repos/rclone/rclone/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\ncurl -fsSL \"https://github.com/rclone/rclone/releases/download/v${RELEASE}/rclone-v${RELEASE}-linux-arm64.zip\" -o \"$temp_file\"\n$STD unzip -j \"$temp_file\" '*/**' -d /opt/rclone\ncd /opt/rclone\nRCLONE_PASSWORD=$(head -c 16 /dev/urandom | xxd -p -c 16)\n$STD htpasswd -cb -B /opt/login.pwd admin \"$RCLONE_PASSWORD\"\n{\n  echo \"rclone-Credentials\"\n  echo \"rclone User Name: admin\"\n  echo \"rclone Password: $RCLONE_PASSWORD\"\n} >>~/rclone.creds\necho \"${RELEASE}\" >/opt/rclone_version.txt\nrm -f \"$temp_file\"\nmsg_ok \"Installed rclone\"\n\nmsg_info \"Enabling rclone Service\"\ncat <<EOF >/etc/init.d/rclone\n#!/sbin/openrc-run\ndescription=\"rclone Service\"\ncommand=\"/opt/rclone/rclone\"\ncommand_args=\"rcd --rc-web-gui --rc-web-gui-no-open-browser --rc-addr :3000 --rc-htpasswd /opt/login.pwd\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/rclone.pid\"\n\ndepend() {\n    use net\n}\nEOF\nchmod +x /etc/init.d/rclone\n$STD rc-update add rclone default\nmsg_ok \"Enabled rclone Service\"\n\nmsg_info \"Starting rclone\"\n$STD service rclone start\nmsg_ok \"Started rclone\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nrm -rf \"$temp_file\"\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-redis-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://redis.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Redis\"\n$STD apk add redis\n$STD sed -i 's/^bind .*/bind 0.0.0.0/' /etc/redis.conf\n$STD rc-update add redis default\n$STD rc-service redis start\nmsg_ok \"Installed Redis\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-redlib-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: andrej-kocijan (Andrej Kocijan)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/redlib-org/redlib\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"redlib\" \"redlib-org/redlib\" \"prebuild\" \"latest\" \"/opt/redlib\" \"redlib-aarch64-unknown-linux-musl.tar.gz\"\n\nmsg_info \"Configuring Redlib\"\ncat <<EOF >/opt/redlib/redlib.conf\n############################################\n# Redlib Instance Configuration File\n# Uncomment and edit values as needed\n############################################\n\n## Instance settings\nADDRESS=0.0.0.0\nPORT=5252                           # Integer (0-65535) - Internal port\n#REDLIB_SFW_ONLY=off                # [\"on\", \"off\"] - Filter all NSFW content\n#REDLIB_BANNER=                     # String - Displayed on instance info page\n#REDLIB_ROBOTS_DISABLE_INDEXING=off # [\"on\", \"off\"] - Disable search engine indexing\n#REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io # Pushshift frontend for removed links\n#REDLIB_ENABLE_RSS=off              # [\"on\", \"off\"] - Enable RSS feed generation\n#REDLIB_FULL_URL=                   # String - Needed for proper RSS URLs\n\n## Default user settings\n#REDLIB_DEFAULT_THEME=system        # Theme (system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight, tokyoNight, icebergDark, doomone, libredditBlack, libredditDark, libredditLight)\n#REDLIB_DEFAULT_FRONT_PAGE=default  # [\"default\", \"popular\", \"all\"]\n#REDLIB_DEFAULT_LAYOUT=card         # [\"card\", \"clean\", \"compact\"]\n#REDLIB_DEFAULT_WIDE=off            # [\"on\", \"off\"]\n#REDLIB_DEFAULT_POST_SORT=hot       # [\"hot\", \"new\", \"top\", \"rising\", \"controversial\"]\n#REDLIB_DEFAULT_COMMENT_SORT=confidence # [\"confidence\", \"top\", \"new\", \"controversial\", \"old\"]\n#REDLIB_DEFAULT_BLUR_SPOILER=off    # [\"on\", \"off\"]\n#REDLIB_DEFAULT_SHOW_NSFW=off       # [\"on\", \"off\"]\n#REDLIB_DEFAULT_BLUR_NSFW=off       # [\"on\", \"off\"]\n#REDLIB_DEFAULT_USE_HLS=off         # [\"on\", \"off\"]\n#REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off # [\"on\", \"off\"]\n#REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off # [\"on\", \"off\"]\n#REDLIB_DEFAULT_SUBSCRIPTIONS=      # Example: sub1+sub2+sub3\n#REDLIB_DEFAULT_HIDE_AWARDS=off     # [\"on\", \"off\"]\n#REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off # [\"on\", \"off\"]\n#REDLIB_DEFAULT_HIDE_SCORE=off      # [\"on\", \"off\"]\n#REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=off # [\"on\", \"off\"]\n#REDLIB_DEFAULT_FIXED_NAVBAR=on     # [\"on\", \"off\"]\n#REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS=off # [\"on\", \"off\"]\nEOF\nmsg_ok \"Configured Redlib\"\n\nmsg_info \"Creating Redlib Service\"\ncat <<EOF >/etc/init.d/redlib\n#!/sbin/openrc-run\n\nname=\"Redlib\"\ndescription=\"Redlib Service\"\ncommand=\"/opt/redlib/redlib\"\npidfile=\"/run/redlib.pid\"\nsupervisor=\"supervise-daemon\"\ncommand_background=\"yes\"\n\ndepend() {\n    need net\n}\n\nstart_pre() {\n\n    set -a\n    . /opt/redlib/redlib.conf\n    set +a\n\n    : ${ADDRESS:=0.0.0.0}\n    : ${PORT:=5252}\n\n    command_args=\"-a ${ADDRESS} -p ${PORT}\"\n}\nEOF\n$STD chmod +x /etc/init.d/redlib\n$STD rc-update add redlib default\nmsg_ok \"Created Redlib Service\"\n\nmsg_info \"Starting Redlib Service\"\n$STD rc-service redlib start\nmsg_ok \"Started Redlib Service\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-rustdeskserver-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rustdesk/rustdesk-server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nRELEASE=$(curl -s https://api.github.com/repos/lejianwen/rustdesk-server/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\nmsg_info \"Installing RustDesk Server v${RELEASE}\"\ntemp_file1=$(mktemp)\ncurl -fsSL \"https://github.com/rustdesk/rustdesk-server/releases/download/${RELEASE}/rustdesk-server-linux-arm64.zip\" -o \"$temp_file1\"\n$STD unzip \"$temp_file1\"\nmv arm64 /opt/rustdesk-server\nmkdir -p /root/.config/rustdesk\ncd /opt/rustdesk-server\n./rustdesk-utils genkeypair >/tmp/rustdesk_keys.txt\ngrep \"Public Key\" /tmp/rustdesk_keys.txt | awk '{print $3}' >/root/.config/rustdesk/id_ed25519.pub\ngrep \"Secret Key\" /tmp/rustdesk_keys.txt | awk '{print $3}' >/root/.config/rustdesk/id_ed25519\nchmod 600 /root/.config/rustdesk/id_ed25519\nchmod 644 /root/.config/rustdesk/id_ed25519.pub\nrm /tmp/rustdesk_keys.txt\necho \"${RELEASE}\" >~/.rustdesk-server\nmsg_ok \"Installed RustDesk Server v${RELEASE}\"\n\nAPIRELEASE=$(curl -s https://api.github.com/repos/lejianwen/rustdesk-api/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\nmsg_info \"Installing RustDesk API v${APIRELEASE}\"\ntemp_file2=$(mktemp)\ncurl -fsSL \"https://github.com/lejianwen/rustdesk-api/releases/download/v${APIRELEASE}/linux-arm64.tar.gz\" -o \"$temp_file2\"\n$STD tar zxvf \"$temp_file2\"\nmv release /opt/rustdesk-api\ncd /opt/rustdesk-api\nADMINPASS=$(head -c 16 /dev/urandom | xxd -p -c 16)\n$STD ./apimain reset-admin-pwd \"$ADMINPASS\"\n{\n  echo \"RustDesk WebUI\"\n  echo \"\"\n  echo \"Username: admin\"\n  echo \"Password: $ADMINPASS\"\n} >>~/rustdesk.creds\necho \"${APIRELEASE}\" >~/.rustdesk-api\nmsg_ok \"Installed RustDesk API v${APIRELEASE}\"\n\nmsg_info \"Enabling RustDesk Server Services\"\ncat <<EOF >/etc/init.d/rustdesk-server-hbbs\n#!/sbin/openrc-run\ndescription=\"RustDesk HBBS Service\"\ndirectory=\"/opt/rustdesk-server\"\ncommand=\"/opt/rustdesk-server/hbbs\"\ncommand_args=\"\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/rustdesk-server-hbbs.pid\"\noutput_log=\"/var/log/rustdesk-hbbs.log\"\nerror_log=\"/var/log/rustdesk-hbbs.err\"\n\ndepend() {\n    use net\n}\nEOF\n\ncat <<EOF >/etc/init.d/rustdesk-server-hbbr\n#!/sbin/openrc-run\ndescription=\"RustDesk HBBR Service\"\ndirectory=\"/opt/rustdesk-server\"\ncommand=\"/opt/rustdesk-server/hbbr\"\ncommand_args=\"\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/rustdesk-server-hbbr.pid\"\noutput_log=\"/var/log/rustdesk-hbbr.log\"\nerror_log=\"/var/log/rustdesk-hbbr.err\"\n\ndepend() {\n    use net\n}\nEOF\n\ncat <<EOF >/etc/init.d/rustdesk-api\n#!/sbin/openrc-run\ndescription=\"RustDesk API Service\"\ndirectory=\"/opt/rustdesk-api\"\ncommand=\"/opt/rustdesk-api/apimain\"\ncommand_args=\"\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/rustdesk-api.pid\"\noutput_log=\"/var/log/rustdesk-api.log\"\nerror_log=\"/var/log/rustdesk-api.err\"\n\ndepend() {\n    use net\n}\nEOF\nchmod +x /etc/init.d/rustdesk-server-hbbs\nchmod +x /etc/init.d/rustdesk-server-hbbr\nchmod +x /etc/init.d/rustdesk-api\n$STD rc-update add rustdesk-server-hbbs default\n$STD rc-update add rustdesk-server-hbbr default\n$STD rc-update add rustdesk-api default\nmsg_ok \"Enabled RustDesk Server Services\"\n\nmsg_info \"Starting RustDesk Server\"\n$STD service rustdesk-server-hbbs start\n$STD service rustdesk-server-hbbr start\n$STD service rustdesk-api start\nmsg_ok \"Started RustDesk Server\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nrm -f \"$temp_file1\" \"$temp_file2\"\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-rustypaste-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/orhun/rustypaste\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing RustyPaste\"\n$STD apk add --no-cache rustypaste --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community\nmsg_ok \"Installed RustyPaste\"\n\nmsg_info \"Configuring RustyPaste\"\nmkdir -p /var/lib/rustypaste\nsed -i 's|^address = \".*\"|address = \"0.0.0.0:8000\"|' /etc/rustypaste/config.toml\nmsg_ok \"Configured RustyPaste\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/etc/init.d/rustypaste\n#!/sbin/openrc-run\n\nname=\"rustypaste\"\ndescription=\"RustyPaste - A minimal file upload/pastebin service\"\ncommand=\"/usr/bin/rustypaste\"\ncommand_args=\"\"\ncommand_user=\"root\"\ncommand_background=true\npidfile=\"/run/${RC_SVCNAME}.pid\"\ndirectory=\"/var/lib/rustypaste\"\n\ndepend() {\n    need net\n    after firewall\n}\n\nstart_pre() {\n    export CONFIG=/etc/rustypaste/config.toml\n    checkpath --directory --owner root:root --mode 0755 /var/lib/rustypaste\n}\nEOF\nchmod +x /etc/init.d/rustypaste\n$STD rc-update add rustypaste default\n$STD rc-service rustypaste start\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/alpine-syncthing-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://syncthing.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setup Syncthing\"\n$STD apk add --no-cache syncthing\nrc-service syncthing start\nsleep 3\nrc-service syncthing stop\nsed -i \"{s/127.0.0.1:8384/0.0.0.0:8384/g}\" /var/lib/syncthing/.local/state/syncthing/config.xml\nmsg_ok \"Setup Syncthing\"\n\nmsg_info \"Enabling Syncthing Service\"\n$STD rc-update add syncthing default\nmsg_ok \"Enabled Syncthing Service\"\n\nmsg_info \"Starting Syncthing\"\n$STD rc-service syncthing start\nmsg_ok \"Started Syncthing\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-teamspeak-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021 (Slaviša Arežina)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://teamspeak.com/en/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apk add --no-cache \\\n  ca-certificates \\\n  libstdc++ \\\n  libc6-compat\nmsg_ok \"Installed dependencies\"\n\nRELEASE=$(curl -fsSL https://teamspeak.com/en/downloads/#server | sed -n 's/.*teamspeak3-server_linux_amd64-\\([0-9.]*[0-9]\\).*/\\1/p' | awk 'NR==1')\nmsg_info \"Installing Teamspeak Server v${RELEASE}\"\nmkdir -p /opt/teamspeak-server\ncd /opt/teamspeak-server\ncurl -fsSL \"https://files.teamspeak-services.com/releases/server/${RELEASE}/teamspeak3-server_linux_arm64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\ntar xf ts3server.tar.bz2 --strip-components=1\nmkdir -p logs data lib\nmv *.so lib\ntouch data/ts3server.sqlitedb data/query_ip_blacklist.txt data/query_ip_whitelist.txt .ts3server_license_accepted\necho \"${RELEASE}\" >~/.teamspeak-server\nmsg_ok \"Installed TeamSpeak Server v${RELEASE}\"\n\nmsg_info \"Enabling TeamSpeak Server Service\"\ncat <<EOF >/etc/init.d/teamspeak\n#!/sbin/openrc-run\n\nname=\"TeamSpeak Server\"\ndescription=\"TeamSpeak 3 Server\"\ncommand=\"/opt/teamspeak-server/ts3server_startscript.sh\"\ncommand_args=\"start\"\noutput_log=\"/var/log/teamspeak.out.log\"\nerror_log=\"/var/log/teamspeak.err.log\"\ncommand_background=true\npidfile=\"/run/teamspeak-server.pid\"\ndirectory=\"/opt/teamspeak-server\"\n\ndepend() {\n    need net\n    use dns\n}\nEOF\nchmod +x /etc/init.d/teamspeak\n$STD rc-update add teamspeak default\nmsg_ok \"Enabled TeamSpeak Server Service\"\n\nmsg_info \"Starting TeamSpeak Server\"\n$STD service teamspeak start\nmsg_ok \"Started TeamSpeak Server\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\nrm -r ts3server.tar.bz* LICENSE* CHANGELOG doc serverquerydocs tsdns redist\n$STD apk cache clean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/alpine-tinyauth-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021) | Co-Author: Stavros (steveiliop56)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/steveiliop56/tinyauth\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add --no-cache openssl apache2-utils\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Tinyauth\"\nmkdir -p /opt/tinyauth\nRELEASE=$(curl -s https://api.github.com/repos/steveiliop56/tinyauth/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\ncurl -fsSL \"https://github.com/steveiliop56/tinyauth/releases/download/v${RELEASE}/tinyauth-arm64\" -o /opt/tinyauth/tinyauth\nchmod +x /opt/tinyauth/tinyauth\nPASS=$(openssl rand -base64 8 | tr -dc 'a-zA-Z0-9' | head -c 8)\nUSER=$(htpasswd -Bbn \"tinyauth\" \"${PASS}\")\n\ncat <<EOF >/opt/tinyauth/credentials.txt\nTinyauth Credentials\nUsername: tinyauth\nPassword: ${PASS}\nEOF\necho \"${RELEASE}\" >~/.tinyauth\nmsg_ok \"Installed Tinyauth\"\n\nread -r -p \"${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): \" app_url\n\ncat <<EOF >/opt/tinyauth/.env\nTINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db\nTINYAUTH_AUTH_USERS='${USER}'\nTINYAUTH_APPURL=${app_url}\nEOF\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/etc/init.d/tinyauth\n#!/sbin/openrc-run\ndescription=\"Tinyauth Service\"\n\nset -a\nENV_FILE=\"/opt/tinyauth/.env\"\n[ -f \"$ENV_FILE\" ] && . \"$ENV_FILE\"\nset +a\n\ncommand=\"/opt/tinyauth/tinyauth\"\ndirectory=\"/opt/tinyauth\"\ncommand_user=\"root\"\ncommand_background=\"true\"\npidfile=\"/var/run/tinyauth.pid\"\n\ndepend() {\n    use net\n}\nEOF\nchmod +x /etc/init.d/tinyauth\n$STD rc-update add tinyauth default\nmsg_ok \"Enabled Tinyauth Service\"\n\nmsg_info \"Starting Tinyauth\"\n$STD service tinyauth start\nmsg_ok \"Started Tinyauth\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-traefik-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add ca-certificates\n$STD update-ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Traefik\"\n$STD apk add traefik --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community\nmsg_ok \"Installed Traefik\"\n\nread -p \"${TAB3}Enable Traefik WebUI (Port 8080)? [y/N]: \" enable_webui\nif [[ \"$enable_webui\" =~ ^[Yy]$ ]]; then\n  msg_info \"Configuring Traefik WebUI\"\n  sed -i 's/localhost//g' /etc/traefik/traefik.yaml\n  msg_ok \"Configured Traefik WebUI\"\nfi\n\nmsg_info \"Enabling and starting Traefik service\"\n$STD rc-update add traefik default\nsed -i '/^command=.*/i directory=\"/etc/traefik\"' /etc/init.d/traefik\n$STD rc-service traefik start\nmsg_ok \"Traefik service started\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-transmission-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://transmissionbt.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Transmission\"\n$STD apk add --no-cache transmission-cli transmission-daemon\n$STD rc-service transmission-daemon start\nsleep 5\n$STD rc-service transmission-daemon stop\nsed -i '{s/\"rpc-whitelist-enabled\": true/\"rpc-whitelist-enabled\": false/g; s/\"rpc-host-whitelist-enabled\": true,/\"rpc-host-whitelist-enabled\": false,/g}' /var/lib/transmission/config/settings.json\nmsg_ok \"Installed Transmission\"\n\nmsg_info \"Enabling Transmission Service\"\n$STD rc-update add transmission-daemon default\nmsg_ok \"Enabled Transmission Service\"\n\nmsg_info \"Starting Transmission\"\n$STD rc-service transmission-daemon start\nmsg_ok \"Started Transmission\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-valkey-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pshankinclarke (lazarillo)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://valkey.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Valkey\"\n$STD apk add valkey valkey-openrc valkey-cli\nsed -i 's/^bind .*/bind 0.0.0.0/' /etc/valkey/valkey.conf\n\nPASS=\"$(head -c 100 /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c32)\"\necho \"requirepass $PASS\" >>/etc/valkey/valkey.conf\necho \"$PASS\" >~/valkey.creds\nchmod 600 ~/valkey.creds\n\nMEMTOTAL_MB=$(free -m | grep ^Mem: | awk '{print $2}')\nMAXMEMORY_MB=$((MEMTOTAL_MB * 75 / 100))\n\n{\n  echo \"\"\n  echo \"# Memory-optimized settings for small-scale deployments\"\n  echo \"maxmemory ${MAXMEMORY_MB}mb\"\n  echo \"maxmemory-policy allkeys-lru\"\n  echo \"maxmemory-samples 10\"\n} >>/etc/valkey/valkey.conf\nmsg_ok \"Installed Valkey\"\n\n# Note: Alpine's valkey package is compiled without TLS support\n# For TLS, use the Debian-based valkey script instead\n\n$STD rc-update add valkey default\n$STD rc-service valkey start\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/alpine-vaultwarden-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dani-garcia/vaultwarden\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add --no-cache \\\n  openssl \\\n  argon2\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Alpine-Vaultwarden\"\n$STD apk add --no-cache vaultwarden\nsed -i 's|export WEB_VAULT_ENABLED=.*|export WEB_VAULT_ENABLED=true|' /etc/conf.d/vaultwarden\necho -e \"export ADMIN_TOKEN=''\" >>/etc/conf.d/vaultwarden\necho -e \"export ROCKET_ADDRESS=0.0.0.0\" >>/etc/conf.d/vaultwarden\necho -e \"export ROCKET_TLS='{certs=\\\"/etc/ssl/certs/vaultwarden-selfsigned.crt\\\",key=\\\"/etc/ssl/private/vaultwarden-selfsigned.key\\\"}'\" >>/etc/conf.d/vaultwarden\n$STD openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout /etc/ssl/private/vaultwarden-selfsigned.key -out /etc/ssl/certs/vaultwarden-selfsigned.crt -subj \"/CN=localhost\" -addext \"subjectAltName=DNS:localhost\"\nchown vaultwarden:vaultwarden /etc/ssl/certs/vaultwarden-selfsigned.crt\nchown vaultwarden:vaultwarden /etc/ssl/private/vaultwarden-selfsigned.key\nmsg_ok \"Installed Alpine-Vaultwarden\"\n\nmsg_info \"Installing Web-Vault\"\n$STD apk add --no-cache vaultwarden-web-vault\nmsg_ok \"Installed Web-Vault\"\n\nmsg_info \"Starting Alpine-Vaultwarden\"\n$STD rc-service vaultwarden start\n$STD rc-update add vaultwarden default\nmsg_ok \"Started Alpine-Vaultwarden\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-wakapi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wakapi.dev/ | https://github.com/muety/wakapi\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add --no-cache \\\n  ca-certificates \\\n  tzdata\n$STD update-ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"wakapi\" \"muety/wakapi\" \"prebuild\" \"latest\" \"/opt/wakapi\" \"wakapi_linux_amd64.zip\"\n\nmsg_info \"Configuring Wakapi\"\nLOCAL_IP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)\ncd /opt/wakapi\nsed -i 's/listen_ipv6: ::1/listen_ipv6: \"-\"/g' config.yml\nsed -i 's/listen_ipv4: 127.0.0.1/listen_ipv4: \"0.0.0.0\"/g' config.yml\nsed -i \"s/public_url: http:\\/\\/localhost:3000/public_url: http:\\/\\/$LOCAL_IP:3000/g\" config.yml\nmsg_ok \"Configured Wakapi\"\n\nmsg_info \"Enabling Wakapi Service\"\ncat <<EOF >/etc/init.d/wakapi\n#!/sbin/openrc-run\ndescription=\"Wakapi Service\"\ndirectory=\"/opt/wakapi\"\ncommand=\"/opt/wakapi/wakapi\"\ncommand_args=\"-config config.yml\"\ncommand_background=\"true\"\ncommand_user=\"root\"\npidfile=\"/var/run/wakapi.pid\"\n\ndepend() {\n    use net\n}\nEOF\nchmod +x /etc/init.d/wakapi\n$STD rc-update add wakapi default\nmsg_ok \"Enabled Wakapi Service\"\n\nmsg_info \"Starting Wakapi\"\n$STD rc-service wakapi start\nmsg_ok \"Started Wakapi\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-wireguard-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.wireguard.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add \\\n  iptables \\\n  openrc\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing WireGuard\"\n$STD apk add --no-cache wireguard-tools\nif [[ ! -L /etc/init.d/wg-quick.wg0 ]]; then\n  ln -s /etc/init.d/wg-quick /etc/init.d/wg-quick.wg0\nfi\n\nprivate_key=$(wg genkey)\ncat <<EOF >/etc/wireguard/wg0.conf\n[Interface]\nPrivateKey = ${private_key}\nAddress = 10.0.0.1/24\nSaveConfig = true\nPostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;\nPostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;\nListenPort = 51820\nEOF\necho \"net.ipv4.ip_forward=1\" >>/etc/sysctl.conf\n$STD rc-update add sysctl\n$STD sysctl -p /etc/sysctl.conf\nmsg_ok \"Installed WireGuard\"\n\nread -rp \"${TAB3}Do you want to install WGDashboard? (y/N): \" INSTALL_WGD\nif [[ \"$INSTALL_WGD\" =~ ^[Yy]$ ]]; then\n  msg_info \"Installing additional dependencies for WGDashboard\"\n  $STD apk add --no-cache \\\n    python3 \\\n    py3-pip \\\n    git \\\n    sudo \\\n    musl-dev \\\n    linux-headers \\\n    gcc \\\n    python3-dev\n  msg_ok \"Installed additional dependencies for WGDashboard\"\n  msg_info \"Installing WGDashboard\"\n  git clone -q https://github.com/WGDashboard/WGDashboard.git /etc/wgdashboard\n  cd /etc/wgdashboard/src\n  chmod u+x wgd.sh\n  $STD ./wgd.sh install\n  msg_ok \"Installed WGDashboard\"\n\n  msg_info \"Creating Service for WGDashboard\"\n  cat <<EOF >/etc/init.d/wg-dashboard\n#!/sbin/openrc-run\n\ndescription=\"WireGuard Dashboard Service\"\n\ndepend() {\n    need net\n    after firewall\n}\n\nstart() {\n    ebegin \"Starting WGDashboard\"\n    cd /etc/wgdashboard/src/\n    ./wgd.sh start &\n    eend $?\n}\n\nstop() {\n    ebegin \"Stopping WGDashboard\"\n    pkill -f \"wgd.sh\"\n    eend $?\n}\nEOF\n  chmod +x /etc/init.d/wg-dashboard\n  $STD rc-update add wg-dashboard default\n  $STD rc-service wg-dashboard start\n  msg_ok \"Created Service for WGDashboard\"\n\nfi\n\nmsg_info \"Starting Services\"\n$STD rc-update add wg-quick.wg0 default\n$STD rc-service wg-quick.wg0 start\nmsg_ok \"Started Services\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/alpine-zigbee2mqtt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zigbee2mqtt.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Alpine-Zigbee2MQTT\"\nmkdir -p /root/.z2m /etc/zigbee2mqtt\n$STD apk add zigbee2mqtt\nln -s /etc/zigbee2mqtt/ /root/.z2m\nchown -R root:root /etc/zigbee2mqtt /root/.z2m\nsed -i -e 's/#datadir=\"\\/var\\/lib\\/zigbee2mqtt\"/datadir=\"\\/etc\\/zigbee2mqtt\"/' -e 's/#command_user=\"zigbee2mqtt\"/command_user=\"root\"/' /etc/conf.d/zigbee2mqtt\n$STD rc-update add zigbee2mqtt\n$STD rc-service zigbee2mqtt restart\nmsg_ok \"Installed Alpine-Zigbee2MQTT\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/ampache-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ampache/ampache\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  flac \\\n  vorbis-tools \\\n  lame \\\n  ffmpeg \\\n  inotify-tools \\\n  libavcodec-extra \\\n  libmp3lame-dev \\\n  libtheora-dev \\\n  libvorbis-dev \\\n  libvpx-dev\nmsg_ok \"Installed dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\nsetup_mariadb\nMARIADB_DB_USER=\"ampache\" MARIADB_DB_NAME=\"ampache\" setup_mariadb_db\n\nfetch_and_deploy_gh_release \"ampache\" \"ampache/ampache\" \"prebuild\" \"latest\" \"/opt/ampache\" \"ampache-*_all_php8.4.zip\"\n\nmsg_info \"Setting up Ampache\"\nrm -rf /var/www/html\nln -s /opt/ampache/public /var/www/html\nmv /opt/ampache/public/rest/.htaccess.dist /opt/ampache/public/rest/.htaccess\nmv /opt/ampache/public/play/.htaccess.dist /opt/ampache/public/play/.htaccess\ncp /opt/ampache/config/ampache.cfg.php.dist /opt/ampache/config/ampache.cfg.php\nchmod 664 /opt/ampache/public/rest/.htaccess /opt/ampache/public/play/.htaccess\nmsg_ok \"Set up Ampache\"\n\nmsg_info \"Configuring Database Connection\"\nsed -i -e 's|^database_hostname = .*|database_hostname = \"localhost\"|' \\\n  -e 's|^database_name = .*|database_name = \"ampache\"|' \\\n  -e 's|^database_username = .*|database_username = \"ampache\"|' \\\n  -e \"s|^database_password = .*|database_password = \\\"${MARIADB_DB_PASS}\\\"|\" /opt/ampache/config/ampache.cfg.php\nchown -R www-data:www-data /opt/ampache\nmsg_ok \"Configured Database Connection\"\n\nmsg_info \"Importing Database Schema\"\nmariadb -u ampache -p\"${MARIADB_DB_PASS}\" ampache </opt/ampache/resources/sql/ampache.sql\nmsg_ok \"Imported Database Schema\"\n\nmsg_info \"Configuring PHP\"\nsed -i -e 's/upload_max_filesize = .*/upload_max_filesize = 100M/' \\\n  -e 's/post_max_size = .*/post_max_size = 100M/' \\\n  -e 's/max_execution_time = .*/max_execution_time = 600/' \\\n  -e 's/memory_limit = .*/memory_limit = 512M/' /etc/php/8.4/apache2/php.ini\n$STD a2enmod rewrite\n$STD systemctl restart apache2\nmsg_ok \"Configured PHP\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/anchor-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ZhFahim/anchor\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"anchor\" PG_DB_USER=\"anchor\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"anchor\" \"ZhFahim/anchor\" \"tarball\"\n\nmsg_info \"Building Server\"\ncd /opt/anchor/server\n$STD pnpm install --frozen-lockfile\n$STD pnpm prisma generate\n$STD pnpm build\n[[ -d src/generated ]] && mkdir -p dist/src && cp -R src/generated dist/src/\nmsg_ok \"Built Server\"\n\nmsg_info \"Building Web Interface\"\ncd /opt/anchor/web\n$STD pnpm install --frozen-lockfile\nSERVER_URL=http://127.0.0.1:3001 $STD pnpm build\ncp -r .next/static .next/standalone/.next/static\ncp -r public .next/standalone/public\nmsg_ok \"Built Web Interface\"\n\nmsg_info \"Configuring Application\"\nJWT_SECRET=$(openssl rand -base64 32)\ncat <<EOF >/opt/anchor/.env\nAPP_URL=http://${LOCAL_IP}:3000\nJWT_SECRET=${JWT_SECRET}\nDATABASE_URL=postgresql://anchor:${PG_DB_PASS}@localhost:5432/anchor\nPG_HOST=localhost\nPG_USER=anchor\nPG_PASSWORD=${PG_DB_PASS}\nPG_DATABASE=anchor\nPG_PORT=5432\nEOF\nmsg_ok \"Configured Application\"\n\nmsg_info \"Running Database Migrations\"\ncd /opt/anchor/server\nset -a && source /opt/anchor/.env && set +a\n$STD pnpm prisma migrate deploy\nmsg_ok \"Ran Database Migrations\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/anchor-server.service\n[Unit]\nDescription=Anchor API Server\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/anchor/server\nEnvironmentFile=/opt/anchor/.env\nExecStart=/usr/bin/node dist/src/main.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/anchor-web.service\n[Unit]\nDescription=Anchor Web Interface\nAfter=network.target anchor-server.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/anchor/web/.next/standalone\nEnvironmentFile=/opt/anchor/.env\nEnvironment=PORT=3000 HOSTNAME=0.0.0.0 NODE_ENV=production\nExecStart=/usr/bin/node server.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now anchor-server anchor-web\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/anytype-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://anytype.io\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_mongodb\n\nmsg_info \"Configuring MongoDB Replica Set\"\ncat <<EOF >>/etc/mongod.conf\n\nreplication:\n  replSetName: \"rs0\"\nEOF\nsystemctl restart mongod\nfor i in $(seq 1 30); do\n  if mongosh --quiet --eval \"db.adminCommand('ping')\" &>/dev/null; then\n    break\n  fi\n  sleep 2\ndone\n$STD mongosh --eval 'rs.initiate({_id: \"rs0\", members: [{_id: 0, host: \"127.0.0.1:27017\"}]})'\nmsg_ok \"Configured MongoDB Replica Set\"\n\nmsg_info \"Installing Redis Stack\"\nsetup_deb822_repo \\\n  \"redis-stack\" \\\n  \"https://packages.redis.io/gpg\" \\\n  \"https://packages.redis.io/deb\" \\\n  \"jammy\" \\\n  \"main\"\n$STD apt install -y redis-stack-server\nsystemctl enable -q --now redis-stack-server\nmsg_ok \"Installed Redis Stack\"\n\nfetch_and_deploy_gh_release \"anytype\" \"grishy/any-sync-bundle\" \"prebuild\" \"latest\" \"/opt/anytype\" \"any-sync-bundle_*_linux_amd64.tar.gz\"\nchmod +x /opt/anytype/any-sync-bundle\n\nmsg_info \"Configuring Anytype\"\nmkdir -p /opt/anytype/data/storage\ncat <<EOF >/opt/anytype/.env\nANY_SYNC_BUNDLE_CONFIG=/opt/anytype/data/bundle-config.yml\nANY_SYNC_BUNDLE_CLIENT_CONFIG=/opt/anytype/data/client-config.yml\nANY_SYNC_BUNDLE_INIT_STORAGE=/opt/anytype/data/storage/\nANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS=${LOCAL_IP}\nANY_SYNC_BUNDLE_INIT_MONGO_URI=mongodb://127.0.0.1:27017/\nANY_SYNC_BUNDLE_INIT_REDIS_URI=redis://127.0.0.1:6379/\nANY_SYNC_BUNDLE_LOG_LEVEL=info\nEOF\nmsg_ok \"Configured Anytype\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/anytype.service\n[Unit]\nDescription=Anytype Sync Server (any-sync-bundle)\nAfter=network-online.target mongod.service redis-stack-server.service\nWants=network-online.target\nRequires=mongod.service redis-stack-server.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/anytype\nEnvironmentFile=/opt/anytype/.env\nExecStart=/opt/anytype/any-sync-bundle start-bundle\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now anytype\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apache-cassandra-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cassandra.apache.org/_/index.html\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"11\" setup_java\n\nmsg_info \"Installing Apache Cassandra\"\nsetup_deb822_repo \\\n  \"cassandra\" \\\n  \"https://downloads.apache.org/cassandra/KEYS\" \\\n  \"https://debian.cassandra.apache.org\" \\\n  \"41x\" \\\n  \"main\"\n$STD apt install -y cassandra cassandra-tools\nsed -i -e 's/^rpc_address: localhost/#rpc_address: localhost/g' -e 's/^# rpc_interface: eth1/rpc_interface: eth0/g' /etc/cassandra/cassandra.yaml\nmsg_ok \"Installed Apache Cassandra\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apache-couchdb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://couchdb.apache.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Apache CouchDB\"\nERLANG_COOKIE=$(openssl rand -base64 32)\nADMIN_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\ndebconf-set-selections <<<\"couchdb couchdb/cookie string $ERLANG_COOKIE\"\ndebconf-set-selections <<<\"couchdb couchdb/mode select standalone\"\ndebconf-set-selections <<<\"couchdb couchdb/bindaddress string 0.0.0.0\"\ndebconf-set-selections <<<\"couchdb couchdb/adminpass password $ADMIN_PASS\"\ndebconf-set-selections <<<\"couchdb couchdb/adminpass_again password $ADMIN_PASS\"\nsetup_deb822_repo \\\n  \"couchdb\" \\\n  \"https://couchdb.apache.org/repo/keys.asc\" \\\n  \"https://apache.jfrog.io/artifactory/couchdb-deb\" \\\n  \"$(get_os_info codename)\" \\\n  \"main\"\n$STD apt install -y couchdb\n{\n  echo \"CouchDB Credentials\"\n  echo \"CouchDB Erlang Cookie: $ERLANG_COOKIE\"\n  echo \"CouchDB Admin Password: $ADMIN_PASS\"\n} >>~/couchdb.creds\nmsg_ok \"Installed Apache CouchDB\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apache-guacamole-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://guacamole.apache.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  libcairo2-dev \\\n  libjpeg62-turbo-dev \\\n  libpng-dev \\\n  libtool-bin \\\n  uuid-dev \\\n  libvncserver-dev \\\n  freerdp3-dev \\\n  libssh2-1-dev \\\n  libtelnet-dev \\\n  libwebsockets-dev \\\n  libpulse-dev \\\n  libvorbis-dev \\\n  libwebp-dev \\\n  libssl-dev \\\n  libpango1.0-dev \\\n  libswscale-dev \\\n  libavcodec-dev \\\n  libavutil-dev \\\n  libavformat-dev\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"17\" setup_java\nsetup_mariadb\nMARIADB_DB_NAME=\"guacamole_db\" MARIADB_DB_USER=\"guacamole_user\" setup_mariadb_db\n\nmsg_info \"Setup Apache Tomcat\"\nTOMCAT_VERSION=$(curl -fsSL https://dlcdn.apache.org/tomcat/tomcat-9/ | grep -oP '(?<=href=\")v[^\"/]+(?=/\")' | sed 's/^v//' | sort -V | tail -n1)\nmkdir -p /opt/apache-guacamole/{tomcat9,server}\ncurl -fsSL \"https://dlcdn.apache.org/tomcat/tomcat-9/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz\" | tar -xz -C /opt/apache-guacamole/tomcat9 --strip-components=1\nuseradd -r -d /opt/apache-guacamole/tomcat9 -s /bin/false tomcat\nchown -R tomcat: /opt/apache-guacamole/tomcat9\nchmod -R g+r /opt/apache-guacamole/tomcat9/conf\nchmod g+x /opt/apache-guacamole/tomcat9/conf\necho \"${TOMCAT_VERSION}\" >~/.guacamole_tomcat\nmsg_ok \"Setup Apache Tomcat ${TOMCAT_VERSION}\"\n\nmsg_info \"Setup Apache Guacamole\"\nmkdir -p /etc/guacamole/{extensions,lib}\nGUAC_SERVER_VERSION=$(curl -fsSL https://api.github.com/repos/apache/guacamole-server/tags | jq -r '.[].name' | grep -v -- '-RC' | head -n 1)\nGUAC_CLIENT_VERSION=$(curl -fsSL https://api.github.com/repos/apache/guacamole-client/tags | jq -r '.[].name' | grep -v -- '-RC' | head -n 1)\nMYSQL_CONNECTOR_VERSION=$(curl -fsSL \"https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/maven-metadata.xml\" | grep -oP '<latest>\\K[^<]+')\ncurl -fsSL \"https://api.github.com/repos/apache/guacamole-server/tarball/refs/tags/${GUAC_SERVER_VERSION}\" | tar -xz --strip-components=1 -C /opt/apache-guacamole/server\ncd /opt/apache-guacamole/server\nexport CPPFLAGS=\"-Wno-error=deprecated-declarations\"\n$STD autoreconf -fi\n$STD ./configure --with-init-dir=/etc/init.d --enable-allow-freerdp-snapshots\n$STD make\n$STD make install\n$STD ldconfig\necho \"${GUAC_SERVER_VERSION}\" >~/.guacamole_server\ncurl -fsSL \"https://downloads.apache.org/guacamole/${GUAC_CLIENT_VERSION}/binary/guacamole-${GUAC_CLIENT_VERSION}.war\" -o \"/opt/apache-guacamole/tomcat9/webapps/guacamole.war\"\necho \"${GUAC_CLIENT_VERSION}\" >~/.guacamole_client\ncurl -fsSL \"https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/${MYSQL_CONNECTOR_VERSION}/mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar\" -o \"/etc/guacamole/lib/mysql-connector-j.jar\"\necho \"${MYSQL_CONNECTOR_VERSION}\" >~/.guacamole_mysql_connector\ncd /root\ncurl -fsSL \"https://downloads.apache.org/guacamole/${GUAC_SERVER_VERSION}/binary/guacamole-auth-jdbc-${GUAC_SERVER_VERSION}.tar.gz\" -o \"/root/guacamole-auth-jdbc-${GUAC_SERVER_VERSION}.tar.gz\"\n$STD tar -xf ~/guacamole-auth-jdbc-\"$GUAC_SERVER_VERSION\".tar.gz\nmv ~/guacamole-auth-jdbc-\"$GUAC_SERVER_VERSION\"/mysql/guacamole-auth-jdbc-mysql-\"$GUAC_SERVER_VERSION\".jar /etc/guacamole/extensions/\necho \"${GUAC_SERVER_VERSION}\" >~/.guacamole_auth_jdbc\nmsg_ok \"Setup Apache Guacamole\"\n\nmsg_info \"Importing Database Schema\"\ncd ~/guacamole-auth-jdbc-\"${GUAC_SERVER_VERSION}\"/mysql/schema\ncat *.sql | mariadb -u root ${MARIADB_DB_NAME}\n{\n  echo \"mysql-hostname: 127.0.0.1\"\n  echo \"mysql-port: 3306\"\n  echo \"mysql-database: $MARIADB_DB_NAME\"\n  echo \"mysql-username: $MARIADB_DB_USER\"\n  echo \"mysql-password: $MARIADB_DB_PASS\"\n} >>/etc/guacamole/guacamole.properties\nrm -rf ~/guacamole-auth-jdbc-\"$GUAC_SERVER_VERSION\"{,.tar.gz}\nmsg_ok \"Imported Database Schema\"\n\nmsg_info \"Setup Service\"\ncat <<EOF >/etc/guacamole/guacd.conf\n[server]\nbind_host = 127.0.0.1\nbind_port = 4822\nEOF\nJAVA_HOME=$(update-alternatives --query javadoc | grep Value: | head -n1 | sed 's/Value: //' | sed 's@bin/javadoc$@@')\ncat <<EOF >/etc/systemd/system/tomcat.service\n[Unit]\nDescription=Apache Tomcat Web Application Container\nAfter=network.target\n[Service]\nType=forking\nEnvironment=\"JAVA_HOME=${JAVA_HOME}\"\nEnvironment=\"CATALINA_PID=/opt/apache-guacamole/tomcat9/temp/tomcat.pid\"\nEnvironment=\"CATALINA_HOME=/opt/apache-guacamole/tomcat9/\"\nEnvironment=\"CATALINA_BASE=/opt/apache-guacamole/tomcat9/\"\nEnvironment=\"CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC\"\nEnvironment=\"JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom\"\nExecStart=/opt/apache-guacamole/tomcat9/bin/startup.sh\nExecStop=/opt/apache-guacamole/tomcat9/bin/shutdown.sh\nUser=tomcat\nGroup=tomcat\nUMask=0007\nRestartSec=10\nRestart=always\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/guacd.service\n[Unit]\nDescription=Guacamole Proxy Daemon (guacd)\nAfter=mysql.service tomcat.service\nRequires=mysql.service tomcat.service\n[Service]\nType=forking\nExecStart=/etc/init.d/guacd start\nExecStop=/etc/init.d/guacd stop\nExecReload=/etc/init.d/guacd restart\nPIDFile=/var/run/guacd.pid\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mysql tomcat guacd\nmsg_ok \"Setup Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apache-tika-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/apache/tika/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  software-properties-common \\\n  gdal-bin \\\n  tesseract-ocr \\\n  tesseract-ocr-eng \\\n  tesseract-ocr-ita \\\n  tesseract-ocr-fra \\\n  tesseract-ocr-spa \\\n  tesseract-ocr-deu\n\n$STD echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections\necho \"deb http://deb.debian.org/debian bookworm contrib non-free\" > /etc/apt/sources.list.d/contrib.list\n$STD apt-get update\n$STD apt-get install -y \\\n  xfonts-utils \\\n  fonts-freefont-ttf \\\n  fonts-liberation \\\n  ttf-mscorefonts-installer \\\n  cabextract\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup OpenJDK\"\n$STD apt-get install -y \\\n  openjdk-17-jre-headless\nmsg_ok \"Setup OpenJDK\"\n\nmsg_info \"Installing Apache Tika\"\nmkdir -p /opt/apache-tika\ncd /opt/apache-tika\nRELEASE=\"$(curl -fsSL https://dlcdn.apache.org/tika/ | grep -oP '(?<=href=\")[0-9]+\\.[0-9]+\\.[0-9]+(?=/\")' | sort -V | tail -n1)\"\ncurl -fsSL \"https://dlcdn.apache.org/tika/${RELEASE}/tika-server-standard-${RELEASE}.jar\" -o tika-server-standard-${RELEASE}.jar\nmv tika-server-standard-${RELEASE}.jar tika-server-standard.jar\necho \"${RELEASE}\" >/opt/${APPLICATION}_version.txt\nmsg_ok \"Installed Apache Tika\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/apache-tika.service\n[Unit]\nDescription=Apache Tika\nDocumentation=https://tika.apache.org/\nAfter=syslog.target network.target\n\n[Service]\nUser=root\nRestart=always\nType=simple\nExecStart=java -jar /opt/apache-tika/tika-server-standard.jar --host 0.0.0.0 --port 9998\nExecReload=/bin/kill -HUP \\$MAINPID\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now apache-tika\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apache-tomcat-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tomcat.apache.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nread -r -p \"${TAB3}Which Tomcat version would you like to install? (9, 10.1, 11): \" version\ncase $version in\n9)\n  TOMCAT_VERSION=\"9\"\n  echo \"Which LTS Java version would you like to use? (8, 11, 17, 21): \"\n  read -r jdk_version\n  case $jdk_version in\n  8 | 11 | 17 | 21)\n    JAVA_VERSION=\"$jdk_version\" setup_java\n    ;;\n  *)\n    msg_error \"Invalid JDK version selected. Please enter 8, 11, 17 or 21.\"\n    exit 64\n    ;;\n  esac\n  ;;\n10 | 10.1)\n  TOMCAT_VERSION=\"10\"\n  echo \"Which LTS Java version would you like to use? (11, 17, 21): \"\n  read -r jdk_version\n  case $jdk_version in\n  11 | 17 | 21)\n    JAVA_VERSION=\"$jdk_version\" setup_java\n    ;;\n  *)\n    msg_error \"Invalid JDK version selected. Please enter 11, 17 or 21.\"\n    exit 64\n    ;;\n  esac\n  ;;\n11)\n  TOMCAT_VERSION=\"11\"\n  echo \"Which LTS Java version would you like to use? (17, 21): \"\n  read -r jdk_version\n  case $jdk_version in\n  17 | 21)\n    JAVA_VERSION=\"$jdk_version\" setup_java\n    ;;\n  *)\n    msg_error \"Invalid JDK version selected. Please enter 17 or 21.\"\n    exit 64\n    ;;\n  esac\n  ;;\n*)\n  msg_error \"Invalid Tomcat version selected. Please enter 9, 10.1 or 11.\"\n  exit 64\n  ;;\nesac\n\nmsg_info \"Installing Tomcat $TOMCAT_VERSION\"\nLATEST_VERSION=$(curl -fsSL \"https://dlcdn.apache.org/tomcat/tomcat-$TOMCAT_VERSION/\" | grep -oP 'v[0-9]+\\.[0-9]+\\.[0-9]+(-M[0-9]+)?/' | sort -V | tail -n 1 | sed 's/\\/$//; s/v//')\nTOMCAT_URL=\"https://dlcdn.apache.org/tomcat/tomcat-$TOMCAT_VERSION/v$LATEST_VERSION/bin/apache-tomcat-$LATEST_VERSION.tar.gz\"\ncurl -fsSL \"$TOMCAT_URL\" -o \"/tmp/tomcat.tar.gz\"\nmkdir -p /opt/tomcat-$TOMCAT_VERSION\ntar --strip-components=1 -xzf /tmp/tomcat.tar.gz -C /opt/tomcat-$TOMCAT_VERSION\nchown -R root:root /opt/tomcat-$TOMCAT_VERSION\nrm -f /tmp/tomcat.tar.gz\ncat <<EOF >/etc/systemd/system/tomcat.service\n[Unit]\nDescription=Apache Tomcat Web Application Container\nAfter=network.target\n\n[Service]\nType=forking\nUser=$(whoami)\nGroup=$(whoami)\nEnvironment=JAVA_HOME=/usr/lib/jvm/temurin-${jdk_version}-jdk-arm64\nEnvironment=CATALINA_HOME=/opt/tomcat-$TOMCAT_VERSION\nEnvironment=CATALINA_BASE=/opt/tomcat-$TOMCAT_VERSION\nEnvironment=CATALINA_PID=/opt/tomcat-$TOMCAT_VERSION/temp/tomcat.pid\nExecStart=/opt/tomcat-$TOMCAT_VERSION/bin/catalina.sh start\nExecStop=/opt/tomcat-$TOMCAT_VERSION/bin/catalina.sh stop\nPIDFile=/opt/tomcat-$TOMCAT_VERSION/temp/tomcat.pid\nSuccessExitStatus=143\nRestart=on-abnormal\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tomcat\nmsg_ok \"Tomcat $LATEST_VERSION installed and started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apprise-api-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: SystemIdleProcess\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/caronc/apprise-api\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  git\nmsg_ok \"Installed Dependencies\" \n\nPYTHON_VERSION=\"3.12\" setup_uv\nfetch_and_deploy_gh_release \"apprise\" \"caronc/apprise-api\" \"tarball\"\n\nmsg_info \"Setup Apprise-API\"\ncd /opt/apprise\ncp ./requirements.txt /etc/requirements.txt\n$STD uv pip install -r requirements.txt gunicorn supervisor --system\ncp -fr apprise_api/static /usr/share/nginx/html/s/\nmv apprise_api/ webapp\ntouch /etc/nginx/server-override.conf\ntouch /etc/nginx/location-override.conf\nmkdir -p /config/store /attach /plugin /tmp/apprise /opt/apprise/logs\nchmod 1777 /tmp/apprise && chmod 777 /config /config/store /attach /plugin /opt/apprise/logs\nsed -i \\\n  -e '/[[]program:nginx]/,/^[[]/ s|stdout_logfile=/dev/stdout|stdout_logfile=/opt/apprise/logs/nginx.log|' \\\n  -e '/[[]program:nginx]/,/^[[]/ s|stderr_logfile=/dev/stderr|stderr_logfile=/opt/apprise/logs/nginx_error.log|' \\\n  -e '/[[]program:gunicorn]/,/^[[]/ s|stdout_logfile=/dev/stdout|stdout_logfile=/opt/apprise/logs/gunicorn.log|' \\\n  -e '/[[]program:gunicorn]/,/^[[]/ s|stderr_logfile=/dev/stderr|stderr_logfile=/opt/apprise/logs/gunicorn_error.log|' \\\n  -e '/[[]supervisord]/,/^[[]/ s|logfile=/dev/null|logfile=/opt/apprise/logs/supervisor.log|' \\\n  -e 's|_maxbytes=0|_maxbytes=10485760|g' \\\n  /opt/apprise/webapp/etc/supervisord.conf\nmsg_ok \"Setup Apprise-API\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/apprise-api.service\n[Unit]\nDescription=Apprise-API Service\nAfter=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/apprise\nExecStart=/opt/apprise/webapp/supervisord-startup\nRestart=always\nRestartSec=30\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now apprise-api\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/apt-cacher-ng-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wiki.debian.org/AptCacherNg\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Apt-Cacher NG\"\nDEBIAN_FRONTEND=noninteractive $STD apt -o Dpkg::Options::=\"--force-confold\" install -y apt-cacher-ng avahi-daemon\nsed -i 's/# PassThroughPattern: .* # this would allow CONNECT to everything/PassThroughPattern: .*/' /etc/apt-cacher-ng/acng.conf\ncat <<EOF >/etc/apt/apt.conf.d/00aptproxy.conf\nAcquire::http::Proxy \"http://localhost:3142\";\nEOF\nsystemctl enable -q --now apt-cacher-ng\nmsg_ok \"Installed Apt-Cacher NG\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/archivebox-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://archivebox.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  git \\\n  expect \\\n  libssl-dev \\\n  libldap2-dev \\\n  libsasl2-dev \\\n  procps \\\n  dnsutils \\\n  ripgrep \\\n  chromium\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Python Dependencies\"\n$STD apt-get install -y \\\n  python3-ldap \\\n  python3-msgpack \\\n  python3-regex\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Installed Python Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"@postlight/parser@latest,single-file-cli@latest\" setup_nodejs\nPYTHON_VERSION=\"3.13\" setup_uv\n\nmsg_info \"Installing Playwright\"\n$STD uv pip install playwright --system\n$STD playwright install-deps chromium\nmsg_ok \"Installed Playwright\"\n\nmsg_info \"Installing ArchiveBox\"\nmkdir -p /opt/archivebox/{data,.npm,.cache,.local}\n$STD adduser --system --shell /bin/bash --gecos 'Archive Box User' --group --disabled-password --home /home/archivebox archivebox\nchown -R archivebox:archivebox /opt/archivebox/{data,.npm,.cache,.local}\nchmod -R 755 /opt/archivebox/data\n$STD uv pip install archivebox --system\ncd /opt/archivebox/data\nexpect <<EOF\nset timeout -1\nlog_user 0\n\nspawn sudo -u archivebox playwright install chromium\nspawn sudo -u archivebox archivebox setup\n\nexpect \"Username\"\nsend \"\\r\"\n\nexpect \"Email address\"\nsend \"\\r\"\n\nexpect \"Password\"\nsend \"community-scripts.org\\r\"\n\nexpect \"Password (again)\"\nsend \"community-scripts.org\\r\"\n\nexpect eof\nEOF\nmsg_ok \"Installed ArchiveBox\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/archivebox.service\n[Unit]\nDescription=ArchiveBox Server\nAfter=network.target\n\n[Service]\nUser=archivebox\nWorkingDirectory=/opt/archivebox/data\nExecStart=/usr/local/bin/archivebox server 0.0.0.0:8000\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now archivebox\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/argus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://release-argus.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"Argus\" \"release-argus/Argus\" \"singlefile\" \"latest\" \"/opt/argus\" \"Argus*linux-arm64\"\n\nmsg_info \"Setup Argus Config\"\ncat <<EOF >/opt/argus/config.yml\nsettings:\n  log:\n    level: INFO\n    timestamps: false\n  data:\n    database_file: data/argus.db\n  web:\n    listen_host: 0.0.0.0\n    listen_port: 8080\n    route_prefix: /\n\ndefaults:\n  service:\n    options:\n      interval: 30m\n      semantic_versioning: true\n    latest_version:\n      allow_invalid_certs: false\n      use_prerelease: false\n    dashboard:\n      auto_approve: true\n  webhook:\n    desired_status_code: 201\n\nservice:\n  release-argus/argus:\n    latest_version:\n      type: github\n      url: release-argus/argus\n    dashboard:\n      icon: https://raw.githubusercontent.com/release-argus/Argus/master/web/ui/react-app/public/favicon.svg\n      icon_link_to: https://release-argus.io\n      web_url: https://github.com/release-argus/Argus/blob/master/CHANGELOG.md\n\n  community-scripts/ProxmoxVE:\n    latest_version:\n      type: github\n      url: community-scripts/ProxmoxVE\n      use_prerelease: false\n    dashboard:\n      icon: https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/images/logo.png\n      icon_link_to: https://community-scripts.org/\n      web_url: https://github.com/community-scripts/ProxmoxVE/releases\nEOF\nmsg_ok \"Setup Config\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/argus.service\n[Unit]\nDescription=Argus\nAfter=network.target\n[Service]\nType=simple\nWorkingDirectory=/opt/argus\nExecStart=/opt/argus/Argus\nRestart=on-failure\nRestartSec=5\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now argus\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/aria2-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://aria2.github.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Aria2\"\n$STD apt install -y aria2\nmsg_ok \"Installed Aria2\"\n\nread -r -p \"${TAB3}Would you like to add AriaNG? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Dependencies\"\n  $STD apt-get install -y nginx \n  $STD apt-get install -y unzip \n  systemctl disable -q --now nginx\n  rm /etc/nginx/sites-enabled/*\n  msg_ok \"Installed Dependencies\"\n\n  fetch_and_deploy_gh_release \"ariang\" \"mayswind/ariang\" \"prebuild\" \"latest\" \"/var/www\" \"AriaNg-*-AllInOne.zip\"\n\n  msg_info \"Configure nginx\"\n  cat <<EOF >/etc/nginx/conf.d/ariang.conf\nserver {\n    listen 6880 default_server;\n    listen [::]:6880 default_server;\n\n    server_name _;\n\n    root /var/www;\n    index index.html;\n\n    location / {\n        try_files \\$uri \\$uri/ =404;\n    }\n}\nEOF\n  cp /lib/systemd/system/nginx.service /lib/systemd/system/ariang.service\n  systemctl enable -q --now ariang\n  msg_ok \"Configured nginx\"\nfi\n\nmsg_info \"Creating Service\"\nmkdir -p /root/downloads\nrpc_secret=$(openssl rand -base64 8)\necho \"rpc-secret: $rpc_secret\" >>~/rpc.secret\ncat <<EOF >/root/aria2.daemon\ndir=/root/downloads\nfile-allocation=falloc\nmax-connection-per-server=4\nmax-concurrent-downloads=2\nmax-overall-download-limit=0\nmin-split-size=25M\nrpc-allow-origin-all=true\nrpc-secret=${rpc_secret}\ninput-file=/var/tmp/aria2c.session\nsave-session=/var/tmp/aria2c.session\nEOF\n\ncat <<EOF >/etc/systemd/system/aria2.service\n[Unit]\nDescription=Aria2c download manager\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nExecStartPre=/usr/bin/env touch /var/tmp/aria2c.session\nExecStart=/usr/bin/aria2c --console-log-level=warn --enable-rpc --rpc-listen-all --conf-path=/root/aria2.daemon\nTimeoutStopSec=20\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now aria2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/asterisk-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://asterisk.org\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libsrtp2-dev \\\n  build-essential \\\n  libedit-dev \\\n  uuid-dev \\\n  libjansson-dev \\\n  libxml2-dev \\\n  libsqlite3-dev\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Fetching Asterisk Versions\"\nASTERISK_LIST=$(curl -fsSL https://downloads.asterisk.org/pub/telephony/asterisk/ \\\n  | grep -oE 'asterisk-[0-9]+\\.[0-9]+\\.[0-9]+\\.tar\\.gz' \\\n  | sed 's/asterisk-//' \\\n  | sed 's/\\.tar\\.gz//' \\\n  | sort -V)\n# LTS: Major 20, 22, 24, 26\nLTS_VERSION=$(echo \"$ASTERISK_LIST\" | grep -E '^2(0|2|4|6)\\.' | tail -n1 || true)\n# Standard: Major 21, 23, 25, 27\nSTD_VERSION=$(echo \"$ASTERISK_LIST\" | grep -E '^2(1|3|5|7)\\.' | tail -n1 || true)\nCERT_VERSION=$(curl -fsSL https://downloads.asterisk.org/pub/telephony/certified-asterisk/ \\\n  | grep -oE 'asterisk-certified-[0-9]+\\.[0-9]+-cert[0-9]+\\.tar\\.gz' \\\n  | sed -E 's/asterisk-certified-//' \\\n  | sed -E 's/\\.tar\\.gz//' \\\n  | sort -V | tail -n1 || true)\nmsg_ok \"Fetched Versions\"\n\ncat <<EOF\nChoose Asterisk version to install:\n1) Latest Standard ($STD_VERSION)\n2) Latest LTS ($LTS_VERSION)\n3) Latest Certified ($CERT_VERSION)\nEOF\nread -rp \"Enter choice [1-3]: \" ASTERISK_CHOICE\n\nCERTIFIED=0\ncase \"$ASTERISK_CHOICE\" in\n  2)\n    ASTERISK_VERSION=\"$LTS_VERSION\"\n    ;;\n  3)\n    ASTERISK_VERSION=\"$CERT_VERSION\"\n    CERTIFIED=1\n    ;;\n  *)\n    ASTERISK_VERSION=\"$STD_VERSION\"\n    ;;\nesac\n\nif [[ \"$CERTIFIED\" == \"1\" ]]; then\n  RELEASE=\"asterisk-certified-${ASTERISK_VERSION}.tar.gz\"\n  DOWNLOAD_URL=\"https://downloads.asterisk.org/pub/telephony/certified-asterisk/$RELEASE\"\nelse\n  RELEASE=\"asterisk-${ASTERISK_VERSION}.tar.gz\"\n  DOWNLOAD_URL=\"https://downloads.asterisk.org/pub/telephony/asterisk/$RELEASE\"\nfi\n\nmsg_info \"Downloading Asterisk ($RELEASE)\"\ntemp_file=$(mktemp)\ncurl -fsSL \"$DOWNLOAD_URL\" -o \"$temp_file\"\nmkdir -p /opt/asterisk\ntar zxf \"$temp_file\" --strip-components=1 -C /opt/asterisk\ncd /opt/asterisk\nrm -f \"$temp_file\"\nmsg_ok \"Downloaded Asterisk ($RELEASE)\"\n\nmsg_info \"Installing Asterisk\"\n$STD ./contrib/scripts/install_prereq install\n$STD ./configure\n$STD make -j$(nproc)\n$STD make install\n$STD make config\n$STD make install-logrotate\n$STD make samples\nmkdir -p /etc/radiusclient-ng/\nln /etc/radcli/radiusclient.conf /etc/radiusclient-ng/radiusclient.conf\nsystemctl enable -q --now asterisk\nmsg_ok \"Installed Asterisk\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/audiobookshelf-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.audiobookshelf.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y gnupg\n$STD apt-get install -y ffmpeg\n$STD apt-get install -y git\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Node.js\"\n$STD apt-get update\n$STD apt-get install -y nodejs\n$STD apt-get install -y npm\nmsg_ok \"Installed Node.js\"\n\nmsg_info \"Installing audiobookshelf (Patience)\"\n$STD git clone https://github.com/advplyr/audiobookshelf /opt/audiobookshelf\ncd /opt/audiobookshelf\nmv ./.devcontainer/dev.js ./dev.js\nsed -i 's/Port: 3333/Port: 13378/' ./dev.js\nsed -i \"s|ConfigPath: Path.resolve('config'),|ConfigPath: '/usr/share/audiobookshelf/config',|\" ./dev.js\nsed -i \"s|MetadataPath: Path.resolve('metadata'),|MetadataPath: '/usr/share/audiobookshelf/metadata',|\" ./dev.js\n$STD npm ci \ncd client\n$STD npm ci\n$STD npm run generate\nmkdir -p /usr/share/audiobookshelf/config\nmkdir -p /usr/share/audiobookshelf/metadata\nmsg_ok \"Installed audiobookshelf\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/audiobookshelf_client.service\n[Unit]\nDescription=Audiobookshelf Client Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/audiobookshelf/client\nExecStart=/usr/bin/npm run dev\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/audiobookshelf_server.service\n[Unit]\nDescription=Audiobookshelf Server Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/audiobookshelf/\nExecStart=/usr/bin/npm run dev\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now audiobookshelf_server.service\nsystemctl enable -q --now audiobookshelf_client.service\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/authelia-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.authelia.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"authelia\" \"authelia/authelia\" \"binary\"\n\nMAX_ATTEMPTS=3\nattempt=0\nwhile true; do\n  attempt=$((attempt + 1))\n  read -rp \"${TAB3}Enter your domain or IP (ex. example.com or 192.168.1.100): \" DOMAIN\n  if [[ -z \"$DOMAIN\" ]]; then\n    if ((attempt >= MAX_ATTEMPTS)); then\n      DOMAIN=\"${LOCAL_IP:-localhost}\"\n      msg_warn \"Using fallback: $DOMAIN\"\n      break\n    fi\n    msg_warn \"Domain cannot be empty! (Attempt $attempt/$MAX_ATTEMPTS)\"\n  elif [[ \"$DOMAIN\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n    valid_ip=true\n    IFS='.' read -ra octets <<<\"$DOMAIN\"\n    for octet in \"${octets[@]}\"; do\n      if ((octet > 255)); then\n        valid_ip=false\n        break\n      fi\n    done\n    if $valid_ip; then\n      break\n    else\n      msg_warn \"Invalid IP address!\"\n    fi\n  elif [[ \"$DOMAIN\" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\\.[a-zA-Z]{2,}$ ]]; then\n    break\n  else\n    msg_warn \"Invalid domain format!\"\n  fi\ndone\nmsg_info \"Setting Authelia up\"\ntouch /etc/authelia/emails.txt\nJWT_SECRET=$(openssl rand -hex 64)\nSESSION_SECRET=$(openssl rand -hex 64)\nSTORAGE_KEY=$(openssl rand -hex 64)\n\nif [[ \"$DOMAIN\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n  AUTHELIA_URL=\"https://${DOMAIN}:9091\"\nelse\n  AUTHELIA_URL=\"https://auth.${DOMAIN}\"\nfi\necho \"$AUTHELIA_URL\" >/etc/authelia/.authelia_url\n\ncat <<EOF >/etc/authelia/users.yml\nusers:\n  authelia:\n    disabled: false\n    displayname: \"Authelia Admin\"\n    password: \"\\$argon2id\\$v=19\\$m=65536,t=3,p=4\\$ZBopMzXrzhHXPEZxRDVT2w\\$SxWm96DwhOsZyn34DLocwQEIb4kCDsk632PuiMdZnig\"\n    groups: []\nEOF\ncat <<EOF >/etc/authelia/configuration.yml\nauthentication_backend:\n  file:\n    path: /etc/authelia/users.yml\naccess_control:\n  default_policy: one_factor\nsession:\n  secret: \"${SESSION_SECRET}\"\n  name: 'authelia_session'\n  same_site: 'lax'\n  inactivity: '5m'\n  expiration: '1h'\n  remember_me: '1M'\n  cookies:\n    - domain: \"${DOMAIN}\"\n      authelia_url: \"${AUTHELIA_URL}\"\nstorage:\n  encryption_key: \"${STORAGE_KEY}\"\n  local:\n    path: /etc/authelia/db.sqlite\nidentity_validation:\n  reset_password:\n    jwt_secret: \"${JWT_SECRET}\"\n    jwt_lifespan: '5 minutes'\n    jwt_algorithm: 'HS256'\nnotifier:\n  filesystem:\n    filename: /etc/authelia/emails.txt\nEOF\ntouch /etc/authelia/emails.txt\nchown -R authelia:authelia /etc/authelia\nsystemctl enable -q --now authelia\nmsg_ok \"Authelia Setup completed\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/autobrr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://autobrr.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"autobrr\" \"autobrr/autobrr\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"autobrr_*_linux_arm64.tar.gz\"\n\nmsg_info \"Configuring Autobrr\"\nmkdir -p /root/.config/autobrr\ncat <<EOF >>/root/.config/autobrr/config.toml\n# https://autobrr.com/configuration/autobrr\nhost = \"0.0.0.0\"\nport = 7474\nlogLevel = \"DEBUG\"\nsessionSecret = \"$(openssl rand -base64 24)\"\nEOF\nmsg_ok \"Configured Autobrr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/autobrr.service\n[Unit]\nDescription=autobrr service\nAfter=syslog.target network-online.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nExecStart=/usr/local/bin/autobrr --config=/root/.config/autobrr/\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q autobrr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/autocaliweb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://codeberg.org/gelbphoenix/autocaliweb\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y --no-install-recommends \\\n    python3-dev \\\n    sqlite3 \\\n    build-essential \\\n    libldap2-dev \\\n    libssl-dev \\\n    libsasl2-dev \\\n    imagemagick \\\n    ghostscript \\\n    libmagic1 \\\n    libxi6 \\\n    libxslt1.1 \\\n    libxtst6 \\\n    libxrandr2 \\\n    libxkbfile1 \\\n    libxcomposite1 \\\n    libopengl0 \\\n    libnss3 \\\n    libxkbcommon0 \\\n    libegl1 \\\n    libxdamage1 \\\n    libgl1 \\\n    libglx-mesa0 \\\n    xz-utils \\\n    xdg-utils \\\n    inotify-tools \\\n    binutils \\\n    unrar-free \\\n    zip\nmsg_ok \"Installed dependencies\"\n\nfetch_and_deploy_gh_release \"kepubify\" \"pgaskin/kepubify\" \"singlefile\" \"latest\" \"/usr/bin\" \"kepubify-linux-arm64\"\nKEPUB_VERSION=\"$(/usr/bin/kepubify --version | awk '{print $2}')\"\nfetch_and_deploy_gh_release \"calibre\" \"kovidgoyal/calibre\" \"prebuild\" \"latest\" \"/opt/calibre\" \"calibre-*-arm64.txz\"\n\nmsg_info \"Installing Calibre\"\n$STD /opt/calibre/calibre_postinstall\nCALIBRE_VERSION=$(cat ~/.calibre)\nmsg_ok \"Installed Calibre\"\n\nsetup_uv\n\nfetch_and_deploy_codeberg_release \"autocaliweb\" \"gelbphoenix/autocaliweb\" \"tarball\" \"latest\" \"/opt/autocaliweb\"\n\nmsg_info \"Configuring Autocaliweb\"\nINSTALL_DIR=\"/opt/autocaliweb\"\nCONFIG_DIR=\"/etc/autocaliweb\"\nCALIBRE_LIB_DIR=\"/opt/calibre-library\"\nINGEST_DIR=\"/opt/acw-book-ingest\"\nSERVICE_USER=\"acw\"\nSERVICE_GROUP=\"acw\"\nSCRIPTS_DIR=\"${INSTALL_DIR}/scripts\"\nexport VIRTUAL_ENV=\"${INSTALL_DIR}/venv\"\n\nmkdir -p \"$CONFIG_DIR\"/{.config/calibre/plugins,log_archive,.acw_conversion_tmp}\nmkdir -p \"$CONFIG_DIR\"/processed_books/{converted,imported,failed,fixed_originals}\nmkdir -p \"$INSTALL_DIR\"/{metadata_change_logs,metadata_temp}\nmkdir -p {\"$CALIBRE_LIB_DIR\",\"$INGEST_DIR\"}\necho \"$CALIBRE_VERSION\" >\"$INSTALL_DIR\"/CALIBRE_RELEASE\necho \"${KEPUB_VERSION#v}\" >\"$INSTALL_DIR\"/KEPUBIFY_RELEASE\nsed 's/^/v/' ~/.autocaliweb >\"$INSTALL_DIR\"/ACW_RELEASE\n\ncd \"$INSTALL_DIR\"\n$STD uv venv --clear \"$VIRTUAL_ENV\"\n$STD uv sync --all-extras --active\ncat <<EOF >./dirs.json\n{\n  \"ingest_folder\": \"$INGEST_DIR\",\n  \"calibre_library_dir\": \"$CALIBRE_LIB_DIR\",\n  \"tmp_conversion_dir\": \"$CONFIG_DIR/.acw_conversion_tmp\"\n}\nEOF\nuseradd -s /usr/sbin/nologin -d \"$CONFIG_DIR\" -M \"$SERVICE_USER\"\nln -sf \"$CONFIG_DIR\"/.config/calibre/plugins \"$CONFIG_DIR\"/calibre_plugins\ncat <<EOF >\"$INSTALL_DIR\"/.env\nACW_INSTALL_DIR=$INSTALL_DIR\nACW_CONFIG_DIR=$CONFIG_DIR\nACW_USER=$SERVICE_USER\nACW_GROUP=$SERVICE_GROUP\nLIBRARY_DIR=$CALIBRE_LIB_DIR\nEOF\nmsg_ok \"Configured Autocaliweb\"\n\nmsg_info \"Creating ACWSync Plugin for KOReader\"\ncd \"$INSTALL_DIR\"/koreader/plugins\nPLUGIN_DIGEST=\"$(find acwsync.koplugin -type f -name \"*.lua\" -o -name \"*.json\" | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)\"\necho \"Plugin files digest: $PLUGIN_DIGEST\" >acwsync.koplugin/${PLUGIN_DIGEST}.digest\necho \"Build date: $(date)\" >>acwsync.koplugin/${PLUGIN_DIGEST}.digest\necho \"Files included:\" >>acwsync.koplugin/${PLUGIN_DIGEST}.digest\n$STD zip -r koplugin.zip acwsync.koplugin/\ncp -r koplugin.zip \"$INSTALL_DIR\"/cps/static\nmsg_ok \"Created ACWSync Plugin\"\n\nmsg_info \"Initializing databases\"\nKEPUBIFY_PATH=$(command -v kepubify 2>/dev/null || echo \"/usr/bin/kepubify\")\nEBOOK_CONVERT_PATH=$(command -v ebook-convert 2>/dev/null || echo \"/usr/bin/ebook-convert\")\nCALIBRE_BIN_DIR=$(dirname \"$EBOOK_CONVERT_PATH\")\ncurl -fsSL https://codeberg.org/gelbphoenix/autocaliweb/raw/branch/main/library/metadata.db -o \"$CALIBRE_LIB_DIR\"/metadata.db\ncurl -fsSL https://codeberg.org/gelbphoenix/autocaliweb/raw/branch/main/library/app.db -o \"$CONFIG_DIR\"/app.db\nsqlite3 \"$CONFIG_DIR/app.db\" <<EOS\nUPDATE settings SET\n    config_kepubifypath='$KEPUBIFY_PATH',\n    config_converterpath='$EBOOK_CONVERT_PATH',\n    config_binariesdir='$CALIBRE_BIN_DIR',\n    config_calibre_dir='$CALIBRE_LIB_DIR',\n    config_logfile='$CONFIG_DIR/autocaliweb.log',\n    config_access_logfile='$CONFIG_DIR/access.log'\nWHERE 1=1;\nEOS\nmsg_ok \"Initialized databases\"\n\nmsg_info \"Creating scripts and service files\"\n\n# auto-ingest watcher\ncat <<EOF >\"$SCRIPTS_DIR\"/ingest_watcher.sh\n#!/bin/bash\n\nINSTALL_PATH=\"$INSTALL_DIR\"\nWATCH_FOLDER=\\$(grep -o '\"ingest_folder\": \"[^\"]*' \\${INSTALL_PATH}/dirs.json | grep -o '[^\"]*\\$')\necho \"[acw-ingest-service] Watching folder: \\$WATCH_FOLDER\"\n\n# Monitor the folder for new files\n/usr/bin/inotifywait -m -r --format=\"%e %w%f\" -e close_write -e moved_to \"\\$WATCH_FOLDER\" |\nwhile read -r events filepath ; do\n    echo \"[acw-ingest-service] New files detected - \\$filepath - Starting Ingest Processor...\"\n    # Use the Python interpreter from the virtual environment\n    \\${INSTALL_PATH}/venv/bin/python \\${INSTALL_PATH}/scripts/ingest_processor.py \"\\$filepath\"\ndone\nEOF\n\n# auto-zipper\ncat <<EOF >\"$SCRIPTS_DIR\"/auto_zipper_wrapper.sh\n#!/bin/bash\n\n# Source virtual environment\nsource ${INSTALL_DIR}/venv/bin/activate\n\nWAKEUP=\"23:59\"\n\nwhile true; do\n    # Replace expr with modern Bash arithmetic (safer and less prone to parsing issues)\n    # fix: expr: non-integer argument and sleep: missing operand\n    SECS=\\$(( \\$(date -d \"\\$WAKEUP\" +%s) - \\$(date -d \"now\" +%s) ))\n    if [[ \\$SECS -lt 0 ]]; then\n        SECS=\\$(( \\$(date -d \"tomorrow \\$WAKEUP\" +%s) - \\$(date -d \"now\" +%s) ))\n    fi\n    echo \"[acw-auto-zipper] Next run in \\$SECS seconds.\"\n    sleep \\$SECS &\n    wait \\$!\n\n    # Use virtual environment python\n    python ${SCRIPTS_DIR}/auto_zip.py\n\n    if [[ \\$? == 1 ]]; then\n    echo \"[acw-auto-zipper] Error occurred during script initialisation.\"\n    elif [[ \\$? == 2 ]]; then\n    echo \"[acw-auto-zipper] Error occurred while zipping today's files.\"\n    elif [[ \\$? == 3 ]]; then\n    echo \"[acw-auto-zipper] Error occurred while trying to remove zipped files.\"\n    fi\n\n    sleep 60\ndone\nEOF\n\n# metadata change detector\ncat <<EOF >\"$SCRIPTS_DIR\"/metadata_change_detector_wrapper.sh\n#!/bin/bash\n# metadata_change_detector_wrapper.sh - Wrapper for periodic metadata enforcement\n\n# Source virtual environment\nsource ${INSTALL_DIR}/venv/bin/activate\n\n# Configuration\nCHECK_INTERVAL=300  # Check every 5 minutes (300 seconds)\nMETADATA_LOGS_DIR=\"${INSTALL_DIR}/metadata_change_logs\"\n\necho \"[metadata-change-detector] Starting metadata change detector service...\"\necho \"[metadata-change-detector] Checking for changes every \\$CHECK_INTERVAL seconds\"\n\nwhile true; do\n    # Check if there are any log files to process\n    if [ -d \"\\$METADATA_LOGS_DIR\" ] && [ \"\\$(ls -A \\$METADATA_LOGS_DIR 2>/dev/null)\" ]; then\n        echo \"[metadata-change-detector] Found metadata change logs, processing...\"\n\n        # Process each log file\n        for log_file in \"\\$METADATA_LOGS_DIR\"/*.json; do\n            if [ -f \"\\$log_file\" ]; then\n                log_name=\\$(basename \"\\$log_file\")\n                echo \"[metadata-change-detector] Processing log: \\$log_name\"\n\n                # Call cover_enforcer.py with the log file\n                 ${INSTALL_DIR}/venv/bin/python  ${SCRIPTS_DIR}/cover_enforcer.py --log \"\\$log_name\"\n\n                if [ \\$? -eq 0 ]; then\n                    echo \"[metadata-change-detector] Successfully processed \\$log_name\"\n                else\n                    echo \"[metadata-change-detector] Error processing \\$log_name\"\n                fi\n            fi\n        done\n    else\n        echo \"[metadata-change-detector] No metadata changes detected\"\n    fi\n\n    echo \"[metadata-change-detector] Sleeping for \\$CHECK_INTERVAL seconds...\"\n    sleep \\$CHECK_INTERVAL\ndone\nEOF\nchmod +x \"$SCRIPTS_DIR\"/{ingest_watcher.sh,auto_zipper_wrapper.sh,metadata_change_detector_wrapper.sh}\nchown -R \"$SERVICE_USER\":\"$SERVICE_GROUP\" {\"$INSTALL_DIR\",\"$CONFIG_DIR\",\"$INGEST_DIR\",\"$CALIBRE_LIB_DIR\"}\n\ncat <<EOF >/etc/systemd/system/autocaliweb.service\n[Unit]\nDescription=Autocaliweb\nAfter=network.target\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=$SERVICE_USER\nGroup=$SERVICE_GROUP\nWorkingDirectory=$INSTALL_DIR\nEnvironment=PATH=$INSTALL_DIR/venv/bin:/usr/bin:/bin\nEnvironment=PYTHONPATH=$SCRIPTS_DIR:$INSTALL_DIR\nEnvironment=PYTHONDONTWRITEBYTECODE=1\nEnvironment=PYTHONUNBUFFERED=1\nEnvironment=CALIBRE_DBPATH=$CONFIG_DIR\nEnvironmentFile=$INSTALL_DIR/.env\nExecStart=$INSTALL_DIR/venv/bin/python $INSTALL_DIR/cps.py -p $CONFIG_DIR/app.db\n\nRestart=always\nRestartSec=10\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/acw-ingest-service.service\n[Unit]\nDescription=Autocaliweb Ingest Processor Service\nAfter=autocaliweb.service\nRequires=autocaliweb.service\n\n[Service]\nUser=${SERVICE_USER}\nGroup=${SERVICE_GROUP}\nWorkingDirectory=${INSTALL_DIR}\nEnvironment=CALIBRE_DBPATH=${CONFIG_DIR}\nEnvironment=HOME=${CONFIG_DIR}\nExecStart=/bin/bash ${SCRIPTS_DIR}/ingest_watcher.sh\nRestart=always\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/acw-auto-zipper.service\n[Unit]\nDescription=Autocaliweb Auto Zipper Service\nAfter=network.target\n\n[Service]\nUser=${SERVICE_USER}\nGroup=${SERVICE_GROUP}\nWorkingDirectory=${INSTALL_DIR}\nEnvironment=CALIBRE_DBPATH=${CONFIG_DIR}\nExecStart=${SCRIPTS_DIR}/auto_zipper_wrapper.sh\nRestart=always\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/metadata-change-detector.service\n[Unit]\nDescription=Autocaliweb Metadata Change Detector\nAfter=network.target\n\n[Service]\nUser=${SERVICE_USER}\nGroup=${SERVICE_GROUP}\nWorkingDirectory=${INSTALL_DIR}\nExecStart=/bin/bash ${SCRIPTS_DIR}/metadata_change_detector_wrapper.sh\nRestart=always\nStandardOutput=journal\nStandardError=journal\nEnvironment=CALIBRE_DBPATH=${CONFIG_DIR}\nEnvironment=HOME=${CONFIG_DIR}\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl -q enable --now autocaliweb acw-ingest-service acw-auto-zipper metadata-change-detector\nmsg_ok \"Created scripts and service files\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/babybuddy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/babybuddy/babybuddy\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  uwsgi \\\n  uwsgi-plugin-python3 \\\n  libopenjp2-7-dev \\\n  libpq-dev \\\n  nginx \\\n  python3\nmsg_ok \"Installed Dependencies\"\n\nsetup_uv\nfetch_and_deploy_gh_release \"babybuddy\" \"babybuddy/babybuddy\" \"tarball\"\n\nmsg_info \"Installing Babybuddy\"\nmkdir -p /opt/data\ncd /opt/babybuddy\n$STD uv venv --clear .venv\n$STD source .venv/bin/activate\n$STD uv pip install -r requirements.txt\ncp babybuddy/settings/production.example.py babybuddy/settings/production.py\nSECRET_KEY=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)\nALLOWED_HOSTS=$(hostname -I | tr ' ' ',' | sed 's/,$//')\",127.0.0.1,localhost\"\nsed -i \\\n  -e \"s/^SECRET_KEY = \\\"\\\"/SECRET_KEY = \\\"$SECRET_KEY\\\"/\" \\\n  -e \"s/^ALLOWED_HOSTS = \\[\\\"\\\"\\]/ALLOWED_HOSTS = \\[$(echo \\\"$ALLOWED_HOSTS\\\" | sed 's/,/\\\",\\\"/g')\\]/\" \\\n  babybuddy/settings/production.py\n\nexport DJANGO_SETTINGS_MODULE=babybuddy.settings.production\n$STD python manage.py migrate\nchown -R www-data:www-data /opt/data\nchmod 640 /opt/data/db.sqlite3\nchmod 750 /opt/data\nmsg_ok \"Installed Babybuddy\"\n\nmsg_info \"Configuring uWSGI\"\ncat <<EOF >/etc/uwsgi/apps-available/babybuddy.ini\n[uwsgi]\nplugins = python3\nproject = babybuddy\nbase_dir = /opt/babybuddy\nchdir = %(base_dir)\nvirtualenv = %(base_dir)/.venv\nmodule = %(project).wsgi:application\nenv = DJANGO_SETTINGS_MODULE=%(project).settings.production\nmaster = True\nvacuum = True\nsocket = /var/run/uwsgi/app/babybuddy/socket\nchmod-socket = 660\nuid = www-data\ngid = www-data\nEOF\nln -sf /etc/uwsgi/apps-available/babybuddy.ini /etc/uwsgi/apps-enabled/babybuddy.ini\nservice uwsgi restart\nmsg_ok \"Configured uWSGI\"\n\nmsg_info \"Configuring NGINX\"\ncat <<EOF >/etc/nginx/sites-available/babybuddy\nupstream babybuddy {\n    server unix:///var/run/uwsgi/app/babybuddy/socket;\n}\n\nserver {\n    listen 80;\n    server_name _;\n\n    location / {\n        uwsgi_pass babybuddy;\n        include uwsgi_params;\n    }\n\n    location /media {\n        alias /opt/data/media;\n    }\n}\nEOF\n\nln -sf /etc/nginx/sites-available/babybuddy /etc/nginx/sites-enabled/babybuddy\nrm /etc/nginx/sites-enabled/default\nsystemctl enable -q --now nginx\nservice nginx reload\nmsg_ok \"Configured NGINX\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/backrest-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: ksad (enirys31)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://garethgeorge.github.io/backrest/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"backrest\" \"garethgeorge/backrest\" \"prebuild\" \"latest\" \"/opt/backrest/bin\" \"backrest_Linux_arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/backrest/.env\nBACKREST_PORT=9898\nBACKREST_CONFIG=/opt/backrest/config/config.json\nBACKREST_DATA=/opt/backrest/data\nXDG_CACHE_HOME=/opt/backrest/cache\nEOF\n\ncat <<EOF >/etc/systemd/system/backrest.service\n[Unit]\nDescription=Backrest\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/opt/backrest/bin/backrest\nEnvironmentFile=/opt/backrest/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now backrest\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/baikal-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sabre.io/baikal/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\nPHP_APACHE=\"YES\" PHP_VERSION=\"8.3\" setup_php\nsetup_composer\nfetch_and_deploy_gh_release \"baikal\" \"sabre-io/Baikal\" \"tarball\"\nPG_DB_NAME=\"baikal_db\" PG_DB_USER=\"baikal_user\" PG_DB_PASS=\"$(openssl rand -base64 12)\" setup_postgresql_db\n\nmsg_info \"Configuring Baikal\"\ncd /opt/baikal\n$STD composer install\ncat <<EOF >/opt/baikal/config/baikal.yaml\ndatabase:\n    backend: pgsql\n    pgsql_host: localhost\n    pgsql_dbname: $PG_DB_NAME\n    pgsql_username: $PG_DB_USER\n    pgsql_password: $PG_DB_PASS\nEOF\nchown -R www-data:www-data /opt/baikal/\nchmod -R 755 /opt/baikal/\nmsg_ok \"Installed Baikal\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/baikal.conf\n<VirtualHost *:80>\n    ServerName baikal\n    DocumentRoot /opt/baikal/html\n\n    RewriteEngine on\n    RewriteRule /.well-known/carddav /dav.php [R=308,L]\n    RewriteRule /.well-known/caldav  /dav.php [R=308,L]\n    RewriteCond %{REQUEST_URI} ^/dav.php$ [NC]\n    RewriteRule ^(.*)$ /dav.php/ [R=301,L]\n        \n    <Directory /opt/baikal/html>\n        Options FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    <IfModule mod_expires.c>\n        ExpiresActive Off\n    </IfModule>\n\n    ErrorLog /var/log/apache2/baikal_error.log\n    CustomLog /var/log/apache2/baikal_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite baikal\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bambuddy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Adrian-RDA\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/maziggy/bambuddy\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y libglib2.0-0 ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"bambuddy\" \"maziggy/bambuddy\" \"tarball\" \"latest\" \"/opt/bambuddy\"\n\nmsg_info \"Setting up Python Environment\"\ncd /opt/bambuddy\n$STD uv venv\n$STD uv pip install -r requirements.txt\nmsg_ok \"Set up Python Environment\"\n\nmsg_info \"Building Frontend\"\ncd /opt/bambuddy/frontend\n$STD npm install\n$STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Configuring Bambuddy\"\nmkdir -p /opt/bambuddy/data /opt/bambuddy/logs\ncat <<EOF >/opt/bambuddy/.env\nDEBUG=false\nLOG_LEVEL=INFO\nLOG_TO_FILE=true\nEOF\nmsg_ok \"Configured Bambuddy\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/bambuddy.service\n[Unit]\nDescription=Bambuddy - Bambu Lab Print Management\nDocumentation=https://github.com/maziggy/bambuddy\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/bambuddy\nExecStart=/opt/bambuddy/.venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port 8000\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now bambuddy\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bar-assistant-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01 | CanbiZ\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/karlomikus/bar-assistant\n# Source: https://github.com/karlomikus/vue-salt-rim\n# Source: https://www.meilisearch.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  redis-server \\\n  nginx \\\n  lsb-release \\\n  libvips\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"pdo-sqlite\" setup_php\nsetup_composer\nNODE_VERSION=\"22\" setup_nodejs\nsetup_meilisearch\nfetch_and_deploy_gh_release \"bar-assistant\" \"karlomikus/bar-assistant\" \"tarball\" \"latest\" \"/opt/bar-assistant\"\nfetch_and_deploy_gh_release \"vue-salt-rim\" \"karlomikus/vue-salt-rim\" \"tarball\" \"latest\" \"/opt/vue-salt-rim\"\n\nmsg_info \"Configuring PHP\"\nPHPVER=$(php -r 'echo PHP_MAJOR_VERSION . \".\" . PHP_MINOR_VERSION . \"\\n\";')\nsed -i.bak -E 's/^\\s*;?\\s*ffi\\.enable\\s*=.*/ffi.enable=true/' /etc/php/${PHPVER}/fpm/php.ini\n$STD systemctl reload php${PHPVER}-fpm\nmsg_info \"configured PHP\"\n\nmsg_info \"Installing Bar Assistant\"\ncd /opt/bar-assistant\ncp /opt/bar-assistant/.env.dist /opt/bar-assistant/.env\nmkdir -p /opt/bar-assistant/resources/data\ncurl -fsSL https://github.com/bar-assistant/data/archive/refs/heads/v5.tar.gz | tar -xz --strip-components=1 -C /opt/bar-assistant/resources/data\nsed -i -e \"s|^APP_URL=|APP_URL=http://${LOCAL_IP}/bar/|\" \\\n  -e \"s|^MEILISEARCH_HOST=|MEILISEARCH_HOST=http://127.0.0.1:7700|\" \\\n  -e \"s|^MEILISEARCH_KEY=|MEILISEARCH_KEY=${MEILISEARCH_MASTER_KEY}|\" \\\n  -e \"s|^MEILISEARCH_API_KEY=|MEILISEARCH_API_KEY=${MEILISEARCH_API_KEY}|\" \\\n  -e \"s|^MEILISEARCH_API_KEY_UID=|MEILISEARCH_API_KEY_UID=${MEILISEARCH_API_KEY_UID}|\" \\\n  /opt/bar-assistant/.env\n$STD composer install --no-interaction\n$STD php artisan key:generate\ntouch storage/bar-assistant/database.ba3.sqlite\n$STD php artisan migrate --force\n$STD php artisan storage:link\n$STD php artisan bar:setup-meilisearch\n$STD php artisan scout:sync-index-settings\n$STD php artisan config:cache\n$STD php artisan route:cache\n$STD php artisan event:cache\nmkdir /opt/bar-assistant/storage/bar-assistant/uploads/temp\nchown -R www-data:www-data /opt/bar-assistant\nmsg_ok \"Installed Bar Assistant\"\n\nmsg_info \"Installing Salt Rim\"\ncd /opt/vue-salt-rim\ncat <<EOF >/opt/vue-salt-rim/public/config.js\nwindow.srConfig = {}\nwindow.srConfig.API_URL = \"http://${LOCAL_IP}/bar\"\nwindow.srConfig.MEILISEARCH_URL = \"http://${LOCAL_IP}/search\"\nEOF\n$STD npm install\n$STD npm run build\nmsg_ok \"Installed Salt Rim\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/nginx/sites-available/barassistant.conf\nserver {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n    server_name _;\n\n    location = /favicon.ico { access_log off; log_not_found off; }\n    location = /robots.txt  { access_log off; log_not_found off; }\n\n    client_max_body_size 100M;\n\n    location /bar/ {\n        proxy_pass http://127.0.0.1:8080/;\n        proxy_set_header Host \\$host;\n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n    }\n\n    location /search/ {\n        proxy_pass http://127.0.0.1:7700/;\n    }\n\n    location / {\n        proxy_pass http://127.0.0.1:8081/;\n        proxy_set_header Host \\$host;\n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n    }\n}\n\nserver {\n    listen 127.0.0.1:8080;\n    server_name example.com;\n    root /opt/bar-assistant/public;\n\n    add_header X-Frame-Options \"SAMEORIGIN\";\n    add_header X-Content-Type-Options \"nosniff\";\n\n    index index.php;\n    charset utf-8;\n\n    location / {\n        try_files \\$uri \\$uri/ /index.php?\\$query_string;\n    }\n\n    location = /favicon.ico { access_log off; log_not_found off; }\n    location = /robots.txt  { access_log off; log_not_found off; }\n\n    error_page 404 /index.php;\n\n    location ~ ^/index\\.php(/|$) {\n        fastcgi_pass unix:/var/run/php/php$PHPVER-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME \\$realpath_root\\$fastcgi_script_name;\n        include fastcgi_params;\n        fastcgi_hide_header X-Powered-By;\n    }\n\n    location ~ /\\.(?!well-known).* {\n        deny all;\n    }\n}\n\nserver {\n    listen 127.0.0.1:8081;\n    server_name _;\n    root /opt/vue-salt-rim/dist;\n\n    location / {\n        try_files \\$uri \\$uri/ /index.html;\n    }\n}\nEOF\n\nln -s /etc/nginx/sites-available/barassistant.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bazarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.bazarr.media/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y libicu76\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nfetch_and_deploy_gh_release \"bazarr\" \"morpheus65535/bazarr\" \"prebuild\" \"latest\" \"/opt/bazarr\" \"bazarr.zip\"\n\nmsg_info \"Installing Bazarr\"\nmkdir -p /var/lib/bazarr/\nchmod 775 /opt/bazarr /var/lib/bazarr/\nsed -i.bak 's/--only-binary=Pillow//g' /opt/bazarr/requirements.txt\n$STD uv venv --clear /opt/bazarr/venv --python 3.12\n$STD uv pip install -r /opt/bazarr/requirements.txt --python /opt/bazarr/venv/bin/python3\nmsg_ok \"Installed Bazarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/bazarr.service\n[Unit]\nDescription=Bazarr Daemon\nAfter=syslog.target network.target\n\n[Service]\nWorkingDirectory=/opt/bazarr/\nUMask=0002\nRestart=on-failure\nRestartSec=5\nType=simple\nExecStart=/opt/bazarr/venv/bin/python3 /opt/bazarr/bazarr.py\nKillSignal=SIGINT\nTimeoutStopSec=20\nSyslogIdentifier=bazarr\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now bazarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bentopdf-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alam00000/bentopdf\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  openssl\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"bentopdf\" \"alam00000/bentopdf\" \"tarball\" \"latest\" \"/opt/bentopdf\"\n\nmsg_info \"Setup BentoPDF\"\ncd /opt/bentopdf\n$STD npm ci --no-audit --no-fund\ncp ./.env.example ./.env.production\nexport NODE_OPTIONS=\"--max-old-space-size=3072\"\nexport SIMPLE_MODE=true\nexport VITE_USE_CDN=true\n$STD npm run build:all\ncat <<'EOF' >/opt/bentopdf/dist/config.json\n{}\nEOF\nmsg_ok \"Setup BentoPDF\"\n\nmsg_info \"Creating Service\"\nCERT_CN=\"$(hostname -I | awk '{print $1}')\"\n$STD openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \\\n  -keyout /etc/ssl/private/bentopdf-selfsigned.key \\\n  -out /etc/ssl/certs/bentopdf-selfsigned.crt \\\n  -subj \"/CN=${CERT_CN}\"\n\ncat <<'EOF' >/etc/nginx/sites-available/bentopdf\nserver {\n    listen 8080;\n    server_name _;\n    return 301 https://$host:8443$request_uri;\n}\n\nserver {\n    listen 8443 ssl;\n    server_name _;\n    ssl_certificate /etc/ssl/certs/bentopdf-selfsigned.crt;\n    ssl_certificate_key /etc/ssl/private/bentopdf-selfsigned.key;\n    root /opt/bentopdf/dist;\n    index index.html;\n\n    # Required for LibreOffice WASM (Word/Excel/PowerPoint to PDF via SharedArrayBuffer)\n    add_header Cross-Origin-Opener-Policy \"same-origin\" always;\n    add_header Cross-Origin-Embedder-Policy \"require-corp\" always;\n    add_header Cross-Origin-Resource-Policy \"cross-origin\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n\n    gzip_static on;\n\n    location ~* /libreoffice-wasm/soffice\\.wasm\\.gz$ {\n        gzip off;\n        types {} default_type application/wasm;\n        add_header Content-Encoding gzip;\n        add_header Vary \"Accept-Encoding\";\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    location ~* /libreoffice-wasm/soffice\\.data\\.gz$ {\n        gzip off;\n        types {} default_type application/octet-stream;\n        add_header Content-Encoding gzip;\n        add_header Vary \"Accept-Encoding\";\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    location ~* \\.wasm$ {\n        types {} default_type application/wasm;\n        expires 1y;\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    location ~* \\.(wasm\\.gz|data\\.gz|data)$ {\n        expires 1y;\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    location / {\n        try_files $uri $uri/ $uri.html =404;\n    }\n\n    error_page 404 /404.html;\n}\nEOF\nrm -f /etc/nginx/sites-enabled/default\nln -sf /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/bentopdf\nsystemctl stop nginx\nsystemctl disable -q nginx\nsed -i '/application\\/rss+xml/a\\    application\\/javascript                           mjs;' /etc/nginx/mime.types\n\ncat <<'EOF' >/etc/systemd/system/bentopdf.service\n[Unit]\nDescription=BentoPDF Service\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/sbin/nginx -g \"daemon off;\"\nExecReload=/bin/kill -HUP $MAINPID\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now bentopdf\nmsg_ok \"Created & started service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/beszel-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michelle Zitzerman (Sinofage)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://beszel.dev/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"beszel\" \"henrygd/beszel\" \"prebuild\" \"latest\" \"/opt/beszel\" \"beszel_linux_arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/beszel-hub.service\n[Unit]\nDescription=Beszel Hub Service\nAfter=network.target\n\n[Service]\nExecStart=/opt/beszel/beszel serve --http \"0.0.0.0:8090\"\nWorkingDirectory=/opt/beszel\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now beszel-hub\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bichon-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rustmailer/bichon\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"bichon\" \"rustmailer/bichon\" \"prebuild\" \"latest\" \"/opt/bichon\" \"bichon-*-aarch64-unknown-linux-gnu.tar.gz\"\n\nread -r -p \"${TAB3}Enter the public URL for Bichon (e.g., https://bichon.yourdomain.com) or leave empty to use container IP: \" bichon_url\nif [[ -z \"$bichon_url\" ]]; then\n  msg_info \"No URL provided\"\n  BICHON_PUBLIC_URL=\"http://$LOCAL_IP:15630\"\n  msg_ok \"Using local IP: http://$LOCAL_IP:15630\\n\"\nelse\n  msg_info \"URL provided\"\n  BICHON_PUBLIC_URL=\"$bichon_url\"\n  msg_ok \"Using provided URL: $BICHON_PUBLIC_URL\\n\"\nfi\n\nmsg_info \"Setting up Bichon\"\nmkdir -p /opt/bichon-data\nBICHON_ENC_PASSWORD=$(openssl rand -base64 32 | tr -d \"=+/\" | cut -c1-32)\n\ncat <<EOF >/opt/bichon/bichon.env\nBICHON_ROOT_DIR=/opt/bichon-data\nBICHON_LOG_LEVEL=info\nBICHON_ENCRYPT_PASSWORD=$BICHON_ENC_PASSWORD\nBICHON_PUBLIC_URL=$BICHON_PUBLIC_URL\nBICHON_CORS_ORIGINS=$BICHON_PUBLIC_URL\nEOF\nmsg_ok \"Setup Bichon\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/bichon.service\n[Unit]\nDescription=Bichon service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nEnvironmentFile=/opt/bichon/bichon.env\nWorkingDirectory=/opt/bichon\nExecStart=/opt/bichon/bichon\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now bichon\nmsg_info \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/birdnet-go-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/tphakala/birdnet-go\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libasound2 \\\n  sox \\\n  alsa-utils \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"birdnet\" \"tphakala/birdnet-go\" \"prebuild\" \"latest\" \"/opt/birdnet\" \"birdnet-go-linux-amd64.tar.gz\"\n\nmsg_info \"Setting up BirdNET-Go\"\ncp /opt/birdnet/birdnet-go /usr/local/bin/birdnet-go\nchmod +x /usr/local/bin/birdnet-go\ncp -r /opt/birdnet/libtensorflowlite_c.so /usr/local/lib/ || true\nldconfig\nmkdir -p /opt/birdnet/data/clips\nmsg_ok \"Set up BirdNET-Go\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/birdnet.service\n[Unit]\nDescription=BirdNET\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/birdnet/data\nExecStart=/usr/local/bin/birdnet-go realtime\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now birdnet\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bitmagnet-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bitmagnet-io/bitmagnet\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  iproute2 \\\n  gcc \\\n  musl-dev\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"bitmagnet\" PG_DB_USER=\"bitmagnet\" setup_postgresql_db\nsetup_go\n\nfetch_and_deploy_gh_release \"bitmagnet\" \"bitmagnet-io/bitmagnet\" \"tarball\"\nRELEASE=$(cat ~/.bitmagnet)\n\nmsg_info \"Configuring bitmagnet\"\ncd /opt/bitmagnet\n$STD go build -ldflags \"-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=v${RELEASE}\"\nchmod +x bitmagnet\nmsg_ok \"Configured bitmagnet\"\n\nread -r -p \"${TAB3}Enter your TMDB API key if you have one: \" tmdbapikey\n\ncat <<EOF >/etc/bitmagnet.env\nPOSTGRES_HOST=localhost\nPOSTGRES_USER=${PG_DB_USER}\nPOSTGRES_NAME=${PG_DB_NAME}\nPOSTGRES_PASSWORD=${PG_DB_PASS}\nEOF\n\nif [ -z \"$tmdbapikey\" ]; then\n  echo \"TMDB_ENABLED=false\" >>/etc/bitmagnet.env\nelse\n  echo \"TMDB_API_KEY=$tmdbapikey\" >>/etc/bitmagnet.env\nfi\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/bitmagnet-web.service\n[Unit]\nDescription=bitmagnet Web GUI\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/bitmagnet\nEnvironmentFile=/etc/bitmagnet.env\nExecStart=/opt/bitmagnet/bitmagnet worker run --all\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now bitmagnet-web\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/blocky-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://0xerr0r.github.io/blocky\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"blocky\" \"0xERR0R/blocky\" \"prebuild\" \"latest\" \"/opt/blocky\" \"blocky_*_Linux_arm64.tar.gz\"\n\nmsg_info \"Configuring Blocky\"\nif systemctl is-active systemd-resolved >/dev/null 2>&1; then\n  systemctl disable -q --now systemd-resolved\nfi\ncat <<EOF >/opt/blocky/config.yml\n# configuration documentation: https://0xerr0r.github.io/blocky/latest/configuration/\n\nupstreams:\n  groups:\n    # these external DNS resolvers will be used. Blocky picks 2 random resolvers from the list for each query\n    # format for resolver: [net:]host:[port][/path]. net could be empty (default, shortcut for tcp+udp), tcp+udp, tcp, udp, tcp-tls or https (DoH). If port is empty, default port will be used (53 for udp and tcp, 853 for tcp-tls, 443 for https (Doh))\n    # this configuration is mandatory, please define at least one external DNS resolver\n    default:\n      # Cloudflare\n      - 1.1.1.1\n      # Quad9 DNS-over-TLS server (DoT)\n      - tcp-tls:dns.quad9.net\n\n# optional: use allow/denylists to block queries (for example ads, trackers, adult pages etc.)\nblocking:\n  # definition of denylist groups. Can be external link (http/https) or local file\n  denylists:\n    ads:\n      - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\n  # definition: which groups should be applied for which client\n  clientGroupsBlock:\n    # default will be used, if no special definition for a client name exists\n    default:\n      - ads\n\n# optional: write query information (question, answer, client, duration etc.) to daily csv file\nqueryLog:\n  # optional one of: mysql, postgresql, csv, csv-client. If empty, log to console\n  type:\n\n# optional: use these DNS servers to resolve denylist urls and upstream DNS servers. It is useful if no system DNS resolver is configured, and/or to encrypt the bootstrap queries.\nbootstrapDns:\n  - upstream: tcp-tls:one.one.one.one\n    ips:\n      - 1.1.1.1\n\n# optional: logging configuration\nlog:\n  # optional: Log level (one from trace, debug, info, warn, error). Default: info\n  level: info\nEOF\nmsg_ok \"Configured Blocky\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/blocky.service\n[Unit]\nDescription=Blocky\nAfter=network.target\n[Service]\nUser=root\nWorkingDirectory=/opt/blocky\nExecStart=/opt/blocky/./blocky --config config.yml\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now blocky\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bookstack-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/BookStackApp/BookStack\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  make \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" PHP_MODULE=\"ldap,tidy,mysqli\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"bookstack_db\" MARIADB_DB_USER=\"bookstack_user\" setup_mariadb_db\n\nfetch_and_deploy_gh_release \"bookstack\" \"BookStackApp/BookStack\" \"tarball\"\n\nmsg_info \"Configuring Bookstack (Patience)\"\ncd /opt/bookstack\ncp .env.example .env\nsudo sed -i \"s|APP_URL=.*|APP_URL=http://$LOCAL_IP|g\" /opt/bookstack/.env\nsudo sed -i \"s/DB_DATABASE=.*/DB_DATABASE=$MARIADB_DB_NAME/\" /opt/bookstack/.env\nsudo sed -i \"s/DB_USERNAME=.*/DB_USERNAME=$MARIADB_DB_USER/\" /opt/bookstack/.env\nsudo sed -i \"s/DB_PASSWORD=.*/DB_PASSWORD=$MARIADB_DB_PASS/\" /opt/bookstack/.env\n$STD composer install --no-dev --no-plugins --no-interaction\n$STD php artisan key:generate --no-interaction --force\n$STD php artisan migrate --no-interaction --force\nchown www-data:www-data -R /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage\nchmod -R 755 /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage\nchmod -R 775 /opt/bookstack/storage /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads\nchmod -R 640 /opt/bookstack/.env\n$STD a2enmod rewrite\n$STD a2enmod php8.3\nmsg_ok \"Configured Bookstack\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/bookstack.conf\n<VirtualHost *:80>\n  ServerAdmin webmaster@localhost\n  DocumentRoot /opt/bookstack/public/\n\n  <Directory /opt/bookstack/public/>\n      Options -Indexes +FollowSymLinks\n      AllowOverride None\n      Require all granted\n      <IfModule mod_rewrite.c>\n          <IfModule mod_negotiation.c>\n              Options -MultiViews -Indexes\n          </IfModule>\n\n          RewriteEngine On\n\n          # Handle Authorization Header\n          RewriteCond %{HTTP:Authorization} .\n          RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n\n          # Redirect Trailing Slashes If Not A Folder...\n          RewriteCond %{REQUEST_FILENAME} !-d\n          RewriteCond %{REQUEST_URI} (.+)/$\n          RewriteRule ^ %1 [L,R=301]\n\n          # Handle Front Controller...\n          RewriteCond %{REQUEST_FILENAME} !-d\n          RewriteCond %{REQUEST_FILENAME} !-f\n          RewriteRule ^ index.php [L]\n      </IfModule>\n  </Directory>\n  \n    ErrorLog /var/log/apache2/error.log\n    CustomLog /var/log/apache2/access.log combined\n\n</VirtualHost>\nEOF\n$STD a2ensite bookstack.conf\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bunkerweb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.bunkerweb.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  lsb-release\nmsg_ok \"Installed Dependencies\"\n\nRELEASE=$(get_latest_github_release \"bunkerity/bunkerweb\")\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (install-bunkerweb.sh).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://github.com/bunkerity/bunkerweb/raw/v${RELEASE}/misc/install-bunkerweb.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\nmsg_info \"Installing BunkerWeb (Patience)\"\ncurl -fsSL -o install-bunkerweb.sh \"https://github.com/bunkerity/bunkerweb/raw/v${RELEASE}/misc/install-bunkerweb.sh\"\nchmod +x install-bunkerweb.sh\n$STD ./install-bunkerweb.sh --yes\n$STD apt-mark unhold bunkerweb nginx\ncat <<EOF >/etc/apt/preferences.d/bunkerweb\nPackage: bunkerweb\nPin: version ${RELEASE}\nPin-Priority: 1001\nEOF\necho \"${RELEASE}\" >~/.bunkerweb\nmsg_ok \"Installed BunkerWeb\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/byparr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ThePhaseless/Byparr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt -y install --no-install-recommends \\\n  ffmpeg \\\n  libatk1.0-0 \\\n  libcairo-gobject2 \\\n  libcairo2 \\\n  libdbus-glib-1-2 \\\n  libfontconfig1 \\\n  libfreetype6 \\\n  libgdk-pixbuf-xlib-2.0-0 \\\n  libglib2.0-0 \\\n  libgtk-3-0 \\\n  libpango-1.0-0 \\\n  libpangocairo-1.0-0 \\\n  libpangoft2-1.0-0 \\\n  libx11-6 \\\n  libx11-xcb1 \\\n  libxcb-shm0 \\\n  libxcb1 \\\n  libxcomposite1 \\\n  libxcursor1 \\\n  libxdamage1 \\\n  libxext6 \\\n  libxfixes3 \\\n  libxi6 \\\n  libxrender1 \\\n  libxt6 \\\n  libxtst6 \\\n  xvfb \\\n  fonts-noto-color-emoji \\\n  fonts-unifont \\\n  xfonts-cyrillic \\\n  xfonts-scalable \\\n  fonts-liberation \\\n  fonts-ipafont-gothic \\\n  fonts-wqy-zenhei \\\n  fonts-tlwg-loma-otf\nmsg_ok \"Installed Dependencies\"\n\nsetup_uv\nfetch_and_deploy_gh_release \"Byparr\" \"ThePhaseless/Byparr\" \"tarball\" \"latest\"\n\nmsg_info \"Configuring Byparr\"\ncd /opt/Byparr\n$STD uv sync --link-mode copy\n$STD uv run camoufox fetch\nmsg_ok \"Configured Byparr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/byparr.service\n[Unit]\nDescription=Byparr\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/Byparr\nExecStart=/usr/local/bin/uv run python3 main.py\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now byparr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/bytestash-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/jordan-dalby/ByteStash\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"bytestash\" \"jordan-dalby/ByteStash\" \"tarball\"\n\nmsg_info \"Installing ByteStash\"\nJWT_SECRET=$(openssl rand -base64 32 | tr -d '/+=')\ncd /opt/bytestash/server\n$STD npm install\ncd /opt/bytestash/client\n$STD npm install\nmsg_ok \"Installed ByteStash\"\n\nread -rp \"${TAB3}Do you want to allow registration of multiple accounts? [y/n]: \" allowreg\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/bytestash-backend.service\n[Unit]\nDescription=ByteStash Backend Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/bytestash/server\nExecStart=/usr/bin/node src/app.js\nRestart=always\nEnvironment=JWT_SECRET=$JWT_SECRET\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nif [[ \"$allowreg\" =~ ^[Yy]$ ]]; then\n  sed -i '8i\\Environment=ALLOW_NEW_ACCOUNTS=true' /etc/systemd/system/bytestash-backend.service\nfi\n\ncat <<EOF >/etc/systemd/system/bytestash-frontend.service\n[Unit]\nDescription=ByteStash Frontend Service\nAfter=network.target bytestash-backend.service\n\n[Service]\nWorkingDirectory=/opt/bytestash/client\nExecStart=/usr/bin/npx vite --host\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now bytestash-backend\nsystemctl enable -q --now bytestash-frontend\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/caddy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://caddyserver.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  debian-keyring \\\n  debian-archive-keyring \\\n  apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Caddy\"\nsetup_deb822_repo \\\n  \"caddy\" \\\n  \"https://dl.cloudsmith.io/public/caddy/stable/gpg.key\" \\\n  \"https://dl.cloudsmith.io/public/caddy/stable/deb/debian\" \\\n  \"any-version\"\n$STD apt install -y caddy\nmsg_ok \"Installed Caddy\"\n\nread -r -p \"${TAB3}Would you like to install xCaddy Addon? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  setup_go\n  fetch_and_deploy_gh_release \"xcaddy\" \"caddyserver/xcaddy\" \"binary\"\n\n  msg_info \"Setup xCaddy\"\n  $STD apt install -y git\n  $STD xcaddy build\n  msg_ok \"Setup xCaddy\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/calibre-web-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: mikolaj92\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/janeczku/calibre-web\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  python3-dev \\\n  libldap2-dev \\\n  libsasl2-dev \\\n  libssl-dev \\\n  imagemagick \\\n  libpango-1.0-0 \\\n  libharfbuzz0b \\\n  libpangoft2-1.0-0 \\\n  fonts-liberation\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Calibre (for eBook conversion)\"\n$STD apt install -y calibre\nmsg_ok \"Installed Calibre\"\n\nfetch_and_deploy_gh_release \"Calibre-Web\" \"janeczku/calibre-web\" \"prebuild\" \"latest\" \"/opt/calibre-web\" \"calibre-web*.tar.gz\"\nsetup_uv\n\nmsg_info \"Installing Python Dependencies\"\ncd /opt/calibre-web\n$STD uv venv\n$STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir --upgrade pip setuptools wheel\n$STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir -r requirements.txt\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Creating Service\"\nmkdir -p /opt/calibre-web/data\ncat <<EOF >/etc/systemd/system/calibre-web.service\n[Unit]\nDescription=Calibre-Web Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/calibre-web\nExecStart=/opt/calibre-web/.venv/bin/python /opt/calibre-web/cps.py\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now calibre-web\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/casaos-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://casaos.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://casaos.zimaspace.com/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://get.casaos.io/\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\nmsg_info \"Installing CasaOS (Patience)\"\nDOCKER_CONFIG_PATH='/etc/docker/daemon.json'\nmkdir -p $(dirname $DOCKER_CONFIG_PATH)\necho -e '{\\n  \"log-driver\": \"journald\"\\n}' >/etc/docker/daemon.json\n$STD bash <(curl -fsSL https://get.casaos.io/)\nmsg_ok \"Installed CasaOS\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/changedetection-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://changedetection.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt-get install -y \\\n  git \\\n  build-essential \\\n  dumb-init \\\n  gconf-service \\\n  libjpeg-dev \\\n  libatk-bridge2.0-0 \\\n  libasound2 \\\n  libatk1.0-0 \\\n  libcairo2 \\\n  libcups2 \\\n  libdbus-1-3 \\\n  libexpat1 \\\n  libgbm-dev \\\n  libgbm1 \\\n  libgconf-2-4 \\\n  libgdk-pixbuf2.0-0 \\\n  libglib2.0-0 \\\n  libgtk-3-0 \\\n  libnspr4 \\\n  libnss3 \\\n  libpango-1.0-0 \\\n  libpangocairo-1.0-0 \\\n  qpdf \\\n  xdg-utils \\\n  xvfb \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup Python3\"\n$STD apt-get install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Installing Change Detection\"\nmkdir /opt/changedetection\n$STD pip3 install changedetection.io\ncat <<EOF >/opt/changedetection/.env\nWEBDRIVER_URL=http://127.0.0.1:4444/wd/hub\nPLAYWRIGHT_DRIVER_URL=ws://localhost:3000/chrome?launch=eyJkZWZhdWx0Vmlld3BvcnQiOnsiaGVpZ2h0Ijo3MjAsIndpZHRoIjoxMjgwfSwiaGVhZGxlc3MiOmZhbHNlLCJzdGVhbHRoIjp0cnVlfQ==&blockAds=true\nEOF\nmsg_ok \"Installed Change Detection\"\n\nmsg_info \"Installing Browserless & Playwright\"\nmkdir /opt/browserless\n$STD python3 -m pip install playwright\n$STD git clone https://github.com/browserless/chrome /opt/browserless\n$STD npm ci --include=optional --include=dev --prefix /opt/browserless\n$STD /opt/browserless/node_modules/playwright-core/cli.js install --with-deps &>/dev/null\n$STD /opt/browserless/node_modules/playwright-core/cli.js install --force chrome &>/dev/null\n$STD /opt/browserless/node_modules/playwright-core/cli.js install chromium firefox webkit &>/dev/null\n$STD /opt/browserless/node_modules/playwright-core/cli.js install --force msedge\n$STD npm run build --prefix /opt/browserless\n$STD npm run build:function --prefix /opt/browserless\n$STD npm prune production --prefix /opt/browserless\nmsg_ok \"Installed Browserless & Playwright\"\n\nmsg_info \"Installing Font Packages\"\n$STD apt-get install -y \\\n  fontconfig \\\n  libfontconfig1 \\\n  fonts-freefont-ttf \\\n  fonts-gfs-neohellenic \\\n  fonts-indic fonts-ipafont-gothic \\\n  fonts-kacst fonts-liberation \\\n  fonts-noto-cjk \\\n  fonts-noto-color-emoji \\\n  msttcorefonts \\\n  fonts-roboto \\\n  fonts-thai-tlwg \\\n  fonts-wqy-zenhei\nmsg_ok \"Installed Font Packages\"\n\nmsg_info \"Installing X11 Packages\"\n$STD apt-get install -y \\\n  libx11-6 \\\n  libx11-xcb1 \\\n  libxcb1 \\\n  libxcomposite1 \\\n  libxcursor1 \\\n  libxdamage1 \\\n  libxext6 \\\n  libxfixes3 \\\n  libxi6 \\\n  libxrandr2 \\\n  libxrender1 \\\n  libxss1 \\\n  libxtst6\nmsg_ok \"Installed X11 Packages\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/changedetection.service\n[Unit]\nDescription=Change Detection\nAfter=network-online.target\nAfter=network.target browserless.service\nWants=browserless.service\n\n[Service]\nType=simple\nEnvironmentFile=/opt/changedetection/.env\nWorkingDirectory=/opt/changedetection\nExecStart=changedetection.io -d /opt/changedetection -p 5000\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/browserless.service\n[Unit]\nDescription=browserless service\nAfter=network.target\n\n[Service]\nEnvironment=CONNECTION_TIMEOUT=60000\nWorkingDirectory=/opt/browserless\nExecStart=/opt/browserless/scripts/start.sh\nSyslogIdentifier=browserless\n\n[Install]\nWantedBy=default.target\nEOF\nsystemctl enable -q --now browserless\nsystemctl enable -q --now changedetection\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/channels-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://getchannels.com/dvr-server/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  chromium \\\n  xvfb\nmsg_ok \"Installed Dependencies\"\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://getchannels.com).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://getchannels.com/dvr/setup.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nsetup_hwaccel\n\nmsg_info \"Installing Channels DVR Server (Patience)\"\ncd /opt\n$STD bash <(curl -fsSL https://getchannels.com/dvr/setup.sh)\nmsg_ok \"Installed Channels DVR Server\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/checkmate-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bluewave-labs/Checkmate\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  openssl \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nMONGO_VERSION=\"8.0\" setup_mongodb\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"checkmate\" \"bluewave-labs/Checkmate\" \"tarball\"\n\nmsg_info \"Configuring Checkmate\"\nJWT_SECRET=\"$(openssl rand -hex 32)\"\ncat <<EOF >/opt/checkmate/server/.env\nCLIENT_HOST=\"http://${LOCAL_IP}\"\nJWT_SECRET=\"${JWT_SECRET}\"\nDB_CONNECTION_STRING=\"mongodb://localhost:27017/checkmate_db\"\nTOKEN_TTL=\"99d\"\nORIGIN=\"${LOCAL_IP}\"\nLOG_LEVEL=\"info\"\nSERVER_HOST=0.0.0.0\nSERVER_PORT=52345\nEOF\ncat <<EOF >/opt/checkmate/client/.env.local\nVITE_APP_API_BASE_URL=\"/api/v1\"\nUPTIME_APP_API_BASE_URL=\"/api/v1\"\nVITE_APP_LOG_LEVEL=\"warn\"\nEOF\nmsg_ok \"Configured Checkmate\"\n\nmsg_info \"Installing Checkmate Server\"\ncd /opt/checkmate/server\n$STD npm install\n$STD npm run build\nmsg_ok \"Installed Checkmate Server\"\n\nmsg_info \"Installing Checkmate Client\"\ncd /opt/checkmate/client\n$STD npm install\nVITE_APP_API_BASE_URL=\"/api/v1\" UPTIME_APP_API_BASE_URL=\"/api/v1\" VITE_APP_LOG_LEVEL=\"warn\" $STD npm run build\nmsg_ok \"Installed Checkmate Client\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/checkmate-server.service\n[Unit]\nDescription=Checkmate Server\nAfter=network.target mongod.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/checkmate/server\nEnvironmentFile=/opt/checkmate/server/.env\nExecStart=/usr/bin/npm start\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/checkmate-client.service\n[Unit]\nDescription=Checkmate Client\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/checkmate/client\nExecStart=/usr/bin/npm run preview -- --host 127.0.0.1 --port 5173\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now checkmate-server\n$STD systemctl enable -q --now checkmate-client\nmsg_ok \"Created Services\"\n\nmsg_info \"Configuring Nginx Reverse Proxy\"\ncat <<EOF >/etc/nginx/sites-available/checkmate\nserver {\n  listen 80 default_server;\n  server_name _;\n  \n  client_max_body_size 100M;\n\n  # Client UI\n  location / {\n    proxy_pass http://127.0.0.1:5173;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade \\$http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Host \\$host;\n    proxy_set_header X-Real-IP \\$remote_addr;\n    proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n  }\n\n  # API Server\n  location /api/v1/ {\n    proxy_pass http://127.0.0.1:52345/api/v1/;\n    proxy_http_version 1.1;\n    proxy_set_header Host \\$host;\n    proxy_set_header X-Real-IP \\$remote_addr;\n    proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n  }\n}\nEOF\nln -sf /etc/nginx/sites-available/checkmate /etc/nginx/sites-enabled/checkmate\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx Reverse Proxy\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/checkmk-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://checkmk.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Install Checkmk\"\nRELEASE=$(curl -fsSL https://api.github.com/repos/checkmk/checkmk/tags | grep \"name\" | awk '{print substr($2, 3, length($2)-4) }' | tr ' ' '\\n' | grep -Ev 'rc|b' | sort -V | tail -n 1)\ncurl -fsSL \"https://download.checkmk.com/checkmk/${RELEASE}/check-mk-raw-${RELEASE}_0.bookworm_arm64.deb\" -o \"/opt/checkmk.deb\"\n$STD apt-get install -y /opt/checkmk.deb\nrm -rf /opt/checkmk.deb\necho \"${RELEASE}\" >\"/opt/checkmk_version.txt\"\nmsg_ok \"Installed Checkmk\"\n\nmsg_info \"Creating Service\"\nSITE_NAME=\"monitoring\"\n$STD omd create \"$SITE_NAME\"\nMKPASSWORD=$(openssl rand -base64 18 | tr -d '/+=' | cut -c1-16)\n\necho -e \"$MKPASSWORD\\n$MKPASSWORD\" | su - \"$SITE_NAME\" -c \"cmk-passwd cmkadmin --stdin\"\n$STD omd start \"$SITE_NAME\"\n{\n  echo \"Application-Credentials\"\n  echo \"Username: cmkadmin\"\n  echo \"Password: $MKPASSWORD\"\n  echo \"Site: $SITE_NAME\"\n} >>~/checkmk.creds\nmsg_ok \"Created Service\"\n\ncleanup_lxc\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/cleanuparr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Lucas Zampieri (zampierilucas) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Cleanuparr/Cleanuparr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"Cleanuparr\" \"Cleanuparr/Cleanuparr\" \"prebuild\" \"latest\" \"/opt/cleanuparr\" \"*linux-arm64.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/cleanuparr.service\n[Unit]\nDescription=Cleanuparr Daemon\nAfter=syslog.target network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/cleanuparr\nExecStart=/opt/cleanuparr/Cleanuparr\nRestart=on-failure\nRestartSec=5\nEnvironment=\"PORT=11011\"\nEnvironment=\"CONFIG_DIR=/opt/cleanuparr/config\"\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now cleanuparr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cloudflare-ddns-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: edoardop13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/favonia/cloudflare-ddns\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_go\n\nvar_cf_api_token=\"default\"\nread -rp \"${TAB3}Enter the Cloudflare API token: \" var_cf_api_token\n\nvar_cf_domains=\"default\"\nread -rp \"${TAB3}Enter the domains separated with a comma (*.example.org,www.example.org) \" var_cf_domains\n\nvar_cf_proxied=\"false\"\nwhile true; do\n  read -rp \"${TAB3}Proxied? (y/n): \" answer\n  case \"$answer\" in\n  [Yy]*)\n    var_cf_proxied=\"true\"\n    break\n    ;;\n  [Nn]*)\n    var_cf_proxied=\"false\"\n    break\n    ;;\n  *) echo \"Please answer y or n.\" ;;\n  esac\ndone\nvar_cf_ip6_provider=\"none\"\nwhile true; do\n  read -rp \"${TAB3}Enable IPv6 support? (y/n): \" answer\n  case \"$answer\" in\n  [Yy]*)\n    var_cf_ip6_provider=\"cloudflare.trace\"\n    break\n    ;;\n  [Nn]*)\n    var_cf_ip6_provider=\"none\"\n    break\n    ;;\n  *) echo \"Please answer y or n.\" ;;\n  esac\ndone\nmsg_ok \"Configured Application\"\n\nmsg_info \"Setting up service\"\nmkdir -p /root/go\ncat <<EOF >/etc/systemd/system/cloudflare-ddns.service\n[Unit]\nDescription=Cloudflare DDNS Service (Go run)\nAfter=network.target\n\n[Service]\nEnvironment=\"CLOUDFLARE_API_TOKEN=${var_cf_api_token}\"\nEnvironment=\"DOMAINS=${var_cf_domains}\"\nEnvironment=\"PROXIED=${var_cf_proxied}\"\nEnvironment=\"IP6_PROVIDER=${var_cf_ip6_provider}\"\nEnvironment=\"GOPATH=/root/go\"\nEnvironment=\"GOCACHE=/tmp/go-build\"\nExecStart=/usr/local/bin/go run github.com/favonia/cloudflare-ddns/cmd/ddns@latest\nRestart=always\nRestartSec=300\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now cloudflare-ddns\nmsg_ok \"Setup Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cloudflared-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.cloudflare.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Cloudflared\"\nsetup_deb822_repo \\\n  \"cloudflared\" \\\n  \"https://pkg.cloudflare.com/cloudflare-main.gpg\" \\\n  \"https://pkg.cloudflare.com/cloudflared/\" \\\n  \"any\" \\\n  \"main\"\n$STD apt install -y cloudflared\nmsg_ok \"Installed Cloudflared\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cloudreve-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cloudreve.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"cloudreve\" \"cloudreve/cloudreve\" \"prebuild\" \"latest\" \"/opt/cloudreve\" \"*linux_arm64.tar.gz\"\n\nmsg_info \"Setup Service\"\ncat <<EOF >/etc/systemd/system/cloudreve.service\n[Unit]\nDescription=Cloudreve Service\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/opt/cloudreve/cloudreve\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now cloudreve\nmsg_ok \"Service Setup\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cockpit-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: havardthom\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/cockpit-project/cockpit\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Cockpit\"\nsource /etc/os-release\ncat <<EOF >/etc/apt/sources.list.d/debian-backports.sources\nTypes: deb deb-src\nURIs: http://deb.debian.org/debian\nSuites: ${VERSION_CODENAME}-backports\nComponents: main\nEnabled: yes\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n\n$STD apt update\n$STD apt install -t ${VERSION_CODENAME}-backports cockpit cracklib-runtime --no-install-recommends -y\nsed -i \"s/root//g\" /etc/cockpit/disallowed-users\nmsg_ok \"Installed Cockpit\"\n\nread -r -p \"Would you like to install 45Drives' cockpit-file-sharing, cockpit-identities, and cockpit-navigator  <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install_45drives=true\n  if [[ \"${VERSION_ID}\" -ge 13 ]]; then\n    read -r -p \"Debian ${VERSION_ID} is not officially supported by 45Drives yet, would you like to continue anyway? <y/N> \" prompt\n    if [[ ! \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n      install_45drives=false\n    fi\n  fi\n  if [[ \"$install_45drives\" == \"true\" ]]; then\n    msg_info \"Installing 45Drives' cockpit extensions\"\n    setup_deb822_repo \"45drives\" \\\n      \"https://repo.45drives.com/key/gpg.asc\" \\\n      \"https://repo.45drives.com/enterprise/debian\" \\\n      \"bookworm\" \\\n      \"main\" \\\n      \"arm64\"\n    $STD apt install -y cockpit-file-sharing cockpit-identities cockpit-navigator\n    msg_ok \"Installed 45Drives' cockpit extensions\"\n  fi\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/comfyui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jdacode\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/comfyanonymous/ComfyUI\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\necho\necho \"${TAB3}Choose the GPU type for ComfyUI:\"\necho \"${TAB3}[1]-None  [2]-NVIDIA  [3]-AMD  [4]-Intel\"\nread -rp \"${TAB3}Enter your choice [1-4] (default: 1): \" gpu_choice\ngpu_choice=${gpu_choice:-1}\ncase \"$gpu_choice\" in\n1) comfyui_gpu_type=\"none\" ;;\n2) comfyui_gpu_type=\"nvidia\" ;;\n3) comfyui_gpu_type=\"amd\" ;;\n4) comfyui_gpu_type=\"intel\" ;;\n*)\n  comfyui_gpu_type=\"none\"\n  echo \"${TAB3}Invalid choice. Defaulting to ${comfyui_gpu_type}.\"\n  ;;\nesac\necho\n\nPYTHON_VERSION=\"3.12\" setup_uv\n\nfetch_and_deploy_gh_release \"ComfyUI\" \"comfyanonymous/ComfyUI\" \"tarball\" \"latest\" \"/opt/ComfyUI\"\n\nmsg_info \"Python dependencies\"\n$STD uv venv --clear \"/opt/ComfyUI/venv\"\n\nif [[ \"${comfyui_gpu_type,,}\" == \"nvidia\" ]]; then\n  pytorch_url=\"https://download.pytorch.org/whl/cu130\"\n  if [[ -f \"/opt/ComfyUI/README.md\" ]]; then\n    extracted=$(grep -oP 'pip install.*?--extra-index-url\\s+\\Khttps://download\\.pytorch\\.org/whl/cu\\d+' /opt/ComfyUI/README.md | head -1 || true)\n    [[ -n \"$extracted\" ]] && pytorch_url=\"$extracted\"\n  fi\n  $STD uv pip install \\\n    torch \\\n    torchvision \\\n    torchaudio \\\n    --extra-index-url \"$pytorch_url\" \\\n    --python=\"/opt/ComfyUI/venv/bin/python\"\nelif [[ \"${comfyui_gpu_type,,}\" == \"amd\" ]]; then\n  pytorch_url=\"https://download.pytorch.org/whl/rocm6.4\"\n  if [[ -f \"/opt/ComfyUI/README.md\" ]]; then\n    extracted=$(grep -oP 'pip install.*?--index-url\\s+\\Khttps://download\\.pytorch\\.org/whl/rocm[\\d.]+' /opt/ComfyUI/README.md | grep -v 'nightly' | head -1 || true)\n    [[ -n \"$extracted\" ]] && pytorch_url=\"$extracted\"\n  fi\n  $STD uv pip install \\\n    torch \\\n    torchvision \\\n    torchaudio \\\n    --index-url \"$pytorch_url\" \\\n    --python=\"/opt/ComfyUI/venv/bin/python\"\nelif [[ \"${comfyui_gpu_type,,}\" == \"intel\" ]]; then\n  pytorch_url=\"https://download.pytorch.org/whl/xpu\"\n  if [[ -f \"/opt/ComfyUI/README.md\" ]]; then\n    extracted=$(grep -oP 'pip install.*?--index-url\\s+\\Khttps://download\\.pytorch\\.org/whl/xpu' /opt/ComfyUI/README.md | head -1 || true)\n    [[ -n \"$extracted\" ]] && pytorch_url=\"$extracted\"\n  fi\n  $STD uv pip install \\\n    torch \\\n    torchvision \\\n    torchaudio \\\n    --index-url \"$pytorch_url\" \\\n    --python=\"/opt/ComfyUI/venv/bin/python\"\nfi\n$STD uv pip install -r \"/opt/ComfyUI/requirements.txt\" --python=\"/opt/ComfyUI/venv/bin/python\"\nmsg_ok \"Python dependencies\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/comfyui.service\n[Unit]\nDescription=ComfyUI Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/ComfyUI\nExecStart=/opt/ComfyUI/venv/bin/python /opt/ComfyUI/main.py --listen --port 8188\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now comfyui\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/commafeed-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.commafeed.com/#/welcome\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y rsync\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"25\" setup_java\nfetch_and_deploy_gh_release \"commafeed\" \"Athou/commafeed\" \"prebuild\" \"latest\" \"/opt/commafeed\" \"commafeed-*-h2-jvm.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/commafeed.service\n[Unit]\nDescription=CommaFeed Service\nAfter=network.target\n\n[Service]\nExecStart=java -Xminf0.05 -Xmaxf0.1 -jar quarkus-run.jar\nWorkingDirectory=/opt/commafeed/\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now commafeed\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/configarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: finkerle\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/raydak-labs/configarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git libicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"configarr\" \"raydak-labs/configarr\" \"prebuild\" \"latest\" \"/opt/configarr\" \"configarr-linux-x64.tar.xz\"\n\nmsg_info \"Setup Configarr\"\ncat <<EOF >/opt/configarr/.env\nROOT_PATH=/opt/configarr\nCUSTOM_REPO_ROOT=/opt/configarr/repos\nCONFIG_LOCATION=/opt/configarr/config.yml\nSECRETS_LOCATION=/opt/configarr/secrets.yml\nEOF\n\ncd /opt/configarr\ncurl -fsSLO https://raw.githubusercontent.com/raydak-labs/configarr/refs/heads/main/examples/full/config/config.yml\ncurl -fsSLO https://raw.githubusercontent.com/raydak-labs/configarr/refs/heads/main/examples/full/config/secrets.yml\nsed 's|#localConfigTemplatesPath: /app/templates|#localConfigTemplatesPath: /opt/configarr/templates|' /opt/configarr/config.yml\nmsg_ok \"Setup Configarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/configarr-task.service\n[Unit]\nDescription=Run Configarr Task\n\n[Service]\nType=simple\nWorkingDirectory=/opt/configarr\nExecStart=/opt/configarr/configarr\nEOF\n\ncat <<EOF >/etc/systemd/system/configarr-task.timer\n[Unit]\nDescription=Run Configarr every 5 minutes\n\n[Timer]\nOnBootSec=2min\nOnUnitActiveSec=5min\nPersistent=true\n\n[Install]\nWantedBy=timers.target\nEOF\nsystemctl enable -q --now configarr-task.timer configarr-task.service\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/convertx-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/C4illin/ConvertX\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nsetup_imagemagick\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  assimp-utils \\\n  calibre \\\n  dcraw \\\n  dvisvgm \\\n  ffmpeg \\\n  inkscape \\\n  libreoffice-writer \\\n  libva2 \\\n  libvips-tools \\\n  lmodern \\\n  mupdf-tools \\\n  pandoc \\\n  poppler-utils \\\n  potrace \\\n  python3-numpy \\\n  texlive \\\n  texlive-fonts-recommended \\\n  texlive-latex-extra \\\n  texlive-latex-recommended \\\n  texlive-xetex\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"bun\" setup_nodejs\nfetch_and_deploy_gh_release \"ConvertX\" \"C4illin/ConvertX\" \"tarball\" \"latest\" \"/opt/convertx\"\n\nmsg_info \"Installing ConvertX\"\ncd /opt/convertx\nmkdir -p data\n$STD bun install\n\nJWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)\ncat <<EOF >/opt/convertx/.env\nJWT_SECRET=$JWT_SECRET\nHTTP_ALLOWED=true\nPORT=3000\nEOF\nmsg_ok \"Installed ConvertX\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/convertx.service\n[Unit]\nDescription=ConvertX File Converter\nAfter=network.target\n\n[Service]\nType=exec\nWorkingDirectory=/opt/convertx\nEnvironmentFile=/opt/convertx/.env\nExecStart=/bin/bun dev\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now convertx\nmsg_ok \"Service Created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/coredns-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/coredns/coredns\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"coredns\" \"coredns/coredns\" \"prebuild\" \"latest\" \"/usr/local/bin\" \\\n  \"coredns_*_linux_$(dpkg --print-architecture).tgz\"\nchmod +x /usr/local/bin/coredns\n\nmsg_info \"Configuring CoreDNS\"\nmkdir -p /etc/coredns\ncat <<EOF >/etc/coredns/Corefile\n. {\n    forward . 1.1.1.1 1.0.0.1\n    cache 30\n    log\n    errors\n    health :8080\n    ready :8181\n}\nEOF\nmsg_ok \"Configured CoreDNS\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/coredns.service\n[Unit]\nDescription=CoreDNS DNS Server\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile\nRestart=on-failure\nRestartSec=5\nLimitNOFILE=1048576\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now coredns\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cosmos-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://https://cosmos-cloud.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ca-certificates \\\n  openssl \\\n  snapraid \\\n  avahi-daemon \\\n  fdisk \\\n  mergerfs \\\n  unzip\nmsg_ok \"Installed Dependencies\"\n\nsetup_docker\nfetch_and_deploy_gh_release \"cosmos\" \"azukaar/Cosmos-Server\" \"prebuild\" \"latest\" \"/opt/cosmos\" \"cosmos-cloud-*-arm64.zip\"\n\nmsg_info \"Setting up Cosmos\"\ncd /opt/cosmos\nchmod +x /opt/cosmos/cosmos\nmsg_ok \"Set up Cosmos\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/cosmos.service\n[Unit]\nDescription=Cosmos Cloud service\nConditionFileIsExecutable=/opt/cosmos/start.sh\n\n[Service]\nStartLimitInterval=10\nStartLimitBurst=5\nExecStart=/opt/cosmos/start.sh\n\nWorkingDirectory=/opt/cosmos\n\nRestart=always\n\nRestartSec=2\nEnvironmentFile=-/etc/sysconfig/CosmosCloud\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now cosmos\nmsg_info \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/crafty-controller-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.craftycontrol.com/pages/getting-started/installation/linux/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up TemurinJDK\"\nsetup_java\n$STD apt install -y temurin-{8,11,17,21}-jre\nsudo update-alternatives --set java /usr/lib/jvm/temurin-21-jre-arm64/bin/java\nmsg_ok \"Installed TemurinJDK\"\n\nmsg_info \"Setup Python3\"\n$STD apt install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip \\\n  python3-venv\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nmsg_info \"Installing Crafty-Controller (Patience)\"\nuseradd crafty -m -s /bin/bash\ncd /opt\nmkdir -p /opt/crafty-controller/crafty /opt/crafty-controller/server\nRELEASE=$(curl -fsSL \"https://gitlab.com/api/v4/projects/20430749/releases\" | grep -o '\"tag_name\":\"v[^\"]*\"' | head -n 1 | sed 's/\"tag_name\":\"v//;s/\"//')\necho \"${RELEASE}\" >\"/opt/crafty-controller_version.txt\"\ncurl -fsSL \"https://gitlab.com/crafty-controller/crafty-4/-/archive/v${RELEASE}/crafty-4-v${RELEASE}.zip\" -o \"crafty-4-v${RELEASE}.zip\"\n$STD unzip crafty-4-v\"${RELEASE}\".zip\ncp -a crafty-4-v\"${RELEASE}\"/. /opt/crafty-controller/crafty/crafty-4/\nrm -rf crafty-4-v\"${RELEASE}\"\n\ncd /opt/crafty-controller/crafty\npython3 -m venv .venv\nchown -R crafty:crafty /opt/crafty-controller/\n$STD sudo -u crafty bash -c '\n    source /opt/crafty-controller/crafty/.venv/bin/activate\n    cd /opt/crafty-controller/crafty/crafty-4\n    pip3 install --no-cache-dir -r requirements.txt\n'\nmsg_ok \"Installed Craft-Controller and dependencies\"\n\nmsg_info \"Setting up service\"\ncat <<EOF >/etc/systemd/system/crafty-controller.service\n[Unit]\nDescription=Crafty 4\nAfter=network.target\n\n[Service]\nType=simple\nUser=crafty\nWorkingDirectory=/opt/crafty-controller/crafty/crafty-4\nEnvironment=PATH=/usr/lib/jvm/temurin-21-jre-arm64/bin:/opt/crafty-controller/crafty/.venv/bin:$PATH\nExecStart=/opt/crafty-controller/crafty/.venv/bin/python3 main.py -d\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now crafty-controller\nCREDS_FILE=\"/opt/crafty-controller/crafty/crafty-4/app/config/default-creds.txt\"\nfor i in $(seq 1 30); do\n  [[ -f \"$CREDS_FILE\" ]] && break\n  sleep 2\ndone\nif [[ -f \"$CREDS_FILE\" ]]; then\n  {\n    echo \"Crafty-Controller-Credentials\"\n    echo \"Username: $(grep -oP '(?<=\"username\": \")[^\"]*' \"$CREDS_FILE\")\"\n    echo \"Password: $(grep -oP '(?<=\"password\": \")[^\"]*' \"$CREDS_FILE\")\"\n  } >>~/crafty-controller.creds\nfi\nmsg_ok \"Service started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cronicle-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://cronicle.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"cronicle\" \"jhuckaby/Cronicle\" \"tarball\"\n\nmsg_info \"Configuring Cronicle Primary Server\"\ncd /opt/cronicle\n$STD npm install\n$STD node bin/build.js dist\nsed -i \"s/localhost:3012/${LOCAL_IP}:3012/g\" /opt/cronicle/conf/config.json\n$STD /opt/cronicle/bin/control.sh setup\n$STD /opt/cronicle/bin/control.sh start\nmsg_ok \"Configured Cronicle Primary Server\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cross-seed-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Jakub Matraszek (jmatraszek)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.cross-seed.org\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Setup Cross-Seed\"\n$STD npm install cross-seed@latest -g\n$STD cross-seed gen-config\nmsg_ok \"Setup Cross-Seed\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/cross-seed.service\n[Unit]\nDescription=Cross-Seed daemon Service\nAfter=network.target\n\n[Service]\nExecStart=/usr/bin/cross-seed daemon\nRestart=on-failure\nRestartSec=30\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now cross-seed\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/cryptpad-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/cryptpad/cryptpad\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\n\nread -rp \"${TAB3}Install OnlyOffice components instead of CKEditor? (Y/N): \" onlyoffice\nfetch_and_deploy_gh_release \"cryptpad\" \"cryptpad/cryptpad\" \"tarball\"\n\nmsg_info \"Setup CryptPad\"\ncd /opt/cryptpad\n$STD npm ci\n$STD npm run install:components\nif [[ \"$onlyoffice\" =~ ^[Yy]$ ]]; then\n  $STD bash -c \"./install-onlyoffice.sh --accept-license\"\nfi\ncp config/config.example.js config/config.js\nsed -i \"51s/localhost/${LOCAL_IP}/g\" /opt/cryptpad/config/config.js\nsed -i \"80s#//httpAddress: 'localhost'#httpAddress: '0.0.0.0'#g\" /opt/cryptpad/config/config.js\n$STD npm run build\nmsg_ok \"Setup CryptPad\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/cryptpad.service\n[Unit]\nDescription=CryptPad Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/cryptpad\nExecStart=/usr/bin/node server\nEnvironment='PWD=\"/opt/cryptpad\"'\nStandardOutput=journal\nStandardError=journal+console\nLimitNOFILE=1000000\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now cryptpad\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/dagu-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://dagu.sh/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"dagu\" \"dagucloud/dagu\" \"prebuild\" \"latest\" \"/opt/dagu\" \"dagu_*_linux_amd64.tar.gz\"\n\nmsg_info \"Setting up Dagu\"\nmkdir -p /opt/dagu/data\nmsg_ok \"Set up Dagu\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/dagu.service\n[Unit]\nDescription=Dagu Workflow Engine\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/dagu\nEnvironment=DAGU_HOME=/opt/dagu/data\nEnvironment=DAGU_HOST=0.0.0.0\nEnvironment=DAGU_PORT=8080\nExecStart=/opt/dagu/dagu start-all\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now dagu\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/dashy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://dashy.to/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" NODE_MODULE=\"yarn\" setup_nodejs\nfetch_and_deploy_gh_release \"dashy\" \"Lissy93/dashy\" \"prebuild\" \"latest\" \"/opt/dashy\" \"dashy-*.tar.gz\"\n\nmsg_info \"Installing Dashy\"\ncd /opt/dashy\n$STD yarn install --ignore-engines --network-timeout 300000\nmsg_ok \"Installed Dashy\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/dashy.service\n[Unit]\nDescription=dashy\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dashy\nEnvironment=NODE_OPTIONS=--openssl-legacy-provider\nExecStart=/usr/bin/node server.js\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now dashy\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/databasus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/databasus/databasus\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  valkey \\\n  mariadb-client \\\n  rclone\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nsetup_go\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Installing Database Clients\"\n# Create PostgreSQL version symlinks for compatibility\nfor v in 12 13 14 15 16 18; do\n  ln -sf /usr/lib/postgresql/17 /usr/lib/postgresql/$v\ndone\n# Install MongoDB Database Tools via direct .deb (no APT repo for Debian 13)\n[[ \"$(get_os_info id)\" == \"ubuntu\" ]] && MONGO_DIST=\"ubuntu2204\" || MONGO_DIST=\"debian12\"\nMONGO_VERSION=$(get_latest_gh_tag \"mongodb/mongo-tools\" \"100.\" || echo \"100.16.1\")\nfetch_and_deploy_from_url \"https://fastdl.mongodb.org/tools/db/mongodb-database-tools-${MONGO_DIST}-x86_64-${MONGO_VERSION}.deb\" \"\"\nmkdir -p /usr/local/mongodb-database-tools/bin\n[[ -f /usr/bin/mongodump ]] && ln -sf /usr/bin/mongodump /usr/local/mongodb-database-tools/bin/mongodump\n[[ -f /usr/bin/mongorestore ]] && ln -sf /usr/bin/mongorestore /usr/local/mongodb-database-tools/bin/mongorestore\n# Create MariaDB and MySQL client symlinks for compatibility\nmkdir -p /usr/local/mariadb-{10.6,12.1}/bin /usr/local/mysql-{5.7,8.0,8.4,9}/bin\nfor dir in /usr/local/mariadb-{10.6,12.1}/bin; do\n  ln -sf /usr/bin/mariadb-dump \"$dir/mariadb-dump\"\n  ln -sf /usr/bin/mariadb \"$dir/mariadb\"\ndone\nfor dir in /usr/local/mysql-{5.7,8.0,8.4,9}/bin; do\n  ln -sf /usr/bin/mariadb-dump \"$dir/mysqldump\"\n  ln -sf /usr/bin/mariadb \"$dir/mysql\"\ndone\nmsg_ok \"Installed Database Clients\"\n\nfetch_and_deploy_gh_release \"databasus\" \"databasus/databasus\" \"tarball\" \"latest\" \"/opt/databasus\"\n\nmsg_info \"Building Databasus (Patience)\"\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\ncd /opt/databasus/frontend\n$STD corepack enable\n$STD corepack prepare pnpm@latest --activate\n$STD pnpm install --frozen-lockfile\n$STD pnpm run build\ncd /opt/databasus/backend\n$STD go mod tidy\n$STD go mod download\n$STD go install github.com/swaggo/swag/cmd/swag@latest\n$STD /root/go/bin/swag init -g cmd/main.go -o swagger\n$STD env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o databasus ./cmd/main.go\nmv /opt/databasus/backend/databasus /opt/databasus/databasus\nmkdir -p /databasus-data/{pgdata,temp,backups,data,logs}\nmkdir -p /opt/databasus/ui/build\nmkdir -p /opt/databasus/migrations\ncp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/\ncp -r /opt/databasus/backend/migrations/* /opt/databasus/migrations/\nchown -R postgres:postgres /databasus-data\nmsg_ok \"Built Databasus\"\n\nmsg_info \"Configuring Databasus\"\nJWT_SECRET=$(openssl rand -hex 32)\nENCRYPTION_KEY=$(openssl rand -hex 32)\n# Install goose for migrations\n$STD go install github.com/pressly/goose/v3/cmd/goose@latest\nln -sf /root/go/bin/goose /usr/local/bin/goose\ncat <<EOF >/.env\n# Environment\nENV_MODE=production\n\n# Server\nSERVER_PORT=4005\nSERVER_HOST=0.0.0.0\n\n# Database\nDATABASE_DSN=host=localhost user=postgres password=postgres dbname=databasus port=5432 sslmode=disable\nDATABASE_URL=postgres://postgres:postgres@localhost:5432/databasus?sslmode=disable\n\n# Migrations\nGOOSE_DRIVER=postgres\nGOOSE_DBSTRING=postgres://postgres:postgres@localhost:5432/databasus?sslmode=disable\nGOOSE_MIGRATION_DIR=/opt/databasus/migrations\n\n# Valkey (Redis-compatible cache)\nVALKEY_HOST=localhost\nVALKEY_PORT=6379\n\n# Security\nJWT_SECRET=${JWT_SECRET}\nENCRYPTION_KEY=${ENCRYPTION_KEY}\n\n# Paths\nDATA_DIR=/databasus-data/data\nBACKUP_DIR=/databasus-data/backups\nLOG_DIR=/databasus-data/logs\nEOF\nchmod 600 /.env\nmsg_ok \"Configured Databasus\"\n\nmsg_info \"Configuring Valkey\"\ncat <<EOF >/etc/valkey/valkey.conf\nport 6379\nbind 127.0.0.1\nprotected-mode yes\nsave \"\"\nmaxmemory 256mb\nmaxmemory-policy allkeys-lru\nEOF\nsystemctl enable -q --now valkey-server\nsystemctl restart valkey-server\nmsg_ok \"Configured Valkey\"\n\nmsg_info \"Creating Database\"\n# Configure PostgreSQL to allow local password auth for databasus\nPG_HBA=\"/etc/postgresql/17/main/pg_hba.conf\"\nif ! grep -q \"databasus\" \"$PG_HBA\"; then\n  sed -i '/^local\\s*all\\s*all/i local   databasus   postgres                                trust' \"$PG_HBA\"\n  sed -i '/^host\\s*all\\s*all\\s*127/i host    databasus   postgres        127.0.0.1/32            trust' \"$PG_HBA\"\n  systemctl reload postgresql\nfi\n$STD sudo -u postgres psql -c \"CREATE DATABASE databasus;\" 2>/dev/null || true\n$STD sudo -u postgres psql -c \"ALTER USER postgres WITH SUPERUSER CREATEROLE CREATEDB;\" 2>/dev/null || true\nmsg_ok \"Created Database\"\n\nmsg_info \"Creating Databasus Service\"\ncat <<EOF >/etc/systemd/system/databasus.service\n[Unit]\nDescription=Databasus - Database Backup Management\nAfter=network.target postgresql.service valkey.service\nRequires=postgresql.service valkey.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/databasus\nEnvironmentFile=/.env\nExecStart=/opt/databasus/databasus\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl daemon-reload\n$STD systemctl enable -q --now databasus\nmsg_ok \"Created Databasus Service\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/databasus\nserver {\n    listen 80;\n    server_name _;\n\n    location / {\n        proxy_pass http://127.0.0.1:4005;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade \\$http_upgrade;\n        proxy_set_header Connection 'upgrade';\n        proxy_set_header Host \\$host;\n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n        proxy_cache_bypass \\$http_upgrade;\n        proxy_buffering off;\n        proxy_read_timeout 86400s;\n        proxy_send_timeout 86400s;\n    }\n}\nEOF\nln -sf /etc/nginx/sites-available/databasus /etc/nginx/sites-enabled/databasus\nrm -f /etc/nginx/sites-enabled/default\n$STD nginx -t\n$STD systemctl enable -q --now nginx\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/dawarich-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Freika/dawarich\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  cmake \\\n  git \\\n  imagemagick \\\n  libffi-dev \\\n  libgeos-dev \\\n  libgeos++-dev \\\n  libjemalloc2 \\\n  libjemalloc-dev \\\n  libmagickwand-dev \\\n  libpq-dev \\\n  libssl-dev \\\n  libvips-dev \\\n  libxml2-dev \\\n  libxslt-dev \\\n  libyaml-dev \\\n  nginx \\\n  redis-server\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" PG_MODULES=\"postgis-3\" setup_postgresql\nPG_DB_NAME=\"dawarich_db\" PG_DB_USER=\"dawarich\" PG_DB_EXTENSIONS=\"postgis\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"dawarich\" \"Freika/dawarich\" \"tarball\" \"latest\" \"/opt/dawarich/app\"\n\nmsg_info \"Setting up Directories\"\nmkdir -p /opt/dawarich/app/{storage,log,tmp/pids,tmp/cache,tmp/sockets}\nmsg_ok \"Set up Directories\"\n\nmsg_info \"Configuring Environment\"\nSECRET_KEY_BASE=$(openssl rand -hex 64)\nOTP_ENCRYPTION_PRIMARY_KEY=$(openssl rand -hex 64)\nOTP_ENCRYPTION_DETERMINISTIC_KEY=$(openssl rand -hex 64)\nOTP_ENCRYPTION_KEY_DERIVATION_SALT=$(openssl rand -hex 64)\nRELEASE=$(get_latest_github_release \"Freika/dawarich\")\ncat <<EOF >/opt/dawarich/.env\nRAILS_ENV=production\nSECRET_KEY_BASE=${SECRET_KEY_BASE}\nOTP_ENCRYPTION_PRIMARY_KEY=${OTP_ENCRYPTION_PRIMARY_KEY}\nOTP_ENCRYPTION_DETERMINISTIC_KEY=${OTP_ENCRYPTION_DETERMINISTIC_KEY}\nOTP_ENCRYPTION_KEY_DERIVATION_SALT=${OTP_ENCRYPTION_KEY_DERIVATION_SALT}\nDATABASE_HOST=localhost\nDATABASE_USERNAME=${PG_DB_USER}\nDATABASE_PASSWORD=${PG_DB_PASS}\nDATABASE_NAME=${PG_DB_NAME}\nREDIS_URL=redis://127.0.0.1:6379/0\nBACKGROUND_PROCESSING_CONCURRENCY=10\nAPPLICATION_HOST=${LOCAL_IP}\nAPPLICATION_HOSTS=${LOCAL_IP},localhost\nTIME_ZONE=UTC\nDISABLE_TELEMETRY=true\nAPP_VERSION=${RELEASE}\nEOF\nmsg_ok \"Configured Environment\"\n\nNODE_VERSION=\"22\" setup_nodejs\nRUBY_VERSION=$(cat /opt/dawarich/app/.ruby-version 2>/dev/null || echo \"3.4.6\")\nRUBY_VERSION=${RUBY_VERSION} RUBY_INSTALL_RAILS=\"false\" setup_ruby\n\nmsg_info \"Installing Dawarich\"\ncd /opt/dawarich/app\nsource /root/.profile\nexport PATH=\"/root/.rbenv/shims:/root/.rbenv/bin:$PATH\"\neval \"$(/root/.rbenv/bin/rbenv init - bash)\"\nset -a && source /opt/dawarich/.env && set +a\n$STD gem install bundler\n$STD bundle config set --local deployment 'true'\n$STD bundle config set --local without 'development test'\n$STD bundle install\nif [[ -f /opt/dawarich/package.json ]]; then\n  cd /opt/dawarich\n  $STD npm install\n  cd /opt/dawarich/app\nelif [[ -f /opt/dawarich/app/package.json ]]; then\n  $STD npm install\nfi\n$STD bundle exec rake assets:precompile\n$STD bundle exec rails db:schema:load\n$STD bundle exec rails db:seed || msg_warn \"Database seed failed (upstream rgeo-geojson issue), app will still work\"\n$STD bundle exec rake data:migrate\nmsg_ok \"Installed Dawarich\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/dawarich-web.service\n[Unit]\nDescription=Dawarich Web Server\nAfter=network.target postgresql.service redis-server.service\nRequires=postgresql.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dawarich/app\nEnvironmentFile=/opt/dawarich/.env\nExecStart=/root/.rbenv/shims/bundle exec puma -C config/puma.rb\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/dawarich-worker.service\n[Unit]\nDescription=Dawarich Sidekiq Worker\nAfter=network.target postgresql.service redis-server.service\nRequires=postgresql.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dawarich/app\nEnvironmentFile=/opt/dawarich/.env\nExecStart=/root/.rbenv/shims/bundle exec sidekiq -C config/sidekiq.yml\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now redis-server dawarich-web dawarich-worker\nmsg_ok \"Created Services\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/dawarich.conf\nupstream dawarich {\n    server 127.0.0.1:3000;\n}\n\nserver {\n    listen 80;\n    server_name _;\n\n    root /opt/dawarich/app/public;\n    client_max_body_size 100M;\n\n    location ~ ^/(assets|packs)/ {\n        expires max;\n        add_header Cache-Control \"public, immutable\";\n        try_files \\$uri =404;\n    }\n\n    location / {\n        try_files \\$uri @rails;\n    }\n\n    location @rails {\n        proxy_pass http://dawarich;\n        proxy_http_version 1.1;\n        proxy_set_header Host \\$host;\n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n        proxy_set_header Upgrade \\$http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n        proxy_redirect off;\n        proxy_buffering off;\n    }\n}\nEOF\nln -sf /etc/nginx/sites-available/dawarich.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\nsystemctl enable -q --now nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ddclient-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 mitchscobell\n# Author: mitchscobell\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ddclient.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing ddclient\"\nDEBIAN_FRONTEND=noninteractive $STD apt -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" install -y ddclient\nmsg_ok \"Installed ddclient\"\n\nmsg_info \"Creating ddclient service\"\ncat << EOF >/etc/ddclient.conf\nprotocol=namecheap\nuse=web, web=dynamicdns.park-your-domain.com/getip\nprotocol=namecheap\nuse=web, web=dynamicdns.park-your-domain.com/getip\nserver=dynamicdns.park-your-domain.com\nlogin=yourdomain.com\npassword='your-ddns-password'\n@,www\nEOF\nchmod 600 /etc/ddclient.conf\nsystemctl enable -q --now ddclient\nmsg_ok \"Created ddclient service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/debian-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.debian.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/deconz-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.phoscon.de/en/conbee2/software#deconz\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting Phoscon Repository\"\nsetup_deb822_repo \\\n  \"deconz\" \\\n  \"http://phoscon.de/apt/deconz.pub.key\" \\\n  \"http://phoscon.de/apt/deconz\" \\\n  \"generic\"\nmsg_ok \"Setup Phoscon Repository\"\n\nmsg_info \"Installing deConz\"\ncurl -fsSL \"http://ports.ubuntu.com/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_arm64.deb\" -o $(basename \"http://ports.ubuntu.com/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_arm64.deb\")\n$STD dpkg -i libssl1.1_1.1.1f-1ubuntu2.24_arm64.deb\n$STD apt-get update\n$STD apt-get install -y deconz\nrm -rf \"$libssl\"\nmsg_ok \"Installed deConz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/lib/systemd/system/deconz.service\n[Unit]\nDescription=deCONZ: ZigBee gateway -- REST API\nWants=deconz-init.service deconz-update.service\nStartLimitIntervalSec=0\n\n[Service]\nUser=root\nExecStart=/usr/bin/deCONZ -platform minimal --http-port=80\nRestart=on-failure\nRestartSec=30\nAmbientCapabilities=CAP_NET_BIND_SERVICE CAP_KILL CAP_SYS_BOOT CAP_SYS_TIME\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now deconz\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/deluge-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.deluge-torrent.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  python3-pip \\\n  python3-libtorrent \\\n  python3-setuptools\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Deluge\"\nmkdir -p ~/.config/pip\ncat >~/.config/pip/pip.conf <<EOF\n[global]\nbreak-system-packages = true\nEOF\n$STD pip install deluge[all]\nmsg_ok \"Installed Deluge\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/deluged.service\n[Unit]\nDescription=Deluge Bittorrent Client Daemon\nDocumentation=man:deluged\nAfter=network-online.target\n\n[Service]\nType=simple\nUMask=007\nExecStart=/usr/local/bin/deluged -d\nRestart=on-failure\nTimeoutStopSec=300\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/deluge-web.service\n[Unit]\nDescription=Deluge Bittorrent Client Web Interface\nDocumentation=man:deluge-web\nAfter=deluged.service\nWants=deluged.service\n\n[Service]\nType=simple\nUMask=027\nExecStart=/usr/local/bin/deluge-web -d\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q deluged.service deluge-web.service\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/discopanel-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: DragoQC\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://discopanel.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"discopanel\" \"nickheyer/discopanel\" \"prebuild\" \"latest\" \"/opt/discopanel\" \"discopanel-linux-amd64.tar.gz\"\nsetup_docker\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/discopanel.service\n[Unit]\nDescription=DiscoPanel Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/discopanel\nExecStart=/opt/discopanel/discopanel-linux-amd64\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now discopanel\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/dispatcharr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: ekke85\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dispatcharr/Dispatcharr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3-dev \\\n  libpq-dev \\\n  nginx \\\n  redis-server \\\n  ffmpeg \\\n  procps \\\n  vlc-bin \\\n  vlc-plugin-base \\\n  streamlink\nmsg_ok \"Installed Dependencies\"\n\nsetup_uv\nNODE_VERSION=\"24\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"dispatcharr_db\" PG_DB_USER=\"dispatcharr_usr\" setup_postgresql_db\nfetch_and_deploy_gh_release \"dispatcharr\" \"Dispatcharr/Dispatcharr\" \"tarball\"\n\nmsg_info \"Installing Python Dependencies with uv\"\ncd /opt/dispatcharr\n$STD uv venv --clear\n$STD uv sync\n$STD uv pip install gunicorn gevent celery redis daphne\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Configuring Dispatcharr\"\ninstall -d -m 755 \\\n  /data/{logos,recordings,plugins,db} \\\n  /data/uploads/{m3us,epgs} \\\n  /data/{m3us,epgs}\nchown -R root:root /data\nDJANGO_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | cut -c1-50)\nexport DATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\"\nexport POSTGRES_DB=$PG_DB_NAME\nexport POSTGRES_USER=$PG_DB_USER\nexport POSTGRES_PASSWORD=$PG_DB_PASS\nexport POSTGRES_HOST=localhost\nexport DJANGO_SECRET_KEY=$DJANGO_SECRET\n$STD uv run python manage.py migrate --noinput\n$STD uv run python manage.py collectstatic --noinput\ncat <<EOF >/opt/dispatcharr/.env\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\nPOSTGRES_DB=$PG_DB_NAME\nPOSTGRES_USER=$PG_DB_USER\nPOSTGRES_PASSWORD=$PG_DB_PASS\nPOSTGRES_HOST=localhost\nCELERY_BROKER_URL=redis://localhost:6379/0\nDJANGO_SECRET_KEY=$DJANGO_SECRET\nEOF\ncd /opt/dispatcharr/frontend\nnode -e \"const p=require('./package.json');p.overrides=p.overrides||{};p.overrides['webworkify-webpack']='2.1.3';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2));\"\nrm -f package-lock.json\n$STD npm install --no-audit --progress=false\n$STD npm run build\nmsg_ok \"Configured Dispatcharr\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/dispatcharr.conf\nserver {\n    listen 9191;\n    server_name _;\n    client_max_body_size 100M;\n\n    # Serve static assets with correct MIME types\n    location /assets/ {\n        alias /opt/dispatcharr/frontend/dist/assets/;\n        expires 30d;\n        add_header Cache-Control \"public, immutable\";\n\n        # Explicitly set MIME types for webpack-built assets\n        types {\n            text/javascript js;\n            text/css css;\n            image/png png;\n            image/svg+xml svg svgz;\n            font/woff2 woff2;\n            font/woff woff;\n            font/ttf ttf;\n        }\n    }\n\n    location /static/ {\n        alias /opt/dispatcharr/static/;\n        expires 30d;\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    location /media/ {\n        alias /opt/dispatcharr/media/;\n    }\n\n    location /ws/ {\n        proxy_pass http://127.0.0.1:8001;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade \\$http_upgrade;\n        proxy_set_header Connection \"Upgrade\";\n        proxy_set_header Host \\$host;\n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n    }\n\n    # All other requests proxy to Gunicorn\n    location / {\n        include proxy_params;\n        proxy_pass http://127.0.0.1:5656;\n    }\n}\nEOF\nln -sf /etc/nginx/sites-available/dispatcharr.conf /etc/nginx/sites-enabled/dispatcharr.conf\nrm -f /etc/nginx/sites-enabled/default\nsystemctl restart nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/opt/dispatcharr/start-gunicorn.sh\n#!/usr/bin/env bash\ncd /opt/dispatcharr\nset -a\nsource .env\nset +a\nexec uv run gunicorn \\\\\n    --workers=4 \\\\\n    --worker-class=gevent \\\\\n    --timeout=300 \\\\\n    --bind 0.0.0.0:5656 \\\\\n    dispatcharr.wsgi:application\nEOF\nchmod +x /opt/dispatcharr/start-gunicorn.sh\n\ncat <<EOF >/opt/dispatcharr/start-celery.sh\n#!/usr/bin/env bash\ncd /opt/dispatcharr\nset -a\nsource .env\nset +a\nexec uv run celery -A dispatcharr worker -l info -c 4\nEOF\nchmod +x /opt/dispatcharr/start-celery.sh\n\ncat <<EOF >/opt/dispatcharr/start-celerybeat.sh\n#!/usr/bin/env bash\ncd /opt/dispatcharr\nset -a\nsource .env\nset +a\nexec uv run celery -A dispatcharr beat -l info\nEOF\nchmod +x /opt/dispatcharr/start-celerybeat.sh\n\ncat <<EOF >/opt/dispatcharr/start-daphne.sh\n#!/usr/bin/env bash\ncd /opt/dispatcharr\nset -a\nsource .env\nset +a\nexec uv run daphne -b 0.0.0.0 -p 8001 dispatcharr.asgi:application\nEOF\nchmod +x /opt/dispatcharr/start-daphne.sh\n\ncat <<EOF >/etc/systemd/system/dispatcharr.service\n[Unit]\nDescription=Dispatcharr Web Server\nAfter=network.target postgresql.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dispatcharr\nExecStart=/opt/dispatcharr/start-gunicorn.sh\nRestart=on-failure\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/dispatcharr-celery.service\n[Unit]\nDescription=Dispatcharr Celery Worker\nAfter=network.target redis-server.service\nRequires=dispatcharr.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dispatcharr\nExecStart=/opt/dispatcharr/start-celery.sh\nRestart=on-failure\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/dispatcharr-celerybeat.service\n[Unit]\nDescription=Dispatcharr Celery Beat Scheduler\nAfter=network.target redis-server.service\nRequires=dispatcharr.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dispatcharr\nExecStart=/opt/dispatcharr/start-celerybeat.sh\nRestart=on-failure\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/dispatcharr-daphne.service\n[Unit]\nDescription=Dispatcharr WebSocket Server (Daphne)\nAfter=network.target\nRequires=dispatcharr.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/dispatcharr\nExecStart=/opt/dispatcharr/start-daphne.sh\nRestart=on-failure\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now dispatcharr dispatcharr-celery dispatcharr-celerybeat dispatcharr-daphne\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/docker-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.docker.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nDOCKER_LATEST_VERSION=$(get_latest_github_release \"moby/moby\")\nPORTAINER_LATEST_VERSION=$(get_latest_github_release \"portainer/portainer\")\nPORTAINER_AGENT_LATEST_VERSION=$(get_latest_github_release \"portainer/agent\")\n\nmsg_info \"Installing Docker $DOCKER_LATEST_VERSION (with Compose, Buildx)\"\nDOCKER_CONFIG_PATH='/etc/docker/daemon.json'\nmkdir -p $(dirname $DOCKER_CONFIG_PATH)\necho -e '{\\n  \"log-driver\": \"journald\"\\n}' >/etc/docker/daemon.json\n$STD sh <(curl -fsSL https://get.docker.com)\nmsg_ok \"Installed Docker $DOCKER_LATEST_VERSION\"\n\nread -r -p \"${TAB3}Would you like to add Portainer (UI)? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Portainer $PORTAINER_LATEST_VERSION\"\n  docker volume create portainer_data >/dev/null\n  $STD docker run -d \\\n    -p 8000:8000 \\\n    -p 9443:9443 \\\n    --name=portainer \\\n    --restart=always \\\n    -v /var/run/docker.sock:/var/run/docker.sock \\\n    -v portainer_data:/data \\\n    portainer/portainer-ce:latest\n  msg_ok \"Installed Portainer $PORTAINER_LATEST_VERSION\"\nelse\n  read -r -p \"${TAB3}Would you like to install the Portainer Agent (for remote management)? <y/N> \" prompt_agent\n  if [[ ${prompt_agent,,} =~ ^(y|yes)$ ]]; then\n    msg_info \"Installing Portainer Agent $PORTAINER_AGENT_LATEST_VERSION\"\n    $STD docker run -d \\\n      -p 9001:9001 \\\n      --name portainer_agent \\\n      --restart=always \\\n      -v /var/run/docker.sock:/var/run/docker.sock \\\n      -v /var/lib/docker/volumes:/var/lib/docker/volumes \\\n      portainer/agent\n    msg_ok \"Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION\"\n  fi\nfi\n\nread -r -p \"${TAB3}Expose Docker TCP socket (insecure) ? [n = No, l = Local only (127.0.0.1), a = All interfaces (0.0.0.0)] <n/l/a>: \" socket_choice\ncase \"${socket_choice,,}\" in\nl)\n  socket=\"tcp://127.0.0.1:2375\"\n  ;;\na)\n  socket=\"tcp://0.0.0.0:2375\"\n  ;;\n*)\n  socket=\"\"\n  ;;\nesac\n\nif [[ -n \"$socket\" ]]; then\n  msg_info \"Enabling Docker TCP socket on $socket\"\n  $STD apt-get install -y jq\n\n  tmpfile=$(mktemp)\n  jq --arg sock \"$socket\" '. + { \"hosts\": [\"unix:///var/run/docker.sock\", $sock] }' /etc/docker/daemon.json >\"$tmpfile\" && mv \"$tmpfile\" /etc/docker/daemon.json\n\n  mkdir -p /etc/systemd/system/docker.service.d\n  cat <<EOF >/etc/systemd/system/docker.service.d/override.conf\n[Service]\nExecStart=\nExecStart=/usr/bin/dockerd\nEOF\n\n  $STD systemctl daemon-reexec\n  $STD systemctl daemon-reload\n\n  if systemctl restart docker; then\n    msg_ok \"Docker TCP socket available on $socket\"\n  else\n    msg_error \"Docker failed to restart. Check journalctl -xeu docker.service\"\n    exit 150\n  fi\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/docmost-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docmost.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  redis \\\n  make\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$(curl -s https://raw.githubusercontent.com/docmost/docmost/main/package.json | jq -r '.packageManager | split(\"@\")[1]')\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"docmost_db\" PG_DB_USER=\"docmost_user\" setup_postgresql_db\nfetch_and_deploy_gh_release \"docmost\" \"docmost/docmost\" \"tarball\"\n\nmsg_info \"Configuring Docmost (Patience)\"\ncd /opt/docmost\n\n# Fix: Docmost EE (audit logs etc.) lives in a git submodule that is NOT\n# included in GitHub tarballs.  The community NoopAuditService exists but\n# is only exported by CoreModule – child modules such as UserModule cannot\n# resolve it.  Making CoreModule @Global() exposes the token app-wide.\nif [[ ! -f /opt/docmost/apps/server/src/ee/ee.module.ts ]] \\\n  && ! grep -q '@Global()' /opt/docmost/apps/server/src/core/core.module.ts 2>/dev/null; then\n  sed -i '/^  Module,$/a\\  Global,' /opt/docmost/apps/server/src/core/core.module.ts\n  sed -i '/^@Module({$/i @Global()' /opt/docmost/apps/server/src/core/core.module.ts\nfi\n\nmv .env.example .env\nmkdir data\nsed -i -e \"s|APP_SECRET=.*|APP_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)|\" \\\n  -e \"s|DATABASE_URL=.*|DATABASE_URL=\\\"postgres://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME?schema=public\\\"|\" \\\n  -e \"s|FILE_UPLOAD_SIZE_LIMIT=.*|FILE_UPLOAD_SIZE_LIMIT=50mb|\" \\\n  -e \"s|DRAWIO_URL=.*|DRAWIO_URL=https://embed.diagrams.net|\" \\\n  -e \"s|DISABLE_TELEMETRY=.*|DISABLE_TELEMETRY=true|\" \\\n  -e \"s|APP_URL=.*|APP_URL=http://$LOCAL_IP:3000|\" \\\n  /opt/docmost/.env\nexport NODE_OPTIONS=\"--max-old-space-size=2048\"\n$STD pnpm install\n$STD pnpm build\nmsg_ok \"Configured Docmost\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/docmost.service\n[Unit]\nDescription=Docmost Service\nAfter=network.target postgresql.service\n\n[Service]\nWorkingDirectory=/opt/docmost\nExecStart=/usr/bin/pnpm start\nRestart=always\nEnvironmentFile=/opt/docmost/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now docmost\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/dolibarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dolibarr/dolibarr/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  php-imap \\\n  debconf-utils\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\n\nmsg_info \"Setting up Database\"\nROOT_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD mariadb -u root -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '$ROOT_PASS'; flush privileges;\"\n{\n  echo \"Dolibarr DB Credentials\"\n  echo \"MariaDB Root Password: $ROOT_PASS\"\n} >>~/dolibarr.creds\nmsg_ok \"Set up database\"\n\nmsg_info \"Setup Dolibarr\"\nBASE=\"https://sourceforge.net/projects/dolibarr/files/Dolibarr%20installer%20for%20Debian-Ubuntu%20(DoliDeb)/\"\nRELEASE=$(curl -fsSL \"$BASE\" | grep -oP '(?<=/Dolibarr%20installer%20for%20Debian-Ubuntu%20%28DoliDeb%29/)\\d+(\\.\\d+)+(?=/)' | sort -V | tail -n1)\nFILE=$(curl -fsSL \"${BASE}${RELEASE}/\" | grep -oP 'dolibarr_[^\"]+_all.deb' | head -n1)\ncurl -fsSL \"https://altushost-swe.dl.sourceforge.net/project/dolibarr/Dolibarr%20installer%20for%20Debian-Ubuntu%20(DoliDeb)/${RELEASE}/${FILE}?viasf=1\" -o \"\"$FILE\"\"\necho \"dolibarr dolibarr/reconfigure-webserver multiselect apache2\" | debconf-set-selections\n$STD apt-get install ./$FILE -y\n$STD apt install -f\nrm -rf ~/$FILE\necho \"${RELEASE}\" >\"/opt/${APPLICATION}_version.txt\"\nmsg_ok \"Setup Dolibarr\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/domain-locker-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Lissy93/domain-locker\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y whois\nmsg_ok \"Installed dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"domainlocker_db\" PG_DB_USER=\"domainlocker\" setup_postgresql_db\nNODE_VERSION=\"22\" setup_nodejs\n\nfetch_and_deploy_gh_release \"domain-locker\" \"Lissy93/domain-locker\" \"tarball\"\n\nmsg_info \"Installing Modules (patience)\"\ncd /opt/domain-locker\n$STD npm install\nmsg_ok \"Installed Modules\"\n\nmsg_info \"Building Domain-Locker (a lot of patience)\"\ncat <<EOF >/opt/domain-locker.env\n# Database connection\nDL_PG_HOST=localhost\nDL_PG_PORT=5432\nDL_PG_USER=$PG_DB_USER\nDL_PG_PASSWORD=$PG_DB_PASS\nDL_PG_NAME=$PG_DB_NAME\n\n# Build + Runtime\nDL_ENV_TYPE=selfHosted\nNITRO_PRESET=node_server\nNODE_ENV=production\nEOF\nset -a\nsource /opt/domain-locker.env\nset +a\n$STD npm run build\nmsg_info \"Built Domain-Locker\"\n\nmsg_info \"Building Database schema\"\nexport PGPASSWORD=\"$DL_PG_PASSWORD\"\n$STD psql -h \"$DL_PG_HOST\" -p \"$DL_PG_PORT\" -U \"$DL_PG_USER\" -d \"$DL_PG_NAME\" -f \"/opt/domain-locker/db/schema.sql\"\nmsg_ok \"Built Database schema\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/domain-locker.service\n[Unit]\nDescription=Domain-Locker Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/domain-locker.env\nWorkingDirectory=/opt/domain-locker\nExecStart=/opt/domain-locker/start.sh\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now domain-locker\nmsg_info \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/domain-monitor-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Hosteroid/domain-monitor\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y --no-install-recommends \\\n  libicu-dev \\\n  libzip-dev \\\n  libpng-dev \\\n  libjpeg62-turbo-dev \\\n  libfreetype6-dev \\\n  libxml2-dev \\\n  libcurl4-openssl-dev \\\n  libonig-dev \\\n  pkg-config\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"domain_monitor\" MARIADB_DB_USER=\"domainmonitor\" setup_mariadb_db\nfetch_and_deploy_gh_release \"domain-monitor\" \"Hosteroid/domain-monitor\" \"prebuild\" \"latest\" \"/opt/domain-monitor\" \"domain-monitor-v*.zip\"\n\nmsg_info \"Setting up Domain Monitor\"\nENC_KEY=$(openssl rand -base64 32 | tr -d '\\n')\ncd /opt/domain-monitor\n$STD composer install\ncp env.example.txt .env\nsed -i -e \"s|^APP_ENV=.*|APP_ENV=production|\" \\\n  -e \"s|^APP_ENCRYPTION_KEY=.*|APP_ENCRYPTION_KEY=$ENC_KEY|\" \\\n  -e \"s|^SESSION_COOKIE_HTTPONLY=.*|SESSION_COOKIE_HTTPONLY=0|\" \\\n  -e \"s|^DB_USERNAME=.*|DB_USERNAME=$MARIADB_DB_USER|\" \\\n  -e \"s|^DB_PASSWORD=.*|DB_PASSWORD=$MARIADB_DB_PASS|\" \\\n  -e \"s|^DB_DATABASE=.*|DB_DATABASE=$MARIADB_DB_NAME|\" .env\necho \"0 0 * * * www-data /usr/bin/php /opt/domain-monitor/cron/check_domains.php\" >>/etc/crontab\n\ncat <<EOF >/etc/apache2/sites-enabled/000-default.conf\n<VirtualHost *:80>\n    ServerName domainmonitor.local\n    DocumentRoot \"/opt/domain-monitor/public\"\n\n    <Directory \"/opt/domain-monitor/public\">\n        AllowOverride All\n        Require all granted\n    </Directory>\n</VirtualHost>\nEOF\nchown -R www-data:www-data /opt/domain-monitor\n$STD a2enmod rewrite headers\n$STD systemctl reload apache2\nmsg_ok \"Setup Domain Monitor\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/donetick-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: fstof\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/donetick/donetick\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"donetick\" \"donetick/donetick\" \"prebuild\" \"latest\" \"/opt/donetick\" \"donetick_Linux_arm64.tar.gz\"\n\nmsg_info \"Setup Donetick\"\ncd /opt/donetick\nTOKEN=$(openssl rand -hex 16)\nsed -i -e \"s/change_this_to_a_secure_random_string_32_characters_long/${TOKEN}/g\" config/selfhosted.yaml\nmsg_ok \"Setup Donetick\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/donetick.service\n[Unit]\nDescription=donetick Service\nAfter=network.target\n\n[Service]\nEnvironment=\"DT_ENV=selfhosted\"\nWorkingDirectory=/opt/donetick\nExecStart=/opt/donetick/donetick\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now donetick\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/dotnetaspwebapi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-9.0&tabs=linux-ubuntu\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get update\n$STD apt-get install -y \\\n  ssh \\\n  software-properties-common\n\n$STD add-apt-repository -y ppa:dotnet/backports\n$STD apt-get install -y \\\n  dotnet-sdk-9.0 \\\n  vsftpd \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nvar_project_name=\"default\"\nread -r -p \"${TAB3}Type the assembly name of the project: \" var_project_name\n\nmsg_info \"Setting up FTP Server\"\nuseradd ftpuser\nFTP_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nusermod --password $(echo ${FTP_PASS} | openssl passwd -1 -stdin) ftpuser\nmkdir -p /var/www/html\nusermod -d /var/www/html ftp\nusermod -d /var/www/html ftpuser\nchown ftpuser /var/www/html\n\nsed -i \"s|#write_enable=YES|write_enable=YES|g\" /etc/vsftpd.conf\nsed -i \"s|#chroot_local_user=YES|chroot_local_user=NO|g\" /etc/vsftpd.conf\n\nsystemctl restart -q vsftpd.service\n\n{\n  echo \"FTP-Credentials\"\n  echo \"Username: ftpuser\"\n  echo \"Password: $FTP_PASS\"\n} >>~/ftp.creds\n\nmsg_ok \"FTP server setup completed\"\n\nmsg_info \"Setting up Nginx Server\"\nrm -f /var/www/html/index.nginx-debian.html\n\nsed \"s/\\$var_project_name/$var_project_name/g\" >myfile <<'EOF' >/etc/nginx/sites-available/default\nmap $http_connection $connection_upgrade {\n  \"~*Upgrade\" $http_connection;\n  default keep-alive;\n}\nserver {\n  listen        80;\n  server_name   $var_project_name.com *.$var_project_name.com;\n  location / {\n      proxy_pass         http://127.0.0.1:5000/;\n      proxy_http_version 1.1;\n      proxy_set_header   Upgrade $http_upgrade;\n      proxy_set_header   Connection $connection_upgrade;\n      proxy_set_header   Host $host;\n      proxy_cache_bypass $http_upgrade;\n      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;\n      proxy_set_header   X-Forwarded-Proto $scheme;\n  }\n}\nEOF\nsystemctl reload nginx\nmsg_ok \"Nginx Server Created\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/kestrel-aspnetapi.service\n[Unit]\nDescription=.NET Web API App running on Linux\n\n[Service]\nWorkingDirectory=/var/www/html\nExecStart=/usr/bin/dotnet /var/www/html/$var_project_name.dll\nRestart=always\n# Restart service after 10 seconds if the dotnet service crashes:\nRestartSec=10\nKillSignal=SIGINT\nSyslogIdentifier=dotnet-${var_project_name}\nUser=root\nEnvironment=ASPNETCORE_ENVIRONMENT=Production\nEnvironment=DOTNET_NOLOGO=true\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now kestrel-aspnetapi\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/drawdb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/drawdb-io/drawdb\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y nginx\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"20\" setup_nodejs\nfetch_and_deploy_gh_tag \"drawdb\" \"drawdb-io/drawdb\" \"latest\" \"/opt/drawdb\"\n\nmsg_info \"Building Frontend\"\ncd /opt/drawdb\n$STD npm ci\nNODE_OPTIONS=\"--max-old-space-size=4096\" $STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Applying crypto.randomUUID Polyfill\"\nsed -i '/<head>/a <script>if(!crypto.randomUUID){crypto.randomUUID=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,function(c){return(c^(crypto.getRandomValues(new Uint8Array(1))[0]&(15>>c/4))).toString(16)})}};</script>' /opt/drawdb/dist/index.html\nmsg_ok \"Applied Polyfill\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/conf.d/drawdb.conf\nserver {\n    listen 3000;\n    server_name _;\n    root /opt/drawdb/dist;\n\n    location / {\n        try_files \\$uri /index.html;\n    }\n}\nEOF\nrm -f /etc/nginx/sites-enabled/default\nsystemctl enable -q --now nginx\nsystemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/drawio-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.drawio.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y tomcat11\nmsg_ok \"Installed Dependencies\"\n\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"drawio\" \"jgraph/drawio\" \"singlefile\" \"latest\" \"/var/lib/tomcat11/webapps\" \"draw.war\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/duplicati-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/duplicati/duplicati/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libice6 \\\n  libsm6 \\\n  libfontconfig1\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"duplicati\" \"duplicati/duplicati\" \"binary\" \"latest\" \"/opt/duplicati\" \"duplicati-*-linux-arm64-gui.deb\"\n\nmsg_info \"Configuring duplicati\"\nDECRYPTKEY=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nADMINPASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n{\n  echo \"Admin password = ${ADMINPASS}\"\n  echo \"Database encryption key = ${DECRYPTKEY}\"\n} >>~/duplicati.creds\nmsg_ok \"Configured duplicati\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/duplicati.service\n[Unit]\nDescription=Duplicati Service\nAfter=network.target\n\n[Service]\nExecStart=/usr/bin/duplicati-server --webservice-interface=any --webservice-password=$ADMINPASS --settings-encryption-key=$DECRYPTKEY\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now duplicati\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ebusd-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joerg Heinemann (heinemannj)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/john30/ebusd\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing ebusd\"\nfetch_and_deploy_gh_release \"ebusd\" \"john30/ebusd\" \"binary\" \"latest\" \"\" \"ebusd-*_arm64-trixie_mqtt1.deb\"\nsystemctl enable -q ebusd\nmsg_ok \"Installed ebusd\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/elementsynapse-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/element-hq/synapse\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  debconf-utils\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n\necho \"${TAB3}It is important to choose the name for your server before you install Synapse, because it cannot be changed later.\"\necho \"${TAB3}The server name determines the “domain” part of user-ids for users on your server: these will all be of the format @user:my.domain.name. It also determines how other matrix servers will reach yours for federation.\"\nread -p \"${TAB3}Please enter the name for your server: \" servername\n\nmsg_info \"Installing Element Synapse\"\nsetup_deb822_repo \"matrix-org\" \\\n  \"https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg\" \\\n  \"https://packages.matrix.org/debian/\" \\\n  \"$(get_os_info codename)\" \\\n  \"main\"\necho \"matrix-synapse-py3 matrix-synapse/server-name string $servername\" | debconf-set-selections\necho \"matrix-synapse-py3 matrix-synapse/report-stats boolean false\" | debconf-set-selections\necho \"exit 101\" >/usr/sbin/policy-rc.d\nchmod +x /usr/sbin/policy-rc.d\n$STD apt install matrix-synapse-py3 -y\nrm -f /usr/sbin/policy-rc.d\nsed -i 's/127.0.0.1/0.0.0.0/g' /etc/matrix-synapse/homeserver.yaml\nsed -i 's/'\\''::1'\\'', //g' /etc/matrix-synapse/homeserver.yaml\nSECRET=$(openssl rand -hex 32)\nADMIN_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\necho \"enable_registration_without_verification: true\" >>/etc/matrix-synapse/homeserver.yaml\necho \"registration_shared_secret: ${SECRET}\" >>/etc/matrix-synapse/homeserver.yaml\n\ncat <<EOF >>/etc/matrix-synapse/homeserver.yaml\n\n# MatrixRTC / Element Call configuration\nexperimental_features:\n  msc3266_enabled: true\n  msc4222_enabled: true\n\nmax_event_delay_duration: 24h\n\nrc_message:\n  per_second: 0.5\n  burst_count: 30\n\nrc_delayed_event_mgmt:\n  per_second: 1\n  burst_count: 20\nEOF\nsystemctl enable -q --now matrix-synapse\n$STD register_new_matrix_user -a --user admin --password \"$ADMIN_PASS\" --config /etc/matrix-synapse/homeserver.yaml\n{\n  echo \"Matrix-Credentials\"\n  echo \"Admin username: admin\"\n  echo \"Admin password: $ADMIN_PASS\"\n} >>~/matrix.creds\nsystemctl stop matrix-synapse\nsed -i '34d' /etc/matrix-synapse/homeserver.yaml\nsystemctl start matrix-synapse\nmsg_ok \"Installed Element Synapse\"\n\nfetch_and_deploy_gh_release \"synapse-admin\" \"etkecc/synapse-admin\" \"tarball\"\n\nmsg_info \"Installing Synapse-Admin\"\ncd /opt/synapse-admin\n$STD yarn global add serve\n$STD yarn install --ignore-engines\n$STD yarn build\nmv ./dist ../ &&\n  rm -rf * &&\n  mv ../dist ./\nmsg_ok \"Installed Synapse-Admin\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/synapse-admin.service\n[Unit]\nDescription=Synapse-Admin Service\nAfter=network.target\nRequires=matrix-synapse.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/synapse-admin\nExecStart=/usr/local/bin/serve -s dist -l 5173\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now synapse-admin\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/emby-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://emby.media/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"emby\" \"MediaBrowser/Emby.Releases\" \"binary\"\n\nsetup_hwaccel \"emby\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/emqx-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.emqx.com/en\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y ca-certificates\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Fetching latest EMQX Enterprise version\"\nLATEST_VERSION=$(curl -fsSL https://www.emqx.com/en/downloads/enterprise | grep -oP '/en/downloads/enterprise/v\\K[0-9]+\\.[0-9]+\\.[0-9]+' | sort -V | tail -n1)\nif [[ -z \"$LATEST_VERSION\" ]]; then\n  msg_error \"Failed to determine latest EMQX version\"\n  exit 250\nfi\nmsg_ok \"Latest version: v$LATEST_VERSION\"\n\nDOWNLOAD_URL=\"https://www.emqx.com/en/downloads/enterprise/v$LATEST_VERSION/emqx-enterprise-${LATEST_VERSION}-debian12-arm64.deb\"\nDEB_FILE=\"/tmp/emqx-enterprise-${LATEST_VERSION}-debian12-arm64.deb\"\n\nmsg_info \"Downloading EMQX v$LATEST_VERSION\"\n$STD curl -fsSL -o \"$DEB_FILE\" \"$DOWNLOAD_URL\"\nmsg_ok \"Downloaded EMQX\"\n\nmsg_info \"Installing EMQX\"\n$STD apt install -y \"$DEB_FILE\"\nrm -f \"$DEB_FILE\"\necho \"$LATEST_VERSION\" >~/.emqx\nmsg_ok \"Installed EMQX\"\n\nread -r -p \"${TAB3}Would you like to disable the EMQX MQ feature? (reduces disk/CPU usage) <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Disabling EMQX MQ feature\"\n  mkdir -p /etc/emqx\n  if ! grep -q \"^mq.enable\" /etc/emqx/emqx.conf 2>/dev/null; then\n    echo \"mq.enable = false\" >>/etc/emqx/emqx.conf\n  else\n    sed -i 's/^mq.enable.*/mq.enable = false/' /etc/emqx/emqx.conf\n  fi\n  msg_ok \"Disabled EMQX MQ feature\"\nfi\n\nmsg_info \"Starting EMQX service\"\n$STD systemctl enable -q --now emqx\nmsg_ok \"Enabled EMQX service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n\n"
  },
  {
    "path": "install/endurain-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: johanngrobe\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/joaovitoriasilva/endurain\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y build-essential\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nNODE_VERSION=\"24\" setup_nodejs\nPG_VERSION=\"17\" PG_MODULES=\"postgis\" setup_postgresql\nPG_DB_NAME=\"enduraindb\" PG_DB_USER=\"endurain\" setup_postgresql_db\nfetch_and_deploy_codeberg_release \"endurain\" \"endurain-project/endurain\" \"tarball\" \"latest\" \"/opt/endurain\"\n\nmsg_info \"Setting up Endurain\"\ncd /opt/endurain\nrm -rf \\\n  /opt/endurain/{docs,example.env,screenshot_01.png} \\\n  /opt/endurain/docker* \\\n  /opt/endurain/*.yml\nmkdir -p /opt/endurain_data/{data,logs}\nSECRET_KEY=$(openssl rand -hex 32)\nFERNET_KEY=$(openssl rand -base64 32)\nENDURAIN_HOST=http://${LOCAL_IP}:8080\ncat <<EOF >/opt/endurain/.env\nDB_PASSWORD=${PG_DB_PASS}\n\nSECRET_KEY=${SECRET_KEY}\nFERNET_KEY=${FERNET_KEY}\n\nTZ=Europe/Berlin\nENDURAIN_HOST=${ENDURAIN_HOST}\nBEHIND_PROXY=false\n\nPOSTGRES_DB=${PG_DB_NAME}\nPOSTGRES_USER=${PG_DB_USER}\nPGDATA=/var/lib/postgresql/${PG_DB_NAME}\n\nDB_DATABASE=${PG_DB_NAME}\nDB_USER=${PG_DB_USER}\nDB_PORT=5432\nDB_HOST=localhost\n\nDATABASE_URL=postgresql+psycopg://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\n\nBACKEND_DIR=\"/opt/endurain/backend/app\"\nFRONTEND_DIR=\"/opt/endurain/frontend/app/dist\"\nDATA_DIR=\"/opt/endurain_data/data\"\nLOGS_DIR=\"/opt/endurain_data/logs\"\n\n#SMTP_HOST=smtp.protonmail.ch\n#SMTP_PORT=587\n#SMTP_USERNAME=your-email@example.com\n#SMTP_PASSWORD=your-app-password\n#SMTP_SECURE=true\n#SMTP_SECURE_TYPE=starttls\nEOF\nmsg_ok \"Setup Endurain\"\n\nmsg_info \"Building Frontend\"\ncd /opt/endurain/frontend/app\n$STD npm ci --prefer-offline\n$STD npm run build\ncat <<EOF >/opt/endurain/frontend/app/dist/env.js\nwindow.env = {\n  ENDURAIN_HOST: \"${ENDURAIN_HOST}\"\n}\nEOF\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Setting up Backend\"\ncd /opt/endurain/backend\n$STD uv tool install poetry\n$STD uv tool update-shell\nexport PATH=\"/root/.local/bin:$PATH\"\n$STD poetry self add poetry-plugin-export\n$STD poetry export -f requirements.txt --output requirements.txt --without-hashes\n$STD uv venv --clear\n$STD uv pip install -r requirements.txt\nmsg_ok \"Setup Backend\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/endurain.service\n[Unit]\nDescription=Endurain FastAPI Backend\nAfter=network.target postgresql.service\n\n[Service]\nWorkingDirectory=/opt/endurain/backend/app\nEnvironmentFile=/opt/endurain/.env\nExecStart=/opt/endurain/backend/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080\nRestart=always\nRestartSec=5\nUser=root\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now endurain\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/erpnext-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/frappe/erpnext\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  build-essential \\\n  python3-dev \\\n  libffi-dev \\\n  libssl-dev \\\n  redis-server \\\n  nginx \\\n  supervisor \\\n  fail2ban \\\n  xvfb \\\n  libfontconfig1 \\\n  libxrender1 \\\n  fontconfig \\\n  libjpeg-dev \\\n  libmariadb-dev \\\n  python3-pip\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" NODE_MODULE=\"yarn\" setup_nodejs\nUV_PYTHON=\"3.13\" setup_uv\nsetup_mariadb\n\nmsg_info \"Configuring MariaDB for ERPNext\"\ncat <<EOF >/etc/mysql/mariadb.conf.d/50-erpnext.cnf\n[mysqld]\ncharacter-set-server=utf8mb4\ncollation-server=utf8mb4_unicode_ci\n\n[client]\ndefault-character-set=utf8mb4\nEOF\n$STD systemctl restart mariadb\nmsg_ok \"Configured MariaDB for ERPNext\"\n\nmsg_info \"Installing wkhtmltopdf\"\nWKHTMLTOPDF_URL=\"https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bookworm_amd64.deb\"\n$STD curl -fsSL -o /tmp/wkhtmltox.deb \"$WKHTMLTOPDF_URL\"\n$STD apt install -y /tmp/wkhtmltox.deb\nrm -f /tmp/wkhtmltox.deb\nmsg_ok \"Installed wkhtmltopdf\"\n\nmsg_info \"Installing Frappe Bench\"\nuseradd -m -s /bin/bash frappe\nchown frappe:frappe /opt\necho \"frappe ALL=(ALL) NOPASSWD:ALL\" >/etc/sudoers.d/frappe\n$STD sudo -u frappe bash -c 'export PATH=\"$HOME/.local/bin:$PATH\"; uv tool install frappe-bench'\nmsg_ok \"Installed Frappe Bench\"\n\nmsg_info \"Initializing Frappe Bench\"\nADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nDB_ROOT_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmysql -u root -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '${DB_ROOT_PASS}'; FLUSH PRIVILEGES;\"\n$STD sudo -u frappe bash -c 'export PATH=\"$HOME/.local/bin:$PATH\"; cd /opt && bench init --frappe-branch version-15 frappe-bench'\n$STD sudo -u frappe bash -c 'export PATH=\"$HOME/.local/bin:$PATH\"; cd /opt/frappe-bench && bench get-app erpnext --branch version-15'\n\nmsg_info \"Starting Redis Services for Site Setup\"\n$STD sudo -u frappe bash -c 'redis-server /opt/frappe-bench/config/redis_queue.conf --daemonize yes'\n$STD sudo -u frappe bash -c 'redis-server /opt/frappe-bench/config/redis_cache.conf --daemonize yes'\nsleep 3\nmsg_ok \"Started Redis Services for Site Setup\"\n\n$STD sudo -u frappe bash -c \"export PATH=\\\"\\$HOME/.local/bin:\\$PATH\\\"; cd /opt/frappe-bench && bench new-site site1.local --db-root-username root --db-root-password \\\"$DB_ROOT_PASS\\\" --admin-password \\\"$ADMIN_PASS\\\" --install-app erpnext --set-default\"\nmsg_ok \"Initialized Frappe Bench\"\n\nmsg_info \"Configuring ERPNext\"\ncat <<EOF >/opt/frappe-bench/.env\nADMIN_PASSWORD=${ADMIN_PASS}\nDB_ROOT_PASSWORD=${DB_ROOT_PASS}\nSITE_NAME=site1.local\nEOF\n{\n  echo \"ERPNext Credentials\"\n  echo \"==================\"\n  echo \"Admin Username: Administrator\"\n  echo \"Admin Password: ${ADMIN_PASS}\"\n  echo \"DB Root Password: ${DB_ROOT_PASS}\"\n  echo \"Site Name: site1.local\"\n} >~/erpnext.creds\n$STD systemctl enable --now redis-server\nmsg_ok \"Configured ERPNext\"\n\nmsg_info \"Setting up Production\"\nBENCH_PY=\"/home/frappe/.local/share/uv/tools/frappe-bench/bin/python\"\n$STD sudo -u frappe bash -c \"curl -fsSL https://bootstrap.pypa.io/get-pip.py | \\\"${BENCH_PY}\\\"\"\n$STD sudo -u frappe bash -c 'export PATH=\"$HOME/.local/bin:$PATH\"; uv tool install ansible'\nln -sf /home/frappe/.local/bin/ansible* /usr/local/bin/\n$STD bash -c 'export PATH=\"/home/frappe/.local/bin:$PATH\"; cd /opt/frappe-bench && bench setup production frappe --yes'\nln -sf /opt/frappe-bench/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.conf\n$STD supervisorctl reread\n$STD supervisorctl update\n$STD systemctl enable --now supervisor\nmsg_ok \"Set up Production\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ersatztv-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ersatztv.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_hwaccel\nfetch_and_deploy_gh_release \"ersatztv\" \"ErsatzTV/ErsatzTV\" \"prebuild\" \"latest\" \"/opt/ErsatzTV\" \"*linux-arm64.tar.gz\"\nfetch_and_deploy_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\" \"prebuild\" \"latest\" \"/opt/ErsatzTV-ffmpeg\" \"*-linuxarm64-gpl-7.1.tar.xz\"\n\nmsg_info \"Set ErsatzTV-ffmpeg links\"\nchmod +x /opt/ErsatzTV-ffmpeg/bin/*\nln -sf /opt/ErsatzTV-ffmpeg/bin/ffmpeg /usr/local/bin/ffmpeg\nln -sf /opt/ErsatzTV-ffmpeg/bin/ffplay /usr/local/bin/ffplay\nln -sf /opt/ErsatzTV-ffmpeg/bin/ffprobe /usr/local/bin/ffprobe\nmsg_ok \"ffmpeg links set\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ersatzTV.service\n[Unit]\nDescription=ErsatzTV Service\nAfter=multi-user.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/ErsatzTV\nExecStart=/opt/ErsatzTV/ErsatzTV\nRestart=always\nRestartSec=30\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ersatzTV\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/esphome-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://esphome.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\n\nmsg_info \"Setting up Virtual Environment\"\nmkdir -p /opt/esphome\nmkdir -p /root/config\ncd /opt/esphome\n$STD uv venv --clear /opt/esphome/.venv\n$STD /opt/esphome/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/esphome/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/esphome/.venv/bin/python -m pip install esphome tornado esptool\nmsg_ok \"Setup and Installed ESPHome\"\n\nmsg_info \"Linking esphome to /usr/local/bin\"\nrm -f /usr/local/bin/esphome\nln -s /opt/esphome/.venv/bin/esphome /usr/local/bin/esphome\nmsg_ok \"Linked esphome binary\"\n\nmsg_info \"Creating Service\"\nmkdir -p /root/config\ncat <<EOF >/etc/systemd/system/esphomeDashboard.service\n[Unit]\nDescription=ESPHome Dashboard\nAfter=network.target\n\n[Service]\nExecStart=/opt/esphome/.venv/bin/esphome dashboard /root/config/\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now esphomeDashboard\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/evcc-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/evcc-io/evcc\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up evcc Repository\"\nsetup_deb822_repo \\\n  \"evcc-stable\" \\\n  \"https://dl.evcc.io/public/evcc/stable/gpg.EAD5D0E07B0EC0FD.key\" \\\n  \"https://dl.evcc.io/public/evcc/stable/deb/debian/\" \\\n  \"$(get_os_info codename)\" \\\n  \"main\"\n$STD apt update\nmsg_ok \"evcc Repository setup sucessfully\"\n\nmsg_info \"Installing evcc\"\n$STD apt install -y evcc\nsystemctl enable -q --now evcc\nmsg_ok \"Installed evcc\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/excalidraw-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/excalidraw/excalidraw\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y xdg-utils\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\nfetch_and_deploy_gh_release \"excalidraw\" \"excalidraw/excalidraw\" \"tarball\"\n\nmsg_info \"Configuring Excalidraw\"\ncd /opt/excalidraw\n$STD yarn\nmsg_ok \"Setup Excalidraw\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/excalidraw.service\n[Unit]\nDescription=Excalidraw Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/excalidraw\nExecStart=/usr/bin/yarn start --host\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now excalidraw\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/fhem-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://fhem.de/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y avahi-daemon\nmsg_ok \"Installed Dependencies\"\n\nsetup_deb822_repo \\\n  \"fhem\" \\\n  \"https://debian.fhem.de/archive.key\" \\\n  \"https://debian.fhem.de/nightly/\" \\\n  \"/\" \\\n  \" \"\n\nmsg_info \"Setting up FHEM\"\n$STD apt install -y fhem\nmsg_ok \"Setup FHEM\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/fileflows-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kkroboth\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://fileflows.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ffmpeg \\\n  pciutils \\\n  imagemagick\nmsg_ok \"Installed Dependencies\"\n\nsetup_hwaccel\n\nmsg_info \"Installing ASP.NET Core Runtime\"\ncurl -SL -o aspnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.16/aspnetcore-runtime-8.0.16-linux-arm64.tar.gz\n$STD mkdir -p /usr/share/dotnet\n$STD tar -zxf aspnet.tar.gz -C /usr/share/dotnet\nln -s /usr/share/dotnet/dotnet /usr/bin/dotnet\n$STD rm -f aspnet.tar.gz\nmsg_ok \"Installed ASP.NET Core Runtime\"\n\nfetch_and_deploy_from_url \"https://fileflows.com/downloads/zip\" \"/opt/fileflows\"\n\n$STD ln -svf /usr/bin/ffmpeg /usr/local/bin/ffmpeg\n$STD ln -svf /usr/bin/ffprobe /usr/local/bin/ffprobe\n$STD rm -rf /opt/fileflows/Server/runtimes/win-*\n\nread -r -p \"${TAB3}Do you want to install FileFlows Server or Node? (S/N): \" install_server\n\nif [[ \"$install_server\" =~ ^[Ss]$ ]]; then\n  msg_info \"Installing FileFlows Server\"\n  cd /opt/fileflows/Server\n  $STD dotnet FileFlows.Server.dll --systemd install --root true\n  systemctl enable -q --now fileflows\n  msg_ok \"Installed FileFlows Server\"\nelse\n  msg_info \"Installing FileFlows Node\"\n  cd /opt/fileflows/Node\n  $STD dotnet FileFlows.Node.dll\n  $STD dotnet FileFlows.Node.dll --systemd install --root true\n  systemctl enable -q --now fileflows-node\n  msg_ok \"Installed FileFlows Node\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/firefly-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: quantumryuu | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://firefly-iii.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.5\" PHP_APACHE=\"YES\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"firefly\" MARIADB_DB_USER=\"firefly\" setup_mariadb_db\n\nfetch_and_deploy_gh_release \"firefly\" \"firefly-iii/firefly-iii\" \"prebuild\" \"latest\" \"/opt/firefly\" \"FireflyIII-*.zip\"\nfetch_and_deploy_gh_release \"dataimporter\" \"firefly-iii/data-importer\" \"prebuild\" \"latest\" \"/opt/firefly/dataimporter\" \"DataImporter-v*.tar.gz\"\n\nmsg_info \"Configuring Firefly III (Patience)\"\nchown -R www-data:www-data /opt/firefly\nchmod -R 775 /opt/firefly/storage\ncd /opt/firefly\ncp .env.example .env\nsed -i \"s/DB_HOST=.*/DB_HOST=localhost/\" /opt/firefly/.env\nsed -i \"s/DB_PASSWORD=.*/DB_PASSWORD=$MARIADB_DB_PASS/\" /opt/firefly/.env\n$STD composer install --no-dev --no-plugins --no-interaction\n$STD php artisan firefly:upgrade-database\n$STD php artisan firefly:correct-database\n$STD php artisan firefly:report-integrity\n$STD php artisan firefly:laravel-passport-keys\nmsg_ok \"Configured Firefly III\"\n\nmsg_info \"Configuring Data Importer\"\ncp /opt/firefly/dataimporter/.env.example /opt/firefly/dataimporter/.env\nsed -i \"s#FIREFLY_III_URL=#FIREFLY_III_URL=http://${LOCAL_IP}#g\" /opt/firefly/dataimporter/.env\nchown -R www-data:www-data /opt/firefly\nmsg_ok \"Configured Data Importer\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/firefly.conf\n<VirtualHost *:80>\n  ServerAdmin webmaster@localhost\n  DocumentRoot /opt/firefly/public/\n\n   <Directory /opt/firefly/public>\n        Options FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n  \n  RedirectMatch 301 ^/dataimporter$ /dataimporter/\n\n  Alias /dataimporter/ /opt/firefly/dataimporter/public/\n\n    <Directory /opt/firefly/dataimporter/public/>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n    <FilesMatch \\.php$>\n        SetHandler application/x-httpd-php\n    </FilesMatch>\n\n    ErrorLog /var/log/apache2/error.log\n    CustomLog /var/log/apache2/access.log combined\n\n</VirtualHost>\nEOF\nchown www-data:www-data /opt/firefly/storage/oauth-*.key\n$STD a2enmod php8.5\n$STD a2enmod rewrite\n$STD a2ensite firefly.conf\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/fireshare-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ShaneIsrael/fireshare\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  build-essential \\\n  cmake \\\n  pkg-config \\\n  yasm \\\n  nasm \\\n  libx264-dev \\\n  libx265-dev \\\n  libvpx-dev \\\n  libaom-dev \\\n  libopus-dev \\\n  libvorbis-dev \\\n  libass-dev \\\n  libfreetype6-dev \\\n  libmp3lame-dev \\\n  nginx-extras \\\n  supervisor \\\n  libldap2-dev \\\n  libsasl2-dev \\\n  libssl-dev \\\n  libffi-dev \\\n  libc-dev\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=24 setup_nodejs\nPYTHON_VERSION=3.14 setup_uv\n\nfetch_and_deploy_gh_release \"fireshare\" \"ShaneIsrael/fireshare\" \"tarball\"\n\nmsg_info \"Compiling SVT-AV1 (Patience)\"\ncd /tmp\n$STD git clone --depth 1 --branch v1.8.0 https://gitlab.com/AOMediaCodec/SVT-AV1.git\ncd SVT-AV1/Build\n$STD cmake .. -G \"Unix Makefiles\" -DCMAKE_BUILD_TYPE=Release\n$STD make -j$(nproc)\n$STD make install\nmsg_ok \"Compiled SVT-AV1\"\n\nmsg_info \"Installing NVDEC headers\"\ncd /tmp\n$STD git clone --depth 1 --branch n12.1.14.0 https://github.com/FFmpeg/nv-codec-headers.git\ncd nv-codec-headers\n$STD make install\n$STD ldconfig\nmsg_ok \"Installed NVDEC headers\"\n\nmsg_info \"Compiling ffmpeg (Patience)\"\ncd /tmp\ncurl -fsSL https://ffmpeg.org/releases/ffmpeg-6.1.tar.xz -o \"ffmpeg-6.1.tar.xz\"\n$STD tar -xf ffmpeg-6.1.tar.xz\ncd ffmpeg-6.1\n$STD ./configure \\\n  --prefix=/usr/local \\\n  --enable-gpl \\\n  --enable-version3 \\\n  --enable-nonfree \\\n  --enable-ffnvcodec \\\n  --enable-libx264 \\\n  --enable-libx265 \\\n  --enable-libvpx \\\n  --enable-libaom \\\n  --enable-libopus \\\n  --enable-libvorbis \\\n  --enable-libmp3lame \\\n  --enable-libass \\\n  --enable-libfreetype \\\n  --enable-libsvtav1 \\\n  --disable-debug \\\n  --disable-doc\n$STD make -j$(nproc)\n$STD make install\n$STD ldconfig\nmsg_ok \"Compiled ffmpeg\"\n\nmsg_info \"Configuring Fireshare (Patience)\"\nADMIN_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nSECRET=$(openssl rand -base64 48)\nmkdir -p /opt/fireshare-{data,videos,images,processed}\ncd /opt/fireshare\n$STD uv venv\n$STD .venv/bin/python -m ensurepip --upgrade\n$STD .venv/bin/python -m pip install --upgrade --break-system-packages pip\nln -sf /usr/local/bin/ffmpeg /usr/bin/ffmpeg\nln -sf /usr/local/bin/ffprobe /usr/bin/ffprobe\necho \"/usr/local/lib\" >/etc/ld.so.conf.d/usr-local.conf\necho \"/usr/local/cuda/lib64\" >>/etc/ld.so.conf.d/usr-local.conf\necho \"/usr/local/nvidia/lib\" >>/etc/ld.so.conf.d/nvidia.conf\necho \"/usr/local/nvidia/lib64\" >>/etc/ld.so.conf.d/nvidia.conf\nldconfig\n$STD .venv/bin/python -m pip install --no-cache-dir --break-system-packages --ignore-installed app/server\ncp .venv/bin/fireshare /usr/local/bin/fireshare\nexport FLASK_APP=\"/opt/fireshare/app/server/fireshare:create_app()\"\nexport DATA_DIRECTORY=/opt/fireshare-data\nexport IMAGE_DIRECTORY=/opt/fireshare-images\nexport VIDEO_DIRECTORY=/opt/fireshare-videos\nexport PROCESSED_DIRECTORY=/opt/fireshare-processed\n$STD uv run flask db upgrade\n\ncat <<EOF >/opt/fireshare/fireshare.env\nFLASK_APP=\"/opt/fireshare/app/server/fireshare:create_app()\"\nDOMAIN=\nENVIRONMENT=production\nDATA_DIRECTORY=/opt/fireshare-data\nIMAGE_DIRECTORY=/opt/fireshare-images\nVIDEO_DIRECTORY=/opt/fireshare-videos\nPROCESSED_DIRECTORY=/opt/fireshare-processed\nTEMPLATE_PATH=/opt/fireshare/app/server/fireshare/templates\nSECRET_KEY=${SECRET}\nADMIN_PASSWORD=${ADMIN_PASSWORD}\nTZ=UTC\nLD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64:/usr/local/lib:/usr/local/cuda/lib64:\\$LD_LIBRARY_PATH\nPATH=/usr/local/bin:$PATH\nENABLE_TRANSCODING=\nTRANSCODE_GPU=\nNVIDIA_DRIVER_CAPABILITIES=\nEOF\n\ncd /opt/fireshare/app/client\n$STD npm install\n$STD npm run build\nsystemctl stop nginx\ncp /opt/fireshare/app/nginx/prod.conf /etc/nginx/nginx.conf\nsed -i 's|root /processed/|root /opt/fireshare-processed/|g' /etc/nginx/nginx.conf\nsed -i 's/^user[[:space:]]\\+nginx;/user  root;/' /etc/nginx/nginx.conf\nsed -i 's|root[[:space:]]\\+/app/build;|root /opt/fireshare/app/client/build;|' /etc/nginx/nginx.conf\nsystemctl start nginx\n\ncat <<EOF >~/fireshare.creds\nFireshare Admin Credentials\n========================\nUsername: admin\nPassword: ${ADMIN_PASSWORD}\nEOF\nmsg_ok \"Configured Fireshare\"\n\nmsg_info \"Creating services\"\ncat <<EOF >/etc/systemd/system/fireshare.service\n[Unit]\nDescription=Fireshare Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/fireshare/app/server\nExecStart=/opt/fireshare/.venv/bin/gunicorn --bind=127.0.0.1:5000 \"fireshare:create_app(init_schedule=True)\" --workers 3 --threads 3 --preload\nRestart=always\nEnvironmentFile=/opt/fireshare/fireshare.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now fireshare\nmsg_ok \"Created services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/fladder-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: wendyliga\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/DonutWare/Fladder\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y nginx\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"Fladder\" \"DonutWare/Fladder\" \"prebuild\" \"latest\" \"/opt/fladder\" \"Fladder-Web-*.zip\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/conf.d/fladder.conf\nserver {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n\n    server_name _;\n\n    root /opt/fladder;\n    index index.html;\n\n    location / {\n        try_files \\$uri \\$uri/ /index.html;\n    }\n}\nEOF\nrm -f /etc/nginx/sites-enabled/default\nrm -f /etc/nginx/sites-available/default\nsystemctl enable -q --now nginx\nsystemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/flaresolverr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/FlareSolverr/FlareSolverr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y apt-transport-https\n$STD apt-get install -y xvfb\n$STD apt-get install -y wget\n$STD apt-get install -y git\n$STD apt-get install -y openssh-server\n$STD apt-get install -y chromium-common\n$STD apt-mark hold chromium\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Chrome\"\nsetup_deb822_repo \\\n  \"google-chrome\" \\\n  \"https://dl.google.com/linux/linux_signing_key.pub\" \\\n  \"https://dl.google.com/linux/chrome/deb/\" \\\n  \"stable\"\n$STD apt update\n$STD apt install -y google-chrome-stable\n# remove google-chrome.list added by google-chrome-stable\nif [ -f /etc/apt/sources.list.d/google-chrome.list ]; then\n  rm /etc/apt/sources.list.d/google-chrome.list\nfi\nmsg_ok \"Installed Chrome\"\n\nfetch_and_deploy_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\" \"prebuild\" \"latest\" \"/opt/flaresolverr\" \"flaresolverr_linux_arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/flaresolverr.service\n[Unit]\nDescription=FlareSolverr\nAfter=network.target\n[Service]\nSyslogIdentifier=flaresolverr\nRestart=always\nRestartSec=5\nType=simple\nEnvironment=\"LOG_LEVEL=info\"\nEnvironment=\"CAPTCHA_SOLVER=none\"\nWorkingDirectory=/opt/flaresolverr\nExecStart=python3 /opt/flaresolverr/src/flaresolverr.py\nTimeoutStopSec=30\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now flaresolverr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/flatnotes-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dullage/flatnotes\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"flatnotes\" \"dullage/flatnotes\" \"tarball\"\nUSE_UVX=\"YES\" setup_uv\nNODE_VERSION=\"22\" setup_nodejs\n\nmsg_info \"Setting up Flatnotes\"\ncd /opt/flatnotes\n$STD /usr/local/bin/uvx migrate-to-uv\n$STD /usr/local/bin/uv sync\nmkdir -p /opt/flatnotes/data\ncd /opt/flatnotes/client\n$STD npm install\n$STD npm run build\n\ncat <<EOF >/opt/flatnotes/.env\nFLATNOTES_AUTH_TYPE='none'\nFLATNOTES_PATH='/opt/flatnotes/data/'\n#FLATNOTES_USERNAME='username'\n#FLATNOTES_PASSWORD='password'\n#FLATNOTES_SECRET_KEY='secret-key'\nEOF\nmsg_ok \"Setup Flatnotes\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/flatnotes.service\n[Unit]\nDescription=Flatnotes\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/flatnotes\nEnvironmentFile=/opt/flatnotes/.env\nExecStart=/opt/flatnotes/.venv/bin/python -m uvicorn main:app --app-dir server --host 0.0.0.0 --port 8080 --proxy-headers\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now flatnotes\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/flowiseai-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://flowiseai.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"20\" NODE_MODULE=\"pnpm\" setup_nodejs\n\nmsg_info \"Installing FlowiseAI (Patience)\"\n$STD pnpm add -g flowise \\\n  @opentelemetry/exporter-trace-otlp-grpc \\\n  @opentelemetry/exporter-trace-otlp-proto \\\n  @opentelemetry/sdk-trace-node \\\n  langchainhub\nmkdir -p /opt/flowiseai\ncurl -fsSL \"https://raw.githubusercontent.com/FlowiseAI/Flowise/main/packages/server/.env.example\" -o \"/opt/flowiseai/.env\"\nmsg_ok \"Installed FlowiseAI\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/flowise.service\n[Unit]\nDescription=FlowiseAI\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/flowiseai/.env\nExecStart=flowise start\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now flowise\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/fluid-calendar-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dotnetfactory/fluid-calendar\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  zip\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"fluiddb\" PG_DB_USER=\"fluiduser\" setup_postgresql_db\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"fluid-calendar\" \"dotnetfactory/fluid-calendar\" \"tarball\"\n\nmsg_info \"Configuring fluid-calendar\"\nNEXTAUTH_SECRET=\"$(openssl rand -base64 44 | tr -dc 'a-zA-Z0-9' | cut -c1-32)\"\necho \"NextAuth Secret: $NEXTAUTH_SECRET\" >>~/$APPLICATION.creds\ncat <<EOF >/opt/fluid-calendar/.env\nDATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\"\n\n# Change the URL below to your external URL\nNEXTAUTH_URL=\"http://localhost:3000\"\nNEXT_PUBLIC_APP_URL=\"http://localhost:3000\"\nNEXTAUTH_SECRET=\"${NEXTAUTH_SECRET}\"\nNEXT_PUBLIC_SITE_URL=\"http://localhost:3000\"\n\nNEXT_PUBLIC_ENABLE_SAAS_FEATURES=false\n\nRESEND_API_KEY=\nRESEND_EMAIL=\nEOF\nexport NEXT_TELEMETRY_DISABLED=1\ncd /opt/fluid-calendar\n$STD npm install --legacy-peer-deps\n$STD npm run prisma:generate\n$STD npx prisma migrate deploy\n$STD npm run build:os\nmsg_ok \"Configured fluid-calendar\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/fluid-calendar.service\n[Unit]\nDescription=Fluid Calendar Application\nAfter=network.target postgresql.service\n\n[Service]\nRestart=always\nWorkingDirectory=/opt/fluid-calendar\nEnvironmentFile=/opt/fluid-calendar/.env\nExecStart=/usr/bin/npm run start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now fluid-calendar\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/foldergram-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/foldergram/foldergram\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y --no-install-recommends ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=25 NODE_MODULE=\"corepack\" setup_nodejs\n\nfetch_and_deploy_gh_release \"foldergram\" \"foldergram/foldergram\" \"tarball\"\n\nmsg_info \"Configuring Foldergram\"\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n$STD corepack enable\ncd /opt/foldergram\n$STD pnpm install\n$STD pnpm run build\nmkdir -p /opt/foldergram_media\ncat <<EOF >/opt/foldergram_media/foldergram.env\nNODE_ENV=production\nSERVER_PORT=4141\nDATA_ROOT=/opt/foldergram_media\nGALLERY_ROOT=/opt/foldergram_media/gallery\nDB_DIR=/opt/foldergram_media/db\nTHUMBNAILS_DIR=/opt/foldergram_media/thumbnails\nPREVIEWS_DIR=/opt/foldergram_media/previews\nIMAGE_DETAIL_SOURCE=preview\nDERIVATIVE_MODE=eager\nGALLERY_EXCLUDED_FOLDERS=\nEOF\nmsg_ok \"Configured Foldergram\"\n\nmsg_info \"Creating services\"\ncat <<EOF >/etc/systemd/system/foldergram.service\n[Unit]\nDescription=Foldergram Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/foldergram\nExecStart=/usr/bin/pnpm start\nRestart=always\nEnvironmentFile=/opt/foldergram_media/foldergram.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now foldergram\nmsg_ok \"Created services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/forgejo-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://forgejo.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n    git \\\n    git-lfs\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_codeberg_release \"forgejo\" \"forgejo/forgejo\" \"singlefile\" \"latest\" \"/opt/forgejo\" \"forgejo-*-linux-arm64\"\nln -sf /opt/forgejo/forgejo /usr/local/bin/forgejo\n\nmsg_info \"Setting up Forgejo\"\n$STD adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git\nmkdir /var/lib/forgejo\nchown git:git /var/lib/forgejo\nchmod 750 /var/lib/forgejo\nmkdir /etc/forgejo\nchown root:git /etc/forgejo\nchmod 770 /etc/forgejo\nmsg_ok \"Setup Forgejo\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/forgejo.service\n[Unit]\nDescription=Forgejo\nAfter=syslog.target\nAfter=network.target\n[Service]\nRestartSec=2s\nType=simple\nUser=git\nGroup=git\nWorkingDirectory=/var/lib/forgejo/ \nExecStart=/usr/local/bin/forgejo web --config /etc/forgejo/app.ini\nRestart=always\nEnvironment=USER=git HOME=/home/git FORGEJO_WORK_DIR=/var/lib/forgejo\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now forgejo\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/freepbx-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Arian Nasr (arian-nasr)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.freepbx.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing FreePBX (Patience)\"\ncurl -fsSL https://github.com/FreePBX/sng_freepbx_debian_install/raw/master/sng_freepbx_debian_install.sh -o /opt/sng_freepbx_debian_install.sh\n$STD bash /opt/sng_freepbx_debian_install.sh\nrm /opt/sng_freepbx_debian_install.sh\nmsg_ok \"Installed FreePBX\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/freshrss-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/FreshRSS/FreshRSS\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"freshrss\" PG_DB_USER=\"freshrss_usr\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"freshrss\" \"FreshRSS/FreshRSS\" \"tarball\"\n\nmsg_info \"Configuring FreshRSS\"\ncd /opt/freshrss\nchown -R www-data:www-data /opt/freshrss\nchmod -R g+rX /opt/freshrss\nchmod -R g+w /opt/freshrss/data/\nmsg_ok \"Configured FreshRSS\"\n\nmsg_info \"Setting up cron job for feed refresh\"\ncat <<EOF >/etc/cron.d/freshrss-actualize\n*/15 * * * * www-data /bin/php -f /opt/freshrss/app/actualize_script.php > /tmp/FreshRSS.log 2>&1\nEOF\nchmod 644 /etc/cron.d/freshrss-actualize\nmsg_ok \"Set up Cron - if you need to modify the timing edit file /etc/cron.d/freshrss-actualize\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/freshrss.conf\n<VirtualHost *:80>\n    ServerName freshrss\n    DocumentRoot /opt/freshrss/p\n\n    <Directory /opt/freshrss/p>\n        Options FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/freshrss_error.log\n    CustomLog /var/log/apache2/freshrss_access.log combined\n\n    AllowEncodedSlashes On\n</VirtualHost>\nEOF\n$STD a2ensite freshrss\n$STD a2enmod rewrite deflate expires headers mime setenvif\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/frigate-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://frigate.video/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsource /etc/os-release\nif [[ \"$VERSION_ID\" != \"12\" ]]; then\n  msg_error \"Frigate requires Debian 12 (Bookworm) due to Python 3.11 dependencies\"\n  exit 238\nfi\n\nmsg_info \"Converting APT sources to DEB822 format\"\nif [ -f /etc/apt/sources.list ]; then\n  cat >/etc/apt/sources.list.d/debian.sources <<'EOF'\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bookworm\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bookworm-updates\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://security.debian.org\nSuites: bookworm-security\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n  mv /etc/apt/sources.list /etc/apt/sources.list.bak\n  $STD apt update\nfi\nmsg_ok \"Converted APT sources\"\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  xz-utils \\\n  python3 \\\n  python3-dev \\\n  python3-pip \\\n  gcc \\\n  pkg-config \\\n  libhdf5-dev \\\n  build-essential \\\n  automake \\\n  libtool \\\n  ccache \\\n  libusb-1.0-0-dev \\\n  apt-transport-https \\\n  cmake \\\n  git \\\n  libgtk-3-dev \\\n  libavcodec-dev \\\n  libavformat-dev \\\n  libswscale-dev \\\n  libv4l-dev \\\n  libxvidcore-dev \\\n  libx264-dev \\\n  libjpeg-dev \\\n  libpng-dev \\\n  libtiff-dev \\\n  gfortran \\\n  openexr \\\n  libssl-dev \\\n  libtbbmalloc2 \\\n  libtbb-dev \\\n  libdc1394-dev \\\n  libopenexr-dev \\\n  libgstreamer-plugins-base1.0-dev \\\n  libgstreamer1.0-dev \\\n  tclsh \\\n  libopenblas-dev \\\n  liblapack-dev \\\n  libgomp1 \\\n  make \\\n  moreutils\nmsg_ok \"Installed Dependencies\"\n\nsetup_hwaccel\n\nARCH=$(dpkg --print-architecture 2>/dev/null || uname -m)\nTARGETARCH=\"amd64\"\n[[ \"$ARCH\" == \"arm64\" || \"$ARCH\" == \"aarch64\" ]] && TARGETARCH=\"arm64\"\nexport TARGETARCH\nexport CCACHE_DIR=/root/.ccache\nexport CCACHE_MAXSIZE=2G\nexport APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn\nexport PIP_BREAK_SYSTEM_PACKAGES=1\nexport NVIDIA_VISIBLE_DEVICES=all\nexport NVIDIA_DRIVER_CAPABILITIES=\"compute,video,utility\"\nexport TOKENIZERS_PARALLELISM=true\nexport TRANSFORMERS_NO_ADVISORY_WARNINGS=1\nexport OPENCV_FFMPEG_LOGLEVEL=8\nexport PYTHONWARNINGS=\"ignore:::numpy.core.getlimits\"\nexport HAILORT_LOGGER_PATH=NONE\nexport TF_CPP_MIN_LOG_LEVEL=3\nexport TF_CPP_MIN_VLOG_LEVEL=3\nexport TF_ENABLE_ONEDNN_OPTS=0\nexport AUTOGRAPH_VERBOSITY=0\nexport GLOG_minloglevel=3\nexport GLOG_logtostderr=0\n\nfetch_and_deploy_gh_release \"frigate\" \"blakeblackshear/frigate\" \"tarball\" \"v0.17.1\" \"/opt/frigate\"\n\nmsg_info \"Building Nginx\"\n$STD bash /opt/frigate/docker/main/build_nginx.sh\nsed -e '/s6-notifyoncheck/ s/^#*/#/' -i /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run\nln -sf /usr/local/nginx/sbin/nginx /usr/local/bin/nginx\nmsg_ok \"Built Nginx\"\n\nmsg_info \"Building SQLite Extensions\"\n$STD bash /opt/frigate/docker/main/build_sqlite_vec.sh\nmsg_ok \"Built SQLite Extensions\"\n\nfetch_and_deploy_gh_release \"go2rtc\" \"AlexxIT/go2rtc\" \"singlefile\" \"latest\" \"/usr/local/go2rtc/bin\" \"go2rtc_linux_${TARGETARCH}\"\n\nmsg_info \"Installing Tempio\"\nsed -i 's|/rootfs/usr/local|/usr/local|g' /opt/frigate/docker/main/install_tempio.sh\n$STD bash /opt/frigate/docker/main/install_tempio.sh\nln -sf /usr/local/tempio/bin/tempio /usr/local/bin/tempio\nmsg_ok \"Installed Tempio\"\n\nmsg_info \"Building libUSB\"\nfetch_and_deploy_gh_release \"libusb\" \"libusb/libusb\" \"tarball\" \"v1.0.26\" \"/opt/libusb\"\ncd /opt/libusb\n$STD ./bootstrap.sh\n$STD ./configure CC='ccache gcc' CCX='ccache g++' --disable-udev --enable-shared\n$STD make -j \"$(nproc)\"\ncd /opt/libusb/libusb\nmkdir -p /usr/local/lib /usr/local/include/libusb-1.0 /usr/local/lib/pkgconfig\n$STD bash ../libtool --mode=install /usr/bin/install -c libusb-1.0.la /usr/local/lib\ninstall -c -m 644 libusb.h /usr/local/include/libusb-1.0\ncd /opt/libusb/\ninstall -c -m 644 libusb-1.0.pc /usr/local/lib/pkgconfig\nldconfig\nmsg_ok \"Built libUSB\"\n\nmsg_info \"Bootstrapping pip\"\ncurl_with_retry \"https://bootstrap.pypa.io/get-pip.py\" \"/tmp/get-pip.py\"\nsed -i 's/args.append(\"setuptools\")/args.append(\"setuptools==77.0.3\")/' /tmp/get-pip.py\n$STD python3 /tmp/get-pip.py \"pip\"\nrm -f /tmp/get-pip.py\nmsg_ok \"Bootstrapped pip\"\n\nmsg_info \"Installing Python Dependencies\"\n$STD pip3 install -r /opt/frigate/docker/main/requirements.txt\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Building Python Wheels (Patience)\"\nmkdir -p /wheels\n$STD bash /opt/frigate/docker/main/build_pysqlite3.sh\nfor i in {1..3}; do\n  $STD pip3 wheel --wheel-dir=/wheels -r /opt/frigate/docker/main/requirements-wheels.txt --default-timeout=300 --retries=3 && break\n  [[ $i -lt 3 ]] && sleep 10\ndone\nmsg_ok \"Built Python Wheels\"\n\nNODE_VERSION=\"20\" setup_nodejs\n\nmsg_info \"Downloading Inference Models\"\nmkdir -p /models /openvino-model\ncurl_with_retry \"https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite\" \"/edgetpu_model.tflite\"\ncurl_with_retry \"https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite\" \"/models/cpu_model.tflite\"\ncp /opt/frigate/labelmap.txt /labelmap.txt\nmsg_ok \"Downloaded Inference Models\"\n\nmsg_info \"Downloading Audio Model\"\ncurl_with_retry \"https://www.kaggle.com/api/v1/models/google/yamnet/tfLite/classification-tflite/1/download\" \"/tmp/yamnet.tar.gz\"\n$STD tar xzf /tmp/yamnet.tar.gz -C /\nmv /1.tflite /cpu_audio_model.tflite\ncp /opt/frigate/audio-labelmap.txt /audio-labelmap.txt\nrm -f /tmp/yamnet.tar.gz\nmsg_ok \"Downloaded Audio Model\"\n\nmsg_info \"Installing OpenVino\"\n$STD pip3 install -r /opt/frigate/docker/main/requirements-ov.txt\nmsg_ok \"Installed OpenVino\"\n\nmsg_info \"Building OpenVino Model\"\ncd /models\ncurl_with_retry \"http://download.tensorflow.org/models/object_detection/ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz\" \"ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz\"\n$STD tar -zxf ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz --no-same-owner\nif python3 /opt/frigate/docker/main/build_ov_model.py &>/dev/null; then\n  mkdir -p /openvino-model\n  cp /models/ssdlite_mobilenet_v2.xml /openvino-model/\n  cp /models/ssdlite_mobilenet_v2.bin /openvino-model/\n  OV_LABELS=$(python3 -c \"import omz_tools; import os; print(os.path.join(omz_tools.__path__[0], 'data/dataset_classes/coco_91cl_bkgr.txt'))\" 2>/dev/null)\n  if [[ -n \"$OV_LABELS\" && -f \"$OV_LABELS\" ]]; then\n    ln -sf \"$OV_LABELS\" /openvino-model/coco_91cl_bkgr.txt\n  else\n    OV_LABELS=$(find /usr/local/lib -name \"coco_91cl_bkgr.txt\" 2>/dev/null | head -1)\n    if [[ -n \"$OV_LABELS\" ]]; then\n      ln -sf \"$OV_LABELS\" /openvino-model/coco_91cl_bkgr.txt\n    else\n      curl_with_retry \"https://raw.githubusercontent.com/openvinotoolkit/open_model_zoo/master/data/dataset_classes/coco_91cl_bkgr.txt\" \"/openvino-model/coco_91cl_bkgr.txt\"\n    fi\n  fi\n  sed -i 's/truck/car/g' /openvino-model/coco_91cl_bkgr.txt\n  msg_ok \"Built OpenVino Model\"\nelse\n  msg_warn \"OpenVino build failed (CPU may not support required instructions). Frigate will use CPU model.\"\nfi\n\nmsg_info \"Installing HailoRT Runtime\"\n$STD bash /opt/frigate/docker/main/install_hailort.sh\ncp -a /opt/frigate/docker/main/rootfs/. /\nsed -i '/^.*unset DEBIAN_FRONTEND.*$/d' /opt/frigate/docker/main/install_deps.sh\necho \"libedgetpu1-max libedgetpu/accepted-eula boolean true\" | debconf-set-selections\necho \"libedgetpu1-max libedgetpu/install-confirm-max boolean true\" | debconf-set-selections\necho 'force-overwrite' >/etc/dpkg/dpkg.cfg.d/force-overwrite\n$STD bash /opt/frigate/docker/main/install_deps.sh\nrm -f /etc/dpkg/dpkg.cfg.d/force-overwrite\n$STD pip3 install -U /wheels/*.whl\nldconfig\nmsg_ok \"Installed HailoRT Runtime\"\n\nmsg_info \"Installing MemryX Runtime\"\n$STD bash /opt/frigate/docker/main/install_memryx.sh\nmsg_ok \"Installed MemryX Runtime\"\n\nmsg_info \"Building Frigate Application (Patience)\"\ncd /opt/frigate\n$STD pip3 install -r /opt/frigate/docker/main/requirements-dev.txt\n$STD bash /opt/frigate/.devcontainer/initialize.sh\n$STD make version\ncd /opt/frigate/web\n$STD npm install\n$STD npm run build\nmv /opt/frigate/web/dist/BASE_PATH/monacoeditorwork/* /opt/frigate/web/dist/assets/\nrm -rf /opt/frigate/web/dist/BASE_PATH\ncp -r /opt/frigate/web/dist/* /opt/frigate/web/\nsed -i '/^s6-svc -O \\.$/s/^/#/' /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run\nmsg_ok \"Built Frigate Application\"\n\nmsg_info \"Configuring Frigate\"\nmkdir -p /config /media/frigate\ncp -r /opt/frigate/config/. /config\n\ncurl_with_retry \"https://github.com/intel-iot-devkit/sample-videos/raw/master/person-bicycle-car-detection.mp4\" \"/media/frigate/person-bicycle-car-detection.mp4\"\n\necho \"tmpfs   /tmp/cache      tmpfs   defaults        0       0\" >>/etc/fstab\n\ncat <<EOF >/etc/frigate.env\nDEFAULT_FFMPEG_VERSION=\"7.0\"\nINCLUDED_FFMPEG_VERSIONS=\"7.0:5.0\"\nNVIDIA_VISIBLE_DEVICES=all\nNVIDIA_DRIVER_CAPABILITIES=\"compute,video,utility\"\nTOKENIZERS_PARALLELISM=true\nTRANSFORMERS_NO_ADVISORY_WARNINGS=1\nOPENCV_FFMPEG_LOGLEVEL=8\nPYTHONWARNINGS=\"ignore:::numpy.core.getlimits\"\nHAILORT_LOGGER_PATH=NONE\nTF_CPP_MIN_LOG_LEVEL=3\nTF_CPP_MIN_VLOG_LEVEL=3\nTF_ENABLE_ONEDNN_OPTS=0\nAUTOGRAPH_VERBOSITY=0\nGLOG_minloglevel=3\nGLOG_logtostderr=0\nEOF\n\ncat <<EOF >/config/config.yml\nmqtt:\n  enabled: false\ncameras:\n  test:\n    ffmpeg:\n      inputs:\n        - path: /media/frigate/person-bicycle-car-detection.mp4\n          input_args: -re -stream_loop -1 -fflags +genpts\n          roles:\n            - detect\n    detect:\n      height: 1080\n      width: 1920\n      fps: 5\nauth:\n  enabled: false\ndetect:\n  enabled: false\nEOF\n\nif grep -q -o -m1 -E 'avx[^ ]*|sse4_2' /proc/cpuinfo && [[ -f /openvino-model/ssdlite_mobilenet_v2.xml ]] && [[ -f /openvino-model/coco_91cl_bkgr.txt ]]; then\n  cat <<EOF >>/config/config.yml\nffmpeg:\n  hwaccel_args: auto\ndetectors:\n  detector01:\n    type: openvino\n    device: AUTO\nmodel:\n  width: 300\n  height: 300\n  input_tensor: nhwc\n  input_pixel_format: bgr\n  path: /openvino-model/ssdlite_mobilenet_v2.xml\n  labelmap_path: /openvino-model/coco_91cl_bkgr.txt\nEOF\nelse\n  cat <<EOF >>/config/config.yml\nffmpeg:\n  hwaccel_args: auto\nmodel:\n  path: /models/cpu_model.tflite\nEOF\nfi\nmsg_ok \"Configured Frigate\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/create_directories.service\n[Unit]\nDescription=Create necessary directories for Frigate logs\nBefore=frigate.service go2rtc.service nginx.service\n\n[Service]\nType=oneshot\nExecStart=/bin/bash -c '/bin/mkdir -p /dev/shm/logs/{frigate,go2rtc,nginx} && /bin/touch /dev/shm/logs/{frigate/current,go2rtc/current,nginx/current} && /bin/chmod -R 777 /dev/shm/logs'\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/go2rtc.service\n[Unit]\nDescription=go2rtc streaming service\nAfter=network.target create_directories.service\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nEnvironmentFile=/etc/frigate.env\nExecStartPre=+rm -f /dev/shm/logs/go2rtc/current\nExecStart=/bin/bash -c \"bash /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/go2rtc/run 2> >(/usr/bin/ts '%%Y-%%m-%%d %%H:%%M:%%.S ' >&2) | /usr/bin/ts '%%Y-%%m-%%d %%H:%%M:%%.S '\"\nStandardOutput=file:/dev/shm/logs/go2rtc/current\nStandardError=file:/dev/shm/logs/go2rtc/current\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/frigate.service\n[Unit]\nDescription=Frigate NVR service\nAfter=go2rtc.service create_directories.service\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nEnvironmentFile=/etc/frigate.env\nExecStartPre=+rm -f /dev/shm/logs/frigate/current\nExecStart=/bin/bash -c \"bash /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run 2> >(/usr/bin/ts '%%Y-%%m-%%d %%H:%%M:%%.S ' >&2) | /usr/bin/ts '%%Y-%%m-%%d %%H:%%M:%%.S '\"\nStandardOutput=file:/dev/shm/logs/frigate/current\nStandardError=file:/dev/shm/logs/frigate/current\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/nginx.service\n[Unit]\nDescription=Nginx reverse proxy for Frigate\nAfter=frigate.service create_directories.service\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nExecStartPre=+rm -f /dev/shm/logs/nginx/current\nExecStart=/bin/bash -c \"bash /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run 2> >(/usr/bin/ts '%%Y-%%m-%%d %%H:%%M:%%.S ' >&2) | /usr/bin/ts '%%Y-%%m-%%d %%H:%%M:%%.S '\"\nStandardOutput=file:/dev/shm/logs/nginx/current\nStandardError=file:/dev/shm/logs/nginx/current\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl daemon-reload\nsystemctl enable -q --now create_directories\nsleep 2\nsystemctl enable -q --now go2rtc\nsleep 2\nsystemctl enable -q --now frigate\nsleep 2\nsystemctl enable -q --now nginx\nmsg_ok \"Created Services\"\n\nmsg_info \"Cleaning Up\"\nrm -rf /opt/libusb /wheels /models/*.tar.gz\nmsg_ok \"Cleaned Up\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/fumadocs-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ca-certificates \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\n\nmsg_info \"Preparing Fumadocs - \"\nmkdir -p /opt/fumadocs\ncd /opt/fumadocs\nmsg_ok \"Important: Manual configuration is required after this step.\"\npnpm create fumadocs-app\nPROJECT_NAME=$(find . -maxdepth 1 -type d ! -name '.' ! -name '..' | sed 's|^\\./||')\necho \"$PROJECT_NAME\" >/opt/fumadocs/.projectname\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/fumadocs_$PROJECT_NAME.service\n[Unit]\nDescription=Fumadocs Documentation Server\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/fumadocs/$PROJECT_NAME\nExecStart=/usr/bin/pnpm run dev\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now fumadocs_$PROJECT_NAME\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/garage-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://garagehq.deuxfleurs.fr/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setup Garage\"\nGITEA_RELEASE=$(curl -s https://api.github.com/repos/deuxfleurs-org/garage/tags | jq -r '.[0].name')\ncurl -fsSL \"https://garagehq.deuxfleurs.fr/_releases/${GITEA_RELEASE}/aarch64-unknown-linux-musl/garage\" -o /usr/local/bin/garage\nchmod +x /usr/local/bin/garage\nmkdir -p /var/lib/garage/{data,meta,snapshots}\nmkdir -p /etc/garage\nRPC_SECRET=$(openssl rand -hex 32)\nADMIN_TOKEN=$(openssl rand -base64 32)\nMETRICS_TOKEN=$(openssl rand -base64 32)\n{\n    echo \"Garage Tokens and Secrets\"\n    echo \"RPC Secret: $RPC_SECRET\"\n    echo \"Admin Token: $ADMIN_TOKEN\"\n    echo \"Metrics Token: $METRICS_TOKEN\"\n} >>~/garage.creds\necho $GITEA_RELEASE >>~/.garage\ncat <<EOF >/etc/garage.toml\nmetadata_dir = \"/var/lib/garage/meta\"\ndata_dir = \"/var/lib/garage/data\"\ndb_engine = \"sqlite\"\nreplication_factor = 1\n\nrpc_bind_addr = \"[::]:3901\"\nrpc_public_addr = \"127.0.0.1:3901\"\nrpc_secret = \"${RPC_SECRET}\"\n\n[s3_api]\ns3_region = \"garage\"\napi_bind_addr = \"[::]:3900\"\nroot_domain = \".s3.garage.localhost\"\n\n[s3_web]\nbind_addr = \"[::]:3902\"\nroot_domain = \".web.garage.localhost\"\nindex = \"index.html\"\n\n[k2v_api]\napi_bind_addr = \"[::]:3904\"\n\n[admin]\napi_bind_addr = \"[::]:3903\"\nadmin_token = \"${ADMIN_TOKEN}\"\nmetrics_token = \"${METRICS_TOKEN}\"\nEOF\nmsg_ok \"Set up Garage\"\n\nmsg_info \"Creating service\"\ncat <<'EOF' >/etc/systemd/system/garage.service\n[Unit]\nDescription=Garage Object Storage (Deuxfleurs)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/garage -c /etc/garage.toml server\nRestart=always\nRestartSec=5\nUser=root\nWorkingDirectory=/var/lib/garage\nEnvironment=RUST_LOG=info\nStandardOutput=append:/var/log/garage.log\nStandardError=append:/var/log/garage.log\nLimitNOFILE=65536\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now garage\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gatus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TwiN/gatus\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ca-certificates \\\n  libcap2-bin\nmsg_ok \"Installed Dependencies\"\n\nsetup_go\nfetch_and_deploy_gh_release \"gatus\" \"TwiN/gatus\" \"tarball\"\n\nmsg_info \"Configuring gatus\"\ncd /opt/gatus\n$STD go mod tidy\nCGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus .\nsetcap CAP_NET_RAW+ep gatus\nmv config.yaml config\nmsg_ok \"Configured gatus\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gatus.service\n[Unit]\nDescription=gatus Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/gatus\nExecStart=/opt/gatus/gatus\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gatus\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/geopulse-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/tess1o/geopulse\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  openssl \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" PG_MODULES=\"postgis\" setup_postgresql\nPG_DB_NAME=\"geopulse\" PG_DB_USER=\"geopulse\" PG_DB_EXTENSIONS=\"postgis,postgis_topology\" setup_postgresql_db\n\nmsg_info \"Generating Security Keys\"\nmkdir -p /opt/geopulse/{backend,keys}\nmkdir -p /etc/geopulse /var/www/geopulse /var/lib/geopulse/dumps\nmkdir -p /var/log/geopulse/{backend,nginx}\nopenssl genpkey -algorithm RSA -out /opt/geopulse/keys/jwt-private-key.pem 2>/dev/null\nopenssl rsa -pubout -in /opt/geopulse/keys/jwt-private-key.pem -out /opt/geopulse/keys/jwt-public-key.pem 2>/dev/null\nopenssl rand -base64 32 >/opt/geopulse/keys/ai-encryption-key.txt\nchmod 640 /opt/geopulse/keys/jwt-private-key.pem /opt/geopulse/keys/jwt-public-key.pem /opt/geopulse/keys/ai-encryption-key.txt\nmsg_ok \"Generated Security Keys\"\n\nif [[ \"$(uname -m)\" == \"aarch64\" ]]; then\n  if grep -qi \"raspberry\\|bcm\" /proc/cpuinfo 2>/dev/null; then\n    BINARY_PATTERN=\"geopulse-backend-native-arm64-compat-*\"\n  else\n    BINARY_PATTERN=\"geopulse-backend-native-arm64-[!c]*\"\n  fi\nelse\n  if grep -q avx2 /proc/cpuinfo && grep -q bmi2 /proc/cpuinfo && grep -q fma /proc/cpuinfo; then\n    BINARY_PATTERN=\"geopulse-backend-native-amd64-[!c]*\"\n  else\n    BINARY_PATTERN=\"geopulse-backend-native-amd64-compat-*\"\n  fi\nfi\n\nfetch_and_deploy_gh_release \"geopulse-backend\" \"tess1o/geopulse\" \"singlefile\" \"latest\" \"/opt/geopulse/backend\" \"${BINARY_PATTERN}\"\nfetch_and_deploy_gh_release \"geopulse-frontend\" \"tess1o/geopulse\" \"prebuild\" \"latest\" \"/var/www/geopulse\" \"geopulse-frontend-*.tar.gz\"\n\nmsg_info \"Configuring GeoPulse\"\ncat <<EOF >/etc/geopulse/geopulse.env\nGEOPULSE_PUBLIC_BASE_URL=http://${LOCAL_IP}\nGEOPULSE_UI_URL=http://${LOCAL_IP}\nGEOPULSE_CORS_ENABLED=false\nGEOPULSE_CORS_ORIGINS=\nQUARKUS_HTTP_PORT=8080\nGEOPULSE_POSTGRES_URL=jdbc:postgresql://localhost:5432/${PG_DB_NAME}\nGEOPULSE_POSTGRES_HOST=localhost\nGEOPULSE_POSTGRES_PORT=5432\nGEOPULSE_POSTGRES_DB=${PG_DB_NAME}\nGEOPULSE_POSTGRES_USERNAME=${PG_DB_USER}\nGEOPULSE_POSTGRES_PASSWORD=${PG_DB_PASS}\nGEOPULSE_JWT_PRIVATE_KEY_LOCATION=file:/opt/geopulse/keys/jwt-private-key.pem\nGEOPULSE_JWT_PUBLIC_KEY_LOCATION=file:/opt/geopulse/keys/jwt-public-key.pem\nGEOPULSE_AI_ENCRYPTION_KEY_LOCATION=file:/opt/geopulse/keys/ai-encryption-key.txt\nQUARKUS_LOG_FILE_ENABLE=true\nQUARKUS_LOG_FILE_PATH=/var/log/geopulse/backend/geopulse.log\nQUARKUS_LOG_FILE_ROTATION_MAX_FILE_SIZE=10M\nQUARKUS_LOG_FILE_ROTATION_MAX_BACKUP_INDEX=5\nEOF\nchmod 640 /etc/geopulse/geopulse.env\nmsg_ok \"Configured GeoPulse\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/geopulse-backend.service\n[Unit]\nDescription=GeoPulse Backend\nAfter=network.target postgresql.service\nWants=postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/geopulse/backend\nEnvironmentFile=/etc/geopulse/geopulse.env\nExecStart=/opt/geopulse/backend/geopulse-backend -Dquarkus.http.host=0.0.0.0 -XX:MaximumHeapSizePercent=70 -XX:MaximumYoungGenerationSizePercent=15\nRestart=on-failure\nRestartSec=10\nStandardOutput=append:/var/log/geopulse/backend/geopulse-stdout.log\nStandardError=append:/var/log/geopulse/backend/geopulse-stderr.log\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now geopulse-backend\nmsg_ok \"Created Service\"\n\nmsg_info \"Configuring Nginx\"\nmkdir -p /var/cache/nginx/osm_tiles\ncat <<'EOF' >/etc/nginx/sites-available/geopulse.conf\nproxy_cache_path /var/cache/nginx/osm_tiles levels=1:2 keys_zone=osm_cache:100m max_size=10g inactive=30d use_temp_path=off;\n\nmap $uri $osm_subdomain {\n    ~^/osm/tiles/a/ \"a\";\n    ~^/osm/tiles/b/ \"b\";\n    ~^/osm/tiles/c/ \"c\";\n    default \"a\";\n}\n\nserver {\n    listen 80;\n    server_name _;\n\n    root /var/www/geopulse;\n    index index.html;\n\n    client_max_body_size 100M;\n\n    gzip on;\n    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n    gzip_comp_level 6;\n    gzip_min_length 1000;\n\n    location ~* ^/(?!osm/).*\\.(jpg|jpeg|png|gif|ico|css|js)$ {\n        expires 1y;\n        add_header Cache-Control \"public, max-age=31536000\";\n    }\n\n    location ^~ /osm/tiles/ {\n        resolver 8.8.8.8 valid=300s;\n        resolver_timeout 10s;\n        rewrite ^/osm/tiles/[abc]/(.*)$ /$1 break;\n        proxy_pass https://$osm_subdomain.tile.openstreetmap.org;\n        proxy_cache osm_cache;\n        proxy_cache_key \"$scheme$proxy_host$uri\";\n        proxy_cache_valid 200 30d;\n        proxy_cache_valid 404 1m;\n        proxy_cache_valid 502 503 504 1m;\n        proxy_ignore_headers Cache-Control Expires Set-Cookie;\n        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;\n        proxy_cache_background_update on;\n        proxy_cache_lock on;\n        proxy_set_header Cookie \"\";\n        proxy_set_header Authorization \"\";\n        proxy_set_header User-Agent \"GeoPulse/1.0\";\n        proxy_set_header Host $osm_subdomain.tile.openstreetmap.org;\n        proxy_http_version 1.1;\n        proxy_set_header Connection \"\";\n        proxy_connect_timeout 10s;\n        proxy_read_timeout 10s;\n        expires 30d;\n        add_header Cache-Control \"public, immutable\";\n        add_header X-Cache-Status $upstream_cache_status always;\n    }\n\n    location /api/ {\n        proxy_pass http://localhost:8080/api/;\n        proxy_connect_timeout 3600s;\n        proxy_send_timeout 3600s;\n        proxy_read_timeout 3600s;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location / {\n        try_files $uri $uri/ /index.html;\n    }\n\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header X-XSS-Protection \"1; mode=block\" always;\n\n    access_log /var/log/geopulse/nginx/access.log;\n    error_log /var/log/geopulse/nginx/error.log;\n}\nEOF\nln -sf /etc/nginx/sites-available/geopulse.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\nsystemctl enable -q --now nginx\nsystemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating Admin Helper\"\ncat <<'EOF' >/usr/local/bin/create-geopulse-admin\n#!/usr/bin/env bash\nread -rp \"Enter admin email address: \" ADMIN_EMAIL\nif [[ -z \"$ADMIN_EMAIL\" ]]; then\n  echo \"No email provided. Aborting.\"\n  exit 1\nfi\nsed -i '/^GEOPULSE_ADMIN_EMAIL=/d' /etc/geopulse/geopulse.env\necho \"GEOPULSE_ADMIN_EMAIL=${ADMIN_EMAIL}\" >>/etc/geopulse/geopulse.env\nsystemctl restart geopulse-backend\necho \"Admin email set to '${ADMIN_EMAIL}'. Register with this email in the GeoPulse UI to receive admin privileges.\"\nEOF\nchmod +x /usr/local/bin/create-geopulse-admin\nmsg_ok \"Created Admin Helper\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ghost-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: fabrice1236\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ghost.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  ca-certificates \\\n  libjemalloc2 \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"ghost\" MARIADB_DB_USER=\"ghostuser\" setup_mariadb_db\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\n\nmsg_info \"Installing Ghost CLI\"\n$STD npm install ghost-cli@latest -g\nmsg_ok \"Installed Ghost CLI\"\n\nmsg_info \"Creating Service\"\n$STD adduser --disabled-password --gecos \"Ghost user\" ghost-user\n$STD usermod -aG sudo ghost-user\necho \"ghost-user ALL=(ALL) NOPASSWD:ALL\" | tee /etc/sudoers.d/ghost-user\nmkdir -p /var/www/ghost\nchown -R ghost-user:ghost-user /var/www/ghost\nchmod 775 /var/www/ghost\n$STD sudo -u ghost-user -H sh -c \"cd /var/www/ghost && ghost install --db=mysql --dbhost=localhost --dbuser=$MARIADB_DB_USER --dbpass=$MARIADB_DB_PASS --dbname=$MARIADB_DB_NAME --url=http://localhost:2368 --no-prompt --no-setup-nginx --no-setup-ssl --no-setup-mysql --enable --start --ip 0.0.0.0\"\nrm /etc/sudoers.d/ghost-user\nmsg_ok \"Creating Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ghostfolio-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: lucasfell\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ghostfol.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  openssl \\\n  ca-certificates \\\n  redis-server\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Setting up Database\"\nPG_DB_NAME=\"ghostfolio\" PG_DB_USER=\"ghostfolio\" PG_DB_SCHEMA_PERMS=\"true\" setup_postgresql_db\nREDIS_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nACCESS_TOKEN_SALT=$(openssl rand -base64 32)\nJWT_SECRET_KEY=$(openssl rand -base64 32)\n{\n  echo \"Ghostfolio Credentials\"\n  echo \"Redis Password: $REDIS_PASS\"\n  echo \"Access Token Salt: $ACCESS_TOKEN_SALT\"\n  echo \"JWT Secret Key: $JWT_SECRET_KEY\"\n} >>~/ghostfolio.creds\nmsg_ok \"Set up Database\"\n\nfetch_and_deploy_gh_release \"ghostfolio\" \"ghostfolio/ghostfolio\" \"tarball\" \"latest\" \"/opt/ghostfolio\"\n\nmsg_info \"Setup Ghostfolio\"\nsed -i \"s/# requirepass foobared/requirepass $REDIS_PASS/\" /etc/redis/redis.conf\nsystemctl restart redis-server\ncd /opt/ghostfolio\n$STD npm ci\n$STD npm run build:production\nmsg_ok \"Built Ghostfolio\"\n\necho -e \"\"\nmsg_custom \"🪙\" \"$YW\" \"CoinGecko API keys are optional but provide better cryptocurrency data.\"\nmsg_custom \"🪙\" \"$YW\" \"You can skip this and add them later by editing /opt/ghostfolio/.env\"\necho -e \"\"\nread -rp \"${TAB3}CoinGecko Demo API key (press Enter to skip): \" COINGECKO_DEMO_KEY\nread -rp \"${TAB3}CoinGecko Pro API key (press Enter to skip): \" COINGECKO_PRO_KEY\n\nmsg_info \"Setting up Environment\"\ncat <<EOF >/opt/ghostfolio/.env\nDATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME?connect_timeout=300\nREDIS_HOST=localhost\nREDIS_PORT=6379\nREDIS_PASSWORD=$REDIS_PASS\nACCESS_TOKEN_SALT=$ACCESS_TOKEN_SALT\nJWT_SECRET_KEY=$JWT_SECRET_KEY\nNODE_ENV=production\nPORT=3333\nHOST=0.0.0.0\nTZ=Etc/UTC\nEOF\n\nif [[ -n \"${COINGECKO_DEMO_KEY:-}\" ]]; then\n  echo \"API_KEY_COINGECKO_DEMO=$COINGECKO_DEMO_KEY\" >>/opt/ghostfolio/.env\nfi\n\nif [[ -n \"${COINGECKO_PRO_KEY:-}\" ]]; then\n  echo \"API_KEY_COINGECKO_PRO=$COINGECKO_PRO_KEY\" >>/opt/ghostfolio/.env\nfi\nmsg_ok \"Set up Environment\"\n\nmsg_info \"Running Database Migrations\"\ncd /opt/ghostfolio\n$STD npx prisma migrate deploy\n$STD npx prisma db seed\nmsg_ok \"Database Migrations Complete\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ghostfolio.service\n[Unit]\nDescription=Ghostfolio Investment Tracker\nAfter=network.target postgresql.service redis-server.service\nWants=postgresql.service redis-server.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/ghostfolio/dist/apps/api\nEnvironment=NODE_ENV=production\nEnvironmentFile=/opt/ghostfolio/.env\nExecStart=/usr/bin/node main.js\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now ghostfolio\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gitea-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-author: Rogue-King\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://about.gitea.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  sqlite3\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"gitea\" \"go-gitea/gitea\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"gitea-*-linux-arm64\"\n\nmsg_info \"Configuring Gitea\"\nchmod +x /usr/local/bin/gitea\n$STD adduser --system --group --disabled-password --shell /bin/bash --home /etc/gitea gitea\nmkdir -p /var/lib/gitea/{custom,data,log}\nchown -R gitea:gitea /var/lib/gitea/\nchmod -R 750 /var/lib/gitea/\nchown root:gitea /etc/gitea\nchmod 770 /etc/gitea\nsudo -u gitea ln -s /var/lib/gitea/data/.ssh/ /etc/gitea/.ssh\nmsg_ok \"Configured Gitea\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gitea.service\n[Unit]\nDescription=Gitea (Git with a cup of tea)\nAfter=syslog.target\nAfter=network.target\n\n[Service]\n# Uncomment notify and watchdog if you want to use them\n# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that\n# LimitNOFILE=524288:524288\nRestartSec=2s\nType=simple\n#Type=notify\nUser=gitea\nGroup=gitea\n#The mount point we added to the container\nWorkingDirectory=/var/lib/gitea\n#Create directory in /run\nRuntimeDirectory=gitea\nExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini\nRestart=always\nEnvironment=USER=gitea HOME=/var/lib/gitea/data GITEA_WORK_DIR=/var/lib/gitea\n#WatchdogSec=30s\n#Capabilities to bind to low-numbered ports\n#CapabilityBoundingSet=CAP_NET_BIND_SERVICE\n#AmbientCapabilities=CAP_NET_BIND_SERVICE\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gitea\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gitea-mirror-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/RayLabsHQ/gitea-mirror\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  openssl \\\n  sqlite3 \\\n  unzip \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Bun\"\nexport BUN_INSTALL=/opt/bun\ncurl -fsSL https://bun.sh/install | $STD bash\nln -sf /opt/bun/bin/bun /usr/local/bin/bun\nln -sf /opt/bun/bin/bun /usr/local/bin/bunx\nmsg_ok \"Installed Bun\"\n\nfetch_and_deploy_gh_release \"gitea-mirror\" \"RayLabsHQ/gitea-mirror\" \"tarball\"\n\nmsg_info \"Installing gitea-mirror\"\ncd /opt/gitea-mirror\n$STD bun run setup\n$STD bun run build\nmsg_ok \"Installed gitea-mirror\"\n\nmsg_info \"Creating Services\"\nAPP_SECRET=$(openssl rand -base64 32)\nAPP_VERSION=$(grep -o '\"version\": *\"[^\"]*\"' package.json | cut -d'\"' -f4)\ncat <<EOF >/opt/gitea-mirror.env\n# See here for config options: https://github.com/RayLabsHQ/gitea-mirror/blob/main/docs/ENVIRONMENT_VARIABLES.md\nNODE_ENV=production\nHOST=0.0.0.0\nPORT=4321\nDATABASE_URL=sqlite://data/gitea-mirror.db\nBETTER_AUTH_URL=http://${LOCAL_IP}:4321\nBETTER_AUTH_SECRET=${APP_SECRET}\nnpm_package_version=${APP_VERSION}\nEOF\n\ncat <<EOF >/etc/systemd/system/gitea-mirror.service\n[Unit]\nDescription=Gitea Mirror\nAfter=network.target\n[Service]\nType=simple\nWorkingDirectory=/opt/gitea-mirror\nExecStart=/usr/local/bin/bun dist/server/entry.mjs\nRestart=on-failure\nRestartSec=10\nEnvironmentFile=/opt/gitea-mirror.env\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gitea-mirror\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/github-runner-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.github.com/en/actions/hosting-your-own-runners\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  gh\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Creating runner user (no sudo)\"\nuseradd -m -s /bin/bash runner\nmsg_ok \"Runner user ready\"\n\nfetch_and_deploy_gh_release \"actions-runner\" \"actions/runner\" \"prebuild\" \"latest\" \"/opt/actions-runner\" \"actions-runner-linux-x64-*.tar.gz\"\n\nmsg_info \"Setting ownership for runner user\"\nchown -R runner:runner /opt/actions-runner\nmsg_ok \"Ownership set\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/actions-runner.service\n[Unit]\nDescription=GitHub Actions self-hosted runner\nDocumentation=https://docs.github.com/en/actions/hosting-your-own-runners\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=runner\nWorkingDirectory=/opt/actions-runner\nExecStart=/opt/actions-runner/run.sh\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q actions-runner\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/glance-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/glanceapp/glance\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"glance\" \"glanceapp/glance\" \"prebuild\" \"latest\" \"/opt/glance\" \"glance-linux-arm64.tar.gz\"\n\nmsg_info \"Configuring Glance\"\ncat <<EOF >/opt/glance/glance.yml\npages:\n  - name: Startpage\n    width: slim\n    hide-desktop-navigation: true\n    center-vertically: true\n    columns:\n      - size: full\n        widgets:\n          - type: search\n            autofocus: true\n          - type: bookmarks\n            groups:\n              - title: General\n                links:\n                  - title: Google\n                    url: https://www.google.com/\n                  - title: Helper Scripts\n                    url: https://github.com/community-scripts/ProxmoxVE\nEOF\nmsg_ok \"Configured Glance\"\n\nmsg_info \"Creating Service\"\nservice_path=\"/etc/systemd/system/glance.service\"\necho \"[Unit]\nDescription=Glance Daemon\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/glance\nExecStart=/opt/glance/glance --config /opt/glance/glance.yml\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\" >$service_path\n\nsystemctl enable -q --now glance\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/globaleaks-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Giovanni Pellerano (evilaliv3)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/globaleaks/globaleaks-whistleblowing-software\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setup GlobaLeaks\"\nDISTRO_CODENAME=\"$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\"\ncurl -fsSL https://deb.globaleaks.org/globaleaks.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/globaleaks.gpg\necho \"deb [signed-by=/etc/apt/trusted.gpg.d/globaleaks.gpg] http://deb.globaleaks.org $DISTRO_CODENAME/\" >/etc/apt/sources.list.d/globaleaks.list\necho 'APPARMOR_SANDBOXING=0' >/etc/default/globaleaks\n$STD apt update\n$STD apt -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold install globaleaks\nmsg_ok \"Setup GlobaLeaks\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/glpi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.glpi-project.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  apache2 \\\n  php8.4-{apcu,cli,common,curl,gd,ldap,mysql,xmlrpc,xml,mbstring,bcmath,intl,zip,redis,bz2,soap} \\\n  php-cas \\\n  libapache2-mod-php\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\n\nmsg_info \"Setting up database\"\nDB_NAME=glpi_db\nDB_USER=glpi\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb mysql\n$STD mariadb -u root -e \"CREATE DATABASE $DB_NAME;\"\n$STD mariadb -u root -e \"CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';\"\n$STD mariadb -u root -e \"GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';\"\n$STD mariadb -u root -e \"GRANT SELECT ON \\`mysql\\`.\\`time_zone_name\\` TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;\"\n{\n  echo \"GLPI Database Credentials\"\n  echo \"Database: $DB_NAME\"\n  echo \"Username: $DB_USER\"\n  echo \"Password: $DB_PASS\"\n} >>~/glpi_db.creds\nmsg_ok \"Set up database\"\n\nmsg_info \"Installing GLPi\"\ncd /opt\nRELEASE=$(curl -fsSL https://api.github.com/repos/glpi-project/glpi/releases/latest | grep '\"tag_name\"' | sed -E 's/.*\"tag_name\": \"([^\"]+)\".*/\\1/')\ncurl -fsSL \"https://github.com/glpi-project/glpi/releases/download/${RELEASE}/glpi-${RELEASE}.tgz\" -o $(basename \"https://github.com/glpi-project/glpi/releases/download/${RELEASE}/glpi-${RELEASE}.tgz\")\n$STD tar -xzvf glpi-${RELEASE}.tgz\ncd /opt/glpi\necho \"${RELEASE}\" >/opt/${APPLICATION}_version.txt\nmsg_ok \"Installed GLPi\"\n\nmsg_info \"Setting Downstream file\"\ncat <<EOF >/opt/glpi/inc/downstream.php\n<?php\ndefine('GLPI_CONFIG_DIR', '/etc/glpi/');\nif (file_exists(GLPI_CONFIG_DIR . '/local_define.php')) {\n    require_once GLPI_CONFIG_DIR . '/local_define.php';\n}\nEOF\n\nmv /opt/glpi/config /etc/glpi\nmv /opt/glpi/files /var/lib/glpi\nmv /var/lib/glpi/_log /var/log/glpi\n\ncat <<EOF >/etc/glpi/local_define.php\n<?php\ndefine('GLPI_VAR_DIR', '/var/lib/glpi');\ndefine('GLPI_DOC_DIR', GLPI_VAR_DIR);\ndefine('GLPI_CRON_DIR', GLPI_VAR_DIR . '/_cron');\ndefine('GLPI_DUMP_DIR', GLPI_VAR_DIR . '/_dumps');\ndefine('GLPI_GRAPH_DIR', GLPI_VAR_DIR . '/_graphs');\ndefine('GLPI_LOCK_DIR', GLPI_VAR_DIR . '/_lock');\ndefine('GLPI_PICTURE_DIR', GLPI_VAR_DIR . '/_pictures');\ndefine('GLPI_PLUGIN_DOC_DIR', GLPI_VAR_DIR . '/_plugins');\ndefine('GLPI_RSS_DIR', GLPI_VAR_DIR . '/_rss');\ndefine('GLPI_SESSION_DIR', GLPI_VAR_DIR . '/_sessions');\ndefine('GLPI_TMP_DIR', GLPI_VAR_DIR . '/_tmp');\ndefine('GLPI_UPLOAD_DIR', GLPI_VAR_DIR . '/_uploads');\ndefine('GLPI_CACHE_DIR', GLPI_VAR_DIR . '/_cache');\ndefine('GLPI_LOG_DIR', '/var/log/glpi');\nEOF\nmsg_ok \"Configured Downstream file\"\n\nmsg_info \"Configuring GLPI Database\"\n$STD /usr/bin/php /opt/glpi/bin/console db:install \\\n  --db-host=localhost \\\n  --db-name=$DB_NAME \\\n  --db-user=$DB_USER \\\n  --db-password=$DB_PASS \\\n  --default-language=en_US \\\n  --no-interaction \\\n  --allow-superuser \\\n  --force\nmsg_ok \"Configured GLPI Database\"\n\nmsg_info \"Setting Folder and File Permissions\"\nchown root:root /opt/glpi/ -R\nchown www-data:www-data /etc/glpi -R\nchown www-data:www-data /var/lib/glpi -R\nchown www-data:www-data /var/log/glpi -R\nchown www-data:www-data /opt/glpi/marketplace -Rf\nfind /opt/glpi/ -type f -exec chmod 0644 {} \\;\nfind /opt/glpi/ -type d -exec chmod 0755 {} \\;\nfind /etc/glpi -type f -exec chmod 0644 {} \\;\nfind /etc/glpi -type d -exec chmod 0755 {} \\;\nfind /var/lib/glpi -type f -exec chmod 0644 {} \\;\nfind /var/lib/glpi -type d -exec chmod 0755 {} \\;\nfind /var/log/glpi -type f -exec chmod 0644 {} \\;\nfind /var/log/glpi -type d -exec chmod 0755 {} \\;\nmsg_ok \"Configured Folder and File Permissions\"\n\nmsg_info \"Setup Service\"\ncat <<EOF >/etc/apache2/sites-available/glpi.conf\n<VirtualHost *:80>\n    ServerName localhost\n    DocumentRoot /opt/glpi/public\n\n    <Directory /opt/glpi/public>\n        Require all granted\n        RewriteEngine On\n        RewriteCond %{HTTP:Authorization} ^(.+)$\n        RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n        RewriteCond %{REQUEST_FILENAME} !-f\n        RewriteRule ^(.*)$ index.php [QSA,L]\n    </Directory>\n\n    ErrorLog \\${APACHE_LOG_DIR}/glpi_error.log\n    CustomLog \\${APACHE_LOG_DIR}/glpi_access.log combined\n</VirtualHost>\nEOF\n$STD a2dissite 000-default.conf\n$STD a2enmod rewrite\n$STD a2ensite glpi.conf\nrm -rf /opt/glpi/install/install.php\nrm -rf /opt/glpi-${RELEASE}.tgz\nmsg_ok \"Setup Service\"\n\nmsg_info \"Setup Cronjob\"\necho \"* * * * * php /opt/glpi/front/cron.php\" | crontab -u www-data -\nmsg_ok \"Setup Cronjob\"\n\nmsg_info \"Update PHP Params\"\nPHP_VERSION=$(ls /etc/php/ | grep -E '^[0-9]+\\.[0-9]+$' | head -n 1)\nPHP_INI=\"/etc/php/$PHP_VERSION/apache2/php.ini\"\nsed -i 's/^upload_max_filesize = .*/upload_max_filesize = 20M/' $PHP_INI\nsed -i 's/^post_max_size = .*/post_max_size = 20M/' $PHP_INI\nsed -i 's/^max_execution_time = .*/max_execution_time = 60/' $PHP_INI\nsed -i 's/^max_input_vars = .*/max_input_vars = 5000/' $PHP_INI\nsed -i 's/^memory_limit = .*/memory_limit = 256M/' $PHP_INI\nsed -i 's/^;\\?\\s*session.cookie_httponly\\s*=.*/session.cookie_httponly = On/' $PHP_INI\nsystemctl restart apache2\nmsg_ok \"Update PHP Params\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gluetun-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/qdm12/gluetun\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  openvpn \\\n  wireguard-tools \\\n  iptables\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Configuring iptables\"\n$STD update-alternatives --set iptables /usr/sbin/iptables-legacy\n$STD update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy\nln -sf /usr/sbin/openvpn /usr/sbin/openvpn2.6\nmsg_ok \"Configured iptables\"\n\nsetup_go\n\nfetch_and_deploy_gh_release \"gluetun\" \"qdm12/gluetun\" \"tarball\"\n\nmsg_info \"Building Gluetun\"\ncd /opt/gluetun\n$STD go mod download\nCGO_ENABLED=0 $STD go build -trimpath -ldflags=\"-s -w\" -o /usr/local/bin/gluetun ./cmd/gluetun/\nmsg_ok \"Built Gluetun\"\n\nmsg_info \"Configuring Gluetun\"\nmkdir -p /opt/gluetun-data\ntouch /etc/alpine-release\nln -sf /opt/gluetun-data /gluetun\ncat <<EOF >/opt/gluetun-data/.env\nVPN_SERVICE_PROVIDER=custom\nVPN_TYPE=openvpn\nOPENVPN_CUSTOM_CONFIG=/opt/gluetun-data/custom.ovpn\nOPENVPN_USER=\nOPENVPN_PASSWORD=\nOPENVPN_PROCESS_USER=root\nPUID=0\nPGID=0\nHTTP_CONTROL_SERVER_ADDRESS=:8000\nHTTPPROXY=off\nSHADOWSOCKS=off\nPPROF_ENABLED=no\nPPROF_BLOCK_PROFILE_RATE=0\nPPROF_MUTEX_PROFILE_RATE=0\nPPROF_HTTP_SERVER_ADDRESS=:6060\nFIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on\nHEALTH_SERVER_ADDRESS=127.0.0.1:9999\nDNS_UPSTREAM_RESOLVERS=cloudflare\nLOG_LEVEL=info\nSTORAGE_FILEPATH=/gluetun/servers.json\nPUBLICIP_FILE=/gluetun/ip\nVPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port\nTZ=UTC\nEOF\nmsg_ok \"Configured Gluetun\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gluetun.service\n[Unit]\nDescription=Gluetun VPN Client\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/gluetun-data\nEnvironmentFile=/opt/gluetun-data/.env\nUnsetEnvironment=USER\nExecStartPre=/bin/sh -c 'rm -f /etc/openvpn/target.ovpn'\nExecStart=/usr/local/bin/gluetun\nRestart=on-failure\nRestartSec=5\nAmbientCapabilities=CAP_NET_ADMIN\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gluetun\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/go2rtc-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/AlexxIT/go2rtc\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nUSE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"go2rtc\" \"AlexxIT/go2rtc\" \"singlefile\" \"latest\" \"/opt/go2rtc\" \"go2rtc_linux_arm64\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/go2rtc.service\necho \"[Unit]\nDescription=go2rtc service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/go2rtc\nExecStart=/opt/go2rtc/go2rtc_linux_arm64\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now go2rtc\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gogs-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gogs.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"gogs\" \"gogs/gogs\" \"prebuild\" \"latest\" \"/opt/gogs\" \"gogs_*_linux_amd64.tar.gz\"\n\nmsg_info \"Setting up Gogs\"\nmkdir -p /opt/gogs/{custom/conf,data,log}\nmsg_ok \"Set up Gogs\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gogs.service\n[Unit]\nDescription=Gogs Git Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/gogs\nExecStart=/opt/gogs/gogs web\nRestart=on-failure\nRestartSec=5\nEnvironment=USER=root\nEnvironment=HOME=/root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gogs\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gokapi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Forceu/Gokapi\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"gokapi\" \"Forceu/Gokapi\" \"prebuild\" \"latest\" \"/opt/gokapi\" \"gokapi-linux_arm64.zip\"\n\nmsg_info \"Configuring Gokapi\"\nmkdir -p /opt/gokapi/{data,config}\nchmod +x /opt/gokapi/gokapi-linux_arm64\nmsg_ok \"Configured Gokapi\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gokapi.service\n[Unit]\nDescription=gokapi\n\n[Service]\nType=simple\nEnvironment=GOKAPI_DATA_DIR=/opt/gokapi/data\nEnvironment=GOKAPI_CONFIG_DIR=/opt/gokapi/config\nExecStart=/opt/gokapi/gokapi-linux_arm64\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gokapi\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gotify-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gotify.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"gotify\" \"gotify/server\" \"prebuild\" \"latest\" \"/opt/gotify\" \"gotify-linux-arm64.zip\"\nchmod +x /opt/gotify/gotify-linux-arm64\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gotify.service\n[Unit]\nDescription=Gotify\nRequires=network.target\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/gotify\nExecStart=/opt/gotify/./gotify-linux-arm64\nRestart=always\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gotify\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/grafana-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://grafana.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up Grafana Repository\"\nsetup_deb822_repo \\\n  \"grafana\" \\\n  \"https://apt.grafana.com/gpg.key\" \\\n  \"https://apt.grafana.com\" \\\n  \"stable\" \\\n  \"main\"\nmsg_ok \"Grafana Repository setup sucessfully\"\n\nmsg_info \"Installing Grafana\"\n$STD apt install -y grafana\nsystemctl enable -q --now grafana-server\nmsg_ok \"Installed Grafana\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gramps-web-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.grampsweb.org/ | Github: https://github.com/gramps-project/gramps-web\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  appstream \\\n  build-essential \\\n  ffmpeg \\\n  gettext \\\n  gobject-introspection \\\n  gir1.2-gexiv2-0.10 \\\n  gir1.2-gtk-3.0 \\\n  gir1.2-osmgpsmap-1.0 \\\n  gir1.2-pango-1.0 \\\n  git \\\n  graphviz \\\n  libcairo2-dev \\\n  libgirepository1.0-dev \\\n  libglib2.0-dev \\\n  libicu-dev \\\n  libopencv-dev \\\n  pkg-config \\\n  poppler-utils \\\n  python3-dev \\\n  tesseract-ocr\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nNODE_VERSION=\"22\" setup_nodejs\n\nfetch_and_deploy_gh_release \"gramps-web-api\" \"gramps-project/gramps-web-api\" \"tarball\" \"latest\" \"/opt/gramps-web-api\"\nfetch_and_deploy_gh_release \"gramps-web\" \"gramps-project/gramps-web\" \"tarball\" \"latest\" \"/opt/gramps-web/frontend\"\n\nmsg_info \"Setting up Gramps Web\"\nmkdir -p \\\n  /opt/gramps-web/config \\\n  /opt/gramps-web/data/cache/export \\\n  /opt/gramps-web/data/cache/persistent \\\n  /opt/gramps-web/data/cache/report \\\n  /opt/gramps-web/data/cache/request \\\n  /opt/gramps-web/data/cache/thumbnail \\\n  /opt/gramps-web/data/gramps/grampsdb \\\n  /opt/gramps-web/data/indexdir \\\n  /opt/gramps-web/data/media \\\n  /opt/gramps-web/data/users\n\nSECRET_KEY=\"$(openssl rand -hex 32)\"\ncat <<EOF >/opt/gramps-web/config/config.cfg\nTREE=\"Gramps Web\"\nSECRET_KEY=\"${SECRET_KEY}\"\nBASE_URL=\"http://${LOCAL_IP}:5000\"\nUSER_DB_URI=\"sqlite:////opt/gramps-web/data/users/users.sqlite\"\nSEARCH_INDEX_DB_URI=\"sqlite:////opt/gramps-web/data/indexdir/search_index.db\"\nMEDIA_BASE_DIR=\"/opt/gramps-web/data/media\"\nSTATIC_PATH=\"/opt/gramps-web/frontend/dist\"\nTHUMBNAIL_CACHE_CONFIG={\"CACHE_TYPE\":\"FileSystemCache\",\"CACHE_DIR\":\"/opt/gramps-web/data/cache/thumbnail\",\"CACHE_THRESHOLD\":1000,\"CACHE_DEFAULT_TIMEOUT\":0}\nREQUEST_CACHE_CONFIG={\"CACHE_TYPE\":\"FileSystemCache\",\"CACHE_DIR\":\"/opt/gramps-web/data/cache/request\",\"CACHE_THRESHOLD\":1000,\"CACHE_DEFAULT_TIMEOUT\":0}\nPERSISTENT_CACHE_CONFIG={\"CACHE_TYPE\":\"FileSystemCache\",\"CACHE_DIR\":\"/opt/gramps-web/data/cache/persistent\",\"CACHE_THRESHOLD\":0,\"CACHE_DEFAULT_TIMEOUT\":0}\nREPORT_DIR=\"/opt/gramps-web/data/cache/report\"\nEXPORT_DIR=\"/opt/gramps-web/data/cache/export\"\nEOF\n$STD uv venv -c -p python3.12 /opt/gramps-web/venv\nsource /opt/gramps-web/venv/bin/activate\n$STD uv pip install --no-cache-dir --upgrade pip setuptools wheel\n$STD uv pip install --no-cache-dir gunicorn\n$STD uv pip install --no-cache-dir /opt/gramps-web-api\n\nGRAMPS_VERSION=$(/opt/gramps-web/venv/bin/python3 -c \"import gramps.version; print('%s%s' % (gramps.version.VERSION_TUPLE[0], gramps.version.VERSION_TUPLE[1]))\" 2>/dev/null || echo \"60\")\nGRAMPS_PLUGINS_DIR=\"/opt/gramps-web/data/gramps/gramps${GRAMPS_VERSION}/plugins\"\nmkdir -p \"$GRAMPS_PLUGINS_DIR\"\n\nmsg_info \"Installing Gramps Addons (gramps${GRAMPS_VERSION})\"\n$STD wget -q https://github.com/gramps-project/addons/archive/refs/heads/master.zip -O /tmp/gramps-addons.zip\nfor addon in FilterRules JSON; do\n  unzip -p /tmp/gramps-addons.zip \"addons-master/gramps${GRAMPS_VERSION}/download/${addon}.addon.tgz\" | \\\n    tar -xz -C \"$GRAMPS_PLUGINS_DIR\"\ndone\nrm -f /tmp/gramps-addons.zip\nmsg_ok \"Installed Gramps Addons\"\n\ncd /opt/gramps-web/frontend\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n$STD corepack enable\n$STD npm install\n$STD npm run build\ncd /opt/gramps-web-api\nGRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \\\n  ALEMBIC_CONFIG=/opt/gramps-web-api/alembic.ini \\\n  GRAMPSHOME=/opt/gramps-web/data \\\n  GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \\\n  $STD /opt/gramps-web/venv/bin/python3 -m gramps_webapi user migrate\nmsg_ok \"Set up Gramps Web\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gramps-web.service\n[Unit]\nDescription=Gramps Web Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/gramps-web-api\nEnvironment=GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg\nEnvironment=GRAMPSHOME=/opt/gramps-web/data\nEnvironment=GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb\nEnvironment=PATH=/opt/gramps-web/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=/opt/gramps-web/venv/bin/gunicorn -w 2 -b 0.0.0.0:5000 gramps_webapi.wsgi:app --timeout 120 --limit-request-line 8190\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gramps-web\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/graylog-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://graylog.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nMONGO_VERSION=\"8.2\" setup_mongodb\n\nmsg_info \"Setup Graylog Data Node\"\nPASSWORD_SECRET=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)\ncurl -fsSL \"https://packages.graylog2.org/repo/packages/graylog-7.0-repository_latest.deb\" -o \"graylog-7.0-repository_latest.deb\"\n$STD dpkg -i graylog-7.0-repository_latest.deb\n$STD apt-get update\n$STD apt-get install graylog-datanode -y\nsed -i \"s/password_secret =/password_secret = $PASSWORD_SECRET/g\" /etc/graylog/datanode/datanode.conf\nsystemctl enable -q --now graylog-datanode\nmsg_ok \"Setup Graylog Data Node\"\n\nmsg_info \"Setup ${APPLICATION}\"\n$STD apt-get install graylog-server\nROOT_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)\n{\n  echo \"${APPLICATION} Credentials\"\n  echo \"Admin User: admin\"\n  echo \"Admin Password: ${ROOT_PASSWORD}\"\n} >>~/graylog.creds\nROOT_PASSWORD=$(echo -n $ROOT_PASSWORD | shasum -a 256 | awk '{print $1}')\nsed -i \"s/password_secret =/password_secret = $PASSWORD_SECRET/g\" /etc/graylog/server/server.conf\nsed -i \"s/root_password_sha2 =/root_password_sha2 = $ROOT_PASSWORD/g\" /etc/graylog/server/server.conf\nsed -i 's/#http_bind_address = 127.0.0.1.*/http_bind_address = 0.0.0.0:9000/g' /etc/graylog/server/server.conf\nsystemctl enable -q --now graylog-server\nsleep 5\nsed -i \"s/0\\.0\\.0\\.0:9000/$LOCAL_IP:9000/g\" /var/log/graylog-server/server.log\nmsg_ok \"Setup ${APPLICATION}\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/grist-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: cfurrow | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/gristlabs/grist-core\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  make \\\n  ca-certificates \\\n  python3-venv \\\n  git\nmsg_ok \"Installed Dependencies\"\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn@latest\" setup_nodejs\nfetch_and_deploy_gh_release \"grist\" \"gristlabs/grist-core\" \"tarball\"\n\nmsg_info \"Installing Grist\"\nexport CYPRESS_INSTALL_BINARY=0\nexport NODE_OPTIONS=\"--max-old-space-size=2048\"\ncd /opt/grist\n$STD yarn install\n$STD yarn run build:prod\n$STD yarn run install:python\ncat <<EOF >/opt/grist/.env\nNODE_ENV=production\nGRIST_HOST=0.0.0.0\nEOF\nmsg_ok \"Installed Grist\"\n\nmsg_info \"Create Service\"\ncat <<EOF >/etc/systemd/system/grist.service\n[Unit]\nDescription=Grist\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/grist \nExecStart=/usr/bin/yarn run start:prod\nEnvironmentFile=-/opt/grist/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now grist\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/grocy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://grocy.info/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.5\" PHP_APACHE=\"YES\" setup_php\nfetch_and_deploy_gh_release \"grocy\" \"grocy/grocy\" \"prebuild\" \"latest\" \"/var/www/html\" \"grocy*.zip\"\n\nmsg_info \"Configuring grocy\"\nchown -R www-data:www-data /var/www/html\ncp /var/www/html/config-dist.php /var/www/html/data/config.php\nchmod +x /var/www/html/update.sh\n\ncat <<EOF >/etc/apache2/sites-available/grocy.conf\n<VirtualHost *:80>\n  ServerAdmin webmaster@localhost\n  DocumentRoot /var/www/html/public\n  ErrorLog /var/log/apache2/error.log\n<Directory /var/www/html/public>\n  Options Indexes FollowSymLinks MultiViews\n  AllowOverride All\n  Order allow,deny\n  allow from all\n</Directory>\n</VirtualHost>\nEOF\n$STD a2dissite 000-default.conf\n$STD a2ensite grocy.conf\n$STD a2enmod rewrite\nsystemctl reload apache2\nmsg_ok \"Installed grocy\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/guardian-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: HydroshieldMKII\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/HydroshieldMKII/Guardian\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"guardian\" \"HydroshieldMKII/Guardian\" \"tarball\" \"latest\" \"/opt/guardian\"\n\nmsg_info \"Configuring ${APPLICATION}\"\ncd /opt/guardian/backend\n$STD npm ci\n$STD npm run build\ncd /opt/guardian/frontend\n$STD npm ci\nexport DEPLOYMENT_MODE=standalone\n$STD npm run build\nmsg_ok \"Configured ${APPLICATION}\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/guardian-backend.service\n[Unit]\nDescription=Guardian Backend\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/guardian/backend\nExecStart=/usr/bin/node dist/main.js\nRestart=always\nRestartSec=3\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/guardian-frontend.service\n[Unit]\nDescription=Guardian Frontend\nAfter=guardian-backend.service network.target\nWants=guardian-backend.service\n\n[Service]\nWorkingDirectory=/opt/guardian/frontend\nEnvironment=DEPLOYMENT_MODE=standalone\nExecStart=/usr/bin/npm run start\nRestart=always\nRestartSec=3\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now guardian-backend\nsystemctl enable -q --now guardian-frontend\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/gwn-manager-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.grandstream.com/products/networking-solutions/wi-fi-management/product/gwn-manager\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n    xfonts-utils \\\n    fontconfig\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up GWN Manager (Patience)\"\nRELEASE=$(curl -fsSL https://www.grandstream.com/support/tools#gwntools \\\n  | grep -oP 'https://firmware\\.grandstream\\.com/GWN_Manager-[^\"]+-Ubuntu\\.tar\\.gz')\ndownload_file \"$RELEASE\" \"/tmp/gwnmanager.tar.gz\"\ncd /tmp\ntar -xzf gwnmanager.tar.gz --strip-components=1\n$STD ./install\nmsg_ok \"Setup GWN Manager\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/gwnmanager.service\n[Unit]\nDescription=GWN Manager\nAfter=network.target\nRequires=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/gwn\nExecStart=/gwn/gwn start\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q gwnmanager\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/headscale-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/juanfont/headscale\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"headscale\" \"juanfont/headscale\" \"binary\"\n\nread -r -p \"${TAB3}Would you like to add headscale-admin UI? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  fetch_and_deploy_gh_release \"headscale-admin\" \"GoodiesHQ/headscale-admin\" \"prebuild\" \"latest\" \"/opt/headscale-admin\" \"admin.zip\"\n\n  msg_info \"Configuring headscale-admin\"\n  $STD apt install -y caddy\n  $STD caddy stop\n  rm /etc/caddy/Caddyfile\n  cat <<'EOF' >/etc/caddy/Caddyfile\n:80\n\nredir /admin /admin/\n\nhandle_path /admin/* {\n    root * /opt/headscale-admin\n    encode gzip zstd\n\n    header {\n        X-Content-Type-Options nosniff\n    }\n\n    try_files {path} /opt/headscale-admin/index.html\n    file_server\n}\n\nreverse_proxy localhost:8080\nEOF\n  caddy fmt --overwrite /etc/caddy/Caddyfile\n  systemctl start caddy\n  msg_ok \"Configured headscale-admin\"\nfi\n\nmsg_info \"Starting service\"\nsystemctl enable -q --now headscale\nmsg_ok \"Service started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/healthchecks-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://healthchecks.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  gcc \\\n  python3 \\\n  python3-dev \\\n  python3-venv \\\n  libpq-dev \\\n  libcurl4-openssl-dev \\\n  libssl-dev \\\n  caddy\n\nmkdir -p ~/.config/pip\ncat >~/.config/pip/pip.conf <<EOF\n[global]\nbreak-system-packages = true\nEOF\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=16 setup_postgresql\nPG_DB_NAME=\"healthchecks_db\" PG_DB_USER=\"hc_user\" PG_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13) setup_postgresql_db\n\nmsg_info \"Setup Keys (Admin / Secret)\"\nSECRET_KEY=\"$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)\"\nADMIN_EMAIL=\"admin@community-scripts.org\"\nADMIN_PASSWORD=\"$PG_DB_PASS\"\n{\n  echo \"healthchecks Admin Email: $ADMIN_EMAIL\"\n  echo \"healthchecks Admin Password: $ADMIN_PASSWORD\"\n} >>~/healthchecks.creds\nmsg_ok \"Set up Keys\"\n\nfetch_and_deploy_gh_release \"healthchecks\" \"healthchecks/healthchecks\" \"tarball\"\n\nmsg_info \"Installing Healthchecks (venv)\"\ncd /opt/healthchecks\npython3 -m venv venv\nsource venv/bin/activate\n\n$STD pip install --upgrade pip wheel\n$STD pip install gunicorn -r requirements.txt\nmsg_ok \"Installed Python packages\"\n\ncat <<EOF >/opt/healthchecks/hc/local_settings.py\nDEBUG = False\n\nALLOWED_HOSTS = [\"${LOCAL_IP}\", \"127.0.0.1\", \"localhost\"]\nCSRF_TRUSTED_ORIGINS = [\"http://${LOCAL_IP}\", \"https://${LOCAL_IP}\"]\n\nSECRET_KEY = \"${SECRET_KEY}\"\n\nSITE_ROOT = \"http://${LOCAL_IP}:8000\"\nSITE_NAME = \"MyChecks\"\nDEFAULT_FROM_EMAIL = \"healthchecks@${LOCAL_IP}\"\n\nSTATIC_ROOT = \"/opt/healthchecks/static-collected\"\nCOMPRESS_OFFLINE = True\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.postgresql',\n        'NAME': '${PG_DB_NAME}',\n        'USER': '${PG_DB_USER}',\n        'PASSWORD': '${PG_DB_PASS}',\n        'HOST': '127.0.0.1',\n        'PORT': '5432',\n        'TEST': {'CHARSET': 'UTF8'}\n    }\n}\nEOF\n\nmsg_info \"Running Django setup\"\n$STD python manage.py makemigrations\n$STD python manage.py migrate --noinput\n$STD python manage.py collectstatic --noinput\n$STD python manage.py compress\n\n$STD python manage.py shell <<EOF\nfrom django.contrib.auth import get_user_model\nUser = get_user_model()\nif not User.objects.filter(email=\"${ADMIN_EMAIL}\").exists():\n    User.objects.create_superuser(\"${ADMIN_EMAIL}\", \"${ADMIN_EMAIL}\", \"${ADMIN_PASSWORD}\")\nEOF\nmsg_ok \"Configured Django\"\n\nmsg_info \"Configuring Caddy\"\ncat <<EOF >/etc/caddy/Caddyfile\n{\n    email admin@example.com\n}\n\n${LOCAL_IP} {\n    reverse_proxy 127.0.0.1:8000\n}\nEOF\nmsg_ok \"Configured Caddy\"\n\nmsg_info \"Creating systemd services\"\ncat <<EOF >/etc/systemd/system/healthchecks.service\n[Unit]\nDescription=Healthchecks Service\nAfter=network.target postgresql.service\n\n[Service]\nWorkingDirectory=/opt/healthchecks/\nExecStart=/opt/healthchecks/venv/bin/gunicorn hc.wsgi:application --bind 127.0.0.1:8000\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/healthchecks-sendalerts.service\n[Unit]\nDescription=Healthchecks Sendalerts Service\nAfter=network.target postgresql.service healthchecks.service\n\n[Service]\nWorkingDirectory=/opt/healthchecks/\nExecStart=/opt/healthchecks/venv/bin/python manage.py sendalerts\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now healthchecks healthchecks-sendalerts caddy\nsystemctl reload caddy\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/heimdall-dashboard-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://heimdall.site/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" setup_php\nsetup_composer\nfetch_and_deploy_gh_release \"Heimdall\" \"linuxserver/Heimdall\" \"tarball\"\n\nmsg_info \"Setting up Heimdall-Dashboard\"\ncd /opt/Heimdall\ncp .env.example .env\n$STD php artisan key:generate\nmsg_ok \"Setup Heimdall-Dashboard\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/heimdall.service\n[Unit]\nDescription=Heimdall\nAfter=network.target\n\n[Service]\nRestart=always\nRestartSec=5\nType=simple\nUser=root\nWorkingDirectory=/opt/Heimdall\nExecStart=/usr/bin/php artisan serve --port 7990 --host 0.0.0.0\nTimeoutStopSec=30\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now heimdall\ncd /opt/Heimdall\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD composer dump-autoload\nsystemctl restart heimdall.service\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/hev-socks5-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: miviro\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/heiher/hev-socks5-server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"hev-socks5-server\" \"heiher/hev-socks5-server\" \"singlefile\" \"latest\" \"/opt\" \"hev-socks5-server-linux-arm64\"\n\nmsg_info \"Setup hev-socks5-server\"\nmkdir -p /etc/hev-socks5-server\ndownload_file \"https://raw.githubusercontent.com/heiher/hev-socks5-server/refs/heads/main/conf/main.yml\" \"/etc/hev-socks5-server/main.yml\"\nsed -i 's/^#auth:/auth:/; s/^# file: conf\\/auth.txt/  file: \\/root\\/hev.creds/'  /etc/hev-socks5-server/main.yml\nPASSWORD=$(openssl rand -base64 16)\necho \"admin $PASSWORD 0\" >/root/hev.creds\nmsg_ok \"Setup hev-socks5-server\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/hev-socks5-server.service\n[Unit]\nDescription=hev-socks5-server Service\nAfter=network.target\n\n[Service]\nExecStart=/opt/hev-socks5-server /etc/hev-socks5-server/main.yml\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now hev-socks5-server\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/hivemq-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.hivemq.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"21\" setup_java\nfetch_and_deploy_gh_release \"hivemq\" \"hivemq/hivemq-community-edition\" \"prebuild\" \"latest\" \"/opt/hivemq\" \"hivemq-ce-*.zip\"\n\nmsg_info \"Configuring HiveMQ CE\"\nuseradd -d /opt/hivemq hivemq\nchown -R hivemq:hivemq /opt/hivemq\nchmod +x /opt/hivemq/bin/run.sh\ncp /opt/hivemq/bin/init-script/hivemq.service /etc/systemd/system/hivemq.service\nrm /opt/hivemq/conf/config.xml\nmv /opt/hivemq/conf/examples/configuration/config-sample-tcp-and-websockets.xml /opt/hivemq/conf/config.xml\nmsg_ok \"Configured HiveMQ CE\"\n\nmsg_info \"Starting service\"\nsystemctl enable -q --now hivemq\nmsg_ok \"Service started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/homarr-labs/homarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  redis-server \\\n  nginx \\\n  gettext \\\n  openssl\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=$(curl -s https://raw.githubusercontent.com/homarr-labs/homarr/dev/package.json | jq -r '.engines.node | split(\">=\")[1] | split(\".\")[0]')\nsetup_nodejs\nfetch_and_deploy_gh_release \"homarr\" \"homarr-labs/homarr\" \"prebuild\" \"latest\" \"/opt/homarr\" \"build-debian-arm64.tar.gz\"\n\nmsg_info \"Installing Homarr\"\nmkdir -p /opt/homarr_db\ntouch /opt/homarr_db/db.sqlite\nSECRET_ENCRYPTION_KEY=\"$(openssl rand -hex 32)\"\ncd /opt/homarr\ncat <<EOF >/opt/homarr.env\nDB_DRIVER='better-sqlite3'\nDB_DIALECT='sqlite'\nSECRET_ENCRYPTION_KEY='${SECRET_ENCRYPTION_KEY}'\nDB_URL='/opt/homarr_db/db.sqlite'\nTURBO_TELEMETRY_DISABLED=1\nAUTH_PROVIDERS='credentials'\nNODE_ENV='production'\nREDIS_IS_EXTERNAL='true'\nEOF\nmsg_ok \"Installed Homarr\"\n\nmsg_info \"Copying config files\"\nmkdir -p /appdata/redis\nchown -R redis:redis /appdata/redis\nchmod 744 /appdata/redis\ncp /opt/homarr/redis.conf /etc/redis/redis.conf\nsed -i -e '$a\\' /etc/redis/redis.conf\ngrep -q '^bind 127.0.0.1 -::1$' /etc/redis/redis.conf || echo \"bind 127.0.0.1 -::1\" >>/etc/redis/redis.conf\nrm /etc/nginx/nginx.conf\nmkdir -p /etc/nginx/templates\ncp /opt/homarr/nginx.conf /etc/nginx/templates/nginx.conf\necho $'#!/bin/bash\\ncd /opt/homarr/apps/cli && node ./cli.cjs \"$@\"' >/usr/bin/homarr\nchmod +x /usr/bin/homarr\nmsg_ok \"Copied config files\"\n\nmsg_info \"Creating Services\"\nmkdir -p /etc/systemd/system/redis-server.service.d/\ncat <<EOF >/etc/systemd/system/redis-server.service.d/override.conf\n[Service]\nReadWritePaths=-/appdata/redis -/var/lib/redis -/var/log/redis -/var/run/redis -/etc/redis\nEOF\ncat <<EOF >/etc/systemd/system/homarr.service\n[Unit]\nRequires=redis-server.service\nAfter=redis-server.service\nDescription=Homarr Service\nAfter=network.target\n\n[Service]\nType=exec\nWorkingDirectory=/opt/homarr\nEnvironmentFile=-/opt/homarr.env\nExecStart=/opt/homarr/run.sh\n\n[Install]\nWantedBy=multi-user.target\nEOF\nchmod +x /opt/homarr/run.sh\nsystemctl daemon-reload\nsystemctl enable -q --now redis-server\nsystemctl enable -q --now homarr\nsystemctl disable -q --now nginx\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homeassistant-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.home-assistant.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setup Python3\"\n$STD apt install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip \\\n  python3-venv\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nmsg_info \"Installing runlike\"\n$STD pip install runlike\nmsg_ok \"Installed runlike\"\n\nget_latest_release() {\n  curl -fsSL https://api.github.com/repos/$1/releases/latest | grep '\"tag_name\":' | cut -d'\"' -f4\n}\n\nDOCKER_LATEST_VERSION=$(get_latest_release \"moby/moby\")\nCORE_LATEST_VERSION=$(get_latest_release \"home-assistant/core\")\nPORTAINER_LATEST_VERSION=$(get_latest_release \"portainer/portainer\")\n\nmsg_info \"Installing Docker $DOCKER_LATEST_VERSION\"\nDOCKER_CONFIG_PATH='/etc/docker/daemon.json'\nmkdir -p $(dirname $DOCKER_CONFIG_PATH)\necho -e '{\\n  \"log-driver\": \"journald\"\\n}' >/etc/docker/daemon.json\n$STD sh <(curl -fsSL https://get.docker.com)\nmsg_ok \"Installed Docker $DOCKER_LATEST_VERSION\"\n\nmsg_info \"Pulling Portainer $PORTAINER_LATEST_VERSION Image\"\n$STD docker pull portainer/portainer-ce:latest\nmsg_ok \"Pulled Portainer $PORTAINER_LATEST_VERSION Image\"\n\nmsg_info \"Installing Portainer $PORTAINER_LATEST_VERSION\"\n$STD docker volume create portainer_data\n$STD docker run -d \\\n  -p 8000:8000 \\\n  -p 9443:9443 \\\n  --name=portainer \\\n  --restart=always \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v portainer_data:/data \\\n  portainer/portainer-ce:latest\nmsg_ok \"Installed Portainer $PORTAINER_LATEST_VERSION\"\n\nmsg_info \"Pulling Home Assistant $CORE_LATEST_VERSION Image\"\n$STD docker pull ghcr.io/home-assistant/home-assistant:stable\nmsg_ok \"Pulled Home Assistant $CORE_LATEST_VERSION Image\"\n\nmsg_info \"Installing Home Assistant $CORE_LATEST_VERSION\"\n$STD docker volume create hass_config\n$STD docker run -d \\\n  --name homeassistant \\\n  --privileged \\\n  --restart unless-stopped \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v /dev:/dev \\\n  -v hass_config:/config \\\n  -v /etc/localtime:/etc/localtime:ro \\\n  --net=host \\\n  ghcr.io/home-assistant/home-assistant:stable\nmkdir /root/hass_config\nmsg_ok \"Installed Home Assistant $CORE_LATEST_VERSION\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homebox-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/sysadminsmedia/homebox\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"homebox\" \"sysadminsmedia/homebox\" \"prebuild\" \"latest\" \"/opt/homebox\" \"homebox_Linux_arm64.tar.gz\"\n\nmsg_info \"Configuring Homebox\"\nchmod +x /opt/homebox/homebox\ncat <<EOF >/opt/homebox/.env\n# For possible environment variables check here: https://homebox.software/en/configure-homebox\nHBOX_MODE=production\nHBOX_WEB_PORT=7745\nHBOX_WEB_HOST=0.0.0.0\nEOF\nmsg_ok \"Configured Homebox\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/homebox.service\n[Unit]\nDescription=Start Homebox Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/homebox\nExecStart=/opt/homebox/homebox\nEnvironmentFile=/opt/homebox/.env\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now homebox\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homebridge-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://homebridge.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y avahi-daemon\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up Homebridge Repository\"\nsetup_deb822_repo \\\n  \"homebridge\" \\\n  \"https://repo.homebridge.io/KEY.gpg\" \\\n  \"https://repo.homebridge.io\" \\\n  \"stable\"\nmsg_ok \"Set up Homebridge Repository\"\n\nmsg_info \"Installing Homebridge\"\n$STD apt install -y homebridge\nmsg_ok \"Installed Homebridge\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homelable-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/Pouzor/homelable\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nmap \\\n  iputils-ping \\\n  caddy\nmsg_ok \"Installed Dependencies\"\n\nUV_PYTHON=\"3.13\" setup_uv\nNODE_VERSION=\"20\" setup_nodejs\nfetch_and_deploy_gh_release \"homelable\" \"Pouzor/homelable\" \"tarball\" \"latest\" \"/opt/homelable\"\n\nmsg_info \"Setting up Python Backend\"\ncd /opt/homelable/backend\n$STD uv venv /opt/homelable/backend/.venv\n$STD uv pip install --python /opt/homelable/backend/.venv/bin/python -r requirements.txt\nmsg_ok \"Set up Python Backend\"\n\nmsg_info \"Configuring Homelable\"\nmkdir -p /opt/homelable/data\nSECRET_KEY=$(openssl rand -hex 32)\nBCRYPT_HASH=$(/opt/homelable/backend/.venv/bin/python -c \"from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('admin'))\")\ncat <<EOF >/opt/homelable/backend/.env\nSECRET_KEY=${SECRET_KEY}\nSQLITE_PATH=/opt/homelable/data/homelab.db\nCORS_ORIGINS=[\"http://localhost:3000\",\"http://${LOCAL_IP}:3000\"]\nAUTH_USERNAME=admin\nAUTH_PASSWORD_HASH='${BCRYPT_HASH}'\nSCANNER_RANGES=[\"192.168.1.0/24\"]\nSTATUS_CHECKER_INTERVAL=60\nEOF\nmsg_ok \"Configured Homelable\"\n\nmsg_info \"Creating Password Reset Utility\"\ncat <<'EOF' >/root/change_password.sh\n#!/usr/bin/env bash\n\nNEW_PASS=\"\"\n\nwhile [[ -z \"$NEW_PASS\" ]]; do\n    read -s -p \"Enter new password: \" NEW_PASS\n    echo \"\"\n    if [[ -z \"$NEW_PASS\" ]]; then\n        echo \"Error: Password cannot be blank. Try again.\"\n    fi\ndone\n\nHASH=$(/opt/homelable/backend/.venv/bin/python -c \"from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('${NEW_PASS}'))\")\n\nsed -i \"s|^AUTH_PASSWORD_HASH=.*|AUTH_PASSWORD_HASH='${HASH}'|\" /opt/homelable/backend/.env\n\nsystemctl restart homelable\necho \"Password updated and service restarted successfully!\"\nEOF\nchmod +x /root/change_password.sh\nmsg_ok \"Created Password Reset Utility\"\n\nmsg_info \"Building Frontend\"\ncd /opt/homelable/frontend\n$STD npm ci\n$STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/homelable.service\n[Unit]\nDescription=Homelable Backend\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/homelable/backend\nEnvironmentFile=/opt/homelable/backend/.env\nExecStart=/opt/homelable/backend/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now homelable\nmsg_ok \"Created Service\"\n\nmsg_info \"Configuring Caddy\"\ncat <<EOF >/etc/caddy/Caddyfile\n:3000 {\n    root * /opt/homelable/frontend/dist\n    file_server\n\n    @websocket path /api/v1/status/ws/*\n    handle @websocket {\n        reverse_proxy 127.0.0.1:8000\n    }\n\n    handle /ws/* {\n        reverse_proxy 127.0.0.1:8000\n    }\n\n    handle /api/* {\n        reverse_proxy 127.0.0.1:8000\n    }\n\n    handle {\n        try_files {path} {path}.html /index.html\n    }\n}\nEOF\nsystemctl reload caddy\nmsg_ok \"Configured Caddy\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homepage-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gethomepage.dev/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y jq\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm@latest\" setup_nodejs\nfetch_and_deploy_gh_release \"homepage\" \"gethomepage/homepage\" \"tarball\"\nRELEASE=$(get_latest_github_release \"gethomepage/homepage\")\n\nmsg_info \"Installing Homepage (Patience)\"\nmkdir -p /opt/homepage/config\ncd /opt/homepage\ncp /opt/homepage/src/skeleton/* /opt/homepage/config\necho 'onlyBuiltDependencies=*' >> .npmrc\n$STD pnpm install\nexport NEXT_PUBLIC_VERSION=\"v$RELEASE\"\nexport NEXT_PUBLIC_REVISION=\"source\"\nexport NEXT_PUBLIC_BUILDTIME=$(curl -fsSL https://api.github.com/repos/gethomepage/homepage/releases/latest | jq -r '.published_at')\nexport NEXT_TELEMETRY_DISABLED=1\n$STD pnpm build\necho \"HOMEPAGE_ALLOWED_HOSTS=localhost:3000,${LOCAL_IP}:3000\" >/opt/homepage/.env\nmsg_ok \"Installed Homepage\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/homepage.service\n[Unit]\nDescription=Homepage\nAfter=network.target\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nWorkingDirectory=/opt/homepage/\nExecStart=pnpm start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now homepage\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/homer-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bastienwirtz/homer\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"homer\" \"bastienwirtz/homer\" \"prebuild\" \"latest\" \"/opt/homer\" \"homer.zip\"\ncp /opt/homer/assets/config.yml.dist /opt/homer/assets/config.yml\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/homer.service\n[Unit]\nDescription=Homer Dashboard\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/homer\nExecStart=python3 -m http.server 8010\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now homer\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/hoodik-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/hudikhq/hoodik\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"hoodik\" \"hudikhq/hoodik\" \"prebuild\" \"latest\" \"/opt/hoodik\" \"*x86_64.tar.gz\"\n\nmsg_info \"Configuring Hoodik\"\nmkdir -p /opt/hoodik_data\nJWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)\ncat <<EOF >/opt/hoodik/.env\nDATA_DIR=/opt/hoodik_data\nHTTP_PORT=5443\nHTTP_ADDRESS=0.0.0.0\nJWT_SECRET=${JWT_SECRET}\nAPP_URL=http://${LOCAL_IP}:5443\nSSL_DISABLED=true\nCOOKIE_SECURE=false\nCOOKIE_HTTP_ONLY=false\nMAILER_TYPE=none\nRUST_LOG=hoodik=info,error=info\nEOF\nmsg_ok \"Configured Hoodik\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/hoodik.service\n[Unit]\nDescription=Hoodik - Encrypted File Storage\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/hoodik_data\nEnvironmentFile=/opt/hoodik/.env\nExecStart=/opt/hoodik/hoodik\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now hoodik\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/hortusfox-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/danielbrendel/hortusfox-web\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_mariadb\nMARIADB_DB_NAME=\"hortusfox\" MARIADB_DB_USER=\"hortusfox\" setup_mariadb_db\nPHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" setup_php\nsetup_composer\nfetch_and_deploy_gh_release \"hortusfox\" \"danielbrendel/hortusfox-web\" \"tarball\"\n\nmsg_info \"Configuring .env\"\ncp /opt/hortusfox/.env.example /opt/hortusfox/.env\nsed -i \"s|^DB_HOST=.*|DB_HOST=localhost|\" /opt/hortusfox/.env\nsed -i \"s|^DB_USER=.*|DB_USER=$MARIADB_DB_USER|\" /opt/hortusfox/.env\nsed -i \"s|^DB_PASSWORD=.*|DB_PASSWORD=$MARIADB_DB_PASS|\" /opt/hortusfox/.env\nsed -i \"s|^DB_DATABASE=.*|DB_DATABASE=$MARIADB_DB_NAME|\" /opt/hortusfox/.env\nsed -i \"s|^DB_ENABLE=.*|DB_ENABLE=true|\" /opt/hortusfox/.env\nsed -i \"s|^APP_TIMEZONE=.*|APP_TIMEZONE=Europe/Berlin|\" /opt/hortusfox/.env\nmsg_ok \".env configured\"\n\nmsg_info \"Installing Composer dependencies\"\ncd /opt/hortusfox\n$STD composer install --no-dev --optimize-autoloader\nmsg_ok \"Composer dependencies installed\"\n\nmsg_info \"Running DB migration\"\n$STD php asatru migrate:fresh\nmsg_ok \"Migration finished\"\n\nmsg_info \"Setting up HortusFox\"\n$STD mariadb -u root -D $MARIADB_DB_NAME -e \"INSERT IGNORE INTO AppModel (workspace, language, created_at) VALUES ('Default Workspace', 'en', NOW());\"\n$STD php asatru plants:attributes\n$STD php asatru calendar:classes\nADMIN_EMAIL=\"admin@example.com\"\nADMIN_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\"\nADMIN_HASH=$(php -r \"echo password_hash('$ADMIN_PASS', PASSWORD_BCRYPT);\")\n$STD mariadb -u root -D $MARIADB_DB_NAME -e \"INSERT IGNORE INTO UserModel (name, email, password, admin) VALUES ('Admin', '$ADMIN_EMAIL', '$ADMIN_HASH', 1);\"\n{\n  echo \"\"\n  echo \"HortusFox-Admin-Creds:\"\n  echo \"E-Mail: $ADMIN_EMAIL\"\n  echo \"Passwort: $ADMIN_PASS\"\n} >>~/hortusfox.creds\n$STD mariadb -u root -D $MARIADB_DB_NAME -e \"INSERT IGNORE INTO LocationsModel (name, active, created_at) VALUES ('Home', 1, NOW());\"\nmsg_ok \"Set up HortusFox\"\n\nmsg_info \"Configuring Apache vHost\"\ncat <<EOF >/etc/apache2/sites-available/hortusfox.conf\n<VirtualHost *:80>\n    ServerAdmin webmaster@localhost\n    DocumentRoot /opt/hortusfox/public\n    <Directory /opt/hortusfox/public>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n    ErrorLog \\${APACHE_LOG_DIR}/hortusfox_error.log\n    CustomLog \\${APACHE_LOG_DIR}/hortusfox_access.log combined\n</VirtualHost>\nEOF\nchown -R www-data:www-data /opt/hortusfox\n$STD a2dissite 000-default\n$STD a2ensite hortusfox\n$STD a2enmod rewrite\nsystemctl restart apache2\nmsg_ok \"Apache configured\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/hyperhdr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.hyperhdr.eu/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing HyperHDR\"\nsetup_deb822_repo \\\n  \"hyperhdr\" \\\n  \"https://awawa-dev.github.io/hyperhdr.public.apt.gpg.key\" \\\n  \"https://awawa-dev.github.io\" \\\n  \"$(get_os_info codename)\"\n$STD apt install -y hyperhdr\nmsg_ok \"Installed HyperHDR\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/hyperhdr.service\n[Unit]\nDescription=HyperHDR Service\nAfter=syslog.target network.target\n\n[Service]\nRestart=on-failure\nRestartSec=5\nType=simple\nExecStart=/usr/bin/hyperhdr\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now hyperhdr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/hyperion-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://hyperion-project.org/forum/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y lsb-release\n$STD apt-get install -y apt-transport-https\n$STD apt-get install -y libpython3.11\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up Hyperion repository\"\nsetup_deb822_repo \\\n  \"hyperion\" \\\n  \"https://releases.hyperion-project.org/hyperion.pub.key\" \\\n  \"https://apt.releases.hyperion-project.org\" \\\n  \"$(get_os_info codename)\"\nmsg_ok \"Set up Hyperion repository\"\n\n\nmsg_info \"Installing Hyperion\"\n$STD apt install -y hyperion\nsystemctl enable -q --now hyperion@root\nmsg_ok \"Installed Hyperion\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/igotify-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pfassina\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/androidseb25/iGotify-Notification-Assistent\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\nsetup_deb822_repo \\\n  \"microsoft\" \\\n  \"https://packages.microsoft.com/keys/microsoft-2025.asc\" \\\n  \"https://packages.microsoft.com/debian/13/prod/\" \\\n  \"trixie\" \\\n  \"main\"\n$STD apt install -y aspnetcore-runtime-10.0\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"igotify\" \"androidseb25/iGotify-Notification-Assistent\" \"prebuild\" \"latest\" \"/opt/igotify\" \"iGotify-Notification-Service-amd64-v*.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/igotify/.env\nASPNETCORE_URLS=http://0.0.0.0:80\nASPNETCORE_ENVIRONMENT=Production\nGOTIFY_DEFAULTUSER_PASS=\nGOTIFY_URLS=\nGOTIFY_CLIENT_TOKENS=\nSECNTFY_TOKENS=\nEOF\ncat <<EOF >/etc/systemd/system/igotify.service\n[Unit]\nDescription=iGotify Notification Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/igotify/.env\nWorkingDirectory=/opt/igotify\nExecStart=/usr/bin/dotnet \"/opt/igotify/iGotify Notification Assist.dll\"\nRestart=always\nRestartSec=10\nKillSignal=SIGINT\nTimeoutStopSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now igotify\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/immich-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://immich.app\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nif lscpu | grep -q 'GenuineIntel'; then\n  echo \"\"\n  echo \"\"\n  echo -e \"🤖 ${BL}Immich Machine-Learning Options${CL}\"\n  echo \"─────────────────────────────────────────\"\n  echo \"Please choose your machine-learning type:\"\n  echo \"\"\n  echo \" 1) CPU only (default)\"\n  echo \" 2) **NEW** Intel OpenVINO CPU or iGPU\"\n  echo \"\"\n\n  read -r -p \"${TAB3}Select machine-learning type [1]: \" ML_TYPE\n  ML_TYPE=\"${ML_TYPE:-1}\"\n  if [[ \"$ML_TYPE\" == \"2\" ]]; then\n    touch ~/.openvino\n    $STD apt install -y --no-install-recommends patchelf\n    if [[ -d /dev/dri ]]; then\n      msg_info \"Installing Intel OpenVINO dependencies\"\n      tmp_dir=$(mktemp -d)\n      $STD pushd \"$tmp_dir\"\n      curl_with_retry \"https://raw.githubusercontent.com/immich-app/immich/refs/heads/main/machine-learning/Dockerfile\" \"Dockerfile\"\n      readarray -t INTEL_URLS < <(\n        sed -n \"/intel-[igc|opencl]/p\" ./Dockerfile | awk '{print $3}'\n        sed -n \"/libigdgmm12/p\" ./Dockerfile | awk '{print $3}'\n      )\n      for url in \"${INTEL_URLS[@]}\"; do\n        curl_with_retry \"$url\" \"$(basename \"$url\")\"\n      done\n      $STD apt install -y ./libigdgmm12*.deb\n      rm ./libigdgmm12*.deb\n      $STD apt install -y ./*.deb\n      $STD apt-mark hold libigdgmm12\n      $STD popd\n      rm -rf \"$tmp_dir\"\n      dpkg-query -W -f='${Version}\\n' intel-opencl-icd >~/.intel_version\n      msg_ok \"Installed Intel OpenVINO dependencies\"\n    fi\n  fi\nfi\n\nmsg_info \"Installing dependencies\"\n$STD apt install --no-install-recommends -y \\\n  git \\\n  redis \\\n  autoconf \\\n  build-essential \\\n  python3-dev \\\n  automake \\\n  cmake \\\n  jq \\\n  libtool \\\n  libltdl-dev \\\n  libgdk-pixbuf-2.0-dev \\\n  libbrotli-dev \\\n  libexif-dev \\\n  libexpat1-dev \\\n  libglib2.0-dev \\\n  libgsf-1-dev \\\n  libjpeg62-turbo-dev \\\n  libspng-dev \\\n  liblcms2-dev \\\n  libopenexr-dev \\\n  libgif-dev \\\n  librsvg2-dev \\\n  libexpat1 \\\n  libgcc-s1 \\\n  libgomp1 \\\n  liblqr-1-0 \\\n  libltdl7 \\\n  libopenjp2-7 \\\n  meson \\\n  ninja-build \\\n  pkg-config \\\n  mesa-utils \\\n  mesa-va-drivers \\\n  mesa-vulkan-drivers \\\n  ocl-icd-libopencl1 \\\n  tini \\\n  zlib1g \\\n  libio-compress-brotli-perl \\\n  libwebp7 \\\n  libwebpdemux2 \\\n  libwebpmux3 \\\n  libhwy1t64 \\\n  libdav1d-dev \\\n  libhwy-dev \\\n  libwebp-dev \\\n  libaom-dev \\\n  ccache\n\nsetup_deb822_repo \\\n  \"jellyfin\" \\\n  \"https://repo.jellyfin.org/jellyfin_team.gpg.key\" \\\n  \"https://repo.jellyfin.org/debian\" \\\n  \"$(get_os_info codename)\"\n$STD apt install -y jellyfin-ffmpeg7\nln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin/ffmpeg\nln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/bin/ffprobe\n\n# Set permissions for /dev/dri (only in privileged containers and if /dev/dri exists)\nif [[ \"$CTTYPE\" == \"0\" && -d /dev/dri ]]; then\n  chgrp video /dev/dri 2>/dev/null || true\n  chmod 755 /dev/dri 2>/dev/null || true\n  chmod 660 /dev/dri/* 2>/dev/null || true\n  $STD adduser \"$(id -u -n)\" video 2>/dev/null || true\n  $STD adduser \"$(id -u -n)\" render 2>/dev/null || true\nfi\nmsg_ok \"Dependencies Installed\"\n\nmsg_info \"Installing Mise\"\ncurl -fSs https://mise.jdx.dev/gpg-key.pub | tee /etc/apt/keyrings/mise-archive-keyring.pub 1>/dev/null\necho \"deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=arm64] https://mise.jdx.dev/deb stable main\" >/etc/apt/sources.list.d/mise.list\n$STD apt update\n$STD apt install -y mise\nmsg_ok \"Installed Mise\"\n\nmsg_info \"Configuring Debian Testing Repo\"\nsed -i 's/ trixie-updates/ trixie-updates testing/g' /etc/apt/sources.list.d/debian.sources\ncat <<EOF >/etc/apt/preferences.d/preferences\nPackage: *\nPin: release a=unstable\nPin-Priority: 450\n\nPackage: *\nPin:release a=testing\nPin-Priority: 450\nEOF\n$STD apt update\nmsg_ok \"Configured Debian Testing repo\"\nmsg_info \"Installing packages from Debian Testing repo\"\n$STD apt install -t testing --no-install-recommends -yqq libmimalloc3 libde265-dev\nmsg_ok \"Installed packages from Debian Testing repo\"\n\nsetup_uv\nPG_VERSION=\"16\" PG_MODULES=\"pgvector\" setup_postgresql\n\nVCHORD_RELEASE=\"0.5.3\"\nfetch_and_deploy_gh_release \"VectorChord\" \"tensorchord/VectorChord\" \"binary\" \"${VCHORD_RELEASE}\" \"/tmp\" \"postgresql-16-vchord_*_arm64.deb\"\n\nsed -i \"s/^#shared_preload.*/shared_preload_libraries = 'vchord.so'/\" /etc/postgresql/16/main/postgresql.conf\nsystemctl restart postgresql.service\nPG_DB_NAME=\"immich\" PG_DB_USER=\"immich\" PG_DB_GRANT_SUPERUSER=\"true\" PG_DB_SKIP_ALTER_ROLE=\"true\" setup_postgresql_db\n\nmsg_info \"Installing GCC-13 (available as fallback compiler)\"\n$STD apt install -y gcc-13 g++-13\nmsg_ok \"Installed GCC-13\"\n\nmsg_warn \"Compiling Custom Photo-processing Libraries (can take anywhere from 15min to 2h)\"\nLD_LIBRARY_PATH=/usr/local/lib\nexport LD_RUN_PATH=/usr/local/lib\nSTAGING_DIR=/opt/staging\nBASE_REPO=\"https://github.com/immich-app/base-images\"\nBASE_DIR=${STAGING_DIR}/base-images\nSOURCE_DIR=${STAGING_DIR}/image-source\n$STD git clone -b main \"$BASE_REPO\" \"$BASE_DIR\"\nmkdir -p \"$SOURCE_DIR\"\n\nmsg_info \"(1/5) Compiling libjxl\"\ncd \"$STAGING_DIR\"\nSOURCE=${SOURCE_DIR}/libjxl\nJPEGLI_LIBJPEG_LIBRARY_SOVERSION=\"62\"\nJPEGLI_LIBJPEG_LIBRARY_VERSION=\"62.3.0\"\nLIBJXL_REVISION=\"794a5dcf0d54f9f0b20d288a12e87afb91d20dfc\"\n# : \"${LIBJXL_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libjxl.json)}\"\n$STD git clone https://github.com/libjxl/libjxl.git \"$SOURCE\"\ncd \"$SOURCE\"\n$STD git reset --hard \"$LIBJXL_REVISION\"\n$STD git submodule update --init --recursive --depth 1 --recommend-shallow\n$STD git apply \"$BASE_DIR\"/server/sources/libjxl-patches/jpegli-empty-dht-marker.patch\n$STD git apply \"$BASE_DIR\"/server/sources/libjxl-patches/jpegli-icc-warning.patch\nmkdir build\ncd build\n$STD cmake \\\n  -DCMAKE_BUILD_TYPE=Release \\\n  -DBUILD_TESTING=OFF \\\n  -DJPEGXL_ENABLE_DOXYGEN=OFF \\\n  -DJPEGXL_ENABLE_MANPAGES=OFF \\\n  -DJPEGXL_ENABLE_PLUGIN_GIMP210=OFF \\\n  -DJPEGXL_ENABLE_BENCHMARK=OFF \\\n  -DJPEGXL_ENABLE_EXAMPLES=OFF \\\n  -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \\\n  -DJPEGXL_FORCE_SYSTEM_HWY=ON \\\n  -DJPEGXL_ENABLE_JPEGLI=ON \\\n  -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=ON \\\n  -DJPEGXL_INSTALL_JPEGLI_LIBJPEG=ON \\\n  -DJPEGXL_ENABLE_PLUGINS=ON \\\n  -DJPEGLI_LIBJPEG_LIBRARY_SOVERSION=\"$JPEGLI_LIBJPEG_LIBRARY_SOVERSION\" \\\n  -DJPEGLI_LIBJPEG_LIBRARY_VERSION=\"$JPEGLI_LIBJPEG_LIBRARY_VERSION\" \\\n  -DLIBJPEG_TURBO_VERSION_NUMBER=2001005 \\\n  ..\n$STD cmake --build . -- -j\"$(nproc)\"\n$STD cmake --install .\nldconfig /usr/local/lib\n$STD make clean\ncd \"$STAGING_DIR\"\nrm -rf \"$SOURCE\"/{build,third_party}\nmsg_ok \"(1/5) Compiled libjxl\"\n\nmsg_info \"(2/5) Compiling libheif\"\nSOURCE=${SOURCE_DIR}/libheif\nLIBHEIF_REVISION=\"35dad50a9145332a7bfdf1ff6aef6801fb613d68\"\n# : \"${LIBHEIF_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libheif.json)}\"\n$STD git clone https://github.com/strukturag/libheif.git \"$SOURCE\"\ncd \"$SOURCE\"\n$STD git reset --hard \"$LIBHEIF_REVISION\"\nmkdir build\ncd build\n$STD cmake --preset=release-noplugins \\\n  -DWITH_DAV1D=ON \\\n  -DENABLE_PARALLEL_TILE_DECODING=ON \\\n  -DWITH_LIBSHARPYUV=ON \\\n  -DWITH_LIBDE265=ON \\\n  -DWITH_AOM_DECODER=OFF \\\n  -DWITH_AOM_ENCODER=ON \\\n  -DWITH_X265=OFF \\\n  -DWITH_EXAMPLES=OFF \\\n  ..\n$STD make install -j\"$(nproc)\"\nldconfig /usr/local/lib\n$STD make clean\ncd \"$STAGING_DIR\"\nrm -rf \"$SOURCE\"/build\nmsg_ok \"(2/5) Compiled libheif\"\n\nmsg_info \"(3/5) Compiling libraw\"\nSOURCE=${SOURCE_DIR}/libraw\nLIBRAW_REVISION=\"0b56545a4f828743f28a4345cdfdd4c49f9f9a2a\"\n# : \"${LIBRAW_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libraw.json)}\"\n$STD git clone https://github.com/LibRaw/LibRaw.git \"$SOURCE\"\ncd \"$SOURCE\"\n$STD git reset --hard \"$LIBRAW_REVISION\"\n$STD autoreconf --install\n$STD ./configure --disable-examples\n$STD make -j\"$(nproc)\"\n$STD make install\nldconfig /usr/local/lib\n$STD make clean\ncd \"$STAGING_DIR\"\nmsg_ok \"(3/5) Compiled libraw\"\n\nmsg_info \"(4/5) Compiling imagemagick\"\nSOURCE=$SOURCE_DIR/imagemagick\n: \"${IMAGEMAGICK_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/imagemagick.json)}\"\n$STD git clone https://github.com/ImageMagick/ImageMagick.git \"$SOURCE\"\ncd \"$SOURCE\"\n$STD git reset --hard \"$IMAGEMAGICK_REVISION\"\n$STD ./configure --with-modules CPPFLAGS=\"-DMAGICK_LIBRAW_VERSION_TAIL=202502\"\n$STD make -j\"$(nproc)\"\n$STD make install\nldconfig /usr/local/lib\n$STD make clean\ncd \"$STAGING_DIR\"\nmsg_ok \"(4/5) Compiled imagemagick\"\n\nmsg_info \"(5/5) Compiling libvips\"\nSOURCE=$SOURCE_DIR/libvips\nLIBVIPS_REVISION=\"0c9151a4f416d2f8ae20a755db218f6637050eec\"\n$STD git clone https://github.com/libvips/libvips.git \"$SOURCE\"\ncd \"$SOURCE\"\n$STD git reset --hard \"$LIBVIPS_REVISION\"\n$STD meson setup build --buildtype=release --libdir=lib -Dintrospection=disabled -Dtiff=disabled\ncd build\n$STD ninja install\nldconfig /usr/local/lib\ncd \"$STAGING_DIR\"\nrm -rf \"$SOURCE\"/build\nmsg_ok \"(5/5) Compiled libvips\"\n{\n  echo \"imagemagick: $IMAGEMAGICK_REVISION\"\n  echo \"libheif: $LIBHEIF_REVISION\"\n  echo \"libjxl: $LIBJXL_REVISION\"\n  echo \"libraw: $LIBRAW_REVISION\"\n  echo \"libvips: $LIBVIPS_REVISION\"\n} >~/.immich_library_revisions\nmsg_ok \"Custom Photo-processing Libraries Compiled Successfully\"\n\nINSTALL_DIR=\"/opt/${APPLICATION}\"\nUPLOAD_DIR=\"${INSTALL_DIR}/upload\"\nSRC_DIR=\"${INSTALL_DIR}/source\"\nAPP_DIR=\"${INSTALL_DIR}/app\"\nPLUGIN_DIR=\"${APP_DIR}/corePlugin\"\nML_DIR=\"${APP_DIR}/machine-learning\"\nGEO_DIR=\"${INSTALL_DIR}/geodata\"\nmkdir -p {\"${APP_DIR}\",\"${UPLOAD_DIR}\",\"${GEO_DIR}\",\"${INSTALL_DIR}\"/cache}\n\nfetch_and_deploy_gh_release \"Immich\" \"immich-app/immich\" \"tarball\" \"v2.7.5\" \"$SRC_DIR\"\nPNPM_VERSION=\"$(jq -r '.packageManager | split(\"@\")[1] | split(\"+\")[0]' ${SRC_DIR}/package.json)\"\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm@${PNPM_VERSION}\" setup_nodejs\n\nmsg_info \"Installing Immich (patience)\"\n\ncd \"$SRC_DIR\"/server\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\nexport CI=1\ncorepack enable\n\n# server build\nexport SHARP_IGNORE_GLOBAL_LIBVIPS=true\n$STD pnpm --filter immich --frozen-lockfile build\nunset SHARP_IGNORE_GLOBAL_LIBVIPS\nexport SHARP_FORCE_GLOBAL_LIBVIPS=true\n$STD pnpm --filter immich --frozen-lockfile --prod --no-optional deploy \"$APP_DIR\"\n\n# Patch helmet.json: disable upgrade-insecure-requests for HTTP access\nif [[ -f \"$APP_DIR/helmet.json\" ]]; then\n  jq '.contentSecurityPolicy.directives[\"upgrade-insecure-requests\"] = null' \"$APP_DIR/helmet.json\" >\"$APP_DIR/helmet.json.tmp\" && mv \"$APP_DIR/helmet.json.tmp\" \"$APP_DIR/helmet.json\"\nfi\n\ncp \"$APP_DIR\"/package.json \"$APP_DIR\"/bin\nsed -i \"s|^start|${APP_DIR}/bin/start|\" \"$APP_DIR\"/bin/immich-admin\n\n# openapi & web build\ncd \"$SRC_DIR\"\necho \"packageImportMethod: hardlink\" >>./pnpm-workspace.yaml\n$STD pnpm --filter @immich/sdk --filter immich-web --frozen-lockfile --force install\nunset SHARP_FORCE_GLOBAL_LIBVIPS\nexport SHARP_IGNORE_GLOBAL_LIBVIPS=true\n$STD pnpm --filter @immich/sdk --filter immich-web build\ncp -a web/build \"$APP_DIR\"/www\ncp LICENSE \"$APP_DIR\"\n\n# cli build\n$STD pnpm --filter @immich/sdk --filter @immich/cli --frozen-lockfile install\n$STD pnpm --filter @immich/sdk --filter @immich/cli build\n$STD pnpm --filter @immich/cli --prod --no-optional deploy \"$APP_DIR\"/cli\n\n# plugins\ncd \"$SRC_DIR\"\n$STD mise trust --ignore ./mise.toml\n$STD mise trust ./plugins/mise.toml\ncd plugins\n$STD mise install\n$STD mise run build\nmkdir -p \"$PLUGIN_DIR\"\ncp -r ./dist \"$PLUGIN_DIR\"/dist\ncp ./manifest.json \"$PLUGIN_DIR\"\nmsg_ok \"Installed Immich Server, Web and Plugin Components\"\n\ncd \"$SRC_DIR\"/machine-learning\n$STD useradd -U -s /usr/sbin/nologin -r -M -d \"$INSTALL_DIR\" immich\nmkdir -p \"$ML_DIR\"\n# chown excluding upload dir contents (may be a mount with restricted permissions)\nchown immich:immich \"$INSTALL_DIR\"\nfind \"$INSTALL_DIR\" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +\nchown immich:immich \"$UPLOAD_DIR\" 2>/dev/null || true\nexport VIRTUAL_ENV=\"${ML_DIR}/ml-venv\"\nexport UV_HTTP_TIMEOUT=300\nif [[ -f ~/.openvino ]]; then\n  ML_PYTHON=\"python3.13\"\n  msg_info \"Pre-installing Python ${ML_PYTHON} for machine-learning\"\n  for attempt in $(seq 1 3); do\n    $STD sudo --preserve-env=VIRTUAL_ENV -nu immich uv python install \"${ML_PYTHON}\" && break\n    [[ $attempt -lt 3 ]] && msg_warn \"Python download attempt $attempt failed, retrying...\" && sleep 5\n  done\n  msg_ok \"Pre-installed Python ${ML_PYTHON}\"\n  msg_info \"Installing HW-accelerated machine-learning\"\n  for attempt in $(seq 1 3); do\n    $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -nu immich uv sync --extra openvino --no-dev --active --link-mode copy -n -p \"${ML_PYTHON}\" --managed-python && break\n    [[ $attempt -lt 3 ]] && msg_warn \"uv sync attempt $attempt failed, retrying...\" && sleep 10\n  done\n  sofile=\"${VIRTUAL_ENV}/lib/python${ML_PYTHON#python}/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-x86_64-linux-gnu.so\"\n  if [[ \"$(uname -m)\" == \"aarch64\" || \"$(uname -m)\" == \"arm64\" ]]; then\n    sofile=\"${VIRTUAL_ENV}/lib/python${ML_PYTHON#python}/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-aarch64-linux-gnu.so\"\n  fi\n  patchelf --clear-execstack \"$sofile\"\n  msg_ok \"Installed HW-accelerated machine-learning\"\nelse\n  ML_PYTHON=\"python3.11\"\n  msg_info \"Pre-installing Python ${ML_PYTHON} for machine-learning\"\n  for attempt in $(seq 1 3); do\n    $STD sudo --preserve-env=VIRTUAL_ENV -nu immich uv python install \"${ML_PYTHON}\" && break\n    [[ $attempt -lt 3 ]] && msg_warn \"Python download attempt $attempt failed, retrying...\" && sleep 5\n  done\n  msg_ok \"Pre-installed Python ${ML_PYTHON}\"\n  msg_info \"Installing machine-learning\"\n  for attempt in $(seq 1 3); do\n    $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -nu immich uv sync --extra cpu --no-dev --active --link-mode copy -n -p \"${ML_PYTHON}\" --managed-python && break\n    [[ $attempt -lt 3 ]] && msg_warn \"uv sync attempt $attempt failed, retrying...\" && sleep 10\n  done\n  msg_ok \"Installed machine-learning\"\nfi\ncd \"$SRC_DIR\"\ncp -a machine-learning/{ann,immich_ml} \"$ML_DIR\"\n[[ -f ~/.openvino ]] && sed -i \"/intra_op/s/int = 0/int = os.cpu_count() or 0/\" \"$ML_DIR\"/immich_ml/config.py\nln -sf \"$APP_DIR\"/resources \"$INSTALL_DIR\"\n\ncd \"$APP_DIR\"\ngrep -rl /usr/src | xargs -n1 sed -i \"s|\\/usr/src|$INSTALL_DIR|g\"\ngrep -rlE \"'/build'\" | xargs -n1 sed -i \"s|'/build'|'$APP_DIR'|g\"\nsed -i \"s@\\\"/cache\\\"@\\\"$INSTALL_DIR/cache\\\"@g\" \"$ML_DIR\"/immich_ml/config.py\nln -s \"$UPLOAD_DIR\" \"$APP_DIR\"/upload\nln -s \"$UPLOAD_DIR\" \"$ML_DIR\"/upload\n\nmsg_info \"Installing GeoNames data\"\ncd \"$GEO_DIR\"\ncurl_with_retry \"https://download.geonames.org/export/dump/admin1CodesASCII.txt\" \"admin1CodesASCII.txt\"\ncurl_with_retry \"https://download.geonames.org/export/dump/admin2Codes.txt\" \"admin2Codes.txt\"\ncurl_with_retry \"https://download.geonames.org/export/dump/cities500.zip\" \"cities500.zip\"\ncurl_with_retry \"https://raw.githubusercontent.com/nvkelso/natural-earth-vector/v5.1.2/geojson/ne_10m_admin_0_countries.geojson\" \"ne_10m_admin_0_countries.geojson\"\nunzip -q cities500.zip\ndate --iso-8601=seconds | tr -d \"\\n\" >geodata-date.txt\nrm cities500.zip\ncd \"$INSTALL_DIR\"\nln -s \"$GEO_DIR\" \"$APP_DIR\"\nmsg_ok \"Installed GeoNames data\"\n\nmkdir -p /var/log/immich\ntouch /var/log/immich/{web.log,ml.log}\nmsg_ok \"Installed Immich\"\n\nmsg_info \"Modifying user, creating env file, scripts & services\"\nusermod -aG video,render immich\n\ncat <<EOF >\"${INSTALL_DIR}\"/.env\nTZ=$(cat /etc/timezone)\nIMMICH_VERSION=release\nNODE_ENV=production\nIMMICH_ALLOW_SETUP=true\n\n## Change to 'false' to disable CSP\nIMMICH_HELMET_FILE=true\n\nDB_HOSTNAME=127.0.0.1\nDB_USERNAME=${PG_DB_USER}\nDB_PASSWORD=${PG_DB_PASS}\nDB_DATABASE_NAME=${PG_DB_NAME}\nDB_VECTOR_EXTENSION=vectorchord\n\nREDIS_HOSTNAME=127.0.0.1\nIMMICH_MACHINE_LEARNING_URL=http://127.0.0.1:3003\nMACHINE_LEARNING_CACHE_FOLDER=${INSTALL_DIR}/cache\n## - For OpenVINO only - uncomment below to increase\n## - inference speed while reducing accuracy\n## - Default is FP32\n# MACHINE_LEARNING_OPENVINO_PRECISION=FP16\n\nIMMICH_MEDIA_LOCATION=${UPLOAD_DIR}\nEOF\ncat <<EOF >\"${ML_DIR}\"/ml_start.sh\n#!/usr/bin/env bash\n\ncd ${ML_DIR}\n. ${VIRTUAL_ENV}/bin/activate\n\nset -a\n. ${INSTALL_DIR}/.env\nset +a\n\npython3 -m immich_ml\nEOF\ncat <<EOF >\"$APP_DIR\"/bin/start.sh\n#!/usr/bin/env bash\n\nset -a\n. ${INSTALL_DIR}/.env\nset +a\n\n/usr/bin/node --no-warnings ${APP_DIR}/dist/main.js \"\\$@\"\nEOF\nchmod +x \"$ML_DIR\"/ml_start.sh \"$APP_DIR\"/bin/start.sh\nln -sf \"$APP_DIR\"/cli/bin/immich /usr/bin/immich\nln -sf \"$APP_DIR\"/bin/immich-admin /usr/bin/immich-admin\ncat <<EOF >/etc/systemd/system/immich-web.service\n[Unit]\nDescription=Immich Web Service\nAfter=network.target\nRequires=redis-server.service\nRequires=postgresql.service\nRequires=immich-ml.service\n\n[Service]\nType=simple\nUser=immich\nGroup=immich\nUMask=0077\nWorkingDirectory=${APP_DIR}\nExecStart=${APP_DIR}/bin/start.sh\nRestart=on-failure\nSyslogIdentifier=immich-web\nStandardOutput=append:/var/log/immich/web.log\nStandardError=append:/var/log/immich/web.log\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/immich-ml.service\n[Unit]\nDescription=Immich Machine-Learning\nAfter=network.target\n\n[Service]\nType=simple\nUMask=0077\nUser=immich\nGroup=immich\nWorkingDirectory=${APP_DIR}\nEnvironmentFile=${INSTALL_DIR}/.env\nExecStart=${ML_DIR}/ml_start.sh\nRestart=on-failure\nSyslogIdentifier=immich-machine-learning\nStandardOutput=append:/var/log/immich/ml.log\nStandardError=append:/var/log/immich/ml.log\n\n[Install]\nWantedBy=multi-user.target\nEOF\nchown -R immich:immich /var/log/immich\n# chown excluding upload dir contents (may be a mount with restricted permissions)\nchown immich:immich \"$INSTALL_DIR\"\nfind \"$INSTALL_DIR\" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +\nchown immich:immich \"$UPLOAD_DIR\" 2>/dev/null || true\nsystemctl enable -q --now immich-ml.service immich-web.service\nmsg_ok \"Modified user, created env file, scripts and services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/immichframe-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Thiago Canozzo Lahr (tclahr)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/immichFrame/ImmichFrame\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\nsetup_deb822_repo \\\n  \"microsoft\" \\\n  \"https://packages.microsoft.com/keys/microsoft-2025.asc\" \\\n  \"https://packages.microsoft.com/debian/13/prod/\" \\\n  \"trixie\" \\\n  \"main\"\n$STD apt install -y \\\n  libicu-dev \\\n  libssl-dev \\\n  gettext-base \\\n  dotnet-sdk-8.0 \\\n  aspnetcore-runtime-8.0\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"immichframe\" \"immichFrame/ImmichFrame\" \"tarball\" \"latest\" \"/tmp/immichframe\"\n\nmsg_info \"Setting up ImmichFrame\"\nmkdir -p /opt/immichframe\ncd /tmp/immichframe\n$STD dotnet publish ImmichFrame.WebApi/ImmichFrame.WebApi.csproj \\\n  --configuration Release \\\n  --runtime linux-x64 \\\n  --self-contained false \\\n  --output /opt/immichframe\ncd /tmp/immichframe/immichFrame.Web\n$STD npm ci\n$STD npm run build\ncp -r build/* /opt/immichframe/wwwroot\nrm -rf /tmp/immichframe\nmkdir -p /opt/immichframe/Config\ncurl -fsSL \"https://raw.githubusercontent.com/immichFrame/ImmichFrame/main/docker/Settings.example.yml\" -o /opt/immichframe/Config/Settings.yml\nuseradd -r -s /sbin/nologin -d /opt/immichframe -M immichframe\nchown -R immichframe:immichframe /opt/immichframe\nmsg_ok \"Setup ImmichFrame\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/immichframe.service\n[Unit]\nDescription=ImmichFrame Digital Photo Frame\nAfter=network.target\n\n[Service]\nType=simple\nUser=immichframe\nGroup=immichframe\nWorkingDirectory=/opt/immichframe\nExecStart=/usr/bin/dotnet /opt/immichframe/ImmichFrame.WebApi.dll\nEnvironment=ASPNETCORE_URLS=http://0.0.0.0:8080\nEnvironment=ASPNETCORE_ENVIRONMENT=Production\nEnvironment=DOTNET_CONTENTROOT=/opt/immichframe\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\nSyslogIdentifier=immichframe\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now immichframe\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/infisical-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://infisical.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  redis\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"infisical_db\" PG_DB_USER=\"infisical\" setup_postgresql_db\n\nmsg_info \"Setting up Infisical Repository\"\nsetup_deb822_repo \\\n  \"infisical\" \\\n  \"https://artifacts-infisical-core.infisical.com/infisical.gpg\" \\\n  \"https://artifacts-infisical-core.infisical.com/deb\" \\\n  \"stable\"\nmsg_ok \"Setup Infisical repository\"\n\nmsg_info \"Setting up Infisical\"\nAUTH_SECRET=\"$(openssl rand -base64 32 | tr -d '\\n')\"\nENC_KEY=\"$(openssl rand -hex 16 | tr -d '\\n')\"\n$STD apt install -y infisical-core\nmkdir -p /etc/infisical\ncat <<EOF >/etc/infisical/infisical.rb\ninfisical_core['ENCRYPTION_KEY'] = '$ENC_KEY'\ninfisical_core['AUTH_SECRET'] = '$AUTH_SECRET'\ninfisical_core['HOST'] = '$LOCAL_IP'\ninfisical_core['DB_CONNECTION_URI'] = 'postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}'\ninfisical_core['REDIS_URL'] = 'redis://localhost:6379'\nEOF\n$STD infisical-ctl reconfigure\nmsg_ok \"Setup Infisical\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/influxdb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.influxdata.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up InfluxDB Repository\"\nsetup_deb822_repo \\\n  \"influxdata\" \\\n  \"https://repos.influxdata.com/influxdata-archive.key\" \\\n  \"https://repos.influxdata.com/debian\" \\\n  \"stable\"\nmsg_ok \"Set up InfluxDB Repository\"\n\nread -r -p \"${TAB3}Which version of InfluxDB to install? (1, 2 or 3) \" prompt\nif [[ $prompt == \"3\" ]]; then\n  INFLUX=\"3\"\nelif [[ $prompt == \"2\" ]]; then\n  INFLUX=\"2\"\nelse\n  INFLUX=\"1\"\nfi\n\nmsg_info \"Installing InfluxDB v${INFLUX}\"\nif [[ $INFLUX == \"3\" ]]; then\n  $STD apt install -y influxdb3-core\n  systemctl enable -q --now influxdb3-core\nelif [[ $INFLUX == \"2\" ]]; then\n  $STD apt install -y influxdb2\n  systemctl enable -q --now influxdb\nelse\n  $STD apt install -y influxdb\n  download_file \"https://dl.influxdata.com/chronograf/releases/chronograf_1.10.8_arm64.deb\" \"${HOME}/chronograf_1.10.8_arm64.deb\"\n  $STD dpkg -i \"${HOME}/chronograf_1.10.8_arm64.deb\"\n  rm -rf \"${HOME}/chronograf_1.10.8_arm64.deb\"\n  systemctl enable -q --now influxdb\nfi\nmsg_ok \"Installed InfluxDB\"\n\nread -r -p \"${TAB3}Would you like to add Telegraf? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Telegraf\"\n  $STD apt install -y telegraf\n  msg_ok \"Installed Telegraf\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/inspircd-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.inspircd.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"inspircd\" \"inspircd/inspircd\" \"binary\" \"latest\" \"/opt/inspircd\" \"inspircd_*.deb13u1_amd64.deb\"\n\nmsg_info \"Configuring InspIRCd\"\ncat <<EOF >/etc/inspircd/inspircd.conf\n<define name=\"networkDomain\" value=\"community-scripts.org\">\n<define name=\"networkName\" value=\"Proxmox VE Helper-Scripts\">\n\n<server\n        name=\"irc.&networkDomain;\"\n        description=\"&networkName; IRC server\"\n        network=\"&networkName;\">\n<admin\n       name=\"Admin\"\n       description=\"Supreme Overlord\"\n       email=\"irc@&networkDomain;\">\n<bind address=\"\" port=\"6667\" type=\"clients\">\nEOF\nsystemctl enable -q --now inspircd\nmsg_ok \"Installed InspIRCd\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/inventree-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/inventree/InvenTree\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\ntemp_file=$(mktemp)\ncurl -fsSL \"http://ports.ubuntu.com/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_arm64.deb\" -o \"$temp_file\"\n$STD dpkg -i $temp_file\nrm -f $temp_file\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up InvenTree Repository\"\nsetup_deb822_repo \\\n  \"inventree\" \\\n  \"https://dl.packager.io/srv/inventree/InvenTree/key\" \\\n  \"https://dl.packager.io/srv/deb/inventree/InvenTree/stable/$(get_os_info id)\" \\\n  \"$(get_os_info version)\" \\\n  \"main\"\nmsg_ok \"Set up InvenTree Repository\"\n\nmsg_info \"Installing InvenTree (Patience)\"\nexport SETUP_NO_CALLS=true\n$STD apt install -y inventree\nmsg_ok \"Installed InvenTree\"\n\nmsg_info \"Configuring InvenTree\"\nif [[ -f /etc/inventree/config.yaml ]]; then\n  sed -i \"s|site_url:.*|site_url: http://${LOCAL_IP}|\" /etc/inventree/config.yaml\nfi\n$STD inventree run invoke update\nmsg_ok \"Configured InvenTree\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/investbrain-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Benito Rodríguez (b3ni)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/investbrainapp/investbrain\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  supervisor \\\n  redis-server \\\n  libfreetype-dev \\\n  libjpeg62-turbo-dev \\\n  libpng-dev \\\n  zlib1g-dev \\\n  libzip-dev \\\n  libicu-dev \\\n  libpq-dev\nmsg_ok \"Installed Dependencies\"\n\nexport PHP_VERSION=\"8.4\"\nPHP_FPM=\"YES\" PHP_MODULE=\"pdo-pgsql\" setup_php\nsetup_composer\nNODE_VERSION=\"22\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"investbrain\" PG_DB_USER=\"investbrain\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"Investbrain\" \"investbrainapp/investbrain\" \"tarball\" \"latest\" \"/opt/investbrain\"\n\nmsg_info \"Installing Investbrain\"\nAPP_KEY=$(openssl rand -base64 32)\ncd /opt/investbrain\ncat <<EOF >/opt/investbrain/.env\nAPP_KEY=base64:${APP_KEY}\nAPP_PORT=8000\nAPP_URL=http://${LOCAL_IP}:8000\nASSET_URL=http://${LOCAL_IP}:8000\n\nLOG_CHANNEL=daily\nLOG_LEVEL=warning\n\nREGISTRATION_ENABLED=true\n\nAI_CHAT_ENABLED=false\nOPENAI_API_KEY=\nOPENAI_ORGANIZATION=\n\nMARKET_DATA_PROVIDER=yahoo\nALPHAVANTAGE_API_KEY=\nFINNHUB_API_KEY=\nALPACA_API_KEY=\nALPACA_API_SECRET=\nTWELVEDATA_API_SECRET=\n\nMARKET_DATA_REFRESH=30\nDAILY_CHANGE_TIME=\n\nDB_CONNECTION=pgsql\nDB_HOST=127.0.0.1\nDB_PORT=5432\nDB_DATABASE=${PG_DB_NAME}\nDB_USERNAME=${PG_DB_USER}\nDB_PASSWORD=${PG_DB_PASS}\n\nREDIS_CLIENT=phpredis\nREDIS_HOST=127.0.0.1\nREDIS_PASSWORD=null\nREDIS_PORT=6379\n\nCACHE_STORE=redis\nCACHE_PREFIX=\n\nSESSION_DRIVER=redis\nSESSION_LIFETIME=120\n\nQUEUE_CONNECTION=redis\n\nMAIL_MAILER=log\nMAIL_HOST=127.0.0.1\nMAIL_PORT=2525\nMAIL_FROM_ADDRESS=\"investbrain@${LOCAL_IP}\"\n\nVITE_APP_NAME=Investbrain\n\n# Reverse Proxy Support (uncomment and set APP_URL/ASSET_URL to your domain when using a reverse proxy)\n# APP_URL=https://your-domain.com\n# ASSET_URL=https://your-domain.com\n# TRUSTED_PROXIES=*\nEOF\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD /usr/local/bin/composer install --no-interaction --no-dev --optimize-autoloader\n$STD npm install\n$STD npm run build\nmkdir -p /opt/investbrain/storage/{framework/cache,framework/sessions,framework/views,app,logs}\n$STD php artisan migrate --force\n$STD php artisan storage:link\n$STD php artisan cache:clear\n$STD php artisan view:clear\n$STD php artisan route:clear\n$STD php artisan event:clear\n$STD php artisan route:cache\n$STD php artisan event:cache\nchown -R www-data:www-data /opt/investbrain\nchmod -R 775 /opt/investbrain/bootstrap/cache\nmsg_ok \"Installed Investbrain\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/investbrain.conf\nserver {\n    listen 8000 default_server;\n    listen [::]:8000 default_server;\n    server_name _;\n\n    root /opt/investbrain/public;\n    index index.php;\n\n    client_max_body_size 50M;\n    charset utf-8;\n\n    location = /favicon.ico { access_log off; log_not_found off; }\n    location = /robots.txt  { access_log off; log_not_found off; }\n\n    location / {\n        try_files \\$uri \\$uri/ /index.php?\\$query_string;\n    }\n\n    location ~ \\.php\\$ {\n        fastcgi_pass unix:/var/run/php/php${PHP_VERSION}-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME \\$realpath_root\\$fastcgi_script_name;\n        include fastcgi_params;\n        fastcgi_hide_header X-Powered-By;\n        fastcgi_read_timeout 300;\n    }\n\n    location ~ /\\.(?!well-known).* {\n        deny all;\n    }\n\n    error_log /var/log/nginx/investbrain_error.log;\n    access_log /var/log/nginx/investbrain_access.log;\n}\nEOF\nln -sf /etc/nginx/sites-available/investbrain.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Setting up Supervisor\"\ncat <<EOF >/etc/supervisor/conf.d/investbrain.conf\n[program:investbrain-queue]\nprocess_name=%%(program_name)s_%%(process_num)02d\ncommand=php /opt/investbrain/artisan queue:work --sleep=3 --tries=1 --memory=256 --timeout=3600\nuser=www-data\nautostart=true\nautorestart=true\nredirect_stderr=true\nstdout_logfile=/opt/investbrain/storage/logs/queue.log\nstdout_logfile_maxbytes=50MB\nstdout_logfile_backups=10\nnumprocs=1\nEOF\n$STD supervisorctl reread\n$STD supervisorctl update\n$STD supervisorctl start all\nmsg_ok \"Setup Supervisor\"\n\nmsg_info \"Setting up Cron for Scheduler\"\ncat <<EOF >/etc/cron.d/investbrain-scheduler\n* * * * * www-data php /opt/investbrain/artisan schedule:run >> /dev/null 2>&1\nEOF\nchmod 644 /etc/cron.d/investbrain-scheduler\n$STD systemctl restart cron\nmsg_ok \"Setup Cron for Scheduler\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/invoiceninja-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://invoiceninja.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  supervisor \\\n  libnss3 \\\n  libatk1.0-0 \\\n  libatk-bridge2.0-0 \\\n  libcups2 \\\n  libdrm2 \\\n  libxkbcommon0 \\\n  libxcomposite1 \\\n  libxdamage1 \\\n  libxfixes3 \\\n  libxrandr2 \\\n  libgbm1 \\\n  libasound2 \\\n  libpango-1.0-0 \\\n  libcairo2\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"invoiceninja\" MARIADB_DB_USER=\"invoiceninja\" setup_mariadb_db\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"soap\" setup_php\n\nfetch_and_deploy_gh_release \"invoiceninja\" \"invoiceninja/invoiceninja\" \"prebuild\" \"latest\" \"/opt/invoiceninja\" \"invoiceninja.tar.gz\"\n\nmsg_info \"Configuring InvoiceNinja\"\ncd /opt/invoiceninja\nAPP_KEY=$(php artisan key:generate --show)\ncat <<EOF >/opt/invoiceninja/.env\nAPP_NAME=\"Invoice Ninja\"\nAPP_ENV=production\nAPP_KEY=${APP_KEY}\nAPP_DEBUG=false\nAPP_URL=http://${LOCAL_IP}:8080\n\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=${MARIADB_DB_NAME}\nDB_USERNAME=${MARIADB_DB_USER}\nDB_PASSWORD=${MARIADB_DB_PASS}\n\nMULTI_DB_ENABLED=false\nDEMO_MODE=false\n\nBROADCAST_DRIVER=log\nLOG_CHANNEL=stack\nCACHE_DRIVER=file\nQUEUE_CONNECTION=database\nSESSION_DRIVER=file\nSESSION_LIFETIME=120\n\nMAIL_MAILER=log\nMAIL_HOST=null\nMAIL_PORT=null\nMAIL_USERNAME=null\nMAIL_PASSWORD=null\nMAIL_ENCRYPTION=null\nMAIL_FROM_ADDRESS=\"noreply@localhost\"\nMAIL_FROM_NAME=\"Invoice Ninja\"\n\nREQUIRE_HTTPS=false\nNINJA_ENVIRONMENT=selfhost\nPDF_GENERATOR=snappdf\n\nTRUSTED_PROXIES=*\nINTERNAL_QUEUE_ENABLED=false\nEOF\n\nmkdir -p /opt/invoiceninja/bootstrap/cache\nmkdir -p /opt/invoiceninja/storage/{app/public,framework/{cache/data,sessions,views},logs}\nchown -R www-data:www-data /opt/invoiceninja\nchown -R www-data:www-data /opt/invoiceninja/storage\nchown -R www-data:www-data /opt/invoiceninja/bootstrap/cache\nmsg_ok \"Configured InvoiceNinja\"\n\nmsg_info \"Downloading Chromium for PDF Generation\"\ncd /opt/invoiceninja\n$STD ./vendor/bin/snappdf download\nchown -R www-data:www-data /opt/invoiceninja/vendor/beganovich/snappdf/versions\nmsg_ok \"Downloaded Chromium for PDF Generation\"\n\nmsg_info \"Setting up Database\"\ncd /opt/invoiceninja\n$STD php artisan config:clear\n$STD php artisan cache:clear\n$STD php artisan route:clear\n$STD php artisan view:clear\n$STD php artisan migrate --force\n$STD php artisan db:seed --force\n$STD php artisan ninja:post-update\n$STD php artisan optimize\nchown -R www-data:www-data /opt/invoiceninja\nmsg_ok \"Set up Database\"\n\nmsg_info \"Configuring Nginx\"\ncat <<'EOF' >/etc/nginx/sites-available/invoiceninja\nserver {\n    listen 8080;\n    server_name _;\n    root /opt/invoiceninja/public;\n    index index.php;\n\n    client_max_body_size 50M;\n    charset utf-8;\n\n    gzip on;\n    gzip_types application/javascript application/x-javascript text/javascript text/plain application/xml application/json;\n    gzip_proxied no-cache no-store private expired auth;\n    gzip_min_length 1000;\n\n    location / {\n        try_files $uri $uri/ /index.php?$query_string;\n    }\n\n    location = /index.php {\n        fastcgi_pass unix:/run/php/php8.4-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n        include fastcgi_params;\n        fastcgi_read_timeout 300;\n    }\n\n    location ~ \\.php$ {\n        return 403;\n    }\n\n    location ~ /\\.ht {\n        deny all;\n    }\n\n    error_log /var/log/nginx/invoiceninja_error.log;\n    access_log /var/log/nginx/invoiceninja_access.log;\n}\nEOF\n\nln -sf /etc/nginx/sites-available/invoiceninja /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Setting up Queue Worker\"\ncat <<'EOF' >/etc/supervisor/conf.d/invoiceninja-worker.conf\n[program:invoiceninja-worker]\nprocess_name=%(program_name)s_%(process_num)02d\ncommand=php /opt/invoiceninja/artisan queue:work --sleep=3 --tries=3 --max-time=3600\nautostart=true\nautorestart=true\nstopasgroup=true\nkillasgroup=true\nuser=www-data\nnumprocs=2\nredirect_stderr=true\nstdout_logfile=/var/log/invoiceninja-worker.log\nstopwaitsecs=3600\nEOF\n\ntouch /var/log/invoiceninja-worker.log\nchown www-data:www-data /var/log/invoiceninja-worker.log\n$STD supervisorctl reread\n$STD supervisorctl update\nmsg_ok \"Set up Queue Worker\"\n\nmsg_info \"Setting up Cron\"\ncat <<'EOF' >/etc/cron.d/invoiceninja\n* * * * * www-data cd /opt/invoiceninja && php artisan schedule:run >> /dev/null 2>&1\nEOF\nmsg_ok \"Set up Cron\"\n\nmsg_info \"Enabling Services\"\nsystemctl enable -q --now php8.4-fpm nginx supervisor\nmsg_ok \"Enabled Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/iobroker-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.iobroker.net/#en/intro\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://iobroker.net/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://iobroker.net/install.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Installing ioBroker (Patience)\"\n$STD bash <(curl -fsSL https://iobroker.net/install.sh)\nmsg_ok \"Installed ioBroker\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ironclaw-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/nearai/ironclaw\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  dbus-user-session \\\n  gnome-keyring \\\n  libsecret-tools\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" PG_MODULES=\"pgvector\" setup_postgresql\nPG_DB_NAME=\"ironclaw\" PG_DB_USER=\"ironclaw\" PG_DB_EXTENSIONS=\"vector\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"ironclaw-bin\" \"nearai/ironclaw\" \"prebuild\" \"latest\" \"/usr/local/bin\" \\\n  \"ironclaw-$(uname -m)-unknown-linux-$([[ -f /etc/alpine-release ]] && echo \"musl\" || echo \"gnu\").tar.gz\"\nchmod +x /usr/local/bin/ironclaw\n\nmsg_info \"Configuring IronClaw\"\nmkdir -p /root/.ironclaw\nGATEWAY_TOKEN=$(openssl rand -hex 32)\ncat <<EOF >/root/.ironclaw/.env\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}?sslmode=disable\nGATEWAY_ENABLED=true\nGATEWAY_HOST=0.0.0.0\nGATEWAY_PORT=3000\nGATEWAY_AUTH_TOKEN=${GATEWAY_TOKEN}\nCLI_ENABLED=false\nAGENT_NAME=ironclaw\nRUST_LOG=ironclaw=info,tower_http=info\nEOF\nchmod 600 /root/.ironclaw/.env\nmsg_ok \"Configured IronClaw\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ironclaw.service\n[Unit]\nDescription=IronClaw AI Agent\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/root\nEnvironmentFile=/root/.ironclaw/.env\nExecStart=/usr/bin/dbus-run-session /usr/local/bin/ironclaw\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q ironclaw\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/isponsorblocktv-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Matthew Stern (sternma) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/dmunozv04/iSponsorBlockTV\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nif ! grep -q ' avx ' /proc/cpuinfo 2>/dev/null; then\n  msg_error \"CPU does not support AVX instructions (required by iSponsorBlockTV/PyApp)\"\n  exit 106\nfi\n\nfetch_and_deploy_gh_release \"isponsorblocktv\" \"dmunozv04/iSponsorBlockTV\" \"singlefile\" \"latest\" \"/opt/isponsorblocktv\" \"iSponsorBlockTV-x86_64-linux\"\n\nmsg_info \"Setting up iSponsorBlockTV\"\ninstall -d /var/lib/isponsorblocktv\nmsg_ok \"Set up iSponsorBlockTV\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/isponsorblocktv.service\n[Unit]\nDescription=iSponsorBlockTV\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nEnvironment=iSPBTV_data_dir=/var/lib/isponsorblocktv\nExecStart=/opt/isponsorblocktv/isponsorblocktv\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q isponsorblocktv\nmsg_ok \"Created Service\"\n\nmsg_info \"Creating CLI wrapper\"\ncat <<'EOF' >/usr/local/bin/iSponsorBlockTV\n#!/usr/bin/env bash\nexport iSPBTV_data_dir=\"/var/lib/isponsorblocktv\"\n\nset +e\n/opt/isponsorblocktv/isponsorblocktv \"$@\"\nstatus=$?\nset -e\n\ncase \"${1:-}\" in\n  setup|setup-cli)\n    systemctl restart isponsorblocktv >/dev/null 2>&1 || true\n    ;;\nesac\n\nexit $status\nEOF\nchmod +x /usr/local/bin/iSponsorBlockTV\nln -sf /usr/local/bin/iSponsorBlockTV /usr/bin/iSponsorBlockTV\nmsg_ok \"Created CLI wrapper\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/itsm-ng-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Florianb63\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://itsm-ng.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_mariadb\nmsg_info \"Loading timezone data\"\nmariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb mysql\nmsg_ok \"Loaded timezone data\"\nMARIADB_DB_NAME=\"itsmng_db\" MARIADB_DB_USER=\"itsmng\" MARIADB_DB_EXTRA_GRANTS=\"GRANT SELECT ON \\`mysql\\`.\\`time_zone_name\\`\" setup_mariadb_db\n\nmsg_info \"Installing ITSM-NG\"\nsetup_deb822_repo \\\n  \"itsm-ng\" \\\n  \"http://deb.itsm-ng.org/pubkey.gpg\" \\\n  \"http://deb.itsm-ng.org/$(get_os_info id)/\" \\\n  \"$(get_os_info codename)\"\n$STD apt install -y itsm-ng\ncd /usr/share/itsm-ng\n$STD php bin/console db:install --db-name=\"$MARIADB_DB_NAME\" --db-user=\"$MARIADB_DB_USER\" --db-password=\"$MARIADB_DB_PASS\" --no-interaction\n$STD a2dissite 000-default.conf\necho \"* * * * * www-data php /usr/share/itsm-ng/front/cron.php\" | crontab -\nmsg_ok \"Installed ITSM-NG\"\n\nmsg_info \"Setting permissions\"\nchown -R www-data:www-data /var/lib/itsm-ng\nmkdir -p /usr/share/itsm-ng/css/palettes\nchown -R www-data:www-data /usr/share/itsm-ng/css\nchown -R www-data:www-data /usr/share/itsm-ng/css_compiled\nchown www-data:www-data /etc/itsm-ng/config_db.php\nmsg_ok \"Set permissions\"\n\nmsg_info \"Configuring PHP\"\nPHP_VERSION=$(ls /etc/php/ | grep -E '^[0-9]+\\.[0-9]+$' | head -n 1)\nPHP_INI=\"/etc/php/$PHP_VERSION/apache2/php.ini\"\nsed -i 's/^upload_max_filesize = .*/upload_max_filesize = 20M/' $PHP_INI\nsed -i 's/^post_max_size = .*/post_max_size = 20M/' $PHP_INI\nsed -i 's/^max_execution_time = .*/max_execution_time = 60/' $PHP_INI\nsed -i 's/^[;]*max_input_vars *=.*/max_input_vars = 5000/' \"$PHP_INI\"\nsed -i 's/^memory_limit = .*/memory_limit = 256M/' $PHP_INI\nsed -i 's/^;\\?\\s*session.cookie_httponly\\s*=.*/session.cookie_httponly = On/' $PHP_INI\nsystemctl restart apache2\nrm -rf /usr/share/itsm-ng/install\nmsg_ok \"Configured PHP\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/jackett-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Jackett/Jackett\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"jackett\" \"Jackett/Jackett\" \"prebuild\" \"latest\" \"/opt/Jackett\" \"Jackett.Binaries.LinuxARM64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/jackett.service\n[Unit]\nDescription=Jackett Daemon\nAfter=network.target\n\n[Service]\nSyslogIdentifier=jackett\nRestart=always\nRestartSec=5\nType=simple\nWorkingDirectory=/opt/Jackett\nExecStart=/bin/sh /opt/Jackett/jackett_launcher.sh\nTimeoutStopSec=30\nEnvironmentFile=\"/opt/.env\"\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now jackett\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/jeedom-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Mips2648\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://jeedom.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  lsb-release \\\n  git\nmsg_ok \"Dependencies installed\"\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://github.com/jeedom/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://raw.githubusercontent.com/jeedom/core/master/install/install.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nDEFAULT_BRANCH=\"master\"\nREPO_URL=\"https://github.com/jeedom/core.git\"\n\necho\nwhile true; do\n  read -rp \"${TAB3}Enter branch to use (master, beta, alpha...) (Default: ${DEFAULT_BRANCH}): \" BRANCH\n  BRANCH=\"${BRANCH:-$DEFAULT_BRANCH}\"\n\n  if git ls-remote --heads \"$REPO_URL\" \"refs/heads/$BRANCH\" | grep -q .; then\n    break\n  else\n    msg_error \"Branch '$BRANCH' does not exist on remote. Please try again.\"\n  fi\ndone\n\nmsg_info \"Downloading Jeedom installation script\"\ncd /tmp\nwget -q https://raw.githubusercontent.com/jeedom/core/\"${BRANCH}\"/install/install.sh\nchmod +x install.sh\nmsg_ok \"Installation script downloaded\"\n\nmsg_info \"Install Jeedom main dependencies, please wait\"\n$STD ./install.sh -v \"$BRANCH\" -s 2\nmsg_ok \"Installed Jeedom main dependencies\"\n\nmsg_info \"Install Database\"\n$STD ./install.sh -v \"$BRANCH\" -s 3\nmsg_ok \"Database installed\"\n\nmsg_info \"Install Apache\"\n$STD ./install.sh -v \"$BRANCH\" -s 4\nmsg_ok \"Apache installed\"\n\nmsg_info \"Install PHP and dependencies\"\n$STD ./install.sh -v \"$BRANCH\" -s 5\nmsg_ok \"PHP installed\"\n\nmsg_info \"Download Jeedom core\"\n$STD ./install.sh -v \"$BRANCH\" -s 6\nmsg_ok \"Download done\"\n\nmsg_info \"Database customisation\"\n$STD ./install.sh -v \"$BRANCH\" -s 7\nmsg_ok \"Database customisation done\"\n\nmsg_info \"Jeedom customisation\"\n$STD ./install.sh -v \"$BRANCH\" -s 8\nmsg_ok \"Jeedom customisation done\"\n\nmsg_info \"Configuring Jeedom\"\n$STD ./install.sh -v \"$BRANCH\" -s 9\nmsg_ok \"Jeedom configured\"\n\nmsg_info \"Installing Jeedom\"\n$STD ./install.sh -v \"$BRANCH\" -s 10\nmsg_ok \"Jeedom installed\"\n\nmsg_info \"Post installation\"\n$STD ./install.sh -v \"$BRANCH\" -s 11\nmsg_ok \"Post installation done\"\n\nmsg_info \"Check installation\"\n$STD ./install.sh -v \"$BRANCH\" -s 12\nmsg_ok \"Installation checked, everything is successfuly installed. A reboot is recommended.\"\n\nmotd_ssh\ncustomize\n\nrm -rf /tmp/install.sh\ncleanup_lxc\n"
  },
  {
    "path": "install/jellyfin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://jellyfin.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_custom \"ℹ️\" \"${GN}\" \"If NVIDIA GPU passthrough is detected, you'll be asked whether to install drivers in the container\"\n\nmsg_info \"Installing Dependencies\"\nensure_dependencies libjemalloc2\nif [[ ! -f /usr/lib/libjemalloc.so ]]; then\n  ln -sf /usr/lib/aarch64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so\nfi\nif [[ ! -d /etc/apt/keyrings ]]; then\n  mkdir -p /etc/apt/keyrings\nfi\ncurl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor --yes --output /etc/apt/keyrings/jellyfin.gpg\ncat <<EOF >/etc/apt/sources.list.d/jellyfin.sources\nTypes: deb\nURIs: https://repo.jellyfin.org/${PCT_OSTYPE}\nSuites: ${VERSION}\nComponents: main\nArchitectures: arm64\nSigned-By: /etc/apt/keyrings/jellyfin.gpg\nEOF\n\nmsg_info \"Setting up Jellyfin Repository\"\nsetup_deb822_repo \\\n  \"jellyfin\" \\\n  \"https://repo.jellyfin.org/jellyfin_team.gpg.key\" \\\n  \"https://repo.jellyfin.org/$(get_os_info id)\" \\\n  \"$(get_os_info codename)\"\nmsg_ok \"Set up Jellyfin Repository\"\n\nmsg_info \"Installing Jellyfin\"\n$STD apt install -y jellyfin jellyfin-ffmpeg7\nln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin/ffmpeg\nln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/bin/ffprobe\nmsg_ok \"Installed Jellyfin\"\n\nsetup_hwaccel \"jellyfin\"\n\nmsg_info \"Configuring Jellyfin\"\n# Configure log rotation to prevent disk fill (keeps fail2ban compatibility) (PR: #1690 / Issue: #11224)\ncat <<EOF >/etc/logrotate.d/jellyfin\n/var/log/jellyfin/*.log {\n    daily\n    rotate 3\n    maxsize 100M\n    missingok\n    notifempty\n    compress\n    delaycompress\n    copytruncate\n}\nEOF\nchown -R jellyfin:adm /etc/jellyfin\nsleep 10\nsystemctl restart jellyfin\nmsg_ok \"Configured Jellyfin\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/jenkins-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.jenkins.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"21\" setup_java\nsetup_deb822_repo \\\n  \"jenkins\" \\\n  \"https://pkg.jenkins.io/debian/jenkins.io-2026.key\" \\\n  \"https://pkg.jenkins.io/debian\" \\\n  \"binary/\" \\\n  \" \"\n\nmsg_info \"Setup Jenkins\"\n$STD apt install -y jenkins\nmsg_ok \"Setup Jenkins\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/jitsi-meet-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://jitsi.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y nginx\nmsg_ok \"Installed Dependencies\"\n\nsource /etc/os-release\nsetup_deb822_repo \"jitsi\" \\\n  \"https://download.jitsi.org/jitsi-key.gpg.key\" \\\n  \"https://download.jitsi.org\" \\\n  \"stable/\" \\\n  \"\"\n\nmsg_info \"Installing Jitsi Meet\"\necho \"jitsi-videobridge2 jitsi-videobridge/jvb-hostname string ${LOCAL_IP}\" | debconf-set-selections\necho \"jitsi-meet-web-config jitsi-meet/cert-choice select Generate a new self-signed certificate\" | debconf-set-selections\nDEBIAN_FRONTEND=noninteractive $STD apt install -y jitsi-meet\nmsg_ok \"Installed Jitsi Meet\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/joplin-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://joplinapp.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  rsync\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"joplin\" PG_DB_USER=\"joplin\" setup_postgresql_db\nNODE_VERSION=\"24\" NODE_MODULE=\"yarn,npm,pm2\" setup_nodejs\nmkdir -p /opt/pm2\nexport PM2_HOME=/opt/pm2\n$STD pm2 install pm2-logrotate\n$STD pm2 set pm2-logrotate:max_size 100MB\n$STD pm2 set pm2-logrotate:retain 5\n$STD pm2 set pm2-logrotate:compress tr\n\nfetch_and_deploy_gh_release \"joplin-server\" \"laurent22/joplin\" \"tarball\"\n\nmsg_info \"Setting up Joplin Server (Patience)\"\ncd /opt/joplin-server\nsed -i \"/onenote-converter/d\" packages/lib/package.json\n$STD yarn config set --home enableTelemetry 0\nexport BUILD_SEQUENCIAL=1\n$STD yarn workspaces focus @joplin/server\n$STD yarn workspaces foreach -R --topological-dev --from @joplin/server run build\n$STD yarn workspaces foreach -R --topological-dev --from @joplin/server run tsc\ncat <<EOF >/opt/joplin-server/.env\nPM2_HOME=/opt/pm2\nNODE_ENV=production\nAPP_BASE_URL=http://$LOCAL_IP:22300\nAPP_PORT=22300\nDB_CLIENT=pg\nPOSTGRES_PASSWORD=$PG_DB_PASS\nPOSTGRES_DATABASE=$PG_DB_NAME\nPOSTGRES_USER=$PG_DB_USER\nPOSTGRES_PORT=5432\nPOSTGRES_HOST=localhost\nEOF\nmsg_ok \"Setup Joplin Server\"\n\nmsg_info \"Setting up Service\"\ncat <<EOF >/etc/systemd/system/joplin-server.service\n[Unit]\nDescription=Joplin Server Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/joplin-server/packages/server\nEnvironmentFile=/opt/joplin-server/.env\nExecStart=/usr/bin/yarn start-prod\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now joplin-server\nmsg_ok \"Service Setup\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/jotty-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/fccview/jotty\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\nfetch_and_deploy_gh_release \"jotty\" \"fccview/jotty\" \"prebuild\" \"latest\" \"/opt/jotty\" \"jotty_*_prebuild.tar.gz\"\n\nmsg_info \"Setup jotty\"\nmkdir -p /opt/jotty/data/{users,checklists,notes}\n\ncat <<EOF >/opt/jotty/.env\nNODE_ENV=production\n# --- Uncomment to enable\n# APP_URL=https://your-jotty-domain.com\n# INTERNAL_API_URL=http://localhost:3000\n# HTTPS=true\n# SERVE_PUBLIC_IMAGES=yes\n# SERVE_PUBLIC_FILES=yes\n# SERVE_PUBLIC_VIDEOS=yes\n# STOP_CHECK_UPDATES=yes\n# --- For troubleshooting\n# DEBUGGER=true\n\n# --- SSO with OIDC (optional)\n# SSO_MODE=oidc\n# OIDC_ISSUER=<your-oidc-issuer-url>\n# OIDC_CLIENT_ID=<oidc-client-id>\n# SSO_FALLBACK_LOCAL=yes\n# OIDC_CLIENT_SECRET=your_client_secret\n# OIDC_ADMIN_GROUPS=admins\nEOF\nmsg_ok \"Setup jotty\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/jotty.service\n[Unit]\nDescription=jotty server\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/jotty\nEnvironmentFile=/opt/jotty/.env\nExecStart=/usr/bin/node server.js\nRestart=on-abnormal\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now jotty\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/jupyternotebook-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dave-code-creater (Tan Dat, Ta)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://jupyter.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPYTHON_VERSION=\"3.12\" setup_uv\n\nmsg_info \"Installing Jupyter\"\nmkdir -p /opt/jupyter\ncd /opt/jupyter\n$STD uv venv --clear /opt/jupyter/.venv\n$STD /opt/jupyter/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/jupyter/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/jupyter/.venv/bin/python -m pip install jupyter\nln -s /opt/jupyter/.venv/bin/jupyter /usr/local/bin/jupyter\nln -s /opt/jupyter/.venv/bin/jupyter-lab /usr/local/bin/jupyter-lab\nln -s /opt/jupyter/.venv/bin/jupyter-notebook /usr/local/bin/jupyter-notebook\nmsg_ok \"Installed Jupyter\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/jupyternotebook.service\n[Unit]\nDescription=Jupyter Notebook Server\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/jupyter\nExecStart=/opt/jupyter/.venv/bin/jupyter notebook --ip=0.0.0.0 --port=8888 --allow-root\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now jupyternotebook\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kapowarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Casvt/Kapowarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y python3-pip\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nfetch_and_deploy_gh_release \"kapowarr\" \"Casvt/Kapowarr\" \"tarball\"\n\nmsg_info \"Setup Kapowarr\"\ncd /opt/kapowarr\n$STD uv venv --clear .venv\n$STD source .venv/bin/activate\n$STD uv pip install --upgrade pip\n$STD uv pip install --no-cache-dir -r requirements.txt\nmsg_ok \"Installed Kapowarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/kapowarr.service\n[Unit]\nDescription=Kapowarr Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/kapowarr/\nExecStart=/opt/kapowarr/.venv/bin/python3 Kapowarr.py\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now kapowarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/karakeep-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz) & vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://karakeep.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  ca-certificates \\\n  chromium \\\n  graphicsmagick \\\n  ghostscript \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"monolith\" \"Y2Z/monolith\" \"singlefile\" \"latest\" \"/usr/bin\" \"monolith-gnu-linux-aarch64\"\nfetch_and_deploy_gh_release \"yt-dlp\" \"yt-dlp/yt-dlp-nightly-builds\" \"singlefile\" \"latest\" \"/usr/bin\" \"yt-dlp_linux\"\nsetup_meilisearch\n\nfetch_and_deploy_gh_release \"karakeep\" \"karakeep-app/karakeep\" \"tarball\"\ncd /opt/karakeep\nMODULE_VERSION=\"$(jq -r '.packageManager | split(\"@\")[1]' /opt/karakeep/package.json)\"\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm@${MODULE_VERSION}\" setup_nodejs\n\nmsg_info \"Installing karakeep\"\nexport PUPPETEER_SKIP_DOWNLOAD=\"true\"\nexport PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=\"true\"\nexport NEXT_TELEMETRY_DISABLED=1\nexport CI=\"true\"\ncd /opt/karakeep/apps/web\n$STD pnpm install --frozen-lockfile\n$STD pnpm build\ncd /opt/karakeep/apps/workers\n$STD pnpm install --frozen-lockfile\n$STD pnpm build\ncd /opt/karakeep/apps/cli\n$STD pnpm install --frozen-lockfile\n$STD pnpm build\n$STD pnpm store prune\n\nexport DATA_DIR=/opt/karakeep_data\nkarakeep_SECRET=$(openssl rand -base64 36 | cut -c1-24)\nmkdir -p /etc/karakeep\ncat <<EOF >/etc/karakeep/karakeep.env\nSERVER_VERSION=\"$(sed 's/^v//' ~/.karakeep)\"\nNEXTAUTH_SECRET=\"$karakeep_SECRET\"\nNEXTAUTH_URL=\"http://localhost:3000\"\nDATA_DIR=${DATA_DIR}\nMEILI_ADDR=\"http://127.0.0.1:7700\"\nMEILI_MASTER_KEY=\"$MEILISEARCH_MASTER_KEY\"\nBROWSER_WEB_URL=\"http://127.0.0.1:9222\"\nDB_WAL_MODE=true\n\n# If you're planning to use OpenAI for tagging. Uncomment the following line:\n# OPENAI_API_KEY=\"<API_KEY>\"\n\n# If you're planning to use ollama for tagging, uncomment the following lines:\n# OLLAMA_BASE_URL=\"<OLLAMA_ADDR>\"\n# OLLAMA_KEEP_ALIVE=\"5m\"\n\n# You can change the models used by uncommenting the following lines, and changing them according to your needs:\n# INFERENCE_TEXT_MODEL=\"gpt-4o-mini\"\n# INFERENCE_IMAGE_MODEL=\"gpt-4o-mini\" \n\n# Additional inference defaults\n# INFERENCE_CONTEXT_LENGTH=\"2048\"\n# INFERENCE_ENABLE_AUTO_TAGGING=true\n# INFERENCE_ENABLE_AUTO_SUMMARIZATION=false\n\n# Crawler defaults\n# CRAWLER_NUM_WORKERS=\"1\"\n# CRAWLER_DOWNLOAD_BANNER_IMAGE=true\n# CRAWLER_STORE_SCREENSHOT=true\n# CRAWLER_FULL_PAGE_SCREENSHOT=false\n# CRAWLER_FULL_PAGE_ARCHIVE=false\n# CRAWLER_VIDEO_DOWNLOAD=false\n# CRAWLER_VIDEO_DOWNLOAD_MAX_SIZE=\"50\"\n# CRAWLER_ENABLE_ADBLOCKER=true\nEOF\nmsg_ok \"Installed karakeep\"\n\nmsg_info \"Running Database Migration\"\nmkdir -p ${DATA_DIR}\ncd /opt/karakeep/packages/db\n$STD pnpm migrate\nmsg_ok \"Database Migration Completed\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/karakeep-web.service\n[Unit]\nDescription=karakeep Web\nWants=network.target karakeep-workers.service\nAfter=network.target karakeep-workers.service\n\n[Service]\nExecStart=pnpm start\nWorkingDirectory=/opt/karakeep/apps/web\nEnvironmentFile=/etc/karakeep/karakeep.env\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/karakeep-browser.service\n[Unit]\nDescription=karakeep Headless Browser\nAfter=network.target\n\n[Service]\nUser=root\nExecStart=/usr/bin/chromium --headless --no-sandbox --disable-gpu --disable-dev-shm-usage --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 --hide-scrollbars\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/karakeep-workers.service\n[Unit]\nDescription=karakeep Workers\nWants=network.target karakeep-browser.service meilisearch.service\nAfter=network.target karakeep-browser.service meilisearch.service\n\n[Service]\nExecStart=/usr/bin/node dist/index.js\nWorkingDirectory=/opt/karakeep/apps/workers\nEnvironmentFile=/etc/karakeep/karakeep.env\nRestart=always\nTimeoutStopSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now karakeep-browser karakeep-workers karakeep-web\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kasm-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.kasmweb.com/docs/latest/index.html\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Docker\"\n$STD sh <(curl -fsSL https://get.docker.com/)\nmsg_ok \"Installed Docker\"\n\nmsg_info \"Detecting latest Kasm Workspaces release\"\nKASM_VERSION=$(curl -s https://kasm.com/downloads | grep -oP '<h1[^>]*>.*?</h1>' | sed -E 's/<\\/?h1[^>]*>//g' | grep -oP '\\d+\\.\\d+\\.\\d+')\nKASM_URL=\"https://kasm-static-content.s3.amazonaws.com/kasm_release_${KASM_VERSION:-var_kasm_version}.tar.gz\"\n  \n# KASM_URL=$(curl -fsSL \"https://www.kasm.com/downloads\" | tr '\\n' ' ' | grep -oE 'https://kasm-static-content[^\"]*kasm_release_[0-9]+\\.[0-9]+\\.[0-9]+\\.[a-z0-9]+\\.tar\\.gz' | head -n 1)\n# if [[ -z \"$KASM_URL\" ]]; then\n#   SERVICE_IMAGE_URL=$(curl -fsSL \"https://www.kasm.com/downloads\" | tr '\\n' ' ' | grep -oE 'https://kasm-static-content[^\"]*kasm_release_service_images_amd64_[0-9]+\\.[0-9]+\\.[0-9]+\\.tar\\.gz' | head -n 1)\n#   if [[ -n \"$SERVICE_IMAGE_URL\" ]]; then\n#     KASM_VERSION=$(echo \"$SERVICE_IMAGE_URL\" | sed -E 's/.*kasm_release_service_images_amd64_([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\n#     KASM_URL=\"https://kasm-static-content.s3.amazonaws.com/kasm_release_${KASM_VERSION}.tar.gz\"\n#   fi\n# else\n#   KASM_VERSION=$(echo \"$KASM_URL\" | sed -E 's/.*kasm_release_([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\n# fi\n\nif [[ -z \"$KASM_VERSION\" ]] || [[ -z \"$KASM_URL\" ]]; then\n  msg_error \"Unable to detect latest Kasm release URL.\"\n  exit 250\nfi\nmsg_ok \"Detected Kasm Workspaces version $KASM_VERSION\"\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://www.kasmweb.com/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  install.sh inside tar.gz $KASM_URL\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nmsg_info \"Installing Kasm Workspaces\"\ncurl -fsSL -o \"/opt/kasm_release_${KASM_VERSION}.tar.gz\" \"$KASM_URL\"\ncd /opt\ntar -xf \"kasm_release_${KASM_VERSION}.tar.gz\"\nchmod +x /opt/kasm_release/install.sh\nprintf 'y\\ny\\ny\\n4\\n' | bash /opt/kasm_release/install.sh >~/kasm-install.output 2>&1\nawk '\n  /^Kasm UI Login Credentials$/ {capture=1}\n  capture {print}\n  /^Service Registration Token$/ {in_token=1}\n  in_token && /^-+$/ {dash_count++}\n  in_token && dash_count==2 {exit}\n' ~/kasm-install.output >~/kasm.creds\nrm -f /opt/kasm_release_${KASM_VERSION}.tar.gz\nrm -f ~/kasm-install.output\nmsg_ok \"Installed Kasm Workspaces\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kavita-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.kavitareader.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"Kavita\" \"Kareadita/Kavita\" \"prebuild\" \"latest\" \"/opt/Kavita\" \"kavita-linux-arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/kavita.service\n[Unit]\nDescription=Kavita Server\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/Kavita\nExecStart=/opt/Kavita/Kavita\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nchmod +x /opt/Kavita/Kavita && chown root:root /opt/Kavita/Kavita\nsystemctl enable -q --now kavita\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/keycloak-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: Slaviša Arežina (tremor021), remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/keycloak/keycloak\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=21 setup_java\nPG_VERSION=16 setup_postgresql\n\nmsg_info \"Configuring PostgreSQL\"\nDB_NAME=\"keycloak\"\nDB_USER=\"keycloak\"\nDB_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\"\n$STD sudo -u postgres psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME WITH OWNER $DB_USER ENCODING 'UTF8';\"\n$STD sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;\"\nmsg_ok \"Configured PostgreSQL\"\n\nfetch_and_deploy_gh_release \"keycloak_app\" \"keycloak/keycloak\" \"prebuild\" \"latest\" \"/opt/keycloak\" \"keycloak-*.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/keycloak.service\n[Unit]\nDescription=Keycloak Service\nRequires=network.target\nAfter=syslog.target network-online.target\n\n[Service]\nType=idle\nUser=root\nWorkingDirectory=/opt/keycloak\nExecStart=/opt/keycloak/bin/kc.sh start\nExecStop=/opt/keycloak/bin/kc.sh stop\nRestart=always\nRestartSec=3\nEnvironment=\"JAVA_HOME=/usr/lib/jvm/temurin-21-jdk-arm64\"\nEnvironment=\"KC_DB=postgres\"\nEnvironment=\"KC_DB_USERNAME=$DB_USER\"\nEnvironment=\"KC_DB_PASSWORD=$DB_PASS\"\nEnvironment=\"KC_HTTP_ENABLED=true\"\nEnvironment=\"KC_BOOTSTRAP_ADMIN_USERNAME=tmpadm\"\nEnvironment=\"KC_BOOTSTRAP_ADMIN_PASSWORD=admin123\"\n# Comment following line and uncomment the next 2 if working behind a reverse proxy\nEnvironment=\"KC_HOSTNAME_STRICT=false\"\n#Environment=\"KC_HOSTNAME=keycloak.example.com\"\n#Environment=\"KC_PROXY_HEADERS=xforwarded\"\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now keycloak\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kima-hub-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Chevron7Locked/kima-hub\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  build-essential \\\n  git \\\n  openssl \\\n  ffmpeg \\\n  python3 \\\n  python3-pip \\\n  python3-dev \\\n  python3-numpy \\\n  redis-server\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" PG_MODULES=\"pgvector\" setup_postgresql\nPG_DB_NAME=\"kima\" PG_DB_USER=\"kima\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\nNODE_VERSION=\"22\" setup_nodejs\n\nmsg_info \"Configuring Redis\"\nsystemctl enable -q --now redis-server\nmsg_ok \"Configured Redis\"\n\nfetch_and_deploy_gh_release \"kima-hub\" \"Chevron7Locked/kima-hub\" \"tarball\"\n\nmsg_info \"Installing Python Dependencies\"\nexport PIP_BREAK_SYSTEM_PACKAGES=1\n$STD pip3 install --no-cache-dir \\\n  tensorflow \\\n  essentia-tensorflow \\\n  redis \\\n  psycopg2-binary \\\n  laion-clap \\\n  torch \\\n  torchaudio \\\n  librosa \\\n  transformers \\\n  pgvector \\\n  python-dotenv \\\n  requests\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Downloading Essentia ML Models\"\nmkdir -p /opt/kima-hub/models\ncd /opt/kima-hub/models\ncurl -fsSL -o msd-musicnn-1.pb \"https://essentia.upf.edu/models/autotagging/msd/msd-musicnn-1.pb\"\ncurl -fsSL -o mood_happy-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_happy/mood_happy-msd-musicnn-1.pb\"\ncurl -fsSL -o mood_sad-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_sad/mood_sad-msd-musicnn-1.pb\"\ncurl -fsSL -o mood_relaxed-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_relaxed/mood_relaxed-msd-musicnn-1.pb\"\ncurl -fsSL -o mood_aggressive-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_aggressive/mood_aggressive-msd-musicnn-1.pb\"\ncurl -fsSL -o mood_party-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_party/mood_party-msd-musicnn-1.pb\"\ncurl -fsSL -o mood_acoustic-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_acoustic/mood_acoustic-msd-musicnn-1.pb\"\ncurl -fsSL -o mood_electronic-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/mood_electronic/mood_electronic-msd-musicnn-1.pb\"\ncurl -fsSL -o danceability-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/danceability/danceability-msd-musicnn-1.pb\"\ncurl -fsSL -o voice_instrumental-msd-musicnn-1.pb \"https://essentia.upf.edu/models/classification-heads/voice_instrumental/voice_instrumental-msd-musicnn-1.pb\"\nmsg_ok \"Downloaded Essentia ML Models\"\n\nmsg_info \"Downloading CLAP Model\"\ncurl -fsSL -o /opt/kima-hub/models/music_audioset_epoch_15_esc_90.14.pt \"https://huggingface.co/lukewys/laion_clap/resolve/main/music_audioset_epoch_15_esc_90.14.pt\"\nmsg_ok \"Downloaded CLAP Model\"\n\nmsg_info \"Building Backend\"\ncd /opt/kima-hub/backend\n$STD npm ci\n$STD npm run build\nmsg_ok \"Built Backend\"\n\nmsg_info \"Configuring Backend\"\nSESSION_SECRET=$(openssl rand -hex 32)\nENCRYPTION_KEY=$(openssl rand -hex 32)\ncat <<EOF >/opt/kima-hub/backend/.env\nNODE_ENV=production\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\nREDIS_URL=redis://localhost:6379\nPORT=3006\nMUSIC_PATH=/music\nTRANSCODE_CACHE_PATH=/opt/kima-hub/cache/transcodes\nSESSION_SECRET=${SESSION_SECRET}\nSETTINGS_ENCRYPTION_KEY=${ENCRYPTION_KEY}\nINTERNAL_API_SECRET=$(openssl rand -hex 16)\nEOF\nmsg_ok \"Configured Backend\"\n\nmsg_info \"Running Database Migrations\"\ncd /opt/kima-hub/backend\n$STD npx prisma generate\n$STD npx prisma migrate deploy\nmsg_ok \"Ran Database Migrations\"\n\nmsg_info \"Building Frontend\"\ncd /opt/kima-hub/frontend\n$STD npm ci\nexport NEXT_PUBLIC_BACKEND_URL=http://127.0.0.1:3006\n$STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Configuring Frontend\"\ncat <<EOF >/opt/kima-hub/frontend/.env\nNODE_ENV=production\nBACKEND_URL=http://localhost:3006\nPORT=3030\nEOF\nmsg_ok \"Configured Frontend\"\n\nmsg_info \"Creating Directories\"\nmkdir -p /opt/kima-hub/cache/transcodes\nmkdir -p /music\nmsg_ok \"Created Directories\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/kima-backend.service\n[Unit]\nDescription=Kima Hub Backend\nAfter=network.target postgresql.service redis-server.service\nWants=postgresql.service redis-server.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/kima-hub/backend\nEnvironmentFile=/opt/kima-hub/backend/.env\nExecStart=/usr/bin/node /opt/kima-hub/backend/dist/index.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/kima-frontend.service\n[Unit]\nDescription=Kima Hub Frontend\nAfter=network.target kima-backend.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/kima-hub/frontend\nEnvironmentFile=/opt/kima-hub/frontend/.env\nExecStart=/usr/bin/npm start\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/kima-analyzer.service\n[Unit]\nDescription=Kima Hub Audio Analyzer (Essentia)\nAfter=network.target postgresql.service redis-server.service kima-backend.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/kima-hub/services/audio-analyzer\nEnvironment=DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\nEnvironment=REDIS_URL=redis://localhost:6379\nEnvironment=MUSIC_PATH=/music\nEnvironment=BATCH_SIZE=10\nEnvironment=SLEEP_INTERVAL=5\nEnvironment=NUM_WORKERS=2\nEnvironment=THREADS_PER_WORKER=1\nExecStart=/usr/bin/python3 /opt/kima-hub/services/audio-analyzer/analyzer.py\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/kima-analyzer-clap.service\n[Unit]\nDescription=Kima Hub CLAP Audio Analyzer\nAfter=network.target postgresql.service redis-server.service kima-backend.service kima-analyzer.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/kima-hub/services/audio-analyzer-clap\nEnvironment=DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\nEnvironment=REDIS_URL=redis://localhost:6379\nEnvironment=BACKEND_URL=http://localhost:3006\nEnvironment=MUSIC_PATH=/music\nEnvironment=SLEEP_INTERVAL=5\nEnvironment=NUM_WORKERS=1\nExecStart=/usr/bin/python3 /opt/kima-hub/services/audio-analyzer-clap/analyzer.py\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now kima-backend kima-frontend kima-analyzer kima-analyzer-clap\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kimai-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.kimai.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  apache2 \\\n  git \\\n  expect\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\nsetup_composer\n\nmsg_info \"Setting up database\"\nDB_NAME=kimai_db\nDB_USER=kimai\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nMYSQL_VERSION=$(mariadb --version | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+')\n$STD mariadb -e \"CREATE DATABASE $DB_NAME;\"\n$STD mariadb -e \"CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';\"\n$STD mariadb -e \"GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;\"\n{\n  echo \"Kimai-Credentials\"\n  echo \"Kimai Database User: $DB_USER\"\n  echo \"Kimai Database Password: $DB_PASS\"\n  echo \"Kimai Database Name: $DB_NAME\"\n} >>~/kimai.creds\nmsg_ok \"Set up database\"\n\nfetch_and_deploy_gh_release \"kimai\" \"kimai/kimai\" \"tarball\"\n\nmsg_info \"Setup Kimai\"\ncd /opt/kimai\necho \"export COMPOSER_ALLOW_SUPERUSER=1\" >>~/.bashrc\nsource ~/.bashrc\n$STD composer install --no-dev --optimize-autoloader --no-interaction\ncp .env.dist .env\nsed -i \"/^DATABASE_URL=/c\\DATABASE_URL=mysql://$DB_USER:$DB_PASS@127.0.0.1:3306/$DB_NAME?charset=utf8mb4&serverVersion=mariadb-$MYSQL_VERSION\" /opt/kimai/.env\n$STD bin/console kimai:install -n\n$STD expect <<EOF\nset timeout -1\nlog_user 0\n\nspawn bin/console kimai:user:create admin admin@community-scripts.org ROLE_SUPER_ADMIN\n\nexpect \"Please enter the password:\"\nsend \"community-scripts.org\\r\"\n\nexpect eof\nEOF\n$STD composer update --no-interaction\ncat <<EOF >/opt/kimai/config/packages/local.yaml\nkimai:\n    timesheet:\n        rounding:\n            default:\n                begin: 15\n                end: 15\n\nEOF\nmsg_ok \"Installed Kimai\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/kimai.conf\n<VirtualHost *:80>\n  ServerAdmin webmaster@localhost\n  DocumentRoot /opt/kimai/public/\n\n   <Directory /opt/kimai/public>\n        Options FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n  \n    ErrorLog /var/log/apache2/error.log\n    CustomLog /var/log/apache2/access.log combined\n\n</VirtualHost>\nEOF\n$STD a2ensite kimai.conf\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmsg_info \"Setup Permissions\"\nchown -R :www-data /opt/*\nchmod -R g+r /opt/*\nchmod -R g+rw /opt/*\nchown -R www-data:www-data /opt/*\nchmod -R 777 /opt/*\nmsg_ok \"Setup Permissions\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kitchenowl-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: snazzybean\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TomBursch/kitchenowl\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  build-essential \\\n  gfortran \\\n  pkg-config \\\n  ninja-build \\\n  autoconf \\\n  automake \\\n  libpq-dev \\\n  libffi-dev \\\n  libssl-dev \\\n  libpcre2-dev \\\n  libre2-dev \\\n  libxml2-dev \\\n  libxslt-dev \\\n  libopenblas-dev \\\n  liblapack-dev \\\n  zlib1g-dev \\\n  libjpeg62-turbo-dev \\\n  libsqlite3-dev \\\n  libexpat1-dev \\\n  libicu-dev\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.14\" setup_uv\nfetch_and_deploy_gh_release \"kitchenowl\" \"TomBursch/kitchenowl\" \"tarball\" \"latest\" \"/opt/kitchenowl\"\nrm -rf /opt/kitchenowl/web\nfetch_and_deploy_gh_release \"kitchenowl-web\" \"TomBursch/kitchenowl\" \"prebuild\" \"latest\" \"/opt/kitchenowl/web\" \"kitchenowl_Web.tar.gz\"\n\nmsg_info \"Setting up KitchenOwl\"\ncd /opt/kitchenowl/backend\n$STD uv sync --no-dev\nsed -i 's/default=True/default=False/' /opt/kitchenowl/backend/wsgi.py\nsetup_nltk \"averaged_perceptron_tagger_eng\" \"/nltk_data\"\nJWT_SECRET=$(openssl rand -hex 32)\nmkdir -p /opt/kitchenowl/data\ncat <<EOF >/opt/kitchenowl/kitchenowl.env\nSTORAGE_PATH=/opt/kitchenowl/data\nJWT_SECRET_KEY=${JWT_SECRET}\nNLTK_DATA=/nltk_data\nFRONT_URL=http://${LOCAL_IP}\nFLASK_APP=wsgi.py\nFLASK_ENV=production\nEOF\nset -a\nsource /opt/kitchenowl/kitchenowl.env\nset +a\n$STD uv run flask db upgrade\nmsg_ok \"Set up KitchenOwl\"\n\nmsg_info \"Creating Systemd Service\"\ncat <<EOF >/etc/systemd/system/kitchenowl.service\n[Unit]\nDescription=KitchenOwl Backend\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/kitchenowl/backend\nEnvironmentFile=/opt/kitchenowl/kitchenowl.env\nExecStart=/usr/local/bin/uv run wsgi.py\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now kitchenowl\nmsg_ok \"Created and Started Service\"\n\nmsg_info \"Configuring Nginx\"\nrm -f /etc/nginx/sites-enabled/default\ncat <<'EOF' >/etc/nginx/sites-available/kitchenowl.conf\nserver {\n    listen 80;\n    server_name _;\n\n    root /opt/kitchenowl/web;\n    index index.html;\n\n    client_max_body_size 100M;\n\n    # Security Headers\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header X-XSS-Protection \"1; mode=block\" always;\n    add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n\n    location / {\n        try_files $uri $uri/ /index.html;\n    }\n\n    location /api {\n        proxy_pass http://127.0.0.1:5000;\n        proxy_http_version 1.1;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_connect_timeout 60s;\n        proxy_send_timeout 60s;\n        proxy_read_timeout 60s;\n    }\n\n    location /socket.io {\n        proxy_pass http://127.0.0.1:5000;\n        proxy_http_version 1.1;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n        # WebSocket Timeouts - allow long-lived connections\n        proxy_read_timeout 86400s;\n        proxy_send_timeout 86400s;\n    }\n}\nEOF\nln -sf /etc/nginx/sites-available/kitchenowl.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/koel-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://koel.dev/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  ffmpeg \\\n  cron \\\n  locales\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"koel\" PG_DB_USER=\"koel\" setup_postgresql_db\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" setup_php\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\nsetup_composer\n\nfetch_and_deploy_gh_release \"koel\" \"koel/koel\" \"prebuild\" \"latest\" \"/opt/koel\" \"koel-*.tar.gz\"\n\nmsg_info \"Configuring Koel\"\nmkdir -p /opt/koel_media /opt/koel_sync\ncd /opt/koel\ncat <<EOF >/opt/koel/.env\nAPP_NAME=Koel\nAPP_ENV=production\nAPP_DEBUG=false\nAPP_URL=http://${LOCAL_IP}\nAPP_KEY=\n\nTRUSTED_HOSTS=\n\nDB_CONNECTION=pgsql\nDB_HOST=127.0.0.1\nDB_PORT=5432\nDB_DATABASE=${PG_DB_NAME}\nDB_USERNAME=${PG_DB_USER}\nDB_PASSWORD=${PG_DB_PASS}\n\nSTORAGE_DRIVER=local\nMEDIA_PATH=/opt/koel_media\nARTIFACTS_PATH=\n\nIGNORE_DOT_FILES=true\nAPP_MAX_SCAN_TIME=600\nMEMORY_LIMIT=\n\nSTREAMING_METHOD=php\nSCOUT_DRIVER=tntsearch\n\nUSE_MUSICBRAINZ=true\nMUSICBRAINZ_USER_AGENT=\n\nLASTFM_API_KEY=\nLASTFM_API_SECRET=\n\nSPOTIFY_CLIENT_ID=\nSPOTIFY_CLIENT_SECRET=\n\nYOUTUBE_API_KEY=\n\nCDN_URL=\n\nTRANSCODE_FLAC=false\nFFMPEG_PATH=/usr/bin/ffmpeg\nTRANSCODE_BIT_RATE=128\n\nALLOW_DOWNLOAD=true\nBACKUP_ON_DELETE=true\n\nMEDIA_BROWSER_ENABLED=false\n\nPROXY_AUTH_ENABLED=false\n\nSYNC_LOG_LEVEL=error\nFORCE_HTTPS=\n\nMAIL_FROM_ADDRESS=\"noreply@localhost\"\nMAIL_FROM_NAME=\"Koel\"\nMAIL_MAILER=log\nMAIL_HOST=null\nMAIL_PORT=null\nMAIL_USERNAME=null\nMAIL_PASSWORD=null\nMAIL_ENCRYPTION=null\n\nBROADCAST_CONNECTION=log\nCACHE_DRIVER=file\nFILESYSTEM_DISK=local\nQUEUE_CONNECTION=sync\nSESSION_DRIVER=file\nSESSION_LIFETIME=120\nEOF\n\nmkdir -p /opt/koel/storage/{app/public,framework/{cache/data,sessions,views},logs}\nchown -R www-data:www-data /opt/koel /opt/koel_media /opt/koel_sync\nchmod -R 775 /opt/koel/storage /opt/koel/bootstrap/cache\nmsg_ok \"Configured Koel\"\n\nmsg_info \"Installing Koel (Patience)\"\nexport COMPOSER_ALLOW_SUPERUSER=1\ncd /opt/koel\n$STD composer install --no-interaction --no-dev --optimize-autoloader\n$STD php artisan key:generate --force\n$STD php artisan config:clear\n$STD php artisan cache:clear\n$STD php artisan koel:init --no-assets --no-interaction\nchown -R www-data:www-data /opt/koel\nmsg_ok \"Installed Koel\"\n\nmsg_info \"Tuning PHP-FPM\"\nPHP_FPM_CONF=\"/etc/php/8.4/fpm/pool.d/www.conf\"\nsed -i 's/^pm.max_children = .*/pm.max_children = 15/' \"$PHP_FPM_CONF\"\nsed -i 's/^pm.start_servers = .*/pm.start_servers = 4/' \"$PHP_FPM_CONF\"\nsed -i 's/^pm.min_spare_servers = .*/pm.min_spare_servers = 2/' \"$PHP_FPM_CONF\"\nsed -i 's/^pm.max_spare_servers = .*/pm.max_spare_servers = 8/' \"$PHP_FPM_CONF\"\n$STD systemctl restart php8.4-fpm\nmsg_ok \"Tuned PHP-FPM\"\n\nmsg_info \"Configuring Nginx\"\ncat <<'EOF' >/etc/nginx/sites-available/koel\nserver {\n    listen 80;\n    server_name _;\n    root /opt/koel/public;\n    index index.php;\n\n    client_max_body_size 50M;\n    charset utf-8;\n\n    gzip on;\n    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;\n    gzip_comp_level 9;\n\n    send_timeout 3600;\n\n    location / {\n        try_files $uri $uri/ /index.php?$args;\n    }\n\n    location /media/ {\n        internal;\n        alias $upstream_http_x_media_root;\n    }\n\n    location ~ \\.php$ {\n        try_files $uri $uri/ /index.php?$args;\n        fastcgi_pass unix:/run/php/php8.4-fpm.sock;\n        fastcgi_index index.php;\n        fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n        fastcgi_intercept_errors on;\n        fastcgi_param PATH_INFO $fastcgi_path_info;\n        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n        include fastcgi_params;\n    }\n\n    location ~ /\\.(?!well-known).* {\n        deny all;\n    }\n}\nEOF\nrm -f /etc/nginx/sites-enabled/default\nln -sf /etc/nginx/sites-available/koel /etc/nginx/sites-enabled/koel\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Setting up Cron Job\"\ncat <<'EOF' >/etc/cron.d/koel\n0 * * * * www-data cd /opt/koel && /usr/bin/php artisan koel:scan >/dev/null 2>&1\nEOF\nchmod 644 /etc/cron.d/koel\nmsg_ok \"Set up Cron Job\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/koillection-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://koillection.github.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" NODE_MODULE=\"yarn\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPHP_VERSION=\"8.5\" PHP_APACHE=\"YES\" setup_php\nsetup_composer\nPG_DB_NAME=\"koillection\" PG_DB_USER=\"koillection\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"koillection\" \"benjaminjonard/koillection\" \"tarball\"\n\nmsg_info \"Configuring Koillection\"\ncd /opt/koillection\ncp /opt/koillection/.env /opt/koillection/.env.local\nAPP_SECRET=$(openssl rand -base64 32)\nsed -i -e \"s|^APP_ENV=.*|APP_ENV=prod|\" \\\n  -e \"s|^APP_DEBUG=.*|APP_DEBUG=0|\" \\\n  -e \"s|^APP_SECRET=.*|APP_SECRET=${APP_SECRET}|\" \\\n  -e \"s|^DB_NAME=.*|DB_NAME=${PG_DB_NAME}|\" \\\n  -e \"s|^DB_USER=.*|DB_USER=${PG_DB_USER}|\" \\\n  -e \"s|^DB_PASSWORD=.*|DB_PASSWORD=${PG_DB_PASS}|\" \\\n  /opt/koillection/.env.local\necho 'APP_RUNTIME=\"Symfony\\Component\\Runtime\\SymfonyRuntime\"' >>/opt/koillection/.env.local\nexport COMPOSER_ALLOW_SUPERUSER=1\nexport APP_RUNTIME='Symfony\\Component\\Runtime\\SymfonyRuntime'\n$STD composer install --no-dev -o --no-interaction --classmap-authoritative\n$STD php bin/console doctrine:migrations:migrate --no-interaction\n$STD php bin/console app:translations:dump\ncd assets/\n$STD yarn install\n$STD yarn build\nmkdir -p /opt/koillection/public/uploads\nchown -R www-data:www-data /opt/koillection/public/uploads\nmsg_ok \"Configured Koillection\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/koillection.conf\n<VirtualHost *:80>\n    ServerName koillection\n    DocumentRoot /opt/koillection/public\n    SetEnv APP_RUNTIME \"Symfony\\\\Component\\\\Runtime\\\\SymfonyRuntime\"\n    <Directory /opt/koillection/public>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n        RewriteEngine On\n        RewriteCond %{REQUEST_FILENAME} !-f\n        RewriteCond %{REQUEST_FILENAME} !-d\n        RewriteRule ^(.*)$ index.php/\\$1 [L]\n    </Directory>\n\n    ErrorLog /var/log/apache2/koillection_error.log\n    CustomLog /var/log/apache2/koillection_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite koillection\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kometa-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Kometa-Team/Kometa\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPYTHON_VERSION=\"3.13\" setup_uv\nfetch_and_deploy_gh_release \"kometa\" \"Kometa-Team/Kometa\" \"tarball\"\n\nmsg_info \"Setup Kometa\"\ncd /opt/kometa\n$STD uv pip install -r requirements.txt --system\nmkdir -p config/assets\ncp config/config.yml.template config/config.yml\nmsg_ok \"Setup Kometa\"\n\nread -r -p \"${TAB3}Enter your TMDb API key: \" TMDBKEY\nread -r -p \"${TAB3}Enter your Plex URL: \" PLEXURL\nread -r -p \"${TAB3}Enter your Plex token: \" PLEXTOKEN\nsed -i '/^plex:/,/^[^ ]/{s|  url:.*|  url: '\"$PLEXURL\"'|}' /opt/kometa/config/config.yml\nsed -i '/^plex:/,/^[^ ]/{s|  token:.*|  token: '\"$PLEXTOKEN\"'|}' /opt/kometa/config/config.yml\nsed -i '/^tmdb:/,/^[^ ]/{s|  apikey:.*|  apikey: '\"$TMDBKEY\"'|}' /opt/kometa/config/config.yml\n\nfetch_and_deploy_gh_release \"kometa-quickstart\" \"Kometa-Team/Quickstart\" \"tarball\"\n\nmsg_info \"Installing Kometa Quickstart\"\ncd /opt/kometa-quickstart\n$STD uv venv /opt/kometa-quickstart/.venv\n$STD uv pip install -r requirements.txt -p /opt/kometa-quickstart/.venv/bin/python\nmsg_ok \"Installed Kometa Quickstart\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/kometa.service\n[Unit]\nDescription=Kometa Service\nAfter=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/kometa\nExecStart=/usr/bin/python3 kometa.py\nRestart=always\nRestartSec=30\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/kometa-quickstart.service\n[Unit]\nDescription=Kometa Quickstart\nAfter=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/kometa-quickstart\nExecStart=/opt/kometa-quickstart/.venv/bin/python quickstart.py\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now kometa kometa-quickstart\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/komga-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: madelyn (DysfunctionalProgramming)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://komga.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt -y install \\\n  libarchive-dev \\\n  libjxl-dev \\\n  libheif-dev \\\n  libwebp-dev\nmsg_ok \"Installed dependencies\"\n\nJAVA_VERSION=\"23\" setup_java\nfetch_and_deploy_gh_release \"kepubify\" \"pgaskin/kepubify\" \"singlefile\" \"latest\" \"/usr/bin\" \"kepubify-linux-64bit\"\nUSE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"komga-org\" \"gotson/komga\" \"singlefile\" \"latest\" \"/opt/komga\" \"komga*.jar\"\nmv /opt/komga/komga-*.jar /opt/komga/komga.jar\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/komga.service\n[Unit]\nDescription=Komga\nAfter=syslog.target network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/komga/\nEnvironment=LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu\nExecStart=/usr/bin/java --enable-native-access=ALL-UNNAMED -jar -Xmx2g komga.jar\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now komga\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kubo-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: ulmentflam\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ipfs/kubo\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"kubo\" \"ipfs/kubo\" \"prebuild\" \"latest\" \"/usr/local/kubo\" \"kubo*linux-arm64.tar.gz\"\n\nmsg_info \"Configuring IPFS\"\n$STD ln -s /usr/local/kubo/ipfs /usr/local/bin/ipfs\n$STD ipfs init\nipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\nipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\nipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin \"[\\\"http://${LOCAL_IP}:5001\\\", \\\"http://localhost:3000\\\", \\\"http://127.0.0.1:5001\\\", \\\"https://webui.ipfs.io\\\", \\\"http://0.0.0.0:5001\\\"]\"\nipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '[\"PUT\", \"POST\"]'\necho \"${RELEASE}\" >\"/opt/${APPLICATION}_version.txt\"\n$STD rm \"kubo_${RELEASE}_linux-arm64.tar.gz\"\nmsg_ok \"Installed IPFS\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ipfs.service\n[Unit]\nDescription=IPFS Daemon\nAfter=syslog.target network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/ipfs daemon\nRestart=on-failure\nEnvironment=HOME=/root\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ipfs\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/kutt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tomfrenzel\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/thedevs-network/kutt\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\necho \"${TAB3}How would you like to handle SSL termination?\"\necho \"${TAB3}[i]-Internal (self-signed SSL Certificate)   [e]-External (use your own reverse proxy)\"\nread -rp \"${TAB3}Enter your choice <i/e> (default: i): \" ssl_choice\nssl_choice=${ssl_choice:-i}\ncase \"${ssl_choice,,}\" in\ni)\n  DEFAULT_HOST=\"$LOCAL_IP\"\n\n  msg_info \"Configuring Caddy\"\n  $STD apt install -y caddy\n  cat <<EOF >/etc/caddy/Caddyfile\n$LOCAL_IP {\n    reverse_proxy localhost:3000\n}\nEOF\n  systemctl restart caddy\n  msg_ok \"Configured Caddy\"\n  ;;\ne)\n  read -r -p \"${TAB3}Enter the hostname you want to use for Kutt (eg. kutt.example.com): \" custom_host\n  if [[ \"$custom_host\" ]]; then\n    DEFAULT_HOST=\"$custom_host\"\n  fi\n  ;;\nesac\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"kutt\" \"thedevs-network/kutt\" \"tarball\"\n\nmsg_info \"Configuring Kutt\"\ncd /opt/kutt\ncp .example.env \".env\"\nsed -i \"s|JWT_SECRET=|JWT_SECRET=$(openssl rand -base64 32)|g\" \".env\"\nsed -i \"s|DEFAULT_DOMAIN=.*|DEFAULT_DOMAIN=$DEFAULT_HOST|g\" \".env\"\n$STD npm install\n$STD npm run migrate\nmsg_ok \"Configured Kutt\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/kutt.service\n[Unit]\nDescription=Kutt server\nAfter=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/kutt\nExecStart=/usr/bin/npm start\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now kutt\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/languagetool-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://languagetool.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y fasttext\nmsg_ok \"Installed dependencies\"\n\nJAVA_VERSION=\"21\" setup_java\n\nmsg_info \"Setting up LanguageTool\"\nRELEASE=$(curl -fsSL https://languagetool.org/download/ | grep -oP 'LanguageTool-\\K[0-9]+\\.[0-9]+(\\.[0-9]+)?(?=\\.zip)' | sort -V | tail -n1)\ndownload_file \"https://languagetool.org/download/LanguageTool-stable.zip\" /tmp/LanguageTool-stable.zip\nunzip -q /tmp/LanguageTool-stable.zip -d /opt\nmv /opt/LanguageTool-*/ /opt/LanguageTool/\ndownload_file \"https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin\" /opt/lid.176.bin\nrm -f /tmp/LanguageTool-stable.zip\nmsg_ok \"Setup LanguageTool\"\n\nngram_dir=\"\"\nlang_code=\"\"\nmax_attempts=3\nattempt=0\n\nwhile [[ $attempt -lt $max_attempts ]]; do\n  read -r -p \"${TAB3}Enter language code (en, de, es, fr, nl) to download ngrams or press ENTER to skip: \" lang_code\n\n  if [[ -z \"$lang_code\" ]]; then\n    break\n  fi\n\n  if [[ \"$lang_code\" =~ [[:space:]] ]]; then\n    ((attempt++))\n    remaining=$((max_attempts - attempt))\n    if [[ $remaining -gt 0 ]]; then\n      msg_error \"Please enter only ONE language code. You have $remaining attempt(s) remaining.\"\n    else\n      msg_error \"Maximum attempts reached. Continuing without ngrams.\"\n      lang_code=\"\"\n    fi\n    continue\n  fi\n  break\ndone\n\nif [[ -n \"$lang_code\" ]]; then\n  if [[ \"$lang_code\" =~ ^(en|de|es|fr|nl)$ ]]; then\n    msg_info \"Searching for $lang_code ngrams...\"\n    filename=$(curl -fsSL https://languagetool.org/download/ngram-data/ | grep -oP \"ngrams-${lang_code}-[0-9]+\\.zip\" | sort -uV | tail -n1)\n\n    if [[ -n \"$filename\" ]]; then\n      msg_info \"Downloading $filename\"\n      download_file \"https://languagetool.org/download/ngram-data/${filename}\" \"/tmp/${filename}\"\n\n      mkdir -p /opt/ngrams\n      msg_info \"Extracting $lang_code ngrams to /opt/ngrams\"\n      unzip -q \"/tmp/${filename}\" -d /opt/ngrams\n      rm \"/tmp/${filename}\"\n\n      ngram_dir=\"/opt/ngrams\"\n      msg_ok \"Installed $lang_code ngrams\"\n    else\n      msg_info \"No ngram file found for ${lang_code}\"\n    fi\n  else\n    msg_error \"Invalid language code: $lang_code\"\n  fi\nfi\n\ncat <<EOF >/opt/LanguageTool/server.properties\nfasttextModel=/opt/lid.176.bin\nfasttextBinary=/usr/bin/fasttext\nEOF\nif [[ -n \"$ngram_dir\" ]]; then\n  echo \"languageModel=/opt/ngrams\" >> /opt/LanguageTool/server.properties\nfi\necho \"${RELEASE}\" >~/.languagetool\nmsg_ok \"Setup LanguageTool\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/etc/systemd/system/language-tool.service\n[Unit]\nDescription=LanguageTool Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/LanguageTool\nExecStart=java -cp languagetool-server.jar org.languagetool.server.HTTPServer --config server.properties --public --allow-origin \"*\"\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now language-tool\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/lazylibrarian-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MountyMapleSyrup (MountyMapleSyrup)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://gitlab.com/LazyLibrarian/LazyLibrarian\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n    git \\\n    libpng-dev \\\n    libjpeg-dev \\\n    libtiff-dev \\\n    imagemagick\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup Python3\"\n$STD apt install -y \\\n    pip \\\n    python3-irc\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\n$STD pip install jaraco.stream\n$STD pip install python-Levenshtein\n$STD pip install soupsieve\n$STD pip install pypdf\nmsg_ok \"Setup Python3\"\n\nmsg_info \"Installing LazyLibrarian\"\n$STD git clone https://gitlab.com/LazyLibrarian/LazyLibrarian /opt/LazyLibrarian\ncd /opt/LazyLibrarian\n$STD pip install .\nmsg_ok \"Installed LazyLibrarian\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/lazylibrarian.service\n[Unit]\nDescription=LazyLibrarian Daemon\nAfter=syslog.target network.target\n[Service]\nUMask=0002\nType=simple\nExecStart=/usr/bin/python3 /opt/LazyLibrarian/LazyLibrarian.py\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q lazylibrarian\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/leantime-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Stroopwafe1\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://leantime.io\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" setup_php\nsetup_mariadb\nMARIADB_DB_NAME=\"leantime\" MARIADB_DB_USER=\"leantime\" setup_mariadb_db\nfetch_and_deploy_gh_release \"leantime\" \"Leantime/leantime\" \"prebuild\" \"latest\" \"/opt/leantime\" Leantime*.tar.gz\n\nmsg_info \"Setup Leantime\"\nchown -R www-data:www-data \"/opt/leantime\"\nchmod -R 750 \"/opt/leantime\"\ncat <<EOF >/etc/apache2/sites-enabled/000-default.conf\n<VirtualHost *:80>\n  ServerAdmin webmaster@localhost\n  DocumentRoot /opt/leantime/public\n  DirectoryIndex index.php index.html index.cgi index.pl index.xhtml\n  Options +ExecCGI\n\n  <Directory /opt/leantime/>\n    Options FollowSymLinks\n    Require all granted\n    AllowOverride All\n  </Directory>\n\n  <Location />\n    Require all granted\n  </Location>\n\n  ErrorLog /var/log/apache2/error.log\n  CustomLog /var/log/apache2/access.log combined\n</VirtualHost>\nEOF\nmv \"/opt/leantime/config/sample.env\" \"/opt/leantime/config/.env\"\nsed -i -e \"s|^LEAN_DB_DATABASE.*|LEAN_DB_DATABASE = '$MARIADB_DB_NAME'|\" \\\n  -e \"s|^LEAN_DB_USER.*|LEAN_DB_USER = '$MARIADB_DB_USER'|\" \\\n  -e \"s|^LEAN_DB_PASSWORD.*|LEAN_DB_PASSWORD = '$MARIADB_DB_PASS'|\" \\\n  -e \"s|^LEAN_SESSION_PASSWORD.*|LEAN_SESSION_PASSWORD = '$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)'|\" \\\n  \"/opt/leantime/config/.env\"\n$STD a2enmod -q proxy_fcgi setenvif rewrite\n$STD a2enconf -q \"php8.4-fpm\"\nsed -i -e \"s/^;extension.\\(curl\\|fileinfo\\|gd\\|intl\\|ldap\\|mbstring\\|exif\\|mysqli\\|odbc\\|openssl\\|pdo_mysql\\)/extension=\\1/g\" \"/etc/php/8.4/apache2/php.ini\"\nsystemctl restart apache2\nmsg_ok \"Setup leantime\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/librechat-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/danny-avila/LibreChat\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nMONGO_VERSION=\"8.0\" setup_mongodb\nsetup_meilisearch\nPG_VERSION=\"17\" PG_MODULES=\"pgvector\" setup_postgresql\nPG_DB_NAME=\"ragapi\" PG_DB_USER=\"ragapi\" PG_DB_EXTENSIONS=\"vector\" setup_postgresql_db\nNODE_VERSION=\"24\" setup_nodejs\nUV_PYTHON=\"3.12\" setup_uv\n\nfetch_and_deploy_gh_tag \"librechat\" \"danny-avila/LibreChat\"\nfetch_and_deploy_gh_release \"rag-api\" \"danny-avila/rag_api\" \"tarball\"\n\nmsg_info \"Installing LibreChat Dependencies\"\ncd /opt/librechat\n$STD npm ci\nmsg_ok \"Installed LibreChat Dependencies\"\n\nmsg_info \"Building Frontend\"\n$STD npm run frontend\n$STD npm prune --production\n$STD npm cache clean --force\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Installing RAG API Dependencies\"\ncd /opt/rag-api\n$STD uv venv --python 3.12 --seed .venv\n$STD .venv/bin/pip install -r requirements.lite.txt\nmkdir -p /opt/rag-api/uploads\nmsg_ok \"Installed RAG API Dependencies\"\n\nmsg_info \"Configuring LibreChat\"\nJWT_SECRET=$(openssl rand -hex 32)\nJWT_REFRESH_SECRET=$(openssl rand -hex 32)\nCREDS_KEY=$(openssl rand -hex 32)\nCREDS_IV=$(openssl rand -hex 16)\ncat <<EOF >/opt/librechat/.env\nHOST=0.0.0.0\nPORT=3080\nMONGO_URI=mongodb://127.0.0.1:27017/LibreChat\nDOMAIN_CLIENT=http://${LOCAL_IP}:3080\nDOMAIN_SERVER=http://${LOCAL_IP}:3080\nNO_INDEX=true\nTRUST_PROXY=1\nJWT_SECRET=${JWT_SECRET}\nJWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}\nSESSION_EXPIRY=1000 * 60 * 15\nREFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7\nCREDS_KEY=${CREDS_KEY}\nCREDS_IV=${CREDS_IV}\nALLOW_EMAIL_LOGIN=true\nALLOW_REGISTRATION=true\nALLOW_SOCIAL_LOGIN=false\nALLOW_SOCIAL_REGISTRATION=false\nALLOW_PASSWORD_RESET=false\nALLOW_UNVERIFIED_EMAIL_LOGIN=true\nSEARCH=true\nMEILI_NO_ANALYTICS=true\nMEILI_HOST=http://127.0.0.1:7700\nMEILI_MASTER_KEY=${MEILISEARCH_MASTER_KEY}\nRAG_PORT=8000\nRAG_API_URL=http://127.0.0.1:8000\nAPP_TITLE=LibreChat\nENDPOINTS=openAI,agents,assistants,anthropic,google\n# OPENAI_API_KEY=your-key-here\n# OPENAI_MODELS=\n# ANTHROPIC_API_KEY=your-key-here\n# GOOGLE_KEY=your-key-here\nEOF\nmsg_ok \"Configured LibreChat\"\n\nmsg_info \"Configuring RAG API\"\ncat <<EOF >/opt/rag-api/.env\nVECTOR_DB_TYPE=pgvector\nDB_HOST=127.0.0.1\nDB_PORT=5432\nPOSTGRES_DB=${PG_DB_NAME}\nPOSTGRES_USER=${PG_DB_USER}\nPOSTGRES_PASSWORD=${PG_DB_PASS} \nRAG_HOST=0.0.0.0\nRAG_PORT=8000\nJWT_SECRET=${JWT_SECRET}\nRAG_UPLOAD_DIR=/opt/rag-api/uploads/\nEOF\nmsg_ok \"Configured RAG API\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/librechat.service\n[Unit]\nDescription=LibreChat\nAfter=network.target mongod.service meilisearch.service rag-api.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/librechat\nEnvironmentFile=/opt/librechat/.env\nExecStart=/usr/bin/npm run backend\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/rag-api.service\n[Unit]\nDescription=LibreChat RAG API\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/rag-api\nEnvironmentFile=/opt/rag-api/.env\nExecStart=/opt/rag-api/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now rag-api librechat\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/librenms-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.librenms.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  acl \\\n  fping \\\n  graphviz \\\n  imagemagick \\\n  mtr-tiny \\\n  nginx \\\n  nmap \\\n  rrdtool \\\n  snmp \\\n  snmpd \\\n  whois\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Python Dependencies\"\n$STD apt install -y \\\n  python3-dotenv \\\n  python3-pymysql \\\n  python3-redis \\\n  python3-setuptools \\\n  python3-systemd \\\n  python3-pip \\\n  python3-psutil \\\n  python3-command-runner\nmsg_ok \"Installed Python Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"cli,snmp,gmp\" setup_php\nsetup_mariadb\nsetup_composer\nPYTHON_VERSION=\"3.13\" setup_uv\nMARIADB_DB_NAME=\"librenms\" MARIADB_DB_USER=\"librenms\" MARIADB_DB_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\" setup_mariadb_db\nfetch_and_deploy_gh_release \"librenms\" \"librenms/librenms\" \"tarball\"\n\nmsg_info \"Configuring LibreNMS\"\n$STD useradd librenms -d /opt/librenms -M -r -s \"$(which bash)\"\nmkdir -p /opt/librenms/{rrd,logs,bootstrap/cache,storage,html}\ncd /opt/librenms\nAPP_KEY=$(openssl rand -base64 40 | tr -dc 'a-zA-Z0-9')\n$STD uv venv --clear .venv\n$STD source .venv/bin/activate\n$STD uv pip install -r requirements.txt\ncat <<EOF >/opt/librenms/.env\nDB_DATABASE=${MARIADB_DB_NAME}\nDB_USERNAME=${MARIADB_DB_USER}\nDB_PASSWORD=${MARIADB_DB_PASS}\nAPP_KEY=${APP_KEY}\nEOF\nchown -R librenms:librenms /opt/librenms\nchmod 771 /opt/librenms\nchmod -R ug=rwX /opt/librenms/bootstrap/cache /opt/librenms/storage /opt/librenms/logs /opt/librenms/rrd\nmsg_ok \"Configured LibreNMS\"\n\nmsg_info \"Configure MariaDB\"\nsed -i \"/\\[mariadb\\]/a innodb_file_per_table=1\\nlower_case_table_names=0\" /etc/mysql/mariadb.conf.d/50-server.cnf\nsystemctl enable -q --now mariadb\nmsg_ok \"Configured MariaDB\"\n\nmsg_info \"Configure PHP-FPM\"\ncp /etc/php/8.4/fpm/pool.d/www.conf /etc/php/8.4/fpm/pool.d/librenms.conf\nsed -i \"s/\\[www\\]/\\[librenms\\]/g\" /etc/php/8.4/fpm/pool.d/librenms.conf\nsed -i \"s/user = www-data/user = librenms/g\" /etc/php/8.4/fpm/pool.d/librenms.conf\nsed -i \"s/group = www-data/group = librenms/g\" /etc/php/8.4/fpm/pool.d/librenms.conf\nsed -i \"s/listen = \\/run\\/php\\/php8.4-fpm.sock/listen = \\/run\\/php-fpm-librenms.sock/g\" /etc/php/8.4/fpm/pool.d/librenms.conf\nmsg_ok \"Configured PHP-FPM\"\n\nmsg_info \"Configure Nginx\"\ncat <<EOF >/etc/nginx/sites-enabled/librenms\nserver {\n listen      80;\n server_name ${LOCAL_IP};\n root        /opt/librenms/html;\n index       index.php;\n\n charset utf-8;\n gzip on;\n gzip_types text/css application/javascript text/javascript application/x-javascript image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;\n location / {\n  try_files \\$uri \\$uri/ /index.php?\\$query_string;\n }\n location ~ [^/]\\.php(/|$) {\n  fastcgi_pass unix:/run/php-fpm-librenms.sock;\n  fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n  include fastcgi.conf;\n }\n location ~ /\\.(?!well-known).* {\n  deny all;\n }\n}\nEOF\nrm /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nsystemctl restart php8.4-fpm\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Configure Services\"\nln -s /opt/librenms/lnms /usr/bin/lnms\nmkdir -p /etc/bash_completion.d/\ncp /opt/librenms/misc/lnms-completion.bash /etc/bash_completion.d/\ncp /opt/librenms/snmpd.conf.example /etc/snmp/snmpd.conf\n\nAPP_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nAPP_USER=\"admin\"\n{\n  echo \"LibreNMS Credentials\"\n  echo \"Username: ${APP_USER}\"\n  echo \"Password: ${APP_PASSWORD}\"\n} >>~/librenms.creds\n\n$STD su - librenms -s /bin/bash -c \"cd /opt/librenms && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev\"\n$STD su - librenms -s /bin/bash -c \"cd /opt/librenms && php8.4 artisan migrate --force\"\n$STD su - librenms -s /bin/bash -c \"cd /opt/librenms && php8.4 artisan key:generate --force\"\n$STD su - librenms -s /bin/bash -c \"cd /opt/librenms && lnms db:seed --force\"\n$STD su - librenms -s /bin/bash -c \"cd /opt/librenms && lnms user:add -p ${APP_PASSWORD} ${APP_USER} --role=admin\"\n\nRANDOM_STRING=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9')\nsed -i \"s/RANDOMSTRINGHERE/$RANDOM_STRING/g\" /etc/snmp/snmpd.conf\necho \"SNMP Community String: $RANDOM_STRING\" >>~/librenms.creds\ncurl -qso /usr/bin/distro https://raw.githubusercontent.com/librenms/librenms-agent/master/snmp/distro\nchmod +x /usr/bin/distro\nsystemctl enable -q --now snmpd\n\ncp /opt/librenms/dist/librenms.cron /etc/cron.d/librenms\ncp /opt/librenms/dist/librenms-scheduler.service /opt/librenms/dist/librenms-scheduler.timer /etc/systemd/system/\n\nsystemctl enable -q --now librenms-scheduler.timer\ncp /opt/librenms/misc/librenms.logrotate /etc/logrotate.d/librenms\nmsg_ok \"Configured Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/librespeed-rust-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joseph Stubberfield (stubbers)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/librespeed/speedtest-rust\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"librespeed-rust\" \"librespeed/speedtest-rust\" \"binary\" \"latest\" \"/opt/librespeed-rust\" \"librespeed-rs-aarch64-unknown-linux-gnu.deb\"\n\nmsg_info \"Enabling Service\"\nsystemctl enable -q --now speedtest_rs\nmsg_ok \"Enabled Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/libretranslate-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/LibreTranslate/LibreTranslate\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  pkg-config \\\n  build-essential \\\n  g++ \\\n  cmake \\\n  libprotobuf-dev \\\n  protobuf-compiler \\\n  libsentencepiece-dev \\\n  libicu-dev\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Setup Python3\"\n$STD apt install -y \\\n  python3-pip \\\n  python3-dev \\\n  python3-icu\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nfetch_and_deploy_gh_release \"libretranslate\" \"LibreTranslate/LibreTranslate\" \"tarball\"\n\nmsg_info \"Setup LibreTranslate (Patience)\"\ncd /opt/libretranslate\n$STD uv venv --clear .venv --python 3.12\n$STD source .venv/bin/activate\n$STD uv pip install --upgrade pip\n$STD uv pip install \"setuptools<81\"\n$STD uv pip install Babel==2.12.1\n$STD .venv/bin/python scripts/compile_locales.py\n$STD uv pip install \"numpy<2\"\n$STD uv pip install .\n$STD uv pip install libretranslate\n$STD .venv/bin/python scripts/install_models.py\n\ncat <<EOF >/opt/libretranslate/.env\nLT_PORT=5000\nEOF\nmsg_ok \"Installed LibreTranslate\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/libretranslate.service\n[Unit]\nDescription=LibreTranslate\nAfter=network.target\n\n[Service]\nUser=root\nType=idle\nRestart=always\nEnvironment=\"PATH=/usr/local/lib/python3.11/dist-packages/libretranslate\"\nEnvironmentFile=/opt/libretranslate/.env\nExecStart=/opt/libretranslate/.venv/bin/python3 /opt/libretranslate/.venv/bin/libretranslate --host * --update-models\nExecReload=/bin/kill -s HUP\nKillMode=mixed\nTimeoutStopSec=1\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now libretranslate\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/lidarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://lidarr.audio/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  sqlite3 \\\n  libchromaprint-tools \\\n  mediainfo \\\n\tlibicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"lidarr\" \"Lidarr/Lidarr\" \"prebuild\" \"latest\" \"/opt/Lidarr\" \"Lidarr.master*linux-core-arm64.tar.gz\"\n\nmsg_info \"Configuring Lidarr\"\nmkdir -p /var/lib/lidarr/\nchmod 775 /var/lib/lidarr/\nchmod 775 /opt/Lidarr\nmsg_ok \"Configured Lidarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/lidarr.service\n[Unit]\nDescription=Lidarr Daemon\nAfter=syslog.target network.target\n\n[Service]\nUMask=0002\nType=simple\nExecStart=/opt/Lidarr/Lidarr -nobrowser -data=/var/lib/lidarr/\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now lidarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/limesurvey-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://community.limesurvey.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" PHP_MODULE=\"imap,ldap\" setup_php\nsetup_mariadb\n\nmsg_info \"Configuring MariaDB Database\"\nDB_NAME=limesurvey_db\nDB_USER=limesurvey\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD mariadb -u root -e \"CREATE DATABASE $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\"\n$STD mariadb -u root -e \"CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';\"\n$STD mariadb -u root -e \"GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;\"\n{\n  echo \"LimeSurvey-Credentials\"\n  echo \"LimeSurvey Database User: $DB_USER\"\n  echo \"LimeSurvey Database Password: $DB_PASS\"\n  echo \"LimeSurvey Database Name: $DB_NAME\"\n} >>~/limesurvey.creds\nmsg_ok \"Configured MariaDB Database\"\n\nmsg_info \"Setting up LimeSurvey\"\ntemp_file=$(mktemp)\nRELEASE=$(curl -s https://community.limesurvey.org/downloads/ | grep -oE 'https://download\\.limesurvey\\.org/latest-master/limesurvey[0-9.+]+\\.zip' | head -n1)\ncurl -fsSL \"$RELEASE\" -o \"$temp_file\"\nunzip -q \"$temp_file\" -d /opt\n\ncat <<EOF >/etc/apache2/sites-enabled/000-default.conf\n<VirtualHost *:80>\n  ServerAdmin webmaster@localhost\n  DocumentRoot /opt/limesurvey\n  DirectoryIndex index.php index.html index.cgi index.pl index.xhtml\n  Options +ExecCGI\n\n  <Directory /opt/limesurvey/>\n    Options FollowSymLinks\n    Require all granted\n    AllowOverride All\n  </Directory>\n\n  <Location />\n    Require all granted\n  </Location>\n\n  ErrorLog /var/log/apache2/error.log\n  CustomLog /var/log/apache2/access.log combined\n</VirtualHost>\nEOF\nchown -R www-data:www-data \"/opt/limesurvey\"\nchmod -R 750 \"/opt/limesurvey\"\nsystemctl reload apache2\nrm -rf \"$temp_file\"\nmsg_ok \"Set up LimeSurvey\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/linkding-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (MickLesk)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://linkding.link/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  pkg-config \\\n  python3-dev \\\n  nginx \\\n  libpq-dev \\\n  libicu-dev \\\n  libsqlite3-dev \\\n  libffi-dev\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nsetup_uv\nfetch_and_deploy_gh_release \"linkding\" \"sissbruecker/linkding\" \"tarball\"\n\nmsg_info \"Building Frontend\"\ncd /opt/linkding\n$STD npm ci\n$STD npm run build\nln -sf /usr/lib/aarch64-linux-gnu/mod_icu.so /opt/linkding/libicu.so\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Setting up LinkDing\"\nrm -f bookmarks/settings/dev.py\ntouch bookmarks/settings/custom.py\n$STD uv sync --no-dev --frozen\n$STD uv pip install gunicorn\nmkdir -p data/{favicons,previews,assets}\nADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\ncat <<EOF >/opt/linkding/.env\nLD_SUPERUSER_NAME=admin\nLD_SUPERUSER_PASSWORD=${ADMIN_PASS}\nLD_CSRF_TRUSTED_ORIGINS=http://${LOCAL_IP}:9090\nEOF\nset -a && source /opt/linkding/.env && set +a\n$STD /opt/linkding/.venv/bin/python manage.py generate_secret_key\n$STD /opt/linkding/.venv/bin/python manage.py migrate\n$STD /opt/linkding/.venv/bin/python manage.py enable_wal\n$STD /opt/linkding/.venv/bin/python manage.py create_initial_superuser\n$STD /opt/linkding/.venv/bin/python manage.py collectstatic --no-input\nmsg_ok \"Set up LinkDing\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/linkding.service\n[Unit]\nDescription=linkding Bookmark Manager\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/linkding\nEnvironmentFile=/opt/linkding/.env\nExecStart=/opt/linkding/.venv/bin/gunicorn \\\n  --bind 127.0.0.1:8000 \\\n  --workers 3 \\\n  --threads 2 \\\n  --timeout 120 \\\n  bookmarks.wsgi:application\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/linkding-tasks.service\n[Unit]\nDescription=linkding Background Tasks\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/linkding\nEnvironmentFile=/opt/linkding/.env\nExecStart=/opt/linkding/.venv/bin/python manage.py run_huey\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<'EOF' >/etc/nginx/sites-available/linkding\nserver {\n    listen 9090;\n    server_name _;\n\n    client_max_body_size 20M;\n\n    location /static/ {\n        alias /opt/linkding/static/;\n        expires 30d;\n    }\n\n    location / {\n        proxy_pass http://127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_redirect off;\n    }\n}\nEOF\n$STD rm -f /etc/nginx/sites-enabled/default\n$STD ln -sf /etc/nginx/sites-available/linkding /etc/nginx/sites-enabled/linkding\nsystemctl enable -q --now nginx linkding linkding-tasks\nsystemctl restart nginx\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/linkstack-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://linkstack.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" setup_php\nfetch_and_deploy_gh_release \"linkstack\" \"linkstackorg/linkstack\" \"prebuild\" \"latest\" \"/var/www/html/\" \"linkstack.zip\"\n\nmsg_info \"Configuring LinkStack\"\n$STD a2enmod rewrite\nchown -R www-data:www-data /var/www/html/linkstack\nchmod -R 755 /var/www/html/linkstack\n\ncat <<EOF >/etc/apache2/sites-available/linkstack.conf\n<VirtualHost *:80>\n    ServerAdmin webmaster@localhost\n    DocumentRoot /var/www/html/linkstack\n    ErrorLog /var/log/apache2/linkstack-error.log\n    CustomLog /var/log/apache2/linkstack-access.log combined\n    <Directory /var/www/html/linkstack/>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n</VirtualHost>\nEOF\n$STD a2dissite 000-default.conf\n$STD a2ensite linkstack.conf\n$STD systemctl restart apache2\nmsg_ok \"Configured LinkStack\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/linkwarden-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://linkwarden.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  make \\\n  build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nRUST_CRATES=\"monolith\" setup_rust\nPG_DB_NAME=\"linkwardendb\" PG_DB_USER=\"linkwarden\" setup_postgresql_db\n\nread -r -p \"${TAB3}Would you like to add Adminer? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  setup_adminer\nfi\n\nfetch_and_deploy_gh_release \"linkwarden\" \"linkwarden/linkwarden\" \"tarball\"\n\nmsg_info \"Installing Linkwarden (Patience)\"\nSECRET_KEY=\"$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)\"\necho \"Linkwarden Secret: $SECRET_KEY\" >>\"${HOME}/linkwarden.creds\"\ncd /opt/linkwarden\nyarn_ver=\"4.12.0\"\nif [[ -f package.json ]]; then\n  pkg_manager=$(jq -r '.packageManager // empty' package.json 2>/dev/null || true)\n  if [[ -n \"$pkg_manager\" && \"$pkg_manager\" == yarn@* ]]; then\n    yarn_spec=\"${pkg_manager#yarn@}\"\n    yarn_ver=\"${yarn_spec%%+*}\"\n  fi\nfi\nif command -v corepack >/dev/null 2>&1; then\n  $STD corepack enable\n  $STD corepack prepare \"yarn@${yarn_ver}\" --activate || true\nfi\n$STD yarn\n$STD npx playwright install-deps\n$STD npx playwright install\ncat <<EOF >/opt/linkwarden/.env\nNEXTAUTH_SECRET=${SECRET_KEY}\nNEXTAUTH_URL=http://${LOCAL_IP}:3000\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\nEOF\n$STD yarn prisma:generate\n$STD yarn web:build\n$STD yarn prisma:deploy\nrm -rf ~/.cargo/registry ~/.cargo/git ~/.cargo/.package-cache\nrm -rf /root/.cache/yarn\nrm -rf /opt/linkwarden/.next/cache\nmsg_ok \"Installed Linkwarden\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/linkwarden.service\n[Unit]\nDescription=Linkwarden Service\nAfter=network.target\n\n[Service]\nType=exec\nEnvironment=PATH=$PATH\nWorkingDirectory=/opt/linkwarden\nExecStart=/usr/bin/yarn concurrently:start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now linkwarden\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/listmonk-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://listmonk.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPG_VERSION=\"17\" setup_postgresql\n\nmsg_info \"Configuring PostgreSQL\"\nDB_NAME=listmonk\nDB_USER=listmonk\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\n$STD sudo -u postgres psql -c \"CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME WITH OWNER $DB_USER TEMPLATE template0;\"\n{\n  echo \"listmonk-Credentials\"\n  echo -e \"listmonk Database User: \\e[32m$DB_USER\\e[0m\"\n  echo -e \"listmonk Database Password: \\e[32m$DB_PASS\\e[0m\"\n  echo -e \"listmonk Database Name: \\e[32m$DB_NAME\\e[0m\"\n} >>~/listmonk.creds\nmsg_ok \"Configured PostgreSQL\"\n\nfetch_and_deploy_gh_release \"listmonk\" \"knadh/listmonk\" \"prebuild\" \"latest\" \"/opt/listmonk\" \"listmonk*linux_arm64.tar.gz\"\n\nmsg_info \"Configuring listmonk\"\nmkdir -p /opt/listmonk/uploads\n$STD /opt/listmonk/listmonk --new-config --config /opt/listmonk/config.toml\nsed -i -e 's/address = \"localhost:9000\"/address = \"0.0.0.0:9000\"/' -e 's/^password = \".*\"/password = \"'\"$DB_PASS\"'\"/' /opt/listmonk/config.toml\n$STD /opt/listmonk/listmonk --install --yes --config /opt/listmonk/config.toml\nmsg_ok \"Configured listmonk\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/listmonk.service\n[Unit]\nDescription=Listmonk Service\nWants=network.target\nAfter=postgresql.service\n\n[Service]\nType=simple\nExecStart=/opt/listmonk/listmonk --config /opt/listmonk/config.toml\nRestart=always\nRestartSec=3\nWorkingDirectory=/opt/listmonk\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now listmonk\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/livebook-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: dkuku\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/livebook-dev/livebook\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  build-essential \\\n  ca-certificates \\\n  cmake \\\n  git \\\n  libncurses5-dev\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Creating livebook user\"\nmkdir -p /opt/livebook /data\nexport HOME=/opt/livebook\n$STD adduser --system --group --home /opt/livebook --shell /bin/bash livebook\nmsg_ok \"Created livebook user\"\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://elixir-lang.org).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://elixir-lang.org/install.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\ncurl -fsSO https://elixir-lang.org/install.sh\n$STD sh install.sh elixir@latest otp@latest\n\nmsg_info \"Setup Erlang and Elixir\"\nERLANG_VERSION=$(ls /opt/livebook/.elixir-install/installs/otp/ | head -n1)\nELIXIR_VERSION=$(ls /opt/livebook/.elixir-install/installs/elixir/ | head -n1)\nLIVEBOOK_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)\n\nexport ERLANG_BIN=\"/opt/livebook/.elixir-install/installs/otp/$ERLANG_VERSION/bin\"\nexport ELIXIR_BIN=\"/opt/livebook/.elixir-install/installs/elixir/$ELIXIR_VERSION/bin\"\nexport PATH=\"$ERLANG_BIN:$ELIXIR_BIN:$PATH\"\n\n$STD mix local.hex --force\n$STD mix local.rebar --force\n$STD mix escript.install hex livebook --force\n\ncat <<EOF >/opt/livebook/.env\nexport HOME=/opt/livebook\nexport ERLANG_VERSION=$ERLANG_VERSION\nexport ELIXIR_VERSION=$ELIXIR_VERSION\nexport LIVEBOOK_PORT=8080\nexport LIVEBOOK_IP=\"::\"\nexport LIVEBOOK_HOME=/data\nexport LIVEBOOK_PASSWORD=\"$LIVEBOOK_PASSWORD\"\nexport ESCRIPTS_BIN=/opt/livebook/.mix/escripts\nexport ERLANG_BIN=\"/opt/livebook/.elixir-install/installs/otp/\\${ERLANG_VERSION}/bin\"\nexport ELIXIR_BIN=\"/opt/livebook/.elixir-install/installs/elixir/\\${ELIXIR_VERSION}/bin\"\nexport PATH=\"\\$ESCRIPTS_BIN:\\$ERLANG_BIN:\\$ELIXIR_BIN:\\$PATH\"\nEOF\n{\n  echo \"Livebook-Credentials\"\n  echo \"Livebook Password: $LIVEBOOK_PASSWORD\"\n} >>~/livebook.creds\nmsg_ok \"Installed Erlang $ERLANG_VERSION and Elixir $ELIXIR_VERSION\"\n\nmsg_info \"Installing Livebook\"\ncat <<EOF >/etc/systemd/system/livebook.service\n[Unit]\nDescription=Livebook\nAfter=network.target\n\n[Service]\nType=exec\nUser=livebook\nGroup=livebook\nWorkingDirectory=/data\nEnvironmentFile=-/opt/livebook/.env\nExecStart=/bin/bash -c 'source /opt/livebook/.env && cd /opt/livebook && livebook server'\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nchown -R livebook:livebook /opt/livebook /data\nsystemctl enable -q --now livebook\nmsg_ok \"Installed Livebook\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/lldap-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/lldap/lldap\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing lldap\"\nsource /etc/os-release\nos=$ID\nif [ \"$os\" == \"ubuntu\" ]; then\n  DISTRO=\"xUbuntu\"\nelse\n  DISTRO=\"${os^}\"\nfi\ncurl -fsSL https://download.opensuse.org/repositories/home:Masgalor:LLDAP/${DISTRO}_${VERSION_ID}/Release.key | gpg --dearmor >/usr/share/keyrings/home_Masgalor_LLDAP.gpg\ncat <<EOF >/etc/apt/sources.list.d/home:Masgalor:LLDAP.sources\nTypes: deb\nURIs: http://download.opensuse.org/repositories/home:/Masgalor:/LLDAP/${DISTRO}_${VERSION_ID}/\nSuites: /\nSigned-By: /usr/share/keyrings/home_Masgalor_LLDAP.gpg\nEOF\n$STD apt update\n$STD apt install -y lldap\nsystemctl enable -q --now lldap\nmsg_ok \"Installed lldap\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/loki-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: bysinka-95\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/grafana/loki\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_deb822_repo \\\n  \"grafana\" \\\n  \"https://apt.grafana.com/gpg.key\" \\\n  \"https://apt.grafana.com\" \\\n  \"stable\" \\\n  \"main\"\n\nmsg_info \"Installing Loki\"\n$STD apt install -y loki\nmkdir -p /var/lib/loki/{chunks,boltdb-shipper-active,boltdb-shipper-cache}\nchown -R loki /var/lib/loki\ncat <<EOF >/etc/loki/config.yml\nauth_enabled: false\n\nserver:\n  http_listen_port: 3100\n  log_level: info\n\ncommon:\n  instance_addr: 127.0.0.1\n  path_prefix: /var/lib/loki\n  storage:\n    filesystem:\n      chunks_directory: /var/lib/loki/chunks\n      rules_directory: /var/lib/loki/rules\n  replication_factor: 1\n  ring:\n    kvstore:\n      store: inmemory\n\nschema_config:\n  configs:\n    - from: 2020-10-24\n      store: tsdb\n      object_store: filesystem\n      schema: v13\n      index:\n        prefix: index_\n        period: 24h\n\nquery_range:\n  results_cache:\n    cache:\n      embedded_cache:\n        enabled: true\n        max_size_mb: 100\n\nlimits_config:\n  metric_aggregation_enabled: true\n\nruler:\n  alertmanager_url: http://localhost:9093\nEOF\nchown loki /etc/loki/config.yml\nsystemctl enable -q --now loki\nmsg_ok \"Installed Loki\"\n\nread -rp \"Would you like to install Promtail? (y/N): \" INSTALL_PROMTAIL\nif [[ \"${INSTALL_PROMTAIL,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Promtail\"\n  $STD apt install -y promtail\n  systemctl enable -q --now promtail\n  msg_ok \"Installed Promtail\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/lubelogger-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://lubelogger.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"lubelogger\" \"hargata/lubelog\" \"prebuild\" \"latest\" \"/opt/lubelogger\" \"LubeLogger*linux_x64.zip\"\n\nmsg_info \"Configuring LubeLogger\"\ncd /opt/lubelogger\nchmod 700 /opt/lubelogger/CarCareTracker\ncp /opt/lubelogger/appsettings.json /opt/lubelogger/appsettings_bak.json\njq '.Kestrel = {\"Endpoints\": {\"Http\": {\"Url\": \"http://0.0.0.0:5000\"}}}' /opt/lubelogger/appsettings_bak.json >/opt/lubelogger/appsettings.json\nrm -rf /opt/lubelogger/appsettings_bak.json\nmsg_ok \"Configured LubeLogger\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/lubelogger.service\n[Unit]\nDescription=LubeLogger Daemon\nAfter=network.target\n\n[Service]\nUser=root\n\nType=simple\nWorkingDirectory=/opt/lubelogger\nExecStart=/opt/lubelogger/CarCareTracker\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now lubelogger\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/lychee-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/LycheeOrg/Lychee\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  caddy \\\n  libimage-exiftool-perl \\\n  jpegoptim\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"bcmath,ldap,exif,gd,intl,imagick,redis,zip,pdo_pgsql,pcntl\" setup_php\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"lychee\" PG_DB_USER=\"lychee\" setup_postgresql_db\nsetup_ffmpeg\nsetup_imagemagick\n\nfetch_and_deploy_gh_release \"lychee\" \"LycheeOrg/Lychee\" \"prebuild\" \"latest\" \"/opt/lychee\" \"Lychee.zip\"\n\nmsg_info \"Configuring Application\"\ncd /opt/lychee\ncp .env.example .env\nAPP_KEY=$($STD php artisan key:generate --show)\nsed -i \"s|^APP_KEY=.*|APP_KEY=${APP_KEY}|\" .env\nsed -i \"s|^APP_ENV=.*|APP_ENV=production|\" .env\nsed -i \"s|^APP_DEBUG=.*|APP_DEBUG=false|\" .env\nsed -i \"s|^APP_URL=.*|APP_URL=http://${LOCAL_IP}|\" .env\nsed -i \"s|^DB_CONNECTION=.*|DB_CONNECTION=pgsql|\" .env\nsed -i \"s|^DB_HOST=.*|DB_HOST=127.0.0.1|\" .env\nsed -i \"s|^DB_PORT=.*|DB_PORT=5432|\" .env\nsed -i \"s|^#\\?DB_DATABASE=.*|DB_DATABASE=${PG_DB_NAME}|\" .env\nsed -i \"s|^DB_USERNAME=.*|DB_USERNAME=${PG_DB_USER}|\" .env\nsed -i \"s|^DB_PASSWORD=.*|DB_PASSWORD=${PG_DB_PASS}|\" .env\nmkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache public/dist public/uploads public/sym\ntouch public/dist/user.css public/dist/custom.js\nchmod -R 775 storage bootstrap/cache public/dist public/uploads public/sym\nmsg_ok \"Configured Application\"\n\nmsg_info \"Running Database Migrations\"\ncd /opt/lychee\n$STD php artisan migrate --force\nmsg_ok \"Ran Database Migrations\"\n\nchown -R www-data:www-data /opt/lychee\n\nmsg_info \"Configuring Caddy\"\nPHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . \".\" . PHP_MINOR_VERSION;')\ncat <<EOF >/etc/caddy/Caddyfile\n:80 {\n    root * /opt/lychee/public\n    php_fastcgi unix//run/php/php${PHP_VER}-fpm.sock\n    file_server\n    encode gzip\n}\nEOF\nusermod -aG www-data caddy\nmsg_ok \"Configured Caddy\"\n\nsystemctl enable -q --now php${PHP_VER}-fpm\nsystemctl restart caddy\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/lyrionmusicserver-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://lyrion.org/getting-started/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setup Lyrion Music Server\"\nDEB_URL=$(curl -fsSL 'https://lyrion.org/getting-started/' | grep -oP '<a\\s[^>]*href=\"\\K[^\"]*arm\\.deb(?=\"[^>]*>)' | head -n 1)\nRELEASE=$(echo \"$DEB_URL\" | grep -oP 'lyrionmusicserver_\\K[0-9.]+(?=_arm\\.deb)')\nDEB_FILE=\"/tmp/lyrionmusicserver_${RELEASE}_arm.deb\"\ncurl -fsSL -o \"$DEB_FILE\" \"$DEB_URL\"\n$STD apt install \"$DEB_FILE\" -y\nrm -f \"$DEB_FILE\"\necho \"${RELEASE}\" >\"/opt/lyrion_version.txt\"\nmsg_ok \"Setup Lyrion Music Server v${RELEASE}\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mafl-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mafl.hywax.space/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ca-certificates \\\n  build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn@latest\" setup_nodejs\nfetch_and_deploy_gh_release \"mafl\" \"hywax/mafl\" \"tarball\"\n\nmsg_info \"Installing Mafl\"\nmkdir -p /opt/mafl/data\ncurl -fsSL \"https://raw.githubusercontent.com/hywax/mafl/main/.example/config.yml\" -o \"/opt/mafl/data/config.yml\"\ncd /opt/mafl\nexport NUXT_TELEMETRY_DISABLED=true\n$STD yarn install\n$STD yarn build\nmsg_ok \"Installed Mafl\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/mafl.service\n[Unit]\nDescription=Mafl\nAfter=network.target\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nWorkingDirectory=/opt/mafl/\nExecStart=yarn preview\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mafl\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/magicmirror-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://magicmirror.builders/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"magicmirror\" \"MagicMirrorOrg/MagicMirror\" \"tarball\"\n\nmsg_info \"Configuring MagicMirror\"\ncd /opt/magicmirror\nsed -i -E 's/(\"postinstall\": )\".*\"/\\1\"\"/; s/(\"prepare\": )\".*\"/\\1\"\"/' package.json\n$STD npm run install-mm\ncat <<EOF >/opt/magicmirror/config/config.js\nlet config = {\n        address: \"0.0.0.0\",     \n        port: 8080,\n        basePath: \"/\",  \n        ipWhitelist: [],        \n        useHttps: false,              \n        httpsPrivateKey: \"\",    \n        httpsCertificate: \"\",   \n        language: \"en\",\n        locale: \"en-US\",\n        logLevel: [\"INFO\", \"LOG\", \"WARN\", \"ERROR\"], \n        timeFormat: 24,\n        units: \"metric\",\n        serverOnly:  true,\n        modules: [\n                {\n                        module: \"alert\",\n                },\n                {\n                        module: \"updatenotification\",\n                        position: \"top_bar\"\n                },\n                {\n                        module: \"clock\",\n                        position: \"top_left\"\n                },\n                {\n                        module: \"calendar\",\n                        header: \"US Holidays\",\n                        position: \"top_left\",\n                        config: {\n                                calendars: [\n                                        {\n                                                symbol: \"calendar-check\",\n                                                url: \"webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics\"\n                                        }\n                                ]\n                        }\n                },\n                {\n                        module: \"compliments\",\n                        position: \"lower_third\"\n                },\n                {\n                        module: \"weather\",\n                        position: \"top_right\",\n                        config: {\n                                weatherProvider: \"openweathermap\",\n                                type: \"current\",\n                                location: \"New York\",\n                                locationID: \"5128581\", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city\n                                apiKey: \"YOUR_OPENWEATHER_API_KEY\"\n                        }\n                },\n                {\n                        module: \"weather\",\n                        position: \"top_right\",\n                        header: \"Weather Forecast\",\n                        config: {\n                                weatherProvider: \"openweathermap\",\n                                type: \"forecast\",\n                                location: \"New York\",\n                                locationID: \"5128581\", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city\n                                apiKey: \"YOUR_OPENWEATHER_API_KEY\"\n                        }\n                },\n                {\n                        module: \"newsfeed\",\n                        position: \"bottom_bar\",\n                        config: {\n                                feeds: [\n                                        {\n                                                title: \"New York Times\",\n                                                url: \"https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml\"\n                                        }\n                                ],\n                                showSourceTitle: true,\n                                showPublishDate: true,\n                                broadcastNewsFeeds: true,\n                                broadcastNewsUpdates: true\n                        }\n                },\n        ]\n};\n\n/*************** DO NOT EDIT THE LINE BELOW ***************/\nif (typeof module !== \"undefined\") {module.exports = config;}\nEOF\nmsg_ok \"Configured MagicMirror\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/magicmirror.service\n[Unit]\nDescription=Magic Mirror\nAfter=network.target\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nWorkingDirectory=/opt/magicmirror/\nExecStart=/usr/bin/npm run server\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now magicmirror\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mail-archiver-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/s1t5/mail-archiver\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\ncurl -SL -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0.101/dotnet-sdk-10.0.101-linux-arm64.tar.gz\ncurl -SL -o aspnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.1/aspnetcore-runtime-10.0.1-linux-arm64.tar.gz\n$STD mkdir -p /usr/share/dotnet\n$STD tar -zxf dotnet.tar.gz -C /usr/share/dotnet\n$STD tar -zxf aspnet.tar.gz -C /usr/share/dotnet\nln -s /usr/share/dotnet/dotnet /usr/bin/dotnet\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"mailarchiver_db\" PG_DB_USER=\"mailarchiver\" setup_postgresql_db\nfetch_and_deploy_gh_release \"mail-archiver\" \"s1t5/mail-archiver\" \"tarball\"\n\nmsg_info \"Setting up Mail-Archiver\"\nmv /opt/mail-archiver /opt/mail-archiver-build\ncd /opt/mail-archiver-build\n$STD dotnet restore\n$STD dotnet publish -c Release -o /opt/mail-archiver\ncp /opt/mail-archiver-build/appsettings.json /opt/mail-archiver/appsettings.json\nsed -i \"s|\\\"DefaultConnection\\\": \\\"[^\\\"]*\\\"|\\\"DefaultConnection\\\": \\\"Host=localhost;Database=mailarchiver_db;Username=mailarchiver;Password=$PG_DB_PASS\\\"|\" /opt/mail-archiver/appsettings.json\nrm -rf /opt/mail-archiver-build\n\ncat <<EOF >/opt/mail-archiver/.env\nASPNETCORE_URLS=http://+:5000\nASPNETCORE_ENVIRONMENT=Production\nTZ=UTC\nEOF\nmsg_ok \"Setup Mail-Archiver\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/mail-archiver.service\n[Unit]\nDescription=Mail-Archiver Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/mail-archiver/.env\nWorkingDirectory=/opt/mail-archiver\nExecStart=/usr/bin/dotnet MailArchiver.dll\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mail-archiver\nmsg_info \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/managemydamnlife-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/intri-in/manage-my-damn-life-nextjs\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y build-essential\nmsg_ok \"Installed dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nsetup_mariadb\n\nmsg_info \"Setting up Database\"\nDB_NAME=\"mmdl\"\nDB_USER=\"mmdl\"\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD mariadb -u root -e \"CREATE DATABASE $DB_NAME;\"\n$STD mariadb -u root -e \"CREATE USER '$DB_USER'@'localhost' IDENTIFIED by '$DB_PASS';\"\n$STD mariadb -u root -e \"GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;\"\n{\n  echo \"Manage My Damn Life Credentials\"\n  echo \"Database User: $DB_USER\"\n  echo \"Database Password: $DB_PASS\"\n  echo \"Database Name: $DB_NAME\"\n} >>~/mmdl.creds\nmsg_ok \"Set up Database\"\n\nfetch_and_deploy_gh_release \"mmdl\" \"intri-in/manage-my-damn-life-nextjs\" \"tarball\"\n\nmsg_info \"Configuring ${APPLICATION}\"\ncp /opt/mmdl/sample.env.local /opt/mmdl/.env\nsed -i -e 's|db|localhost|' \\\n  -e \"s|myuser|${DB_USER}|\" \\\n  -e \"s|mypassword|${DB_PASS}|\" \\\n  -e 's|5433|3306|' \\\n  -e 's|DB_DIALECT=postgres|DB_DIALECT=mysql|' \\\n  -e \"s|sample_install_mmdm|${DB_NAME}|\" \\\n  -e \"s|=PASSWORD|=$(openssl rand -base64 40 | tr -dc 'a-zA-Z0-9' | head -c40)|\" \\\n  /opt/mmdl/.env\ncd /opt/mmdl\nexport NEXT_TELEMETRY_DISABLED=1\nexport CI=\"true\"\n$STD npm install\n$STD npm run migrate\n$STD npm run build\nmsg_ok \"Configured ${APPLICATION}\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/mmdl.service\n[Unit]\nDescription=${APPLICATION} Service\nAfter=network.target mariadb.service\n\n[Service]\nWorkingDirectory=/opt/mmdl\nEnvironmentFile=/opt/mmdl/.env\nExecStart=/usr/bin/npm run start\nRestart=on-abnormal\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mmdl\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/manyfold-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01 | Co-Author: SunFlowerOwl\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/manyfold3d/manyfold\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libarchive-dev \\\n  git \\\n  libmariadb-dev \\\n  redis-server \\\n  nginx \\\n  libassimp-dev\nmsg_ok \"Installed Dependencies\"\n\nsetup_imagemagick\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"manyfold\" PG_DB_USER=\"manyfold\" setup_postgresql_db\nNODE_VERSION=\"24\" NODE_MODULE=\"yarn\" setup_nodejs\n\nfetch_and_deploy_gh_release \"manyfold\" \"manyfold3d/manyfold\" \"tarball\" \"latest\" \"/opt/manyfold/app\"\n\nRUBY_INSTALL_VERSION=$(cat /opt/manyfold/app/.ruby-version)\nRUBY_VERSION=${RUBY_INSTALL_VERSION} RUBY_INSTALL_RAILS=\"true\" HOME=/home/manyfold setup_ruby\n\nmsg_info \"Configuring Manyfold\"\nYARN_VERSION=$(grep '\"packageManager\":' /opt/manyfold/app/package.json | sed -E 's/.*\"(yarn@[0-9\\.]+)\".*/\\1/')\nRELEASE=$(get_latest_github_release \"manyfold3d/manyfold\")\nuseradd -m -s /usr/bin/bash manyfold\ncat <<EOF >/opt/manyfold/.env\nexport APP_VERSION=${RELEASE}\nexport GUID=1002\nexport PUID=1001\nexport PUBLIC_PORT=5000\nexport REDIS_URL=redis://127.0.0.1:6379/1\nexport DATABASE_ADAPTER=postgresql\nexport DATABASE_HOST=127.0.0.1\nexport DATABASE_USER=${PG_DB_USER}\nexport DATABASE_PASSWORD=${PG_DB_PASS}\nexport DATABASE_NAME=${PG_DB_NAME}\nexport DATABASE_CONNECTION_POOL=16\nexport MULTIUSER=enabled\nexport HTTPS_ONLY=false\nexport RAILS_ENV=production\nEOF\ncat <<EOF >/opt/manyfold/user_setup.sh\n#!/bin/bash\n\nsource /opt/manyfold/.env\nexport PATH=\"/home/manyfold/.rbenv/bin:\\$PATH\"\neval \"\\$(/home/manyfold/.rbenv/bin/rbenv init - bash)\"\ncd /opt/manyfold/app\nrbenv global $RUBY_INSTALL_VERSION\ngem install bundler\nbundle install\ngem install sidekiq\ngem install foreman\ncorepack enable yarn\nrm -f /opt/manyfold/app/config/credentials.yml.enc\ncorepack prepare $YARN_VERSION --activate\ncorepack use $YARN_VERSION\nexport VISUAL=\"code --wait\"\nbin/rails credentials:edit\nbin/rails db:migrate\nbin/rails assets:precompile\nEOF\n$STD mkdir -p /opt/manyfold_data\nmsg_ok \"Configured Manyfold\"\n\nmsg_info \"Installing Manyfold\"\nchown -R manyfold:manyfold {/home/manyfold,/opt/manyfold}\nchmod +x /opt/manyfold/user_setup.sh\n$STD npm install --global corepack\n$STD sudo -u manyfold bash /opt/manyfold/user_setup.sh\nrm -f /opt/manyfold/user_setup.sh\nmsg_ok \"Installed Manyfold\"\n\nmsg_info \"Creating Services\"\nsource /opt/manyfold/.env\nexport PATH=\"/home/manyfold/.rbenv/shims:/home/manyfold/.rbenv/bin:$PATH\"\n$STD foreman export systemd /etc/systemd/system -a manyfold -u manyfold -f /opt/manyfold/app/Procfile\nfor f in /etc/systemd/system/manyfold-*.service; do\n  sed -i \"s|/bin/bash -lc '|/bin/bash -lc 'source /opt/manyfold/.env \\&\\& |\" \"$f\"\ndone\nsystemctl enable -q --now manyfold.target manyfold-rails.1 manyfold-default_worker.1 manyfold-performance_worker.1\ncat <<EOF >/etc/nginx/sites-available/manyfold.conf\nserver {\n    listen 80;\n    server_name manyfold;\n    root /opt/manyfold/app/public;\n\n    location /cable {\n        proxy_pass http://127.0.0.1:5000;\n        \n        proxy_set_header Host \\$http_host;\n        proxy_set_header X-Forwarded-Host \\$http_host;\n        proxy_set_header X-Forwarded-Port \\$server_port;\n\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade \\$http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n\n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n    }\n\n    location / {\n        try_files \\$uri/index.html \\$uri @rails;\n    }\n\n    location @rails {\n        proxy_pass http://127.0.0.1:5000;\n        \n        proxy_set_header Host \\$http_host;\n        proxy_set_header X-Forwarded-Host \\$http_host;\n        proxy_set_header X-Forwarded-Port \\$server_port;\n        \n        proxy_set_header X-Real-IP \\$remote_addr;\n        proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \\$scheme;\n    }\n}\nEOF\nln -s /etc/nginx/sites-available/manyfold.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\n$STD apt-get -y autoremove\n$STD apt-get -y autoclean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/mariadb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mariadb.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_mariadb\n\nmsg_info \"Setup MariaDB\"\nsed -i 's/^# *\\(port *=.*\\)/\\1/' /etc/mysql/my.cnf\nsed -i 's/^bind-address/#bind-address/g' /etc/mysql/mariadb.conf.d/50-server.cnf\nmsg_ok \"Setup MariaDB\"\n\nread -r -p \"${TAB3}Would you like to add PhpMyAdmin? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing phpMyAdmin\"\n  $STD apt install -y \\\n    apache2 \\\n    php \\\n    php-mysqli \\\n    php-mbstring \\\n    php-zip \\\n    php-gd \\\n    php-json \\\n    php-curl\n\n  curl -fsSL \"https://files.phpmyadmin.net/phpMyAdmin/5.2.2/phpMyAdmin-5.2.2-all-languages.tar.gz\" -o \"phpMyAdmin-5.2.2-all-languages.tar.gz\"\n  mkdir -p /var/www/html/phpMyAdmin\n  tar xf phpMyAdmin-5.2.2-all-languages.tar.gz --strip-components=1 -C /var/www/html/phpMyAdmin\n  cp /var/www/html/phpMyAdmin/config.sample.inc.php /var/www/html/phpMyAdmin/config.inc.php\n  SECRET=$(openssl rand -base64 24)\n  sed -i \"s#\\$cfg\\['blowfish_secret'\\] = '';#\\$cfg['blowfish_secret'] = '${SECRET}';#\" /var/www/html/phpMyAdmin/config.inc.php\n  chmod 660 /var/www/html/phpMyAdmin/config.inc.php\n  chown -R www-data:www-data /var/www/html/phpMyAdmin\n  systemctl restart apache2\n  msg_ok \"Installed phpMyAdmin\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/matomo-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://matomo.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y caddy\nmsg_ok \"Installed Dependencies\"\n\nmkdir -p /opt/matomo\n\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULES=\"pdo_mysql,gd,mbstring,xml,curl,intl,zip,ldap\" setup_php\nsetup_mariadb\nMARIADB_DB_NAME=\"matomo\" MARIADB_DB_USER=\"matomo\" setup_mariadb_db\n\nmsg_info \"Allowing Local TCP Database Access\"\n$STD mariadb -u root -e \"CREATE USER IF NOT EXISTS '$MARIADB_DB_USER'@'127.0.0.1' IDENTIFIED BY '$MARIADB_DB_PASS';\"\n$STD mariadb -u root -e \"ALTER USER '$MARIADB_DB_USER'@'127.0.0.1' IDENTIFIED BY '$MARIADB_DB_PASS';\"\n$STD mariadb -u root -e \"GRANT ALL ON \\`$MARIADB_DB_NAME\\`.* TO '$MARIADB_DB_USER'@'127.0.0.1';\"\n$STD mariadb -u root -e \"FLUSH PRIVILEGES;\"\nmsg_ok \"Allowed Local TCP Database Access\"\n\nfetch_and_deploy_gh_release \"matomo\" \"matomo-org/matomo\" \"prebuild\" \"latest\" \"/opt/matomo\" \"matomo-*.zip\"\n\nmsg_info \"Setting up Matomo\"\nif [[ -d /opt/matomo/matomo ]]; then\n  rm -rf /opt/matomo/tmp \"/opt/matomo/How to install Matomo.html\"\n  find /opt/matomo/matomo -mindepth 1 -maxdepth 1 -exec mv -t /opt/matomo {} +\n  rm -rf /opt/matomo/matomo\nfi\nmkdir -p /opt/matomo/tmp\nchown -R www-data:www-data /opt/matomo\nchmod -R 755 /opt/matomo/tmp\nmsg_ok \"Set up Matomo\"\n\nmsg_info \"Configuring Caddy\"\nPHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . \".\" . PHP_MINOR_VERSION;')\ncat <<EOF >/etc/caddy/Caddyfile\n:80 {\n    root * /opt/matomo\n    @blocked path /config /config/* /tmp /tmp/* /.* /.*/*\n    respond @blocked 403\n    php_fastcgi unix//run/php/php${PHP_VER}-fpm.sock\n    file_server\n    encode gzip\n}\nEOF\nusermod -aG www-data caddy\nmsg_ok \"Configured Caddy\"\n\nsystemctl enable -q --now php${PHP_VER}-fpm\nsystemctl restart caddy\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/matter-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/matter-js/python-matter-server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libuv1 \\\n  libjson-c5 \\\n  libnl-3-200 \\\n  libnl-route-3-200 \\\n  iputils-ping \\\n  iproute2\nmsg_ok \"Installed Dependencies\"\n\nUV_PYTHON=\"3.12\" setup_uv\n\nmsg_info \"Setting up Matter Server\"\nmkdir -p /opt/matter-server/data/credentials\nif [ -L /data ]; then\n  rm -f /data\nfi\nif [ ! -e /data ]; then\n  ln -s /opt/matter-server/data /data\nfi\n$STD uv venv /opt/matter-server/.venv\nMATTER_VERSION=$(get_latest_github_release \"matter-js/python-matter-server\")\n$STD uv pip install --python /opt/matter-server/.venv/bin/python \"python-matter-server[server]==${MATTER_VERSION}\"\necho \"${MATTER_VERSION}\" >~/.matter-server\nmsg_ok \"Set up Matter Server\"\n\nfetch_and_deploy_gh_release \"chip-ota-provider-app\" \"home-assistant-libs/matter-linux-ota-provider\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"chip-ota-provider-app-x86-64\"\n\nmsg_info \"Configuring Network\"\ncat <<EOF >/etc/sysctl.d/99-matter.conf\nnet.ipv4.igmp_max_memberships=1024\nEOF\n$STD sysctl -p /etc/sysctl.d/99-matter.conf\nmsg_ok \"Configured Network\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/matter-server.service\n[Unit]\nDescription=Matter Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nExecStart=/opt/matter-server/.venv/bin/matter-server --storage-path /data --paa-root-cert-dir /data/credentials\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now matter-server\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/matterbridge-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Luligu/matterbridge/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Install Matterbridge\"\nmkdir -p /root/Matterbridge\nNODE_VERSION=\"24\" NODE_MODULE=\"matterbridge\" setup_nodejs\nmsg_ok \"Installed Matterbridge\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/matterbridge.service\n[Unit]\nDescription=matterbridge\nAfter=network-online.target\n\n[Service]\nType=simple\nExecStart=matterbridge -service\nWorkingDirectory=/root/Matterbridge\nStandardOutput=inherit\nStandardError=inherit\nRestart=always\nRestartSec=10s\nTimeoutStopSec=30s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now matterbridge\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mattermost-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kaedon Cleland-Host (dracentis)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mattermost.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://mattermost.com/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://deb.packages.mattermost.com/repo-setup.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nPG_VERSION=\"16\" setup_postgresql\n\nmsg_info \"Setting up PostgreSQL\"\nDB_NAME=mattermost\nDB_USER=mmuser\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME;\"\n$STD sudo -u postgres psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE $DB_NAME to $DB_USER;\"\n$STD sudo -u postgres psql -c \"ALTER DATABASE $DB_NAME OWNER TO $DB_USER;\"\n$STD sudo -u postgres psql -c \"GRANT USAGE, CREATE ON SCHEMA PUBLIC TO $DB_USER;\"\n{\n  echo \"Mattermost Credentials\"\n  echo \"Database User: $DB_USER\"\n  echo \"Database Password: $DB_PASS\"\n  echo \"Database Name: $DB_NAME\"\n} >>~/mattermost.creds\nmsg_ok \"Set up PostgreSQL\"\n\nmsg_info \"Installing Mattermost\"\ncurl -fsSL -o /usr/share/keyrings/mattermost-archive-keyring.gpg https://deb.packages.mattermost.com/pubkey.gpg\nsh -c 'curl -fsSL https://deb.packages.mattermost.com/repo-setup.sh | sudo bash -s mattermost' >/dev/null\n$STD apt update\n$STD apt install -y mattermost\n$STD install -C -m 600 -o mattermost -g mattermost /opt/mattermost/config/config.defaults.json /opt/mattermost/config/config.json\nsed -i -e \"/DataSource/c\\   \\\"DataSource\\\": \\\"postgres://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME?sslmode=disable&connect_timeout=10\\\",\" \\\n  -e \"/SiteURL/c\\   \\\"SiteURL\\\": \\\"http://$LOCAL_IP:8065\\\",\" /opt/mattermost/config/config.json\nsystemctl enable -q --now mattermost\nmsg_ok \"Installed Mattermost\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mealie-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mealie.io\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  libpq-dev \\\n  libwebp-dev \\\n  libsasl2-dev \\\n  libldap2-dev \\\n  libldap-common \\\n  libssl-dev \\\n  libldap2 \\\n  gosu \\\n  iproute2\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nPG_VERSION=\"16\" setup_postgresql\nNODE_MODULE=\"yarn\" NODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"mealie\" \"mealie-recipes/mealie\" \"tarball\"\nPG_DB_NAME=\"mealie_db\" PG_DB_USER=\"mealie_user\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\n\nmsg_info \"Installing Python Dependencies with uv\"\ncd /opt/mealie\n$STD uv sync --frozen --extra pgsql\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Building Frontend\"\nMEALIE_VERSION=$(<$HOME/.mealie)\nexport NUXT_TELEMETRY_DISABLED=1\ncd /opt/mealie/frontend\nSITE_SETTINGS=$(find /opt/mealie/frontend -name \"site-settings.vue\" -path \"*/admin/*\" | head -1)\n$STD sed -i \"s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g\" \"$SITE_SETTINGS\"\n$STD sed -i \"s|value: data.buildId,|value: \\\"v${MEALIE_VERSION}\\\",|g\" \"$SITE_SETTINGS\"\n$STD sed -i \"s|value: data.production ? i18n.t(\\\"about.production\\\") : i18n.t(\\\"about.development\\\"),|value: \\\"bare-metal\\\",|g\" \"$SITE_SETTINGS\"\n$STD yarn install --prefer-offline --frozen-lockfile --non-interactive --production=false --network-timeout 1000000\n$STD yarn generate\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Copying Built Frontend\"\nmkdir -p /opt/mealie/mealie/frontend\ncp -r /opt/mealie/frontend/dist/* /opt/mealie/mealie/frontend/\nmsg_ok \"Copied Frontend\"\n\nsetup_nltk \"averaged_perceptron_tagger_eng\" \"/nltk_data\"\n\nmsg_info \"Writing Environment File\"\nSECRET=$(openssl rand -hex 32)\nmkdir -p /run/secrets\ncat <<EOF >/opt/mealie/mealie.env\nMEALIE_HOME=/opt/mealie\nNLTK_DATA=/nltk_data\nSECRET=${SECRET}\n\nDB_ENGINE=postgres\nPOSTGRES_SERVER=localhost\nPOSTGRES_PORT=5432\nPOSTGRES_USER=${PG_DB_USER}\nPOSTGRES_PASSWORD=${PG_DB_PASS}\nPOSTGRES_DB=${PG_DB_NAME}\n\nPRODUCTION=true\nHOST=0.0.0.0\nPORT=9000\nBASE_URL=http://${LOCAL_IP}:9000\nEOF\nmsg_ok \"Wrote Environment File\"\n\nmsg_info \"Creating Start Script\"\ncat <<'EOF' >/opt/mealie/start.sh\n#!/bin/bash\nset -a\nsource /opt/mealie/mealie.env\nset +a\nexec uv run mealie\nEOF\nchmod +x /opt/mealie/start.sh\nmsg_ok \"Created Start Script\"\n\nmsg_info \"Creating Systemd Service\"\ncat <<'EOF' >/etc/systemd/system/mealie.service\n[Unit]\nDescription=Mealie Recipe Manager\nAfter=network.target postgresql.service\nWants=postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/mealie\nExecStart=/opt/mealie/start.sh\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mealie\nmsg_ok \"Created and Started Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mediamanager-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/maxdorninger/MediaManager\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nread -r -p \"${TAB3}Enter the email address of your first admin user: \" admin_email\nif [[ \"$admin_email\" ]]; then\n  EMAIL=\"$admin_email\"\nfi\n\nsetup_yq\nNODE_VERSION=\"24\" setup_nodejs\nPYTHON_VERSION=\"3.13\" setup_uv\nPG_VERSION=\"17\" setup_postgresql\n\nmsg_info \"Setting up PostgreSQL\"\nDB_NAME=\"mm_db\"\nDB_USER=\"mm_user\"\nDB_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\"\n$STD sudo -u postgres psql -c \"CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME WITH OWNER $DB_USER TEMPLATE template0;\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET client_encoding TO 'utf8';\"\n{\n  echo \"MediaManager Credentials\"\n  echo \"MediaManager Database User: $DB_USER\"\n  echo \"MediaManager Database Password: $DB_PASS\"\n  echo \"MediaManager Database Name: $DB_NAME\"\n} >>~/mediamanager.creds\nmsg_ok \"Set up PostgreSQL\"\n\nfetch_and_deploy_gh_release \"MediaManager\" \"maxdorninger/MediaManager\" \"tarball\" \"latest\" \"/opt/mediamanager\"\n\nmsg_info \"Configuring MediaManager\"\nMM_DIR=\"/opt/mm\"\nMEDIA_DIR=\"${MM_DIR}/media\"\nexport CONFIG_DIR=\"${MM_DIR}/config\"\nexport FRONTEND_FILES_DIR=\"${MM_DIR}/web/build\"\nexport PUBLIC_VERSION=\"\"\nexport PUBLIC_API_URL=\"\"\nexport BASE_PATH=\"/web\"\ncd /opt/mediamanager/web\n$STD npm install --no-fund --no-audit\n$STD npm run build\nmkdir -p {\"$MM_DIR\"/web,\"$MEDIA_DIR\",\"$CONFIG_DIR\"}\ncp -r build \"$FRONTEND_FILES_DIR\"\nexport BASE_PATH=\"\"\nexport VIRTUAL_ENV=\"${MM_DIR}/venv\"\ncd /opt/mediamanager\ncp -r {media_manager,alembic*} \"$MM_DIR\"\n$STD /usr/local/bin/uv sync --locked --active -n -p cpython3.13 --managed-python\nmsg_ok \"Configured MediaManager\"\n\nmsg_info \"Creating config and start script\"\nSECRET=\"$(openssl rand -hex 32)\"\nsed -e \"s/localhost:8/$LOCAL_IP:8/g\" \\\n  -e \"s|/data/|$MEDIA_DIR/|g\" \\\n  -e 's/\"db\"/\"localhost\"/' \\\n  -e \"s/user = \\\"MediaManager\\\"/user = \\\"$DB_USER\\\"/\" \\\n  -e \"s/password = \\\"MediaManager\\\"/password = \\\"$DB_PASS\\\"/\" \\\n  -e \"s/dbname = \\\"MediaManager\\\"/dbname = \\\"$DB_NAME\\\"/\" \\\n  -e \"/^token_secret/s/=.*/= \\\"$SECRET\\\"/\" \\\n  -e \"s/admin@example.com/$EMAIL/\" \\\n  -e '/^admin_emails/s/, .*/]/' \\\n  /opt/mediamanager/config.example.toml >\"$CONFIG_DIR\"/config.toml\n\nmkdir -p \"$MEDIA_DIR\"/{images,tv,movies,torrents}\n\ncat <<EOF >\"$MM_DIR\"/start.sh\n#!/usr/bin/env bash\n\nexport CONFIG_DIR=\"$CONFIG_DIR\"\nexport FRONTEND_FILES_DIR=\"$FRONTEND_FILES_DIR\"\nexport LOG_FILE=\"$CONFIG_DIR/media_manager.log\"\nexport BASE_PATH=\"\"\ncd $MM_DIR\nsource ./venv/bin/activate\n/usr/local/bin/uv run alembic upgrade head\n/usr/local/bin/uv run fastapi run ./media_manager/main.py --port 8000\nEOF\nchmod +x \"$MM_DIR\"/start.sh\nmsg_ok \"Created config and start script\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/mediamanager.service\n[Unit]\nDescription=MediaManager Backend Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=${MM_DIR}\nExecStart=/usr/bin/bash start.sh\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mediamanager\nmsg_ok \"Created service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mediamtx-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bluenviron/mediamtx\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"mediamtx\" \"bluenviron/mediamtx\" \"prebuild\" \"latest\" \"/opt/mediamtx\" \"mediamtx*linux_arm64v8.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/mediamtx.service\n[Unit]\nDescription=MediaMTX\nAfter=syslog.target network-online.target\n\n[Service]\nExecStart=/opt/mediamtx/./mediamtx\nWorkingDirectory=/opt/mediamtx\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mediamtx\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/medusa-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pymedusa/Medusa\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git-core \\\n  mediainfo\n\ncat <<EOF >/etc/apt/sources.list.d/non-free.list\ndeb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware\nEOF\n$STD apt update\n$STD apt install -y unrar\nrm /etc/apt/sources.list.d/non-free.list\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Medusa\"\n$STD git clone https://github.com/pymedusa/Medusa.git /opt/medusa\nmsg_ok \"Installed Medusa\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/medusa.service\n[Unit]\nDescription=Medusa Daemon\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/python3 /opt/medusa/start.py -q --nolaunch --datadir=/opt/medusa\nTimeoutStopSec=25\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now medusa\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/meilisearch-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.meilisearch.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nMEILISEARCH_BIND=\"0.0.0.0:7700\" setup_meilisearch\n\nread -r -p \"${TAB3}Do you want add meilisearch-ui? [y/n]: \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  NODE_VERSION=\"22\" NODE_MODULE=\"pnpm@latest\" setup_nodejs\n  fetch_and_deploy_gh_release \"meilisearch-ui\" \"riccox/meilisearch-ui\" \"tarball\"\n\n  msg_info \"Configuring ${APPLICATION}-ui\"\n  cd /opt/meilisearch-ui\n  sed -i 's|const hash = execSync(\"git rev-parse HEAD\").toString().trim();|const hash = \"unknown\";|' /opt/meilisearch-ui/vite.config.ts\n  $STD pnpm install\n  cat <<EOF >/opt/meilisearch-ui/.env.local\nVITE_SINGLETON_MODE=true\nVITE_SINGLETON_HOST=http://${LOCAL_IP}:7700\nVITE_SINGLETON_API_KEY=${MEILISEARCH_MASTER_KEY}\nEOF\n  msg_ok \"Configured ${APPLICATION}-ui\"\n\n  msg_info \"Creating Meilisearch-UI service\"\n  cat <<EOF >/etc/systemd/system/meilisearch-ui.service\n[Unit]\nDescription=Meilisearch UI Service\nAfter=network.target meilisearch.service\nRequires=meilisearch.service\n\n[Service]\nUser=root\nWorkingDirectory=/opt/meilisearch-ui\nExecStart=/usr/bin/pnpm start\nRestart=always\nRestartSec=5\nStandardOutput=syslog\nStandardError=syslog\nSyslogIdentifier=meilisearch-ui\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now meilisearch-ui\n  msg_ok \"Created Meilisearch-UI service\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/memos-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/usememos/memos\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nARCH=$(dpkg --print-architecture 2>/dev/null || uname -m)\nif [[ \"$ARCH\" == \"arm64\" || \"$ARCH\" == \"aarch64\" ]]; then\n  fetch_and_deploy_gh_release \"memos\" \"usememos/memos\" \"prebuild\" \"v0.25.3\" \"/opt/memos\" \"memos*linux_arm64.tar.gz\"\nelse\n  fetch_and_deploy_gh_release \"memos\" \"usememos/memos\" \"prebuild\" \"latest\" \"/opt/memos\" \"memos*linux_amd64.tar.gz\"\nfi\nmkdir -p /opt/memos_data\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/memos.service\n[Unit]\nDescription=Memos Server\nAfter=network.target\n\n[Service]\nExecStart=/opt/memos/memos\nEnvironment=\"MEMOS_MODE=prod\"\nEnvironment=\"MEMOS_PORT=9030\"\nEnvironment=\"MEMOS_DATA=/opt/memos_data\"\nWorkingDirectory=/opt/memos\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now memos\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/meshcentral-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://meshcentral.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\n\nmsg_info \"Installing MeshCentral\"\nmkdir /opt/meshcentral\ncd /opt/meshcentral\n$STD npm install meshcentral\n$STD node node_modules/meshcentral --install\nmsg_ok \"Installed MeshCentral\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/metabase-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.metabase.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"21\" setup_java\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"metabase_db\" PG_DB_USER=\"metabase\" setup_postgresql_db\n\nmsg_info \"Setting up Metabase\"\nmkdir -p /opt/metabase\nRELEASE=$(get_latest_github_release \"metabase/metabase\")\ncurl -fsSL \"https://downloads.metabase.com/v${RELEASE}.x/metabase.jar\" -o /opt/metabase/metabase.jar\ncd /opt/metabase\n\ncat <<EOF >/opt/metabase/.env\nMB_DB_TYPE=postgres\nMB_DB_DBNAME=$PG_DB_NAME\nMB_DB_PORT=5432\nMB_DB_USER=$PG_DB_USER\nMB_DB_PASS=$PG_DB_PASS\nMB_DB_HOST=localhost\nEOF\necho $RELEASE >~/.metabase\nmsg_ok \"Setup Metabase\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/metabase.service\n[Unit]\nDescription=Metabase Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/metabase/.env\nWorkingDirectory=/opt/metabase\nExecStart=/usr/bin/java --add-opens java.base/java.nio=ALL-UNNAMED -jar metabase.jar\nRestart=always\nSuccessExitStatus=143\nTimeoutStopSec=120\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now metabase\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/metube-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alexta69/metube\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  aria2 \\\n  coreutils \\\n  musl-dev \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\n\nmsg_info \"Installing Deno\"\nexport DENO_INSTALL=\"/usr/local\"\ncurl -fsSL https://deno.land/install.sh | $STD sh -s -- -y\n[[ \":$PATH:\" != *\":/usr/local/bin:\"* ]] &&\n  echo -e \"\\nexport PATH=\\\"/usr/local/bin:\\$PATH\\\"\" >>~/.bashrc &&\n  source ~/.bashrc\nmsg_ok \"Installed Deno\"\n\nfetch_and_deploy_gh_release \"metube\" \"alexta69/metube\" \"tarball\" \"latest\"\n\nmsg_info \"Installing MeTube\"\ncd /opt/metube/ui\nif command -v corepack >/dev/null 2>&1; then\n  $STD corepack enable\n  $STD corepack prepare pnpm --activate || true\nfi\necho 'onlyBuiltDependencies=*' >> .npmrc\n$STD pnpm install --frozen-lockfile\n$STD pnpm run build\ncd /opt/metube\n$STD uv sync\nmkdir -p /opt/metube_downloads /opt/metube_downloads/.metube /opt/metube_downloads/music /opt/metube_downloads/videos\ncat <<EOF >/opt/metube/.env\n# Storage & Directories\nDOWNLOAD_DIR=/opt/metube_downloads\nAUDIO_DOWNLOAD_DIR=/opt/metube_downloads/music\nSTATE_DIR=/opt/metube_downloads/.metube\nTEMP_DIR=/opt/metube_downloads\n\n# Download Behavior\nDOWNLOAD_MODE=limited\nMAX_CONCURRENT_DOWNLOADS=3\nDELETE_FILE_ON_TRASHCAN=false\nDEFAULT_OPTION_PLAYLIST_STRICT_MODE=false\nDEFAULT_OPTION_PLAYLIST_ITEM_LIMIT=0\n\n# File Naming & yt-dlp\nOUTPUT_TEMPLATE=%(title)s.%(ext)s\nOUTPUT_TEMPLATE_CHAPTER=%(title)s - %(section_number)s %(section_title)s.%(ext)s\nOUTPUT_TEMPLATE_PLAYLIST=%(playlist_title)s/%(title)s.%(ext)s\nYTDL_OPTIONS={\"trim_file_name\":200,\"extractor_args\":{\"youtube\":{\"player_client\":[\"default\",\"-tv_simply\"]}}}\n\n# Custom Directories\nCUSTOM_DIRS=true\nCREATE_CUSTOM_DIRS=true\n\n# Basic Setup\nDEFAULT_THEME=auto\nLOGLEVEL=INFO\nENABLE_ACCESSLOG=false\nEOF\nmsg_ok \"Installed MeTube\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/metube.service\n[Unit]\nDescription=Metube - YouTube Downloader\nAfter=network.target\n[Service]\nType=simple\nWorkingDirectory=/opt/metube\nEnvironmentFile=/opt/metube/.env\nExecStart=/opt/metube/.venv/bin/python3 app/main.py\nRestart=always\nUser=root\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now metube\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/minarca-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://minarca.org/en_CA\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  ca-certificates \\\n  lsb-release\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Minarca\"\ncurl -fsSL https://www.ikus-soft.com/archive/minarca/public.key | gpg --dearmor >/usr/share/keyrings/minarca-keyring.gpg\ncat <<EOF >/etc/apt/sources.list.d/minarca.sources\nTypes: deb\nURIs: https://nexus.ikus-soft.com/repository/apt-release-$(lsb_release -sc)/\nSuites: $(lsb_release -sc)\nComponents: main\nArchitectures: arm64\nSigned-By: /usr/share/keyrings/minarca-keyring.gpg\nEOF\n$STD apt update\n$STD apt install -y minarca-server\nmsg_ok \"Installed Minarca\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mini-qr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: doge0420\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/lyqht/mini-qr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libharfbuzz0b \\\n  caddy \\\n  fontconfig\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"20\" setup_nodejs\nfetch_and_deploy_gh_release \"mini-qr\" \"lyqht/mini-qr\" \"tarball\"\n\nmsg_info \"Building MiniQR\"\ncd /opt/mini-qr\n$STD npm install\n$STD npm run build\nmsg_ok \"Built MiniQR\"\n\nmsg_info \"Configuring Caddy\"\ncat <<EOF >/etc/caddy/Caddyfile\n:80 {\n    root * /opt/mini-qr/dist\n    file_server\n\n    # Handle client-side routing\n    try_files {path} /index.html\n\n    # Cache static assets\n    @assets {\n        path /assets/*\n    }\n    header @assets Cache-Control \"public, immutable, max-age=31536000\"\n\n    # Correct MIME types for JS modules\n    @jsmodules {\n        path *.js *.mjs\n    }\n    header @jsmodules Content-Type \"application/javascript\"\n}\nEOF\nsystemctl enable -q --now caddy\nsystemctl reload caddy\nmsg_ok \"Configured Caddy\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/miniflux-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: omernaveedxyz\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://miniflux.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPG_VERSION=17 setup_postgresql\nPG_DB_NAME=\"miniflux_db\" PG_DB_USER=\"miniflux\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\nfetch_and_deploy_gh_release \"miniflux\" \"miniflux/v2\" \"binary\" \"latest\"\n\nmsg_info \"Configuring Miniflux\"\nADMIN_NAME=admin\nADMIN_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\"\ncat <<EOF >/etc/miniflux.conf\n# See https://miniflux.app/docs/configuration.html\nDATABASE_URL=user=$PG_DB_USER password=$PG_DB_PASS dbname=$PG_DB_NAME sslmode=disable\nCREATE_ADMIN=1\nADMIN_USERNAME=$ADMIN_NAME\nADMIN_PASSWORD=$ADMIN_PASS\nLISTEN_ADDR=0.0.0.0:8080\nEOF\n{\n  echo \"ADMIN_USERNAME: $ADMIN_NAME\"\n  echo \"ADMIN_PASSWORD: $ADMIN_PASS\"\n} >>~/miniflux.creds\n$STD miniflux -migrate -config-file /etc/miniflux.conf\nsystemctl enable -q --now miniflux\nmsg_ok \"Configured Miniflux\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/minio-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/minio/minio\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nFEATURE_RICH_VERSION=\"2025-04-22T22-12-26Z\"\n\necho\necho \"MinIO recently removed many management features from the Console UI.\"\necho \"The last feature-complete version is: $FEATURE_RICH_VERSION\"\necho \"Latest versions require the paid edition for full UI functionality.\"\necho\necho \"Choose which version to install:\"\necho \"  [N] Feature-rich community version ($FEATURE_RICH_VERSION) [Recommended]\"\necho \"  [Y] Latest version (may lack UI features)\"\necho\nread -p \"Install latest MinIO version? [y/N]: \" -n 1 -r\necho\nif [[ $REPLY =~ ^[Yy]$ ]]; then\n  USE_LATEST=true\nelse\n  USE_LATEST=false\nfi\n\nmsg_info \"Setting up MinIO\"\nif [[ \"$USE_LATEST\" == \"true\" ]]; then\n  RELEASE=$(curl -fsSL https://api.github.com/repos/minio/minio/releases/latest | grep '\"tag_name\"' | awk -F '\"' '{print $4}')\n  DOWNLOAD_URL=\"https://dl.min.io/server/minio/release/linux-arm64/minio\"\nelse\n  RELEASE=\"$FEATURE_RICH_VERSION\"\n  DOWNLOAD_URL=\"https://dl.min.io/server/minio/release/linux-arm64/archive/minio.RELEASE.${FEATURE_RICH_VERSION}\"\nfi\n\ncurl -fsSL \"$DOWNLOAD_URL\" -o /usr/local/bin/minio\nchmod +x /usr/local/bin/minio\nuseradd -r minio-user -s /sbin/nologin\nmkdir -p /home/minio-user\nchown minio-user:minio-user /home/minio-user\nmkdir -p /data\nchown minio-user:minio-user /data\n\nMINIO_ADMIN_USER=\"minioadmin\"\nMINIO_ADMIN_PASSWORD=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\"\n\ncat <<EOF >/etc/default/minio\nMINIO_ROOT_USER=${MINIO_ADMIN_USER}\nMINIO_ROOT_PASSWORD=${MINIO_ADMIN_PASSWORD}\nEOF\n\n{\n  echo \"\"\n  echo \"MinIO Credentials\"\n  echo \"MinIO Admin User: $MINIO_ADMIN_USER\"\n  echo \"MinIO Admin Password: $MINIO_ADMIN_PASSWORD\"\n} >>~/minio.creds\necho \"${RELEASE}\" >/opt/${APPLICATION,,}_version.txt\nmsg_ok \"Setup MinIO\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/minio.service\n[Unit]\nDescription=MinIO\nDocumentation=https://docs.min.io\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nUser=minio-user\nGroup=minio-user\nEnvironmentFile=-/etc/default/minio\nExecStart=/usr/local/bin/minio server --console-address \":9001\" /data\nRestart=always\nRestartSec=5\nLimitNOFILE=65536\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now minio\nmsg_ok \"Service created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/minthcm-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MintHCM\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/minthcm/minthcm\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.2\"\nPHP_APACHE=\"YES\" PHP_MODULE=\"mysql,redis\" PHP_FPM=\"YES\" setup_php\nsetup_composer\nsetup_mariadb\n$STD mariadb -u root -e \"SET GLOBAL sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'\"\n\nfetch_and_deploy_gh_release \"MintHCM\" \"minthcm/minthcm\" \"tarball\" \"latest\" \"/var/www/MintHCM\"\n\nmsg_info \"Configuring MintHCM\"\nmkdir -p /etc/php/${PHP_VERSION}/mods-available\ncp /var/www/MintHCM/docker/config/000-default.conf /etc/apache2/sites-available/000-default.conf\ncp /var/www/MintHCM/docker/config/php-minthcm.ini /etc/php/${PHP_VERSION}/mods-available/php-minthcm.ini\nmkdir -p \"/etc/php/${PHP_VERSION}/cli/conf.d\" \"/etc/php/${PHP_VERSION}/apache2/conf.d\"\nln -s \"/etc/php/${PHP_VERSION}/mods-available/php-minthcm.ini\" \"/etc/php/${PHP_VERSION}/cli/conf.d/20-minthcm.ini\"\nln -s \"/etc/php/${PHP_VERSION}/mods-available/php-minthcm.ini\" \"/etc/php/${PHP_VERSION}/apache2/conf.d/20-minthcm.ini\"\nchown -R www-data:www-data /var/www/MintHCM\nfind /var/www/MintHCM -type d -exec chmod 755 {} \\;\nfind /var/www/MintHCM -type f -exec chmod 644 {} \\;\nmkdir -p /var/www/script\ncp /var/www/MintHCM/docker/script/generate_config.php /var/www/script/generate_config.php\ncp /var/www/MintHCM/docker/.env /var/www/script/.env\nchown -R www-data:www-data /var/www/script\n$STD a2enmod rewrite\n$STD a2enmod headers\n$STD systemctl restart apache2\nmsg_ok \"Configured MintHCM\"\n\nmsg_info \"Setting up Elasticsearch\"\nsetup_deb822_repo \\\n  \"elasticsearch\" \\\n  \"https://artifacts.elastic.co/GPG-KEY-elasticsearch\" \\\n  \"https://artifacts.elastic.co/packages/7.x/apt\" \\\n  \"stable\"\n$STD apt install -y elasticsearch\necho \"-Xms2g\" >>/etc/elasticsearch/jvm.options\necho \"-Xmx2g\" >>/etc/elasticsearch/jvm.options\n$STD /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment -b\nsystemctl enable -q --now elasticsearch\nmsg_ok \"Set up Elasticsearch\"\n\nmsg_info \"Configuring Database\"\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD mariadb -u root -e \"CREATE USER 'minthcm'@'localhost' IDENTIFIED BY '${DB_PASS}';\"\n$STD mariadb -u root -e \"GRANT ALL ON *.* TO 'minthcm'@'localhost'; FLUSH PRIVILEGES;\"\nsed -i \"s/^DB_HOST=.*/DB_HOST=localhost/\" /var/www/script/.env\nsed -i \"s/^DB_USER=.*/DB_USER=minthcm/\" /var/www/script/.env\nsed -i \"s/^DB_PASS=.*/DB_PASS=$DB_PASS/\" /var/www/script/.env\nsed -i \"s/^ELASTICSEARCH_HOST=.*/ELASTICSEARCH_HOST=localhost/\" /var/www/script/.env\nmsg_ok \"Configured Database\"\n\nmsg_info \"Generating configuration file\"\nset -a\nsource /var/www/script/.env\nset +a\n$STD php /var/www/script/generate_config.php\nmsg_ok \"Generated configuration file\"\n\nmsg_info \"Installing MintHCM\"\ncd /var/www/MintHCM\n$STD sudo -u www-data php MintCLI install </var/www/MintHCM/configMint4\nprintf \"*    *    *    *    *     cd /var/www/MintHCM/legacy; php -f cron.php > /dev/null 2>&1\\n\" >/var/spool/cron/crontabs/www-data\nservice cron start\nrm -f /var/www/MintHCM/configMint4\nmsg_ok \"Installed MintHCM\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mongodb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.mongodb.com/de-de\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\ncpu_info=$(lscpu)\n\nif ! echo \"$cpu_info\" | grep -q 'asimdrdm\\|asimdhf\\|dotprod\\|fp16'; then\n    msg_error \"This machine does not support ARMv8.2-A.\"\n    exit\nfi\n\nread -p \"${TAB3}Do you want to install MongoDB 8.0 instead of 7.0? [y/N]: \" install_mongodb_8\nif [[ \"$install_mongodb_8\" =~ ^[Yy]$ ]]; then\n  MONGO_VERSION=\"8.0\" setup_mongodb\nelse\n  MONGO_VERSION=\"7.0\" setup_mongodb\nfi\nsed -i 's/bindIp: 127.0.0.1/bindIp: 0.0.0.0/' /etc/mongod.conf\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/monica-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.monicahq.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.2\" PHP_APACHE=\"YES\" PHP_MODULE=\"mysqli,pdo-mysql\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"monica\" MARIADB_DB_USER=\"monica\" setup_mariadb_db\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn@latest\" setup_nodejs\nfetch_and_deploy_gh_release \"monica\" \"monicahq/monica\" \"prebuild\" \"latest\" \"/opt/monica\" \"monica-v*.tar.bz2\"\n\nmsg_info \"Configuring monica\"\ncd /opt/monica\ncp /opt/monica/.env.example /opt/monica/.env\nHASH_SALT=$(openssl rand -base64 32)\nsed -i -e \"s|^DB_USERNAME=.*|DB_USERNAME=${MARIADB_DB_USER}|\" \\\n  -e \"s|^DB_PASSWORD=.*|DB_PASSWORD=${MARIADB_DB_PASS}|\" \\\n  -e \"s|^HASH_SALT=.*|HASH_SALT=${HASH_SALT}|\" \\\n  /opt/monica/.env\n$STD composer install --no-dev -o --no-interaction\n$STD yarn config set ignore-engines true\n$STD yarn install\n$STD yarn run production\n$STD php artisan key:generate\n$STD php artisan setup:production --email=admin@community-scripts.org --password=community-scripts.org --force\nchown -R www-data:www-data /opt/monica\nchmod -R 775 /opt/monica/storage\necho \"* * * * * root php /opt/monica/artisan schedule:run >> /dev/null 2>&1\" >>/etc/crontab\nmsg_ok \"Configured monica\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/monica.conf\n<VirtualHost *:80>\n    ServerName monica\n    DocumentRoot /opt/monica/public\n    <Directory /opt/monica/public>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/monica_error.log\n    CustomLog /var/log/apache2/monica_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite monica\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/motioneye-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/motioneye-project/motioneye\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\n$STD apt install -y cifs-utils\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup Python3\"\n$STD apt install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nmsg_info \"Installing Motion\"\n$STD apt install -y motion\nsystemctl stop motion\n$STD systemctl disable motion\nmsg_ok \"Installed Motion\"\n\nmsg_info \"Installing FFmpeg\"\n$STD apt install -y ffmpeg v4l-utils\nmsg_ok \"Installed FFmpeg\"\n\nmsg_info \"Installing MotionEye\"\n$STD apt update\n$STD pip install git+https://github.com/motioneye-project/motioneye.git@dev\nmkdir -p /etc/motioneye\nchown -R root:root /etc/motioneye\nchmod -R 777 /etc/motioneye\ncurl -fsSL \"https://raw.githubusercontent.com/motioneye-project/motioneye/dev/motioneye/extra/motioneye.conf.sample\" -o \"/etc/motioneye/motioneye.conf\"\nmkdir -p /var/lib/motioneye\nmsg_ok \"Installed MotionEye\"\n\nmsg_info \"Creating Service\"\ncurl -fsSL \"https://raw.githubusercontent.com/motioneye-project/motioneye/dev/motioneye/extra/motioneye.systemd\" -o \"/etc/systemd/system/motioneye.service\"\nsed -i 's/^User=.*/User=root/' /etc/systemd/system/motioneye.service\nsystemctl enable -q --now motioneye\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mqtt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://mosquitto.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Mosquitto MQTT Broker\"\nsource /etc/os-release\n$STD apt update\n$STD apt -y install mosquitto mosquitto-clients\n\ncat <<EOF >/etc/mosquitto/conf.d/default.conf\nallow_anonymous false\npersistence true\npassword_file /etc/mosquitto/passwd\nlistener 1883\nEOF\nmsg_ok \"Installed Mosquitto MQTT Broker\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/myip-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ipcheck.ing/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"myip\" \"jason5ng32/MyIP\" \"tarball\"\n\nmsg_info \"Configuring MyIP\"\ncd /opt/myip\ncp .env.example .env\n$STD npm install\n$STD npm run build\nmsg_ok \"Configured MyIP\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/myip.service\n[Unit]\nDescription=MyIP Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/myip\nExecStart=/usr/bin/npm start\nEnvironmentFile=/opt/myip/.env\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now myip\nmsg_ok \"Service created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mylar3-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: davalanche | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/mylar3/mylar3\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\ncat <<EOF >/etc/apt/sources.list.d/non-free.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bookworm\nComponents: non-free non-free-firmware\nEOF\n$STD apt update\n$STD apt install -y unrar\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nfetch_and_deploy_gh_release \"mylar3\" \"mylar3/mylar3\" \"tarball\"\n\nmsg_info \"Installing ${APPLICATION}\"\nmkdir -p /opt/mylar3-data\n$STD uv venv --clear /opt/mylar3/.venv\n$STD /opt/mylar3/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/mylar3/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/mylar3/.venv/bin/python -m pip install --no-cache-dir -r /opt/mylar3/requirements.txt\nmsg_ok \"Installed ${APPLICATION}\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/mylar3.service\n[Unit]\nDescription=Mylar3 Service\nAfter=network-online.target\n\n[Service]\nExecStart=/opt/mylar3/.venv/bin/python /opt/mylar3/Mylar.py --daemon --nolaunch --datadir=/opt/mylar3-data\nGuessMainPID=no\nType=forking\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now mylar3\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/myspeed-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/gnmyt/myspeed\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  ca-certificates \\\n  python3-setuptools\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"myspeed\" \"gnmyt/myspeed\" \"prebuild\" \"latest\" \"/opt/myspeed\" \"MySpeed-*.zip\"\n\nmsg_info \"Configuring MySpeed\"\ncd /opt/myspeed\n$STD npm install\nmsg_ok \"Installed MySpeed\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/myspeed.service\n[Unit]\nDescription=MySpeed\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/node server\nRestart=always\nUser=root\nEnvironment=NODE_ENV=production\nWorkingDirectory=/opt/myspeed \n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now myspeed\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/mysql-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.mysql.com/products/community | https://www.phpmyadmin.net\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  lsb-release\nmsg_ok \"Installed Dependencies\"\n\nRELEASE_REPO=\"mysql-8.0\"\nRELEASE_AUTH=\"mysql_native_password\"\nread -r -p \"${TAB3}Would you like to install the MySQL 8.4 LTS release instead of MySQL 8.0 (bug fix track; EOL April-2026)? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  RELEASE_REPO=\"mysql-8.4-lts\"\n  RELEASE_AUTH=\"caching_sha2_password\"\nfi\n\nmsg_info \"Installing MySQL\"\ncurl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2023 | gpg --dearmor -o /usr/share/keyrings/mysql.gpg\nif [ \"$(lsb_release -si)\" = \"Debian\" ]; then\n  cat <<EOF >/etc/apt/sources.list.d/mysql.sources\nTypes: deb\nURIs: http://repo.mysql.com/apt/debian\nSuites: $(lsb_release -sc)\nComponents: ${RELEASE_REPO}\nSigned-By: /usr/share/keyrings/mysql.gpg\nEOF\nelse\n  cat <<EOF >/etc/apt/sources.list.d/mysql.sources\nTypes: deb\nURIs: http://repo.mysql.com/apt/ubuntu\nSuites: $(lsb_release -sc)\nComponents: ${RELEASE_REPO}\nSigned-By: /usr/share/keyrings/mysql.gpg\nEOF\nfi\n$STD apt update\nexport DEBIAN_FRONTEND=noninteractive\n$STD apt install -y \\\n  mysql-community-client \\\n  mysql-community-server\nmsg_ok \"Installed MySQL\"\n\nmsg_info \"Configure MySQL Server\"\nADMIN_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\n$STD mysql -uroot -p\"$ADMIN_PASS\" -e \"ALTER USER 'root'@'localhost' IDENTIFIED WITH $RELEASE_AUTH BY '$ADMIN_PASS'; FLUSH PRIVILEGES;\"\necho \"\" >~/mysql.creds\necho -e \"MySQL user: root\" >>~/mysql.creds\necho -e \"MySQL password: $ADMIN_PASS\" >>~/mysql.creds\nmsg_ok \"MySQL Server configured\"\n\nread -r -p \"${TAB3}Would you like to add PhpMyAdmin? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/phpmyadmin.sh)\"\nfi\n\nmsg_info \"Start Service\"\nsystemctl enable -q --now mysql\nmsg_ok \"Service started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/n8n-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://n8n.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  python3-setuptools \\\n  graphicsmagick\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Installing n8n (Patience)\"\n$STD npm install -g n8n\nmsg_ok \"Installed n8n\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/n8n.env\nN8N_SECURE_COOKIE=false\nN8N_PORT=5678\nN8N_PROTOCOL=http\nN8N_HOST=${LOCAL_IP}\nEOF\n\ncat <<EOF >/etc/systemd/system/n8n.service\n[Unit]\nDescription=n8n\n\n[Service]\nType=simple\nEnvironmentFile=/opt/n8n.env\nExecStart=n8n start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now n8n\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nagios-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CanbiZ (MickLesk)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/NagiosEnterprises/nagioscore\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  autoconf \\\n  automake \\\n  build-essential \\\n  bc \\\n  dc \\\n  gawk \\\n  gettext \\\n  gperf \\\n  libgd-dev \\\n  libmcrypt-dev \\\n  libnet-snmp-perl \\\n  libssl-dev \\\n  snmp \\\n  apache2 \\\n  apache2-utils\nmsg_ok \"Installed Dependencies\"\n\nPHP_APACHE=\"YES\" setup_php\n\nfetch_and_deploy_gh_release \"nagios\" \"NagiosEnterprises/nagioscore\" \"tarball\"\n\nmsg_info \"Building Nagios Core\"\ncd /opt/nagios\n$STD ./configure --with-httpd-conf=/etc/apache2/sites-enabled\n$STD make all\n$STD make install-groups-users\nusermod -a -G nagios www-data\n$STD make install\n$STD make install-daemoninit\n$STD make install-commandmode\n$STD make install-config\n$STD make install-webconf\n$STD a2enmod rewrite\n$STD a2enmod cgi\nmsg_ok \"Built Nagios Core\"\n\nfetch_and_deploy_gh_release \"nagios-plugins\" \"nagios-plugins/nagios-plugins\" \"tarball\"\n\nmsg_info \"Building Nagios Plugins\"\ncd /opt/nagios-plugins\n$STD ./tools/setup\n$STD ./configure\n$STD make\n$STD make install\nsetcap cap_net_raw+p /bin/ping\nmsg_ok \"Built Nagios Plugins\"\n\nmsg_info \"Configuring Web Authentication\"\n$STD htpasswd -bc /usr/local/nagios/etc/htpasswd.users nagiosadmin nagiosadmin\nchown root:www-data /usr/local/nagios/etc/htpasswd.users\nchmod 640 /usr/local/nagios/etc/htpasswd.users\nmsg_ok \"Configured Web Authentication\"\n\nmsg_info \"Starting Services\"\nsystemctl enable -q apache2\nsystemctl restart apache2\nsystemctl enable -q --now nagios\nmsg_ok \"Started Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nametag-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/mattogodoy/nametag\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"nametag_db\" PG_DB_USER=\"nametag\" setup_postgresql_db\nNODE_VERSION=\"20\" setup_nodejs\nfetch_and_deploy_gh_release \"nametag\" \"mattogodoy/nametag\" \"tarball\" \"latest\" \"/opt/nametag\"\n\nmsg_info \"Setting up Application\"\ncd /opt/nametag\n$STD npm ci\nDATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}\" $STD npx prisma generate\nDATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}\" $STD npx prisma migrate deploy\nmsg_ok \"Set up Application\"\n\nmsg_info \"Configuring Nametag\"\nNEXTAUTH_SECRET=$(openssl rand -base64 32)\nCRON_SECRET=$(openssl rand -base64 16)\nmkdir -p /opt/nametag/data/photos\ncat <<EOF >/opt/nametag/.env\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}\nNEXTAUTH_URL=http://${LOCAL_IP}:3000\nNEXTAUTH_SECRET=${NEXTAUTH_SECRET}\nCRON_SECRET=${CRON_SECRET}\nPHOTO_STORAGE_PATH=/opt/nametag/data/photos\nNODE_ENV=production\nEOF\nmsg_ok \"Configured Nametag\"\n\nmsg_info \"Building Application\"\ncd /opt/nametag\nset -a\nsource /opt/nametag/.env\nset +a\n$STD npm run build\ncp -r /opt/nametag/.next/static /opt/nametag/.next/standalone/.next/static\ncp -r /opt/nametag/public /opt/nametag/.next/standalone/public\nmsg_ok \"Built Application\"\n\nmsg_info \"Running Production Seed\"\ncd /opt/nametag\n$STD npx esbuild prisma/seed.production.ts --platform=node --format=cjs --outfile=prisma/seed.production.js --bundle --external:@prisma/client --external:pg --minify\n$STD node prisma/seed.production.js\nmsg_ok \"Ran Production Seed\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/nametag.service\n[Unit]\nDescription=Nametag - Personal Relationships Manager\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/nametag\nEnvironmentFile=/opt/nametag/.env\nExecStart=/usr/bin/node /opt/nametag/.next/standalone/server.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now nametag\nmsg_ok \"Created Service\"\n\nmsg_info \"Setting up Cron Jobs\"\ncat <<EOF >/etc/cron.d/nametag\n0 8 * * * root curl -sf -H \"Authorization: Bearer ${CRON_SECRET}\" http://127.0.0.1:3000/api/cron/send-reminders >/dev/null 2>&1\n0 3 * * * root curl -sf -H \"Authorization: Bearer ${CRON_SECRET}\" http://127.0.0.1:3000/api/cron/purge-deleted >/dev/null 2>&1\nEOF\nchmod 644 /etc/cron.d/nametag\nmsg_ok \"Set up Cron Jobs\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/navidrome-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/navidrome/navidrome\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"navidrome\" \"navidrome/navidrome\" \"binary\"\n\nmsg_info \"Starting Navidrome\"\nsystemctl enable -q --now navidrome\nmsg_ok \"Started Navidrome\"\n\nread -p \"${TAB3}Do you want to install filebrowser addon? (y/n) \" -n 1 -r\nif [[ $REPLY =~ ^[Yy]$ ]]; then\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/filebrowser.sh)\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/neko-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CanbiZ (MickLesk)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://neko.m1k1o.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  supervisor \\\n  pulseaudio \\\n  dbus-x11 \\\n  xserver-xorg-video-dummy \\\n  xdotool \\\n  xclip \\\n  libgtk-3-0 \\\n  gstreamer1.0-plugins-base \\\n  gstreamer1.0-plugins-good \\\n  gstreamer1.0-plugins-bad \\\n  gstreamer1.0-plugins-ugly \\\n  gstreamer1.0-pulseaudio \\\n  openbox \\\n  firefox-esr \\\n  fonts-noto-color-emoji \\\n  fonts-wqy-zenhei\nmsg_ok \"Installed Dependencies\"\nsystemctl disable -q --now supervisor\n\nmsg_info \"Installing Build Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  pkg-config \\\n  libx11-dev \\\n  libxrandr-dev \\\n  libxtst-dev \\\n  libgtk-3-dev \\\n  libxcvt-dev \\\n  libgstreamer1.0-dev \\\n  libgstreamer-plugins-base1.0-dev\nmsg_ok \"Installed Build Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nsetup_go\n\nfetch_and_deploy_gh_release \"neko\" \"m1k1o/neko\" \"tarball\"\n\nmsg_info \"Building Client\"\ncd /opt/neko/client\n$STD npm install\n$STD npm run build\nmkdir -p /var/www\ncp -r /opt/neko/client/dist/* /var/www/\nmsg_ok \"Built Client\"\n\nmsg_info \"Building Server\"\ncd /opt/neko/server\n$STD ./build\ncp /opt/neko/server/bin/neko /usr/bin/neko\nmkdir -p /etc/neko/plugins\ncp -r /opt/neko/server/bin/plugins/* /etc/neko/plugins/ 2>/dev/null || true\nmsg_ok \"Built Server\"\n\nmsg_info \"Setting up Runtime\"\nuseradd -m -s /bin/bash neko\nusermod -aG audio,video neko\n\nmkdir -p /etc/neko/supervisord /var/www /var/log/neko /tmp/.X11-unix /tmp/runtime-neko /home/neko/.config/pulse /home/neko/.local/share/xorg\nchmod 1777 /tmp/.X11-unix\nchmod 1777 /var/log/neko\nchmod 0700 /tmp/runtime-neko\nchown neko /tmp/.X11-unix /var/log/neko /tmp/runtime-neko\nchown -R neko:neko /home/neko\n\ncp /opt/neko/runtime/xorg.conf /etc/neko/xorg.conf\n# Remove the dummy_touchscreen InputDevice section (requires custom \"neko\" Xorg driver not available bare-metal)\nsed -i '/Section \"InputDevice\"/{N;/dummy_touchscreen/{:l;N;/EndSection/!bl;d}}' /etc/neko/xorg.conf\nsed -i '/dummy_touchscreen/d' /etc/neko/xorg.conf\nsed -i 's/InputDevice  \"dummy_mouse\"/InputDevice  \"dummy_mouse\" \"CorePointer\"/' /etc/neko/xorg.conf\ncp /opt/neko/runtime/default.pa /etc/pulse/default.pa\n\ncat <<EOF >/etc/neko/supervisord.conf\n[supervisord]\nnodaemon=true\nuser=root\npidfile=/var/run/supervisord.pid\nlogfile=/dev/null\nlogfile_maxbytes=0\nloglevel=debug\n\n[include]\nfiles=/etc/neko/supervisord/*.conf\n\n[program:x-server]\nenvironment=HOME=\"/home/neko\",USER=\"neko\"\ncommand=/usr/bin/X :99.0 -config /etc/neko/xorg.conf -noreset -nolisten tcp\nautorestart=true\npriority=300\nuser=neko\nstdout_logfile=/var/log/neko/xorg.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=10\nredirect_stderr=true\n\n[program:pulseaudio]\nenvironment=HOME=\"/home/neko\",USER=\"neko\",DISPLAY=\":99.0\"\ncommand=/usr/bin/pulseaudio --log-level=error --disallow-module-loading --disallow-exit --exit-idle-time=-1\nautorestart=true\npriority=300\nuser=neko\nstdout_logfile=/var/log/neko/pulseaudio.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=10\nredirect_stderr=true\n\n[program:neko]\nenvironment=HOME=\"/home/neko\",USER=\"neko\",DISPLAY=\":99.0\"\ncommand=/usr/bin/neko serve --server.static \"/var/www\"\nstopsignal=INT\nstopwaitsecs=3\nautorestart=true\npriority=800\nuser=neko\nstdout_logfile=/var/log/neko/neko.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=10\nredirect_stderr=true\n\n[unix_http_server]\nfile=/var/run/supervisor.sock\nchmod=0770\nchown=root:neko\n\n[supervisorctl]\nserverurl=unix:///var/run/supervisor.sock\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\nEOF\n\ncat <<EOF >/etc/neko/supervisord/firefox.conf\n[program:firefox]\nenvironment=HOME=\"/home/neko\",USER=\"neko\",DISPLAY=\":99.0\"\ncommand=/usr/bin/firefox-esr --no-remote --display=:99.0 -width 1280 -height 720\nstopsignal=INT\nautorestart=true\npriority=800\nuser=neko\nstdout_logfile=/var/log/neko/firefox.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=10\nredirect_stderr=true\n\n[program:openbox]\nenvironment=HOME=\"/home/neko\",USER=\"neko\",DISPLAY=\":99.0\"\ncommand=/usr/bin/openbox --config-file /etc/neko/openbox.xml\nautorestart=true\npriority=300\nuser=neko\nstdout_logfile=/var/log/neko/openbox.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=10\nredirect_stderr=true\nEOF\n\ncat <<'EOF' >/etc/neko/openbox.xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<openbox_config xmlns=\"http://openbox.org/3.4/rc\" xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n<applications>\n    <application class=\"firefox\" name=\"Navigator\" role=\"browser\">\n      <decor>no</decor>\n      <maximized>true</maximized>\n      <focus>yes</focus>\n      <layer>normal</layer>\n    </application>\n</applications>\n<focus>\n  <focusNew>yes</focusNew>\n  <followMouse>no</followMouse>\n  <focusLast>yes</focusLast>\n  <underMouse>no</underMouse>\n  <focusDelay>200</focusDelay>\n  <raiseOnFocus>no</raiseOnFocus>\n</focus>\n<placement>\n  <policy>Smart</policy>\n  <center>yes</center>\n</placement>\n<desktops>\n  <number>1</number>\n  <firstdesk>1</firstdesk>\n  <popupTime>0</popupTime>\n</desktops>\n</openbox_config>\nEOF\n\ncat <<EOF >/etc/neko/neko.yaml\nserver:\n  bind: \"0.0.0.0:8080\"\n  static: \"/var/www\"\nsession:\n  cookie:\n    enabled: false\nwebrtc:\n  icelite: true\n  nat1to1:\n    - \"${LOCAL_IP}\"\n  epr: \"59000-59100\"\ndesktop:\n  input:\n    enabled: false\nmember:\n  provider: \"multiuser\"\n  multiuser:\n    admin_password: \"admin\"\n    user_password: \"neko\"\nEOF\nmsg_ok \"Set up Runtime\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/neko.service\n[Unit]\nDescription=Neko Virtual Browser\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nEnvironment=USER=neko\nEnvironment=DISPLAY=:99.0\nEnvironment=PULSE_SERVER=unix:/tmp/pulseaudio.socket\nEnvironment=XDG_RUNTIME_DIR=/tmp/runtime-neko\nEnvironment=NEKO_PLUGINS_ENABLED=true\nEnvironment=NEKO_PLUGINS_DIR=/etc/neko/plugins/\nEnvironment=NEKO_CONFIG=/etc/neko/neko.yaml\nExecStart=/usr/bin/supervisord -c /etc/neko/supervisord.conf -n\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now neko\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/neo4j-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: havardthom\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://neo4j.com/product/neo4j-graph-database/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"21\" setup_java\n\nmsg_info \"Installing Neo4j (patience)\"\ncurl -fsSL \"https://debian.neo4j.com/neotechnology.gpg.key\" | gpg --dearmor -o /etc/apt/keyrings/neotechnology.gpg\necho 'deb [signed-by=/etc/apt/keyrings/neotechnology.gpg] https://debian.neo4j.com stable latest' >/etc/apt/sources.list.d/neo4j.list\n$STD apt update\n$STD apt install -y neo4j\nsed -i '/server.default_listen_address/s/^#//' /etc/neo4j/neo4j.conf\nsystemctl enable -q --now neo4j\nmsg_ok \"Installed Neo4j\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/netbird-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: TechHutTV\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://netbird.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing NetBird\"\nsetup_deb822_repo \\\n\t\"netbird\" \\\n\t\"https://pkgs.netbird.io/debian/public.key\" \\\n\t\"https://pkgs.netbird.io/debian\" \\\n\t\"stable\"\n$STD apt install -y netbird\nmsg_ok \"Installed NetBird\"\n\nmsg_info \"Starting NetBird Service\"\nsystemctl enable -q --now netbird\nmsg_ok \"Started NetBird Service\"\n\necho \"\"\necho \"\"\necho -e \"${BL}NetBird Deployment Type${CL}\"\necho \"─────────────────────────────────────────\"\necho \"Are you using NetBird Managed or Self-Hosted?\"\necho \"\"\necho \" 1) NetBird Managed (default) - Use NetBird's managed service\"\necho \" 2) Self-Hosted - Use your own NetBird management server\"\necho \"\"\n\nread -r -p \"${TAB3}Select deployment type [1]: \" DEPLOYMENT_TYPE\nDEPLOYMENT_TYPE=\"${DEPLOYMENT_TYPE:-1}\"\n\nNETBIRD_MGMT_URL=\"\"\ncase \"$DEPLOYMENT_TYPE\" in\n  1)\n    msg_ok \"Using NetBird Managed service\"\n    ;;\n  2)\n    echo \"\"\n    echo -e \"${BL}Self-Hosted Configuration${CL}\"\n    echo \"─────────────────────────────────────────\"\n    echo \"Enter your NetBird management server URL.\"\n    echo \"Example: https://management.example.com\"\n    echo \"\"\n    read -r -p \"Management URL: \" NETBIRD_MGMT_URL\n\n    if [[ -z \"$NETBIRD_MGMT_URL\" ]]; then\n      msg_warn \"No management URL provided. Run 'netbird up --management-url <url>' to connect.\"\n    else\n      NETBIRD_MGMT_URL=\"${NETBIRD_MGMT_URL%/}\"\n      msg_ok \"Management URL configured: ${NETBIRD_MGMT_URL}\"\n    fi\n    ;;\n  *)\n    msg_warn \"Invalid selection. Using NetBird Managed service.\"\n    ;;\nesac\n\necho \"\"\necho \"\"\necho -e \"${BL}NetBird Connection Setup${CL}\"\necho \"─────────────────────────────────────────\"\necho \"Choose how to connect to your NetBird network:\"\necho \"\"\nif [[ \"$DEPLOYMENT_TYPE\" == \"1\" ]]; then\n  echo \" 1) Setup Key (default) - Use a pre-generated setup key\"\n  echo \" 2) SSO Login - Authenticate via browser with your identity provider\"\n  echo \" 3) Skip - Configure later with 'netbird up'\"\nelse\n  echo \" 1) Setup Key (default) - Use a pre-generated setup key\"\n  echo \" 2) Skip - Configure later with 'netbird up'\"\nfi\necho \"\"\n\nread -r -p \"Select authentication method [1]: \" AUTH_METHOD\nAUTH_METHOD=\"${AUTH_METHOD:-1}\"\n\nif [[ \"$DEPLOYMENT_TYPE\" == \"1\" ]]; then\n  case \"$AUTH_METHOD\" in\n    1)\n      echo \"\"\n      echo \"Enter your NetBird setup key from the NetBird dashboard.\"\n      echo \"\"\n      read -r -p \"Setup key: \" NETBIRD_SETUP_KEY\n      echo \"\"\n\n      if [[ -z \"$NETBIRD_SETUP_KEY\" ]]; then\n        msg_warn \"No setup key provided. Run 'netbird up -k <key>' to connect.\"\n      else\n        msg_info \"Connecting to NetBird with setup key\"\n        if $STD netbird up -k \"$NETBIRD_SETUP_KEY\"; then\n          msg_ok \"Connected to NetBird\"\n        else\n          msg_warn \"Connection failed. Run 'netbird up -k <key>' to retry.\"\n        fi\n      fi\n      ;;\n    2)\n      echo \"\"\n      echo -e \"${BL}SSO Authentication${CL}\"\n      echo \"─────────────────────────────────────────\"\n      echo \"A login URL will appear below.\"\n      echo \"Copy the URL and open it in your browser to authenticate.\"\n      echo \"\"\n\n      msg_info \"Starting SSO login\"\n      netbird login 2>&1 || true\n      echo \"\"\n\n      msg_info \"Connecting to NetBird\"\n      if $STD netbird up; then\n        msg_ok \"Connected to NetBird\"\n      else\n        msg_warn \"Connection failed. Run 'netbird up' to retry.\"\n      fi\n      ;;\n    3)\n      msg_ok \"Skipped. Run 'netbird up' to connect.\"\n      ;;\n    *)\n      msg_warn \"Invalid selection. Run 'netbird up' to connect.\"\n      ;;\n  esac\nelse\n  case \"$AUTH_METHOD\" in\n    1)\n      echo \"\"\n      echo \"Enter your NetBird setup key from the NetBird dashboard.\"\n      echo \"\"\n      read -r -p \"Setup key: \" NETBIRD_SETUP_KEY\n      echo \"\"\n\n      if [[ -z \"$NETBIRD_SETUP_KEY\" ]]; then\n        if [[ -z \"$NETBIRD_MGMT_URL\" ]]; then\n          msg_warn \"No setup key provided. Run 'netbird up -k <key> --management-url <url>' to connect.\"\n        else\n          msg_warn \"No setup key provided. Run 'netbird up -k <key> --management-url $NETBIRD_MGMT_URL' to connect.\"\n        fi\n      else\n        if [[ -z \"$NETBIRD_MGMT_URL\" ]]; then\n          msg_error \"Management URL is required for self-hosted deployments. Please configure it first.\"\n        else\n          msg_info \"Connecting to NetBird with setup key\"\n          if $STD netbird up -k \"$NETBIRD_SETUP_KEY\" --management-url \"$NETBIRD_MGMT_URL\"; then\n            msg_ok \"Connected to NetBird\"\n          else\n            msg_warn \"Connection failed. Run 'netbird up -k <key> --management-url $NETBIRD_MGMT_URL' to retry.\"\n          fi\n        fi\n      fi\n      ;;\n    2)\n      if [[ -z \"$NETBIRD_MGMT_URL\" ]]; then\n        msg_ok \"Skipped. Run 'netbird up --management-url <url>' to connect.\"\n      else\n        msg_ok \"Skipped. Run 'netbird up --management-url $NETBIRD_MGMT_URL' to connect.\"\n      fi\n      ;;\n    *)\n      if [[ -z \"$NETBIRD_MGMT_URL\" ]]; then\n        msg_warn \"Invalid selection. Run 'netbird up --management-url <url>' to connect.\"\n      else\n        msg_warn \"Invalid selection. Run 'netbird up --management-url $NETBIRD_MGMT_URL' to connect.\"\n      fi\n      ;;\n  esac\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/netboot-xyz-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://netboot.xyz\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  tftpd-hpa\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"netboot-xyz\" \"netbootxyz/netboot.xyz\" \"prebuild\" \"latest\" \"/var/www/html\" \"menus.tar.gz\"\n\n# x86_64 UEFI bootloaders\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-efi\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-efi-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.efi.dsk\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-snp.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-snp-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-snp.efi.dsk\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-snponly.efi\"\n# x86_64 metal (code-signed) UEFI bootloaders\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal.efi.dsk\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-snp.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-snp-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-snp.efi.dsk\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-snponly.efi\"\n# x86_64 BIOS/Legacy bootloaders\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-kpxe\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.kpxe\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-undionly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-undionly.kpxe\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-kpxe\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal.kpxe\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-lkrn\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.lkrn\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-linux-bin\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-linux.bin\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-dsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.dsk\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-pdsk\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.pdsk\"\n# ARM64 bootloaders\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64-snp.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64-snponly.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-arm64\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-arm64.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-arm64-snp\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-arm64-snp.efi\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-metal-arm64-snponly\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-metal-arm64-snponly.efi\"\n# ISO and IMG images (for virtual/physical media creation)\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-iso\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.iso\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-img\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz.img\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-iso\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64.iso\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-arm64-img\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-arm64.img\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-multiarch-iso\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-multiarch.iso\"\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-multiarch-img\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-multiarch.img\"\n# SHA256 checksums\nUSE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"netboot-xyz-checksums\" \"netbootxyz/netboot.xyz\" \"singlefile\" \"latest\" \"/var/www/html\" \"netboot.xyz-sha256-checksums.txt\"\n\nmsg_info \"Configuring Webserver\"\nrm -f /etc/nginx/sites-enabled/default\ncat <<'EOF' >/etc/nginx/sites-available/netboot-xyz\nserver {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n\n    root /var/www/html;\n    server_name _;\n\n    location / {\n        autoindex on;\n        add_header Access-Control-Allow-Origin \"*\";\n        add_header Access-Control-Allow-Headers \"Content-Type\";\n    }\n\n    # The index.html from menus.tar.gz links bootloaders under /ipxe/ —\n    # serve them from the same root directory via alias\n    location /ipxe/ {\n        alias /var/www/html/;\n        autoindex on;\n        add_header Access-Control-Allow-Origin \"*\";\n    }\n}\nEOF\nln -sf /etc/nginx/sites-available/netboot-xyz /etc/nginx/sites-enabled/netboot-xyz\n$STD systemctl reload nginx\nmsg_ok \"Configured Webserver\"\n\nmsg_info \"Configuring TFTP Server\"\ncat <<EOF >/etc/default/tftpd-hpa\nTFTP_USERNAME=\"tftp\"\nTFTP_DIRECTORY=\"/var/www/html\"\nTFTP_ADDRESS=\"0.0.0.0:69\"\nTFTP_OPTIONS=\"--secure\"\nEOF\nsystemctl enable -q --now tftpd-hpa\nmsg_ok \"Configured TFTP Server\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/netbox-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://netboxlabs.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apache2 \\\n  redis-server \\\n  build-essential \\\n  libxml2-dev \\\n  libxslt1-dev \\\n  libffi-dev \\\n  libpq-dev \\\n  libssl-dev \\\n  zlib1g-dev\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"netbox\" PG_DB_USER=\"netbox\" setup_postgresql_db\n\nmsg_info \"Installing Python\"\n$STD apt install -y \\\n  python3 \\\n  python3-pip \\\n  python3-venv \\\n  python3-dev\nmsg_ok \"Installed Python\"\n\nfetch_and_deploy_gh_release \"netbox\" \"netbox-community/netbox\" \"tarball\"\n\nmsg_info \"Configuring NetBox (Patience)\"\ncd /opt/netbox\nmkdir -p /opt/netbox/netbox/media\n\n$STD adduser --system --group netbox\nchown -R netbox /opt/netbox/netbox\n\nmv /opt/netbox/netbox/netbox/configuration_example.py /opt/netbox/netbox/netbox/configuration.py\n\nSECRET_KEY=$(python3 /opt/netbox/netbox/generate_secret_key.py)\nESCAPED_SECRET_KEY=$(printf '%s\\n' \"$SECRET_KEY\" | sed 's/[&/\\]/\\\\&/g')\n\nsed -i -e 's/ALLOWED_HOSTS = \\[\\]/ALLOWED_HOSTS = [\"*\"]/' \\\n  -e \"s|SECRET_KEY = ''|SECRET_KEY = '${ESCAPED_SECRET_KEY}'|\" \\\n  -e \"/DATABASES = {/,/}/s/'USER': '[^']*'/'USER': '$PG_DB_USER'/\" \\\n  -e \"/DATABASES = {/,/}/s/'PASSWORD': '[^']*'/'PASSWORD': '$PG_DB_PASS'/\" /opt/netbox/netbox/netbox/configuration.py\n\n$STD /opt/netbox/upgrade.sh\nln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping\n\nmv /opt/netbox/contrib/apache.conf /etc/apache2/sites-available/netbox.conf\n$STD openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/netbox.key -out /etc/ssl/certs/netbox.crt -subj \"/C=US/O=NetBox/OU=Certificate/CN=localhost\"\n$STD a2enmod ssl proxy proxy_http headers rewrite\n$STD a2ensite netbox\nsystemctl restart apache2\n\nmv /opt/netbox/contrib/gunicorn.py /opt/netbox/gunicorn.py\nmv /opt/netbox/contrib/*.service /etc/systemd/system/\nsystemctl daemon-reload\nsystemctl enable -q --now netbox netbox-rq\necho -e \"Netbox Secret: \\e[32m$SECRET_KEY\\e[0m\" >>~/netbox.creds\nmsg_ok \"Configured NetBox\"\n\nmsg_info \"Setting up Django Admin\"\nDJANGO_USER=Admin\nDJANGO_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\n\nsource /opt/netbox/venv/bin/activate\n$STD python3 /opt/netbox/netbox/manage.py shell <<EOF\nfrom django.contrib.auth import get_user_model\nUserModel = get_user_model()\nuser = UserModel.objects.create_user('$DJANGO_USER', password='$DJANGO_PASS')\nuser.is_superuser = True\nuser.is_staff = True\nuser.save()\nEOF\n{\n  echo \"\"\n  echo \"Netbox-Django-Credentials\"\n  echo -e \"Django User: \\e[32m$DJANGO_USER\\e[0m\"\n  echo -e \"Django Password: \\e[32m$DJANGO_PASS\\e[0m\"\n} >>~/netbox.creds\nmsg_ok \"Setup Django Admin\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nextcloudpi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nextcloudpi.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://nextcloudpi.com/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://raw.githubusercontent.com/nextcloud/nextcloudpi/master/install.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nmsg_info \"Installing NextCloudPi (Patience)\"\n$STD bash <(curl -fsSL https://raw.githubusercontent.com/nextcloud/nextcloudpi/master/install.sh)\nmsg_ok \"Installed NextCloudPi\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nextexplorer-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/nxzai/nextExplorer\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ripgrep \\\n  imagemagick \\\n  ffmpeg \\\n  libva-drm2 \\\n  libva2 \\\n  mesa-va-drivers \\\n  vainfo\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\n\nfetch_and_deploy_gh_release \"nextExplorer\" \"nxzai/nextExplorer\" \"tarball\" \"latest\" \"/opt/nextExplorer\"\n\nmsg_info \"Building nextExplorer\"\nAPP_DIR=\"/opt/nextExplorer/app\"\nLOCAL_IP=\"$(hostname -I | awk '{print $1}')\"\nmkdir -p \"$APP_DIR\"\nmkdir -p /etc/nextExplorer\ncd /opt/nextExplorer\nexport NODE_ENV=production\n$STD npm ci --omit=dev --workspace backend\nmv node_modules \"$APP_DIR\"\nmv backend/{src,package.json} \"$APP_DIR\"\nunset NODE_ENV\n\nexport NODE_ENV=development\nexport NODE_OPTIONS=\"--max-old-space-size=2048\"\n$STD npm ci --workspace frontend\n$STD npm run -w frontend build -- --sourcemap false\nunset NODE_ENV\nmv frontend/dist/ \"$APP_DIR\"/src/public\nmsg_ok \"Built nextExplorer\"\n\nmsg_info \"Configuring nextExplorer\"\nSECRET=$(openssl rand -hex 32)\ncat <<EOF >/etc/nextExplorer/.env\nNODE_ENV=production\nPORT=3000\n\nVOLUME_ROOT=/mnt\nCONFIG_DIR=/etc/nextExplorer\nCACHE_DIR=/etc/nextExplorer/cache\n# USER_ROOT=\n\nPUBLIC_URL=${LOCAL_IP}:3000\n# TRUST_PROXY=\n# CORS_ORIGINS=\n\nTERMINAL_ENABLED=false\n\nLOG_LEVEL=info\nDEBUG=false\nENABLE_HTTP_LOGGING=false\n\nAUTH_ENABLED=true\nAUTH_MODE=both\nSESSION_SECRET=\"${SECRET}\"\n# AUTH_MAX_FAILED=\n# AUTH_LOCK_MINUTES=\n# AUTH_USER_EMAIL=\n# AUTH_USER_PASSWORD=\n\n# OIDC_ENABLED=\n# OIDC_ISSUER=\n# OIDC_AUTHORIZATION_URL=\n# OIDC_TOKEN_URL=\n# OIDC_USERINFO_URL=\n# OIDC_CLIENT_ID=\n# OIDC_CLIENT_SECRET=\n# OIDC_CALLBACK_URL=\n# OIDC_LOGOUT_URL=\n# OIDC_SCOPES=\n# OIDC_AUTO_CREATE_USERS=true\n\n# SEARCH_DEEP=\n# SEARCH_RIPGREP=\n# SEARCH_MAX_FILESIZE=\n\n# ONLYOFFICE_URL=\n# ONLYOFFICE_SECRET=\n# ONLYOFFICE_LANG=\n# ONLYOFFICE_FORCE_SAVE=\n# ONLYOFFICE_FILE_EXTENSIONS=\n\n# COLLABORA_URL=\n# COLLABORA_DISCOVERY_URL=\n# COLLABORA_SECRET=\n# COLLABORA_LANG=\n# COLLABORA_FILE_EXTENSIONS=\n\nSHOW_VOLUME_USAGE=true\n# USER_DIR_ENABLED=\n# SKIP_HOME=\n\n# EDITOR_EXTENSIONS=\n\n# FFMPEG_PATH=\n# FFPROBE_PATH=\n\n## Hardware acceleration\n# FFMPEG_HWACCEL=vaapi\n# FFMPEG_HWACCEL_DEVICE=/dev/dri/renderD128\n# FFMPEG_HWACCEL_OUTPUT_FORMAT=nv12\n\nFAVORITES_DEFAULT_ICON=outline.StarIcon\n\nSHARES_ENABLED=true\n# SHARES_TOKEN_LENGTH=10\n# SHARES_MAX_PER_USER=100\n# SHARES_DEFAULT_EXPIRY_DAYS=30\n# SHARES_GUEST_SESSION_HOURS=24\n# SHARES_ALLOW_PASSWORD=true\n# SHARES_ALLOW_ANONYMOUS=true\nEOF\nchmod 600 /etc/nextExplorer/.env\n$STD useradd -U -s /usr/sbin/nologin -m -d /home/explorer explorer\nchown -R explorer:explorer \"$APP_DIR\" /etc/nextExplorer\nsed -i \"\\|version|s|$(jq -cr '.version' ${APP_DIR}/package.json)|$(cat ~/.nextexplorer)|\" \"$APP_DIR\"/package.json\nmsg_ok \"Configured nextExplorer\"\n\nmsg_info \"Creating nextExplorer Service\"\ncat <<EOF >/etc/systemd/system/nextexplorer.service\n[Unit]\nDescription=nextExplorer Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=explorer\nGroup=explorer\nWorkingDirectory=/opt/nextExplorer/app\nEnvironmentFile=/etc/nextExplorer/.env\nExecStart=/usr/bin/node ./src/server.js\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now nextexplorer\nmsg_ok \"Created nextExplorer Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nextpvr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nextpvr.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  mediainfo \\\n  libmediainfo-dev \\\n  libc6 \\\n  libgdiplus \\\n  acl \\\n  dvb-tools \\\n  libdvbv5-0 \\\n  dtv-scan-tables \\\n  libc6-dev \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup NextPVR (Patience)\"\ncd /opt\ncurl -fsSL \"https://nextpvr.com/nextpvr-helper.deb\" -o \"/opt/nextpvr-helper.deb\"\n$STD dpkg -i nextpvr-helper.deb\nrm -rf /opt/nextpvr-helper.deb\nmsg_ok \"Installed NextPVR\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nginx-ui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nginxui.com\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  logrotate\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"nginx-ui\" \"0xJacky/nginx-ui\" \"prebuild\" \"latest\" \"/opt/nginx-ui\" \"nginx-ui-linux-arm64.tar.gz\"\n\nmsg_info \"Installing Nginx UI\"\ncp /opt/nginx-ui/nginx-ui /usr/local/bin/nginx-ui\nchmod +x /usr/local/bin/nginx-ui\nrm -rf /opt/nginx-ui\nmsg_ok \"Installed Nginx UI\"\n\nmsg_info \"Configuring Nginx UI\"\nmkdir -p /usr/local/etc/nginx-ui\ncat <<EOF >/usr/local/etc/nginx-ui/app.ini\n[app]\nPageSize = 10\n\n[server]\nHost = 0.0.0.0\nPort = 9000\nRunMode = release\n\n[cert]\nHTTPChallengePort = 9180\n\n[terminal]\nStartCmd = login\nEOF\nmsg_ok \"Configured Nginx UI\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/nginx-ui.service\n[Unit]\nDescription=Another WebUI for Nginx\nDocumentation=https://nginxui.com\nAfter=network.target nginx.service\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/nginx-ui --config /usr/local/etc/nginx-ui/app.ini\nRuntimeDirectory=nginx-ui\nWorkingDirectory=/var/run/nginx-ui\nRestart=on-failure\nTimeoutStopSec=5\nKillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl daemon-reload\nmsg_ok \"Created Service\"\n\nmsg_info \"Starting Service\"\nsystemctl enable -q --now nginx-ui\nrm -rf /etc/nginx/sites-enabled/default\nmsg_ok \"Started Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nginxproxymanager-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nginxproxymanager.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apache2-utils \\\n  logrotate \\\n  build-essential \\\n  git\nmsg_ok \"Installed Dependencies\"\nmsg_info \"Installing Python Dependencies\"\n$STD apt install -y\n  python3 \\\n  python3-dev \\\n  python3-pip \\\n  python3-venv \\\n  python3-cffi \\\n  python3-certbot \\\n  python3-certbot-dns-cloudflare \\\n  golang\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\n$STD pip3 install certbot-dns-multi==4.15.0\n$STD python3 -m venv /opt/certbot/\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Setting up Certbot\"\n$STD python3 -m venv /opt/certbot\n$STD /opt/certbot/bin/pip install --upgrade pip setuptools wheel\n$STD /opt/certbot/bin/pip install certbot certbot-dns-cloudflare\nln -sf /opt/certbot/bin/certbot /usr/local/bin/certbot\nmsg_ok \"Set up Certbot\"\n\nfetch_and_deploy_gh_release \"openresty\" \"openresty/openresty\" \"prebuild\" \"latest\" \"/opt/openresty\" \"openresty-*.tar.gz\"\n\nmsg_info \"Building OpenResty\"\ncd /opt/openresty\n$STD ./configure \\\n  --with-http_v2_module \\\n  --with-http_realip_module \\\n  --with-http_stub_status_module \\\n  --with-http_ssl_module \\\n  --with-http_sub_module \\\n  --with-http_auth_request_module \\\n  --with-pcre-jit \\\n  --with-stream \\\n  --with-stream_ssl_module\n$STD make -j\"$(nproc)\"\n$STD make install\nrm -rf /opt/openresty\n\ncat <<'EOF' >/lib/systemd/system/openresty.service\n[Unit]\nDescription=The OpenResty Application Platform\nAfter=syslog.target network-online.target remote-fs.target nss-lookup.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStartPre=-/bin/mkdir -p /tmp/nginx/body /run/nginx\nExecStartPre=/usr/local/openresty/nginx/sbin/nginx -t\nExecStart=/usr/local/openresty/nginx/sbin/nginx -g 'daemon off;'\n\n[Install]\nWantedBy=multi-user.target\nEOF\nmsg_ok \"Built OpenResty\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\nRELEASE=$(get_latest_github_release \"NginxProxyManager/nginx-proxy-manager\")\nfetch_and_deploy_gh_release \"nginxproxymanager\" \"NginxProxyManager/nginx-proxy-manager\" \"tarball\" \"v${RELEASE}\"\n\nmsg_info \"Setting up Environment\"\nln -sf /usr/bin/python3 /usr/bin/python\nln -sf /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx\nln -sf /usr/local/openresty/nginx/ /etc/nginx\nsed -i \"0,/\\\"version\\\": \\\"[^\\\"]*\\\"/s|\\\"version\\\": \\\"[^\\\"]*\\\"|\\\"version\\\": \\\"$RELEASE\\\"|\" /opt/nginxproxymanager/backend/package.json\nsed -i \"0,/\\\"version\\\": \\\"[^\\\"]*\\\"/s|\\\"version\\\": \\\"[^\\\"]*\\\"|\\\"version\\\": \\\"$RELEASE\\\"|\" /opt/nginxproxymanager/frontend/package.json\nsed -i 's+^daemon+#daemon+g' /opt/nginxproxymanager/docker/rootfs/etc/nginx/nginx.conf\nNGINX_CONFS=$(find /opt/nginxproxymanager -type f -name \"*.conf\")\nfor NGINX_CONF in $NGINX_CONFS; do\n  sed -i 's+include conf.d+include /etc/nginx/conf.d+g' \"$NGINX_CONF\"\ndone\n\nmkdir -p /var/www/html /etc/nginx/logs\ncp -r /opt/nginxproxymanager/docker/rootfs/var/www/html/* /var/www/html/\ncp -r /opt/nginxproxymanager/docker/rootfs/etc/nginx/* /etc/nginx/\ncp /opt/nginxproxymanager/docker/rootfs/etc/letsencrypt.ini /etc/letsencrypt.ini\ncp /opt/nginxproxymanager/docker/rootfs/etc/logrotate.d/nginx-proxy-manager /etc/logrotate.d/nginx-proxy-manager\nln -sf /etc/nginx/nginx.conf /etc/nginx/conf/nginx.conf\nrm -f /etc/nginx/conf.d/dev.conf\n\nmkdir -p /tmp/nginx/body \\\n  /run/nginx \\\n  /data/nginx \\\n  /data/custom_ssl \\\n  /data/logs \\\n  /data/access \\\n  /data/nginx/default_host \\\n  /data/nginx/default_www \\\n  /data/nginx/proxy_host \\\n  /data/nginx/redirection_host \\\n  /data/nginx/stream \\\n  /data/nginx/dead_host \\\n  /data/nginx/temp \\\n  /var/lib/nginx/cache/public \\\n  /var/lib/nginx/cache/private \\\n  /var/cache/nginx/proxy_temp\n\nchmod -R 777 /var/cache/nginx\nchown root /tmp/nginx\n\necho resolver \"$(awk 'BEGIN{ORS=\" \"} $1==\"nameserver\" {print ($2 ~ \":\")? \"[\"$2\"]\": $2}' /etc/resolv.conf);\" >/etc/nginx/conf.d/include/resolvers.conf\n\nif [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]; then\n  $STD openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj \"/O=Nginx Proxy Manager/OU=Dummy Certificate/CN=localhost\" -keyout /data/nginx/dummykey.pem -out /data/nginx/dummycert.pem\nfi\n\nmkdir -p /app/frontend/images\ncp -r /opt/nginxproxymanager/backend/* /app\nmsg_ok \"Set up Environment\"\n\nmsg_info \"Building Frontend\"\nexport NODE_OPTIONS=\"--max_old_space_size=2048 --openssl-legacy-provider\"\ncd /opt/nginxproxymanager/frontend\n# Replace node-sass with sass in package.json before installation\nsed -E -i 's/\"node-sass\" *: *\"([^\"]*)\"/\"sass\": \"\\1\"/g' package.json\n$STD yarn install --network-timeout 600000\n$STD yarn locale-compile\n$STD yarn build\ncp -r /opt/nginxproxymanager/frontend/dist/* /app/frontend\ncp -r /opt/nginxproxymanager/frontend/public/images/* /app/frontend/images\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Initializing Backend\"\nrm -rf /app/config/default.json\nif [ ! -f /app/config/production.json ]; then\n  cat <<'EOF' >/app/config/production.json\n{\n  \"database\": {\n    \"engine\": \"knex-native\",\n    \"knex\": {\n      \"client\": \"better-sqlite3\",\n      \"connection\": {\n        \"filename\": \"/data/database.sqlite\"\n      },\n      \"useNullAsDefault\": true\n    }\n  }\n}\nEOF\nfi\ncd /app\n$STD yarn install --network-timeout 600000\nmsg_ok \"Initialized Backend\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/lib/systemd/system/npm.service\n[Unit]\nDescription=Nginx Proxy Manager\nAfter=network.target\nWants=openresty.service\n\n[Service]\nType=simple\nEnvironment=NODE_ENV=production\nExecStartPre=-mkdir -p /tmp/nginx/body /data/letsencrypt-acme-challenge\nExecStart=/usr/bin/node index.js --abort_on_uncaught_exception --max_old_space_size=250\nWorkingDirectory=/app\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nmsg_ok \"Created Service\"\n\nmsg_info \"Starting Services\"\nsed -i 's/user npm/user root/g; s/^pid/#pid/g' /usr/local/openresty/nginx/conf/nginx.conf\nsed -r -i 's/^([[:space:]]*)su npm npm/\\1#su npm npm/g;' /etc/logrotate.d/nginx-proxy-manager\nsystemctl enable -q --now openresty\nsystemctl enable -q --now npm\nmsg_ok \"Started Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nightscout-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: aendel\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/nightscout/cgm-remote-monitor\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  libssl-dev \\\n  openssl\nmsg_ok \"Installed Dependencies\"\n\nMONGO_VERSION=\"8.0\" setup_mongodb\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"nightscout\" \"nightscout/cgm-remote-monitor\" \"tarball\"\n\nmsg_info \"Installing Nightscout\"\n$STD npm install --prefix /opt/nightscout\nmsg_ok \"Installed Nightscout\"\n\nmsg_info \"Creating Service\"\nuseradd -s /bin/bash -m nightscout\nchown -R nightscout:nightscout /opt/nightscout\nAPI_SECRET=$(openssl rand -hex 16)\ncat <<EOF >/opt/nightscout/my.env\nMONGO_CONNECTION=mongodb://127.0.0.1:27017/nightscout\nBASE_URL=http://localhost:1337\nAPI_SECRET=${API_SECRET}\nDISPLAY_UNITS=mg/dl\nENABLE=careportal boluscalc food bwp cage sage iage iob cob basal ar2 rawbg pushover bgi pump openaps pvb linear custom\nINSECURE_USE_HTTP=true\nEOF\nchown nightscout:nightscout /opt/nightscout/my.env\ncat <<EOF >/etc/systemd/system/nightscout.service\n[Unit]\nDescription=Nightscout CGM Service\nAfter=network.target mongodb.service\n\n[Service]\nType=simple\nUser=nightscout\nWorkingDirectory=/opt/nightscout\nEnvironmentFile=/opt/nightscout/my.env\nExecStart=/usr/bin/npm start\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now nightscout\nmsg_ok \"Created Service\"\n\n{\n  echo \"Nightscout Credentials\"\n  echo \"API_SECRET: ${API_SECRET}\"\n} >> ~/nightscout.creds\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nocodb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.nocodb.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"nocodb\" \"nocodb/nocodb\" \"singlefile\" \"0.301.1\" \"/opt/nocodb/\" \"Noco-linux-arm64\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/nocodb.service\n[Unit]\nDescription=nocodb\n\n[Service]\nType=simple\nRestart=always\nUser=root\nWorkingDirectory=/opt/nocodb\nExecStart=/opt/nocodb/./nocodb\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now nocodb\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/node-red-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nodered.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\n\nmsg_info \"Installing Node-Red\"\n$STD npm install -g --unsafe-perm node-red\necho \"journalctl -f -n 100 -u nodered -o cat\" >/usr/bin/node-red-log\nchmod +x /usr/bin/node-red-log\necho \"systemctl stop nodered\" >/usr/bin/node-red-stop\nchmod +x /usr/bin/node-red-stop\necho \"systemctl start nodered\" >/usr/bin/node-red-start\nchmod +x /usr/bin/node-red-start\necho \"systemctl restart nodered\" >/usr/bin/node-red-restart\nchmod +x /usr/bin/node-red-restart\nmsg_ok \"Installed Node-Red\"\n\nmsg_info \"Creating Service\"\nservice_path=\"/etc/systemd/system/nodered.service\"\necho \"[Unit]\nDescription=Node-RED\nAfter=syslog.target network.target\n\n[Service]\nExecStart=/usr/bin/node-red --max-old-space-size=128 -v\nRestart=on-failure\nKillSignal=SIGINT\n\nSyslogIdentifier=node-red\nStandardOutput=syslog\n\nWorkingDirectory=/root/\nUser=root\nGroup=root\n\n[Install]\nWantedBy=multi-user.target\" >$service_path\nsystemctl enable -q --now nodered\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nodebb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2024 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/NodeBB/NodeBB\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  build-essential \\\n  redis-server \\\n  expect \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nsetup_mongodb\nNODE_VERSION=\"22\" setup_nodejs\n\nmsg_info \"Configuring MongoDB\"\nMONGO_ADMIN_USER=\"admin\"\nMONGO_ADMIN_PWD=\"$(openssl rand -base64 18 | cut -c1-13)\"\nNODEBB_USER=\"nodebb\"\nNODEBB_PWD=\"$(openssl rand -base64 18 | cut -c1-13)\"\nMONGO_CONNECTION_STRING=\"mongodb://${NODEBB_USER}:${NODEBB_PWD}@localhost:27017/nodebb\"\nNODEBB_SECRET=$(uuidgen)\n{\n  echo \"NodeBB-Credentials\"\n  echo \"Mongo Database User: $MONGO_ADMIN_USER\"\n  echo \"Mongo Database Password: $MONGO_ADMIN_PWD\"\n  echo \"NodeBB User: $NODEBB_USER\"\n  echo \"NodeBB Password: $NODEBB_PWD\"\n  echo \"NodeBB Secret: $NODEBB_SECRET\"\n} >>~/nodebb.creds\n\n$STD mongosh <<EOF\nuse admin\ndb.createUser({\n  user: \"$MONGO_ADMIN_USER\",\n  pwd: \"$MONGO_ADMIN_PWD\",\n  roles: [{ role: \"root\", db: \"admin\" }]\n})\n\nuse nodebb\ndb.createUser({\n  user: \"$NODEBB_USER\",\n  pwd: \"$NODEBB_PWD\",\n  roles: [\n    { role: \"readWrite\", db: \"nodebb\" },\n    { role: \"clusterMonitor\", db: \"admin\" }\n  ]\n})\nquit()\nEOF\nsed -i 's/bindIp: 127.0.0.1/bindIp: 0.0.0.0/' /etc/mongod.conf\nsed -i '/security:/d' /etc/mongod.conf\nbash -c 'echo -e \"\\nsecurity:\\n  authorization: enabled\" >> /etc/mongod.conf'\nsystemctl restart mongod\nmsg_ok \"MongoDB configured\"\n\nfetch_and_deploy_gh_release \"nodebb\" \"NodeBB/NodeBB\" \"tarball\"\n\nmsg_info \"Configuring NodeBB\"\ncd /opt/nodebb\ntouch pidfile\nexpect <<EOF >/dev/null 2>&1\nlog_file /dev/null\nset timeout -1\n\nspawn ./nodebb setup\nexpect \"URL used to access this NodeBB\" {\n    send \"http://localhost:4567\\r\"\n}\nexpect \"Please enter a NodeBB secret\" {\n    send \"$NODEBB_SECRET\\r\"\n}\nexpect \"Would you like to submit anonymous plugin usage to nbbpm? (yes)\" {\n    send \"no\\r\"\n}\nexpect \"Which database to use (mongo)\" {\n    send \"mongo\\r\"\n}\nexpect \"Format: mongodb://*\" {\n    send \"$MONGO_CONNECTION_STRING\\r\"\n}\nexpect \"Administrator username\" {\n    send \"community-scripts\\r\"\n}\nexpect \"Administrator email address\" {\n    send \"admin@community-scripts.org\\r\"\n}\nexpect \"Password\" {\n    send \"community-scripts\\r\"\n}\nexpect \"Confirm Password\" {\n    send \"community-scripts\\r\"\n}\nexpect eof\nEOF\nmsg_ok \"Configured NodeBB\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/nodebb.service\n[Unit]\nDescription=NodeBB\nDocumentation=https://docs.nodebb.org\nAfter=system.slice multi-user.target mongod.service\n\n[Service]\nType=forking\nUser=root\n\nWorkingDirectory=/opt/nodebb\nPIDFile=/opt/nodebb/pidfile\nExecStart=/usr/bin/node /opt/nodebb/loader.js\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now nodebb\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nodecast-tv-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/technomancer702/nodecast-tv\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"nodecast-tv\" \"technomancer702/nodecast-tv\" \"tarball\"\nNODE_VERSION=\"20\" setup_nodejs\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Modules\"\ncd /opt/nodecast-tv\n$STD npm install\nmsg_ok \"Installed Modules\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/nodecast-tv.service\n[Unit]\nDescription=nodecast-tv\nAfter=network.target\nWants=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/nodecast-tv\nExecStart=/bin/npm run dev\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now nodecast-tv\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/notifiarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://notifiarr.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up Notifiarr\"\n$STD groupadd notifiarr\n$STD useradd -g notifiarr notifiarr\nsetup_deb822_repo \\\n  \"notifiarr\" \\\n  \"https://packagecloud.io/golift/pkgs/gpgkey\" \\\n  \"https://packagecloud.io/golift/pkgs/ubuntu\" \\\n  \"focal\"\n$STD apt install -y notifiarr\nmsg_ok \"Setup Notifiarr\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/npmplus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ZoeyVid/NPMplus\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apk add \\\n  tzdata \\\n  gawk \\\n  yq\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Docker & Compose\"\n$STD apk add docker\n$STD rc-service docker start\n$STD rc-update add docker default\n\nget_latest_release() {\n  curl -fsSL https://api.github.com/repos/$1/releases/latest | grep '\"tag_name\":' | cut -d'\"' -f4\n}\nDOCKER_COMPOSE_LATEST_VERSION=$(get_latest_release \"docker/compose\")\nDOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}\nmkdir -p $DOCKER_CONFIG/cli-plugins\ncurl -fsSL https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_LATEST_VERSION/docker-compose-linux-aarch64 -o ~/.docker/cli-plugins/docker-compose\nchmod +x $DOCKER_CONFIG/cli-plugins/docker-compose\nmsg_ok \"Installed Docker & Compose\"\n\nmsg_info \"Fetching NPMplus\"\ncd /opt\ncurl -fsSL \"https://raw.githubusercontent.com/ZoeyVid/NPMplus/refs/heads/develop/compose.yaml\" -o compose.yaml\nmsg_ok \"Fetched NPMplus\"\n\nattempts=0\nwhile true; do\n  read -r -p \"${TAB3}Enter your TZ Identifier (e.g., Europe/Berlin): \" TZ_INPUT\n  if validate_tz \"$TZ_INPUT\"; then\n    break\n  fi\n  msg_error \"Invalid timezone! Please enter a valid TZ identifier.\"\n\n  attempts=$((attempts + 1))\n  if [[ \"$attempts\" -ge 3 ]]; then\n    msg_error \"Maximum attempts reached. Exiting.\"\n    exit 254\n  fi\ndone\n\nread -r -p \"${TAB3}Enter your ACME Email: \" ACME_EMAIL_INPUT\n\nyq -i \"\n  .services.npmplus.environment |=\n    (map(select(. != \\\"TZ=*\\\" and . != \\\"ACME_EMAIL=*\\\" and . != \\\"INITIAL_ADMIN_EMAIL=*\\\" and . != \\\"INITIAL_ADMIN_PASSWORD=*\\\")) +\n    [\\\"TZ=$TZ_INPUT\\\", \\\"ACME_EMAIL=$ACME_EMAIL_INPUT\\\", \\\"INITIAL_ADMIN_EMAIL=admin@local.com\\\", \\\"INITIAL_ADMIN_PASSWORD=community-scripts.org\\\"])\n\" /opt/compose.yaml\n\nmsg_info \"Building and Starting NPMplus (Patience)\"\n$STD docker compose up -d\nCONTAINER_ID=\"\"\nfor i in {1..60}; do\n  CONTAINER_ID=$(docker ps --filter \"name=npmplus\" --format \"{{.ID}}\")\n  if [[ -n \"$CONTAINER_ID\" ]]; then\n    STATUS=$(docker inspect --format '{{.State.Health.Status}}' \"$CONTAINER_ID\" 2>/dev/null || echo \"starting\")\n    if [[ \"$STATUS\" == \"healthy\" ]]; then\n      msg_ok \"NPMplus is running and healthy\"\n      break\n    elif [[ \"$STATUS\" == \"unhealthy\" ]]; then\n      msg_error \"NPMplus container is unhealthy! Check logs.\"\n      docker logs \"$CONTAINER_ID\"\n      exit 150\n    fi\n  fi\n  sleep 2\n  [[ $i -eq 60 ]] && msg_error \"NPMplus container did not become healthy within 120s.\" && docker logs \"$CONTAINER_ID\" && exit 150\ndone\nmsg_ok \"Builded and started NPMplus\"\n\nmotd_ssh\ncustomize\n"
  },
  {
    "path": "install/ntfy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ntfy.sh/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up ntfy\"\nsetup_deb822_repo \\\n  \"ntfy\" \\\n  \"https://archive.ntfy.sh/apt/keyring.gpg\" \\\n  \"https://archive.ntfy.sh/apt/\" \\\n  \"stable\"\n$STD apt install -y ntfy\nsystemctl enable -q --now ntfy\nmsg_ok \"Setup ntfy\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nxwitness-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nxvms.com/download/releases/linux\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  make \\\n  net-tools \\\n  ffmpeg \\\n  cifs-utils \\\n  libtalloc2 \\\n  libwbclient0 \\\n  keyutils\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup Nx Witness\"\ncd /tmp\nBASE_URL=\"https://updates.networkoptix.com/default/index.html\"\nRELEASE=$(curl -fsSL \"$BASE_URL\" | grep -oP '(?<=<b>)[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+(?=</b>)' | head -n 1)\nDETAIL_PAGE=$(curl -fsSL \"$BASE_URL#note_$RELEASE\")\nDOWNLOAD_URL=$(echo \"$DETAIL_PAGE\" | grep -oP \"https://updates.networkoptix.com/default/$RELEASE/arm/nxwitness-server-[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+-linux_arm64\\.deb\" | head -n 1)\ncurl -fsSL \"$DOWNLOAD_URL\" -o \"\"nxwitness-server-$RELEASE-linux_arm64.deb\"\"\nexport DEBIAN_FRONTEND=noninteractive\n$STD dpkg -i nxwitness-server-$RELEASE-linux_arm64.deb\nrm -f /tmp/nxwitness-server-$RELEASE-linux_arm64.deb\necho \"${RELEASE}\" >/opt/${APPLICATION}_version.txt\nmsg_ok \"Setup Nx Witness\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/nzbget-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: havardthom\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://nzbget.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_nonfree\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  par2 \\\n  unrar\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing NZBGet\"\nsetup_deb822_repo \\\n  \"nzbgetcom\" \\\n  \"https://nzbgetcom.github.io/nzbgetcom.asc\" \\\n  \"https://nzbgetcom.github.io/deb\" \\\n  \"stable\"\n$STD apt install -y nzbget\nsed -i \"s|SevenZipCmd=7zz|SevenZipCmd=7z|g\" /var/lib/nzbget/nzbget.conf\nsystemctl restart nzbget\nmsg_ok \"Installed NZBGet\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/oauth2-proxy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/oauth2-proxy/oauth2-proxy/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"oauth2-proxy\" \"oauth2-proxy/oauth2-proxy\" \"prebuild\" \"latest\" \"/opt/oauth2-proxy\" \"oauth2-proxy*linux-arm64.tar.gz\"\ntouch /opt/oauth2-proxy/config.toml\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/oauth2-proxy.service\n[Unit]\nDescription=OAuth2-Proxy Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/oauth2-proxy\nExecStart=/opt/oauth2-proxy/oauth2-proxy --config config.toml\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now oauth2-proxy\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/octoprint-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://octoprint.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git \\\n  libyaml-dev \\\n  build-essential\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setup Python3\"\n$STD apt install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip \\\n  python3-venv \\\n  python3-setuptools\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nmsg_info \"Creating user octoprint\"\nuseradd -m -s /bin/bash -p $(openssl passwd -1 octoprint) octoprint\nusermod -aG sudo,tty,dialout octoprint\nchown -R octoprint:octoprint /opt\necho \"octoprint ALL=NOPASSWD: $(command -v systemctl) restart octoprint, $(command -v reboot), $(command -v poweroff)\" >/etc/sudoers.d/octoprint\nmsg_ok \"Created user octoprint\"\n\nmsg_info \"Installing OctoPrint\"\n$STD sudo -u octoprint bash <<EOF\nmkdir /opt/octoprint\ncd /opt/octoprint\npython3 -m venv .\nsource bin/activate\npip install --upgrade pip\npip install wheel\npip install octoprint\nEOF\nmsg_ok \"Installed OctoPrint\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/octoprint.service\n[Unit]\nDescription=The snappy web interface for your 3D printer\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nEnvironment=\"LC_ALL=C.UTF-8\"\nEnvironment=\"LANG=C.UTF-8\"\nType=exec\nUser=octoprint\nExecStart=/opt/octoprint/bin/octoprint serve\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now octoprint\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/odoo-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT |  https://github.com/tteck/Proxmox/raw/main/LICENSE\n# Source: https://github.com/odoo/odoo\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y python3-lxml wkhtmltopdf\ncurl -fsSL \"http://archive.ubuntu.com/ubuntu/pool/universe/l/lxml-html-clean/python3-lxml-html-clean_0.1.1-1_all.deb\" -o /opt/python3-lxml-html-clean.deb\n$STD dpkg -i /opt/python3-lxml-html-clean.deb\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"18\" setup_postgresql\n\nRELEASE=$(curl -fsSL https://nightly.odoo.com/ | grep -oE 'href=\"[0-9]+\\.[0-9]+/nightly\"' | head -n1 | cut -d'\"' -f2 | cut -d/ -f1)\nLATEST_VERSION=$(curl -fsSL \"https://nightly.odoo.com/${RELEASE}/nightly/deb/\" |\n  grep -oP \"odoo_${RELEASE}\\.\\d+_all\\.deb\" |\n  sed -E \"s/odoo_(${RELEASE}\\.[0-9]+)_all\\.deb/\\1/\" |\n  sort -V |\n  tail -n1)\n\nmsg_info \"Setup Odoo $RELEASE\"\ncurl -fsSL https://nightly.odoo.com/${RELEASE}/nightly/deb/odoo_${RELEASE}.latest_all.deb -o /opt/odoo.deb\n$STD apt install -y /opt/odoo.deb\nmsg_ok \"Setup Odoo $RELEASE\"\n\nmsg_info \"Setup PostgreSQL Database\"\nDB_NAME=\"odoo\"\nDB_USER=\"odoo_usr\"\nDB_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME;\"\n$STD sudo -u postgres psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;\"\n$STD sudo -u postgres psql -c \"ALTER DATABASE $DB_NAME OWNER TO $DB_USER;\"\n$STD sudo -u postgres psql -c \"ALTER USER $DB_USER WITH SUPERUSER;\"\n{\n  echo \"Odoo-Credentials\"\n  echo -e \"Odoo Database User: $DB_USER\"\n  echo -e \"Odoo Database Password: $DB_PASS\"\n  echo -e \"Odoo Database Name: $DB_NAME\"\n} >>~/odoo.creds\nmsg_ok \"Setup PostgreSQL\"\n\nmsg_info \"Configuring Odoo\"\nsed -i \\\n  -e \"s|^;*db_host *=.*|db_host = localhost|\" \\\n  -e \"s|^;*db_port *=.*|db_port = 5432|\" \\\n  -e \"s|^;*db_user *=.*|db_user = $DB_USER|\" \\\n  -e \"s|^;*db_password *=.*|db_password = $DB_PASS|\" \\\n  /etc/odoo/odoo.conf\n$STD sudo -u odoo odoo -c /etc/odoo/odoo.conf -d odoo -i base --stop-after-init\nrm -f /opt/odoo.deb\nrm -f /opt/python3-lxml-html-clean.deb\necho \"${LATEST_VERSION}\" >/opt/${APPLICATION}_version.txt\nmsg_ok \"Configured Odoo\"\n\nmsg_info \"Restarting Odoo\"\nsystemctl restart odoo\nmsg_ok \"Restarted Odoo\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ollama-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: havardthom | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ollama.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  pkg-config \\\n  zstd\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Golang\"\nset +o pipefail\ntemp_file=$(mktemp)\ngolang_tarball=$(curl -fsSL https://go.dev/dl/ | grep -oP 'go[\\d\\.]+\\.linux-arm64\\.tar\\.gz' | head -n 1)\ncurl -fsSL \"https://golang.org/dl/${golang_tarball}\" -o \"$temp_file\"\ntar -C /usr/local -xzf \"$temp_file\"\nln -sf /usr/local/go/bin/go /usr/local/bin/go\nrm -f \"$temp_file\"\nset -o pipefail\nmsg_ok \"Installed Golang\"\n\nsetup_hwaccel\n\nmsg_info \"Installing Ollama (Patience)\"\nRELEASE=$(curl -fsSL https://api.github.com/repos/ollama/ollama/releases/latest | grep \"tag_name\" | awk -F '\"' '{print $4}')\nBINDIR=\"/usr/local/bin\"\nmkdir -p $OLLAMA_INSTALL_DIR\nOLLAMA_URL=\"https://github.com/ollama/ollama/releases/download/${RELEASE}/ollama-linux-arm64.tar.zst\"\nTMP_TAR=\"/tmp/ollama.tar.zst\"\necho -e \"\\n\"\nif curl -fL# -C - -o \"$TMP_TAR\" \"$OLLAMA_URL\"; then\n  if tar --zstd -xf \"$TMP_TAR\" -C \"$OLLAMA_INSTALL_DIR\"; then\n    ln -sf \"$OLLAMA_INSTALL_DIR/bin/ollama\" \"$BINDIR/ollama\"\n    echo \"${RELEASE}\" >/opt/Ollama_version.txt\n    msg_ok \"Installed Ollama ${RELEASE}\"\n  else\n    msg_error \"Extraction failed – archive corrupt or incomplete\"\n    exit 251\n  fi\nelse\n  msg_error \"Download failed – $OLLAMA_URL not reachable\"\n  exit 250\nfi\n\nmsg_info \"Creating ollama User and Group\"\nif ! id ollama >/dev/null 2>&1; then\n  useradd -r -s /usr/sbin/nologin -U -m -d /usr/share/ollama ollama\nfi\n$STD usermod -aG ollama $(id -u -n)\nmsg_ok \"Created ollama User and adjusted Groups\"\n\nsetup_hwaccel \"ollama\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ollama.service\n[Unit]\nDescription=Ollama Service\nAfter=network-online.target\n\n[Service]\nType=exec\nExecStart=/usr/local/bin/ollama serve\nEnvironment=HOME=$HOME\nEnvironment=OLLAMA_INTEL_GPU=true\nEnvironment=OLLAMA_HOST=0.0.0.0\nEnvironment=OLLAMA_NUM_GPU=999\nEnvironment=SYCL_CACHE_PERSISTENT=1\nEnvironment=ZES_ENABLE_SYSMAN=1\nRestart=always\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ollama\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/omada-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.tp-link.com/us/support/download/omada-software-controller/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y jsvc\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"21\" setup_java\n\nif lscpu | grep -q 'avx'; then\n  MONGO_VERSION=\"8.0\" setup_mongodb\nelse\n  msg_error \"No AVX detected (CPU-Flag)! We have discontinued support for this. You are welcome to try it manually with a Debian LXC, but due to the many issues with Omada, we currently only support AVX CPUs.\"\n  exit 10\nfi\n\nif ! dpkg -l | grep -q 'libssl1.1'; then\n  msg_info \"Installing libssl (if needed)\"\n  curl -fsSL \"https://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.1_1.1.1w-0+deb11u4_arm64.deb\" -o \"/tmp/libssl.deb\"\n  $STD dpkg -i /tmp/libssl.deb\n  rm -f /tmp/libssl.deb\n  msg_ok \"Installed libssl1.1\"\nfi\n\nmsg_info \"Installing Omada Controller\"\nOMADA_URL=$(curl -fsSL \"https://support.omadanetworks.com/en/download/software/omada-controller/\" |\n  grep -o 'https://static\\.tp-link\\.com/upload/software/[^\"]*linux_x64[^\"]*\\.deb' |\n  head -n1)\nOMADA_PKG=$(basename \"${OMADA_URL}\")\ncurl -fsSL \"${OMADA_URL}\" -o \"${OMADA_PKG}\"\n$STD dpkg -i \"${OMADA_PKG}\"\nrm -rf \"${OMADA_PKG}\"\nVERSION=$(sed -n 's/.*_v\\([0-9.]*\\)_.*_\\([0-9]\\{14\\}\\)\\.deb$/\\1-\\2/p' <<<\"${OMADA_PKG}\")\necho \"${VERSION}\" >$HOME/.omada\nmsg_ok \"Installed Omada Controller\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ombi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ombi.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"ombi\" \"Ombi-app/Ombi\" \"prebuild\" \"latest\" \"/opt/ombi\" \"linux-arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ombi.service\n[Unit]\nDescription=Ombi\nAfter=syslog.target network-online.target\n\n[Service]\nExecStart=/opt/ombi/./Ombi\nWorkingDirectory=/opt/ombi\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ombi\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/omv-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.openmediavault.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing OpenMediaVault (Patience)\"\ncurl -fsSL \"https://packages.openmediavault.org/public/archive.key\" | gpg --dearmor >\"/etc/apt/trusted.gpg.d/openmediavault-archive-keyring.gpg\"\ncat <<EOF >/etc/apt/sources.list.d/openmediavault.list\ndeb [signed-by=/etc/apt/trusted.gpg.d/openmediavault-archive-keyring.gpg] http://packages.openmediavault.org/public sandworm main\nEOF\n\nexport LANG=C.UTF-8\nexport DEBIAN_FRONTEND=noninteractive\nexport APT_LISTCHANGES_FRONTEND=none\n$STD apt update\napt -y --auto-remove --show-upgraded --allow-downgrades --allow-change-held-packages --no-install-recommends --option DPkg::Options::=\"--force-confdef\" --option DPkg::Options::=\"--force-confold\" install openmediavault-keyring openmediavault &>/dev/null\nomv-confdbadm populate &>/dev/null\nmsg_ok \"Installed OpenMediaVault\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/onedev-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://onedev.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  git-lfs\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"21\" setup_java\n\nmsg_info \"Installing OneDev\"\nRELEASE=$(curl -fsSL https://api.github.com/repos/theonedev/onedev/releases/latest | grep '\"tag_name\":' | cut -d'\"' -f4)\ncd /opt\ncurl -fsSL \"https://code.onedev.io/onedev/server/~site/onedev-latest.tar.gz\" -o onedev-latest.tar.gz\ntar -xzf onedev-latest.tar.gz\nmv /opt/onedev-latest /opt/onedev\n$STD /opt/onedev/bin/server.sh install\nsystemctl start onedev\nrm -rf /opt/onedev-latest.tar.gz\necho \"${RELEASE}\" >~/.onedev\nmsg_ok \"Installed OneDev\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/onlyoffice-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  rabbitmq-server \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\n\nmsg_info \"Setup Database\"\nDB_NAME=onlyoffice\nDB_USER=onlyoffice_user\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\n$STD sudo -u postgres psql -c \"CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME WITH OWNER $DB_USER ENCODING 'UTF8' TEMPLATE template0;\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET client_encoding TO 'utf8';\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET timezone TO 'UTC'\"\n{\n  echo \"ONLYOFFICE-Credentials\"\n  echo \"ONLYOFFICE Database User: $DB_USER\"\n  echo \"ONLYOFFICE Database Password: $DB_PASS\"\n  echo \"ONLYOFFICE Database Name: $DB_NAME\"\n} >>~/onlyoffice.creds\nmsg_ok \"Set up Database\"\n\nmsg_info \"Adding ONLYOFFICE GPG Key\"\nGPG_TMP=\"/tmp/onlyoffice.gpg\"\nKEY_URL=\"https://download.onlyoffice.com/GPG-KEY-ONLYOFFICE\"\nTMP_KEY_CONTENT=$(mktemp)\nif curl -fsSL \"$KEY_URL\" -o \"$TMP_KEY_CONTENT\" && grep -q \"BEGIN PGP PUBLIC KEY BLOCK\" \"$TMP_KEY_CONTENT\"; then\n  gpg --quiet --batch --yes --no-default-keyring --keyring \"gnupg-ring:$GPG_TMP\" --import \"$TMP_KEY_CONTENT\" >/dev/null 2>&1\n  chmod 644 \"$GPG_TMP\"\n  chown root:root \"$GPG_TMP\"\n  mv \"$GPG_TMP\" /usr/share/keyrings/onlyoffice.gpg\n  cat <<EOF >/etc/apt/sources.list.d/onlyoffice.sources\nTypes: deb\nURIs: https://download.onlyoffice.com/repo/debian\nSuites: squeeze\nComponents: main\nSigned-By: /usr/share/keyrings/onlyoffice.gpg\nEOF\n  $STD apt update\n  msg_ok \"GPG Key Added\"\nelse\n  msg_error \"Failed to download or verify GPG key from $KEY_URL\"\n  [[ -f \"$TMP_KEY_CONTENT\" ]] && rm -f \"$TMP_KEY_CONTENT\"\n  exit 250\nfi\nrm -f \"$TMP_KEY_CONTENT\"\n\nmsg_info \"Preconfiguring ONLYOFFICE Debconf Settings\"\nRMQ_USER=onlyoffice_rmq\nRMQ_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\nJWT_SECRET=$(openssl rand -hex 16)\n$STD rabbitmqctl add_user $RMQ_USER $RMQ_PASS\n$STD rabbitmqctl set_permissions -p / $RMQ_USER \".*\" \".*\" \".*\"\n$STD rabbitmqctl set_user_tags $RMQ_USER administrator\n\necho onlyoffice-documentserver onlyoffice/db-host string localhost | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/db-user string $DB_USER | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/db-pwd password $DB_PASS | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/db-name string $DB_NAME | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/rabbitmq-host string localhost | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/rabbitmq-user string $RMQ_USER | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/rabbitmq-pwd password $RMQ_PASS | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/jwt-enabled boolean true | debconf-set-selections\necho onlyoffice-documentserver onlyoffice/jwt-secret password $JWT_SECRET | debconf-set-selections\n\necho \"RabbitMQ User: $RMQ_USER\" >>~/onlyoffice.creds\necho \"RabbitMQ Password: $RMQ_PASS\" >>~/onlyoffice.creds\necho \"JWT Secret: $JWT_SECRET\" >>~/onlyoffice.creds\n{\n  echo \"\"\n  echo \"ONLYOFFICE RabbitMQ Credentials\"\n  echo \"User: $RMQ_USER\"\n  echo \"Password: $RMQ_PASS\"\n  echo \"Secret: $JWT_SECRET\"\n} >>~/onlyoffice.creds\nmsg_ok \"Debconf Preconfiguration Done\"\n\nmsg_info \"Installing ttf-mscorefonts-installer\"\necho ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections\n$STD apt install -y ttf-mscorefonts-installer\nmsg_ok \"Installed Microsoft Core Fonts\"\n\nmsg_info \"Installing ONLYOFFICE Docs\"\n$STD apt install -y onlyoffice-documentserver\nmsg_ok \"ONLYOFFICE Docs Installed\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/open-archiver-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://openarchiver.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependendencies\"\n$STD apt install -y valkey\nmsg_ok \"Installed dependendencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"openarchiver_db\" PG_DB_USER=\"openarchiver\" setup_postgresql_db\n\nsetup_meilisearch\nfetch_and_deploy_gh_release \"openarchiver\" \"LogicLabs-OU/OpenArchiver\" \"tarball\"\nJWT_KEY=\"$(openssl rand -hex 32)\"\nSECRET_KEY=\"$(openssl rand -hex 32)\"\n\nmsg_info \"Setting up Open Archiver\"\nmkdir -p /opt/openarchiver-data\ncd /opt/openarchiver\ncp .env.example .env\nsed -i \"s|^NODE_ENV=.*|NODE_ENV=production|g\" /opt/openarchiver/.env\nsed -i \"s|^POSTGRES_DB=.*|POSTGRES_DB=$PG_DB_NAME|g\" /opt/openarchiver/.env\nsed -i \"s|^POSTGRES_USER=.*|POSTGRES_USER=$PG_DB_USER|g\" /opt/openarchiver/.env\nsed -i \"s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$PG_DB_PASS|g\" /opt/openarchiver/.env\nsed -i \"s|^DATABASE_URL=.*|DATABASE_URL=\\\"postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\\\"|g\" /opt/openarchiver/.env\nsed -i \"s|^MEILI_HOST=.*|MEILI_HOST=http://localhost:7700|g\" /opt/openarchiver/.env\nsed -i \"s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$MEILISEARCH_MASTER_KEY|g\" /opt/openarchiver/.env\nsed -i \"s|^REDIS_HOST=.*|REDIS_HOST=localhost|g\" /opt/openarchiver/.env\nsed -i \"s|^REDIS_USER=.*|REDIS_USER=|g\" /opt/openarchiver/.env\nsed -i \"s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=|g\" /opt/openarchiver/.env\nsed -i \"s|^STORAGE_LOCAL_ROOT_PATH=.*|STORAGE_LOCAL_ROOT_PATH=/opt/openarchiver-data|g\" /opt/openarchiver/.env\nsed -i \"s|^JWT_SECRET=.*|JWT_SECRET=$JWT_KEY|g\" /opt/openarchiver/.env\nsed -i \"s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$SECRET_KEY|g\" /opt/openarchiver/.env\nsed -i \"s|^TIKA_URL=.*|TIKA_URL=|g\" /opt/openarchiver/.env\nsed -i \"s|^ORIGIN=.*|ORIGIN=http://$LOCAL_IP:3000|g\" /opt/openarchiver/.env\n$STD pnpm install --shamefully-hoist --frozen-lockfile --prod=false\n$STD pnpm run build:oss\n$STD pnpm db:migrate\nmsg_ok \"Setup Open Archiver\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/openarchiver.service\n[Unit]\nDescription=Open Archiver Service\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=root\nEnvironmentFile=/opt/openarchiver/.env\nWorkingDirectory=/opt/openarchiver\nExecStart=/usr/bin/pnpm docker-start:oss\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now openarchiver\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/opencloud-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://opencloud.eu\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nMAX_ATTEMPTS=3\nservers=(\"opencloud\" \"collabora\" \"wopi\")\nattempt=0\nfor server in \"${servers[@]}\"; do\n  until ((attempt >= MAX_ATTEMPTS)); do\n    attempt=$((attempt + 1))\n    read -rp \"${TAB3}Enter the FQDN of your ${server^} server (ATTEMPT $attempt/$MAX_ATTEMPTS) (eg $server.domain.tld): \" fqdn\n    if [[ -z \"$fqdn\" ]]; then\n      msg_warn \"Domain cannot be empty!\"\n    elif [[ \"$fqdn\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n      msg_warn \"IP address not allowed! Please use a FQDN\"\n    elif [[ \"$fqdn\" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\\.[a-zA-Z]{2,}$ ]]; then\n      export ${server^^}_FQDN=\"$fqdn\"\n      attempt=0\n      break\n    else\n      msg_warn \"Invalid domain format!\"\n    fi\n  done\n  if ((attempt >= MAX_ATTEMPTS)); then\n    msg_error \"No more attempts - aborting script!\"\n    exit 254\n  fi\ndone\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y inotify-tools\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Installing Collabora Online\"\ncurl -fsSL https://collaboraoffice.com/downloads/gpg/collaboraonline-release-keyring.gpg -o /etc/apt/keyrings/collaboraonline-release-keyring.gpg\ncat <<EOF >/etc/apt/sources.list.d/colloboraonline.sources\nTypes: deb\nURIs: https://www.collaboraoffice.com/repos/CollaboraOnline/CODE-deb\nSuites: ./\nSigned-By: /etc/apt/keyrings/collaboraonline-release-keyring.gpg\nEOF\n$STD apt-get update\n$STD apt-get install -y coolwsd code-brand\nsystemctl stop coolwsd\nmkdir -p /etc/systemd/system/coolwsd.service.d\ncat <<EOF >/etc/systemd/system/coolwsd.service.d/override.conf\n[Unit]\nBefore=opencloud-wopi.service\nEOF\nsystemctl daemon-reload\nCOOLPASS=\"$(openssl rand -base64 36)\"\n$STD sudo -u cool coolconfig set-admin-password --user=admin --password=\"$COOLPASS\"\necho \"$COOLPASS\" >~/.coolpass\nmsg_ok \"Installed Collabora Online\"\n\nfetch_and_deploy_gh_release \"opencloud\" \"opencloud-eu/opencloud\" \"singlefile\" \"v5.1.0\" \"/usr/bin\" \"opencloud-*-linux-arm64\"\n\nmsg_info \"Configuring OpenCloud\"\nDATA_DIR=\"/var/lib/opencloud\"\nCONFIG_DIR=\"/etc/opencloud\"\nENV_FILE=\"${CONFIG_DIR}/opencloud.env\"\nmkdir -p \"$DATA_DIR\" \"$CONFIG_DIR\"/web/assets/{apps,themes}\n\ncurl -fsSL https://raw.githubusercontent.com/opencloud-eu/opencloud-compose/refs/heads/main/config/opencloud/csp.yaml -o \"$CONFIG_DIR\"/csp.yaml\ncurl -fsSL https://raw.githubusercontent.com/opencloud-eu/opencloud-compose/refs/heads/main/config/opencloud/proxy.yaml -o \"$CONFIG_DIR\"/proxy.yaml.bak\n\ncat <<EOF >\"$ENV_FILE\"\nOC_URL=https://${OPENCLOUD_FQDN}\nOC_INSECURE=false\nIDM_CREATE_DEMO_USERS=false\nOC_LOG_LEVEL=warning\nOC_CONFIG_DIR=${CONFIG_DIR}\nOC_BASE_DATA_PATH=${DATA_DIR}\nSTORAGE_SYSTEM_OC_ROOT=${DATA_DIR}/storage/metadata\n\n## Web\nWEB_ASSET_CORE_PATH=${CONFIG_DIR}/web/assets\nWEB_ASSET_APPS_PATH=${CONFIG_DIR}/web/assets/apps\nWEB_ASSET_THEMES_PATH=${CONFIG_DIR}/web/assets/themes\n# WEB_UI_THEME_PATH=\n## Uncomment below to create & modify your web UI config\n# WEB_UI_CONFIG_FILE=${CONFIG_DIR}/web/config.json\n\n## Frontend\nFRONTEND_DISABLE_RADICALE=true\nFRONTEND_GROUPWARE_ENABLED=false\nGRAPH_INCLUDE_OCM_SHAREES=true\n\n## Proxy\nPROXY_TLS=false\nPROXY_CSP_CONFIG_FILE_LOCATION=${CONFIG_DIR}/csp.yaml\n\n## Collaboration - requires VALID TLS\nCOLLABORA_DOMAIN=${COLLABORA_FQDN}\nCOLLABORATION_APP_NAME=\"CollaboraOnline\"\nCOLLABORATION_APP_PRODUCT=\"Collabora\"\nCOLLABORATION_APP_ADDR=https://${COLLABORA_FQDN}\nCOLLABORATION_APP_INSECURE=false\nCOLLABORATION_HTTP_ADDR=0.0.0.0:9300\nCOLLABORATION_WOPI_SRC=https://${WOPI_FQDN}\nCOLLABORATION_JWT_SECRET=\n\n## Notifications - Email settings\n# NOTIFICATIONS_SMTP_HOST=\n# NOTIFICATIONS_SMTP_PORT=\n# NOTIFICATIONS_SMTP_SENDER=\n# NOTIFICATIONS_SMTP_USERNAME=\n# NOTIFICATIONS_SMTP_PASSWORD=\n# NOTIFICATIONS_SMTP_AUTHENTICATION=login\n## Encryption method. Possible values are 'starttls', 'ssltls' and 'none'\n# NOTIFICATIONS_SMTP_ENCRYPTION=starttls\n## Allow insecure connections. Defaults to false.\n# NOTIFICATIONS_SMTP_INSECURE=false\n\n## Start additional services at runtime\n## Examples: notifications, antivirus etc.\n## Do not uncomment unless configured above.\n# OC_ADD_RUN_SERVICES=\"notifications\"\n\n## OpenID - via web browser\n## uncomment for OpenID in general\n# OC_EXCLUDE_RUN_SERVICES=idp\n# OC_OIDC_ISSUER=<your auth URL>\n# IDP_DOMAIN=<your auth URL>\n# PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD=none\n# PROXY_OIDC_REWRITE_WELLKNOWN=true\n# PROXY_USER_OIDC_CLAIM=preferred_username\n# PROXY_USER_CS3_CLAIM=username\n## automatically create accounts\n# PROXY_AUTOPROVISION_ACCOUNTS=true\n# WEB_OIDC_SCOPE=openid profile email groups\n# GRAPH_ASSIGN_DEFAULT_USER_ROLE=false\n#\n## uncomment below if using PocketID\n# WEB_OIDC_CLIENT_ID=<generated in PocketID>\n# WEB_OIDC_METADATA_URL=<your auth URL>/.well-known/openid-configuration\n\n## Full Text Search - Apache Tika\n## Requires a separate install of Tika - see https://community-scripts.github.io/ProxmoxVE/scripts?id=apache-tika\n# SEARCH_EXTRACTOR_TYPE=tika\n# FRONTEND_FULL_TEXT_SEARCH_ENABLED=true\n# SEARCH_EXTRACTOR_TIKA_TIKA_URL=<your-tika-url>\n\n## Uncomment below to enable PosixFS Collaborative Mode\n## Increase inotify watch/instance limits on your PVE host:\n### sysctl -w fs.inotify.max_user_watches=1048576\n### sysctl -w fs.inotify.max_user_instances=1024\n# STORAGE_USERS_POSIX_ENABLE_COLLABORATION=true\n# STORAGE_USERS_POSIX_WATCH_TYPE=inotifywait\n# STORAGE_USERS_POSIX_WATCH_FS=true\n# STORAGE_USERS_POSIX_WATCH_PATH=<path-to-storage-or-bind-mount>\n## User files location - experimental - use at your own risk! - ZFS, NFS v4.2+ supported - CIFS/SMB not supported\n# STORAGE_USERS_POSIX_ROOT=<path-to-your-bind_mount>\nEOF\n\ncat <<EOF >/etc/systemd/system/opencloud.service\n[Unit]\nDescription=OpenCloud server\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=opencloud\nGroup=opencloud\nEnvironmentFile=${ENV_FILE}\nExecStart=/usr/bin/opencloud server\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/opencloud-wopi.service\n[Unit]\nDescription=OpenCloud WOPI Server\nWants=coolwsd.service\nAfter=opencloud.service coolwsd.service\n\n[Service]\nType=simple\nUser=opencloud\nGroup=opencloud\nEnvironmentFile=${ENV_FILE}\nExecStartPre=/bin/sleep 10\nExecStart=/usr/bin/opencloud collaboration server\nRestart=always\nKillSignal=SIGKILL\nKillMode=mixed\nTimeoutStopSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n$STD sudo -u cool coolconfig set ssl.enable false\n$STD sudo -u cool coolconfig set ssl.termination true\n$STD sudo -u cool coolconfig set ssl.ssl_verification true\nsed -i \"s|-Policy\\\">|&frame-ancestors https://${OPENCLOUD_FQDN}|\" /etc/coolwsd/coolwsd.xml\nuseradd -r -M -s /usr/sbin/nologin opencloud\nchown -R opencloud:opencloud \"$CONFIG_DIR\" \"$DATA_DIR\"\nsudo -u opencloud opencloud init --config-path \"$CONFIG_DIR\" --insecure no\nOPENCLOUD_SECRET=\"$(sed -n '/jwt/p' \"$CONFIG_DIR\"/opencloud.yaml | awk '{print $2}')\"\nsed -i \"s/JWT_SECRET=/&${OPENCLOUD_SECRET//&/\\\\&}/\" \"$ENV_FILE\"\nmsg_ok \"Configured OpenCloud\"\n\nmsg_info \"Starting services\"\nsystemctl enable -q --now coolwsd opencloud\nsleep 5\nsystemctl enable -q --now opencloud-wopi\nmsg_ok \"Started services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/opengist-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Jonathan (jd-apprentice)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://opengist.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"opengist\" \"thomiceli/opengist\" \"prebuild\" \"latest\" \"/opt/opengist\" \"opengist*linux-arm64.tar.gz\"\nmkdir -p /opt/opengist-data\nsed -i 's|opengist-home:.*|opengist-home: /opt/opengist-data|' /opt/opengist/config.yml\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/opengist.service\n[Unit]\nDescription=Opengist server to manage your Gists\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/opengist\nExecStart=/opt/opengist/opengist --config /opt/opengist/config.yml\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now opengist\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openhab-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.openhab.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"21\" setup_java\n\nmsg_info \"Installing openHAB\"\nsetup_deb822_repo \\\n  \"openhab\" \\\n  \"https://openhab.jfrog.io/artifactory/api/gpg/key/public\" \\\n  \"https://openhab.jfrog.io/artifactory/openhab-linuxpkg\" \\\n  \"stable\" \\\n  \"main\"\n$STD apt install -y openhab\nmsg_ok \"Installed openHAB\"\n\nmsg_info \"Initializing openHAB directories\"\nmkdir -p /var/lib/openhab/{tmp,etc,cache}\nmkdir -p /etc/openhab\nmkdir -p /var/log/openhab\nchown -R openhab:openhab /var/lib/openhab /etc/openhab /var/log/openhab\nmsg_ok \"Initialized openHAB directories\"\n\nmsg_info \"Starting Service\"\nsystemctl daemon-reload\nsystemctl enable -q --now openhab\nmsg_ok \"Started Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openobserve-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://openobserve.ai/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing OpenObserve\"\nmkdir -p /opt/openobserve/data\nRELEASE=$(get_latest_github_release \"openobserve/openobserve\")\ntar zxf <(curl -fsSL https://downloads.openobserve.ai/releases/openobserve/v$RELEASE/openobserve-v$RELEASE-linux-arm64.tar.gz) -C /opt/openobserve\nROOT_PASS=$(openssl rand -base64 18 | cut -c1-13)\n\ncat <<EOF >/opt/openobserve/data/.env\nZO_ROOT_USER_EMAIL = \"admin@example.com\"\nZO_ROOT_USER_PASSWORD = \"${ROOT_PASS}\"\nZO_DATA_DIR = \"/opt/openobserve/data\"\nZO_HTTP_PORT = \"5080\"\nEOF\necho \"${RELEASE}\" >>~/.openobserve\nmsg_ok \"Installed OpenObserve\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/openobserve.service\n[Unit]\nDescription=OpenObserve\nAfter=network.target\n\n[Service]\nType=simple\nEnvironmentFile=/opt/openobserve/data/.env\nExecStart=/opt/openobserve/openobserve\nExecStop=killall -QUIT openobserve\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now openobserve\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openproject-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/opf/openproject\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  build-essential \\\n  autoconf\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"openproject\" PG_DB_USER=\"openproject\" setup_postgresql_db\nAPI_KEY=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\necho \"OpenProject API Key: $API_KEY\" >>~/openproject.creds\nfetch_and_deploy_gh_release \"jemalloc\" \"jemalloc/jemalloc\" \"tarball\"\n\nmsg_info \"Compiling jemalloc (Patience)\"\ncd /opt/jemalloc\n$STD ./autogen.sh\n$STD make\n$STD make install\nmsg_ok \"Compiled jemalloc\"\n\nsetup_deb822_repo \\\n  \"openproject\" \\\n  \"https://packages.openproject.com/srv/deb/opf/openproject/gpg-key.gpg\" \\\n  \"https://packages.openproject.com/srv/deb/opf/openproject/stable/17/debian/\" \\\n  \"12\"\n\nmsg_info \"Installing OpenProject\"\n$STD apt install -y openproject\nmsg_ok \"Installed OpenProject\"\n\nmsg_info \"Configuring OpenProject\"\ncat <<EOF >/etc/openproject/installer.dat\nopenproject/edition default\n\npostgres/retry retry\npostgres/autoinstall reuse\npostgres/db_host 127.0.0.1\npostgres/db_port 5432\npostgres/db_username ${PG_DB_USER}\npostgres/db_password ${PG_DB_PASS}\npostgres/db_name ${PG_DB_NAME}\nserver/autoinstall install\nserver/variant apache2\n\nserver/hostname ${LOCAL_IP}\nserver/server_path_prefix /openproject\nserver/ssl no\nserver/variant apache2\nrepositories/api-key ${API_KEY}\nrepositories/svn-install skip\nrepositories/git-install install\nrepositories/git-path /var/db/openproject/git\nrepositories/git-http-backend /usr/lib/git-core/git-http-backend/\nmemcached/autoinstall install\nopenproject/admin_email admin@example.net\nopenproject/default_language en\nEOF\n$STD sudo openproject configure\nsystemctl stop openproject-web-1\nif ! grep -qF 'Environment=LD_PRELOAD=/usr/local/lib/libjemalloc.so.2' /etc/systemd/system/openproject-web-1.service; then\n  sed -i '/^\\[Service\\]/a Environment=LD_PRELOAD=/usr/local/lib/libjemalloc.so.2' /etc/systemd/system/openproject-web-1.service\nfi\nsystemctl start openproject-web-1\nmsg_ok \"Configured OpenProject\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openthread-br-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://openthread.io/guides/border-router\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  cmake \\\n  ninja-build \\\n  pkg-config \\\n  git \\\n  iproute2 \\\n  libreadline-dev \\\n  libncurses-dev \\\n  rsyslog \\\n  dbus \\\n  libdbus-1-dev \\\n  libjsoncpp-dev \\\n  iptables \\\n  ipset \\\n  bind9 \\\n  libnetfilter-queue1 \\\n  libnetfilter-queue-dev \\\n  libprotobuf-dev \\\n  protobuf-compiler \\\n  socat\nmsg_ok \"Installed Dependencies\"\n\nsetup_nodejs\n\nmsg_info \"Cloning OpenThread Border Router\"\n# git clone is needed to fetch submodules, fetch_and_deploy_gh_release doesn't support this. We use --depth 1 to minimize the amount of data cloned, but it still may take a while.\n$STD git clone --depth 1 https://github.com/openthread/ot-br-posix /opt/ot-br-posix\ncd /opt/ot-br-posix\n$STD git submodule update --depth 1 --init --recursive\nmsg_ok \"Cloned OpenThread Border Router\"\n\nmsg_info \"Building OpenThread Border Router (Patience)\"\nmkdir -p build && cd build\n$STD cmake -GNinja \\\n  -DBUILD_TESTING=OFF \\\n  -DCMAKE_INSTALL_PREFIX=/usr \\\n  -DOTBR_DBUS=ON \\\n  -DOTBR_MDNS=openthread \\\n  -DOTBR_REST=ON \\\n  -DOTBR_WEB=ON \\\n  -DOTBR_BORDER_ROUTING=ON \\\n  -DOTBR_BACKBONE_ROUTER=ON \\\n  -DOT_FIREWALL=ON \\\n  -DOT_POSIX_NAT64_CIDR=\"192.168.255.0/24\" \\\n  ..\n$STD ninja\n$STD ninja install\nmsg_ok \"Built OpenThread Border Router\"\n\nmsg_info \"Configuring Network\"\ncat <<EOF >/etc/sysctl.d/99-otbr.conf\nnet.ipv6.conf.all.forwarding=1\nnet.ipv4.ip_forward=1\nEOF\n$STD sysctl -p /etc/sysctl.d/99-otbr.conf\nmsg_ok \"Configured Network\"\n\nmsg_info \"Configuring Services\"\ncat <<'EOF' >/etc/default/otbr-agent\n# USB example:\n#   OTBR_AGENT_OPTS=\"-I wpan0 -B eth0 --vendor-name OpenThread --model-name BorderRouter --rest-listen-address 0.0.0.0 --rest-listen-port 8081 spinel+hdlc+uart:///dev/ttyACM0\"\n# TCP via socat (for network-attached RCP like SLZB-06/SLZB-MR3):\n\n#   OTBR_AGENT_OPTS=\"-I wpan0 -B eth0 --vendor-name OpenThread --model-name BorderRouter --rest-listen-address 0.0.0.0 --rest-listen-port 8081 spinel+hdlc+forkpty:///usr/bin/socat?forkpty-arg=-,rawer&forkpty-arg=tcp:IP:PORT trel://eth0\"\nOTBR_AGENT_OPTS=\"-I wpan0 -B eth0 --vendor-name OpenThread --model-name BorderRouter --rest-listen-address 0.0.0.0 --rest-listen-port 8081 spinel+hdlc+uart:///dev/ttyACM0\"\nEOF\ncat <<'EOF' >/etc/default/otbr-web\nOTBR_WEB_OPTS=\"-I wpan0 -a 0.0.0.0 -p 80\"\nEOF\nsystemctl enable -q dbus rsyslog otbr-agent otbr-web\nsystemctl enable -q bind9 2>/dev/null || systemctl enable -q named 2>/dev/null || true\nsystemctl start -q dbus rsyslog bind9\nmsg_ok \"Configured Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openwebui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://openwebui.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ffmpeg \\\n  zstd \\\n  build-essential \\\n  libmariadb-dev\nmsg_ok \"Installed Dependencies\"\n\nsetup_hwaccel\n\nPYTHON_VERSION=\"3.12\" setup_uv\n\nmsg_info \"Installing Open WebUI\"\n$STD uv tool install --python 3.12 --constraint <(echo \"numba>=0.60\") open-webui[all]\nmsg_ok \"Installed Open WebUI\"\n\nread -r -p \"${TAB3}Would you like to add Ollama? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Setting up Intel® Repositories\"\n  mkdir -p /usr/share/keyrings\n  curl -fsSL https://repositories.intel.com/gpu/intel-graphics.key | gpg --dearmor -o /usr/share/keyrings/intel-graphics.gpg 2>/dev/null || true\n  cat <<EOF >/etc/apt/sources.list.d/intel-gpu.sources\nTypes: deb\nURIs: https://repositories.intel.com/gpu/ubuntu\nSuites: jammy\nComponents: client\nArchitectures: arm64 i386\nSigned-By: /usr/share/keyrings/intel-graphics.gpg\nEOF\n  curl -fsSL https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor -o /usr/share/keyrings/oneapi-archive-keyring.gpg 2>/dev/null || true\n  cat <<EOF >/etc/apt/sources.list.d/oneAPI.sources\nTypes: deb\nURIs: https://apt.repos.intel.com/oneapi\nSuites: all\nComponents: main\nSigned-By: /usr/share/keyrings/oneapi-archive-keyring.gpg\nEOF\n  $STD apt update\n  msg_ok \"Set up Intel® Repositories\"\n\n  msg_info \"Installing Intel® Level Zero\"\n  # Debian 13+ has newer Level Zero packages in system repos that conflict with Intel repo packages\n  if is_debian && [[ \"$(get_os_version_major)\" -ge 13 ]]; then\n    # Use system packages on Debian 13+ (avoid conflicts with libze1)\n    $STD apt -y install libze1 libze-dev intel-level-zero-gpu 2>/dev/null || {\n      msg_warn \"Failed to install some Level Zero packages, continuing anyway\"\n    }\n  else\n    # Use Intel repository packages for older systems\n    $STD apt -y install intel-level-zero-gpu level-zero level-zero-dev 2>/dev/null || {\n      msg_warn \"Failed to install Intel Level Zero packages, continuing anyway\"\n    }\n  fi\n  msg_ok \"Installed Intel® Level Zero\"\n\n  msg_info \"Installing Intel® oneAPI Base Toolkit (Patience)\"\n  $STD apt install -y --no-install-recommends intel-basekit-2024.1 2>/dev/null || true\n  msg_ok \"Installed Intel® oneAPI Base Toolkit\"\n\n  msg_info \"Installing Ollama\"\n  OLLAMA_RELEASE=$(curl -fsSL https://api.github.com/repos/ollama/ollama/releases/latest | grep \"tag_name\" | awk -F '\"' '{print $4}')\n  curl -fsSLO -C - https://github.com/ollama/ollama/releases/download/${OLLAMA_RELEASE}/ollama-linux-arm64.tar.zst\n  tar --zstd -C /usr -xf ollama-linux-arm64.tar.zst\n  rm -rf ollama-linux-arm64.tar.zst\n  cat <<EOF >/etc/systemd/system/ollama.service\n[Unit]\nDescription=Ollama Service\nAfter=network-online.target\n\n[Service]\nType=exec\nExecStart=/usr/bin/ollama serve\nEnvironment=HOME=$HOME\nEnvironment=OLLAMA_HOST=0.0.0.0\nRestart=always\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now ollama\n  echo \"ENABLE_OLLAMA_API=true\" >/root/.env\n  msg_ok \"Installed Ollama\"\nfi\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/open-webui.service\n[Unit]\nDescription=Open WebUI Service\nAfter=network.target\n\n[Service]\nType=simple\nEnvironmentFile=-/root/.env\nEnvironment=DATA_DIR=/root/.open-webui\nExecStart=/root/.local/bin/open-webui serve\nWorkingDirectory=/root\nRestart=on-failure\nRestartSec=5\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now open-webui\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openziti-controller-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: emoscardini\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/openziti/ziti\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing openziti\"\nmkdir -p --mode=0755 /usr/share/keyrings\ncurl -fsSL https://get.openziti.io/tun/package-repos.gpg | gpg --dearmor -o /usr/share/keyrings/openziti.gpg\ncat <<EOF >/etc/apt/sources.list.d/openziti.sources\nTypes: deb\nURIs: https://packages.openziti.org/zitipax-openziti-deb-stable\nSuites: debian\nComponents: main\nSigned-By: /usr/share/keyrings/openziti.gpg\nEOF\n$STD apt update\n$STD apt install -y openziti-controller openziti-console\nmsg_ok \"Installed openziti\"\n\nread -r -p \"${TAB3}Would you like to go through the auto configuration now? <y/N>\" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  GEN_FQDN=\"controller.${LOCAL_IP}.sslip.io\"\n  read -r -p \"${TAB3}Please enter the controller FQDN [${GEN_FQDN}]: \" ZITI_CTRL_ADVERTISED_ADDRESS\n  ZITI_CTRL_ADVERTISED_ADDRESS=${ZITI_CTRL_ADVERTISED_ADDRESS:-$GEN_FQDN}\n  read -r -p \"${TAB3}Please enter the controller port [1280]: \" ZITI_CTRL_ADVERTISED_PORT\n  ZITI_CTRL_ADVERTISED_PORT=${ZITI_CTRL_ADVERTISED_PORT:-1280}\n  read -r -p \"${TAB3}Please enter the controller admin user [admin]: \" ZITI_USER\n  ZITI_USER=${ZITI_USER:-admin}\n  GEN_PWD=$(head -c128 /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^*_+~' | cut -c 1-12)\n  read -r -p \"${TAB3}Please enter the controller admin password [${GEN_PWD}]:\" ZITI_PWD\n  ZITI_PWD=${ZITI_PWD:-$GEN_PWD}\n  CONFIG_FILE=\"/opt/openziti/etc/controller/bootstrap.env\"\n  sed -i \"s|^ZITI_CTRL_ADVERTISED_ADDRESS=.*|ZITI_CTRL_ADVERTISED_ADDRESS='${ZITI_CTRL_ADVERTISED_ADDRESS}'|\" \"$CONFIG_FILE\"\n  sed -i \"s|^ZITI_CTRL_ADVERTISED_PORT=.*|ZITI_CTRL_ADVERTISED_PORT='${ZITI_CTRL_ADVERTISED_PORT}'|\" \"$CONFIG_FILE\"\n  sed -i \"s|^ZITI_USER=.*|ZITI_USER='${ZITI_USER}'|\" \"$CONFIG_FILE\"\n  sed -i \"s|^ZITI_PWD=.*|ZITI_PWD='${ZITI_PWD}'|\" \"$CONFIG_FILE\"\n  env VERBOSE=0 bash /opt/openziti/etc/controller/bootstrap.bash\n  msg_ok \"Configuration Completed\"\n  systemctl enable -q --now ziti-controller\nelse\n  systemctl enable -q ziti-controller\n  msg_error \"Configration not provided; Please run /opt/openziti/etc/controller/bootstrap.bash to configure the controller and restart the container\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/openziti-tunnel-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: emoscardini\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/openziti/ziti\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing openziti\"\nmkdir -p --mode=0755 /usr/share/keyrings\ncurl -sSLf https://get.openziti.io/tun/package-repos.gpg | gpg --dearmor -o /usr/share/keyrings/openziti.gpg\ncat <<EOF >/etc/apt/sources.list.d/openziti.sources\nTypes: deb\nURIs: https://packages.openziti.org/zitipax-openziti-deb-stable\nSuites: jammy\nComponents: main\nSigned-By: /usr/share/keyrings/openziti.gpg\nEOF\n$STD apt update\n$STD apt install -y ziti-edge-tunnel\nsed -i '0,/^ExecStart/ { /^ExecStart/ { n; s|^ExecStart.*|ExecStart=/opt/openziti/bin/ziti-edge-tunnel run-host --verbose=${ZITI_VERBOSE} --identity-dir=${ZITI_IDENTITY_DIR}| } }' /usr/lib/systemd/system/ziti-edge-tunnel.service\nsystemctl daemon-reload\nmsg_ok \"Installed openziti\"\n\nread -r -p \"${TAB3}Please paste an identity enrollment token(JTW)\" prompt\nif [[ ${prompt} ]]; then\n  msg_info \"Adding identity\"\n  echo \"${prompt}\" >/opt/openziti/etc/identities/identity.jwt\n  chown ziti:ziti /opt/openziti/etc/identities/identity.jwt\n  systemctl enable -q --now ziti-edge-tunnel\n  msg_ok \"Service Started\"\nelse\n  systemctl enable -q ziti-edge-tunnel\n  msg_error \"No identity provided; please place an identity file in /opt/openziti/etc/identities/ and restart the service\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ots-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Luzifer/ots\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  redis-server \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"ots\" \"Luzifer/ots\" \"prebuild\" \"latest\" \"/opt/ots\" \"ots_linux_arm64.tgz\"\ncreate_self_signed_cert\n\nmsg_info \"Setup OTS\"\ncat <<EOF >/opt/ots/.env\nLISTEN=127.0.0.1:3000\nREDIS_URL=redis://127.0.0.1:6379\nSECRET_EXPIRY=604800\nSTORAGE_TYPE=redis\nEOF\nmsg_ok \"Setup OTS\"\n\nmsg_info \"Setting up nginx\"\ncat <<EOF >/etc/nginx/sites-available/ots.conf\nserver {\n    listen 80;\n    listen [::]:80;\n    server_name ots;\n    return 301 https://\\$host\\$request_uri;\n}\nserver {\n  listen 443 ssl;\n  listen [::]:443 ssl;\n  server_name ots;\n\n  ssl_certificate /etc/ssl/ots/ots.crt;\n  ssl_certificate_key /etc/ssl/ots/ots.key;\n\n  location / {\n    add_header X-Robots-Tag noindex;\n\n    proxy_set_header Upgrade \\$http_upgrade;\n    proxy_set_header Connection \"Upgrade\";\n    proxy_set_header Host \\$host;\n    proxy_set_header X-Real-IP \\$remote_addr;\n    proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto \\$scheme;\n    client_max_body_size 64M;\n    proxy_pass http://127.0.0.1:3000/;\n  }\n}\nEOF\n\nln -s /etc/nginx/sites-available/ots.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Configured nginx\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/ots.service\n[Unit]\nDescription=One-Time-Secret Service\nAfter=network-online.target\nRequires=network-online.target\n\n[Service]\nEnvironmentFile=/opt/ots/.env\nExecStart=/opt/ots/ots\nRestart=Always\nRestartSecs=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ots\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/outline-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/outline/outline\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  mkcert \\\n  git \\\n  redis\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"outline\" PG_DB_USER=\"outline\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"outline\" \"outline/outline\" \"tarball\"\n\nmsg_info \"Configuring Outline (Patience)\"\nSECRET_KEY=\"$(openssl rand -hex 32)\"\ncd /opt/outline\ncp .env.sample .env\nexport NODE_ENV=development\nsed -i 's/NODE_ENV=production/NODE_ENV=development/g' /opt/outline/.env\nsed -i \"s/generate_a_new_key/${SECRET_KEY}/g\" /opt/outline/.env\nsed -i \"s/user:pass@postgres/${PG_DB_USER}:${PG_DB_PASS}@localhost/g\" /opt/outline/.env\nsed -i 's/redis:6379/localhost:6379/g' /opt/outline/.env\nsed -i \"5s#URL=#URL=http://${LOCAL_IP}#g\" /opt/outline/.env\nsed -i 's/FORCE_HTTPS=true/FORCE_HTTPS=false/g' /opt/outline/.env\nexport NODE_OPTIONS=\"--max-old-space-size=3584\"\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n$STD corepack enable\n$STD yarn install --immutable\nexport NODE_ENV=production\nsed -i 's/NODE_ENV=development/NODE_ENV=production/g' /opt/outline/.env\n$STD yarn build\nmsg_ok \"Configured Outline\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/outline.service\n[Unit]\nDescription=Outline Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/outline\nExecStart=/usr/bin/yarn start\nRestart=always\nEnvironmentFile=/opt/outline/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now outline\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/owncast-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://owncast.online/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"owncast\" \"owncast/owncast\" \"prebuild\" \"latest\" \"/opt/owncast\" \"owncast*linux-arm64.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/owncast.service\n[Unit]\nDescription=Owncast\nAfter=syslog.target network-online.target\n\n[Service]\nExecStart=/opt/owncast/./owncast\nWorkingDirectory=/opt/owncast\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now owncast\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ownfoil-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pajjski\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/a1ex4/ownfoil\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nsetup_uv\nfetch_and_deploy_gh_release \"ownfoil\" \"a1ex4/ownfoil\" \"tarball\"\n\nmsg_info \"Setting up Ownfoil\"\ncd /opt/ownfoil\n$STD uv venv .venv\n$STD source .venv/bin/activate\n$STD uv pip install -r requirements.txt\nmsg_ok \"Setup ownfoil\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ownfoil.service\n[Unit]\nDescription=ownfoil Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/ownfoil\nExecStart=/opt/ownfoil/.venv/bin/python /opt/ownfoil/app/app.py\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ownfoil\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pairdrop-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pairdrop.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"pairdrop\" \"schlagmichdoch/PairDrop\" \"tarball\"\n\nmsg_info \"Configuring PairDrop\"\ncd /opt/pairdrop\n$STD npm install\nmsg_ok \"Installed PairDrop\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/pairdrop.service\n[Unit]\nDescription=PairDrop Service\nAfter=network.target\n\n[Service]\nExecStart=npm start\nWorkingDirectory=/opt/pairdrop\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now pairdrop\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pangolin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pangolin.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  sqlite3 \\\n  iptables\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"pangolin\" \"fosrl/pangolin\" \"tarball\"\nfetch_and_deploy_gh_release \"gerbil\" \"fosrl/gerbil\" \"singlefile\" \"latest\" \"/usr/bin\" \"gerbil_linux_arm64\"\nfetch_and_deploy_gh_release \"traefik\" \"traefik/traefik\" \"prebuild\" \"latest\" \"/usr/bin\" \"traefik_v*_linux_arm64.tar.gz\"\n\nread -rp \"${TAB3}Enter your Pangolin URL (ex: https://pangolin.example.com): \" pango_url\nread -rp \"${TAB3}Enter your email address: \" pango_email\n\nmsg_info \"Setup Pangolin\"\nSECRET_KEY=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 32)\nBADGER_VERSION=$(get_latest_github_release \"fosrl/badger\" \"false\")\ncd /opt/pangolin\nmkdir -p /opt/pangolin/config/{traefik,db,letsencrypt,logs}\n$STD npm ci\n$STD npm run set:sqlite\n$STD npm run set:oss\nrm -rf server/private\n$STD npm run db:generate\n$STD npm run build\n$STD npm run build:cli\ncp -R .next/standalone ./\n\ncat <<EOF >/usr/local/bin/pangctl\n#!/bin/sh\ncd /opt/pangolin\n./dist/cli.mjs \"$@\"\nEOF\nchmod +x /usr/local/bin/pangctl ./dist/cli.mjs\ncp server/db/names.json ./dist/names.json\ncp server/db/ios_models.json ./dist/ios_models.json\ncp server/db/mac_models.json ./dist/mac_models.json\nmkdir -p /var/config\n\ncat <<EOF >/opt/pangolin/config/config.yml\napp:\n  dashboard_url: \"$pango_url\"\n\ndomains:\n  domain1:\n    base_domain: \"$pango_url\"\n    cert_resolver: \"letsencrypt\"\n\nserver:\n  secret: \"$SECRET_KEY\"\n\ngerbil:\n  base_endpoint: \"${pango_url#https://}\"\n\nflags:\n  require_email_verification: false\n  disable_signup_without_invite: false\n  disable_user_create_org: false\nEOF\n\ncat <<EOF >/opt/pangolin/config/traefik/traefik_config.yml\napi:\n  insecure: true\n  dashboard: true\n\nproviders:\n  http:\n    endpoint: \"http://$LOCAL_IP:3001/api/v1/traefik-config\"\n    pollInterval: \"5s\"\n  file:\n    filename: \"/opt/pangolin/config/traefik/dynamic_config.yml\"\n\nexperimental:\n  plugins:\n    badger:\n      moduleName: \"github.com/fosrl/badger\"\n      version: \"$BADGER_VERSION\"\n\nlog:\n  level: \"INFO\"\n  format: \"common\"\n\ncertificatesResolvers:\n  letsencrypt:\n    acme:\n      httpChallenge:\n        entryPoint: web\n      email: $pango_email\n      storage: \"/opt/pangolin/config/letsencrypt/acme.json\"\n      caServer: \"https://acme-v02.api.letsencrypt.org/directory\"\n\nentryPoints:\n  web:\n    address: \":80\"\n  websecure:\n    address: \":443\"\n    transport:\n      respondingTimeouts:\n        readTimeout: \"30m\"\n    http:\n      tls:\n        certResolver: \"letsencrypt\"\n\nserversTransport:\n  insecureSkipVerify: true\n\nping:\n    entryPoint: \"web\"\nEOF\n\ncat <<EOF >/opt/pangolin/config/traefik/dynamic_config.yml\nhttp:\n  middlewares:\n    redirect-to-https:\n      redirectScheme:\n        scheme: https\n\n  routers:\n    # HTTP to HTTPS redirect router\n    main-app-router-redirect:\n      rule: \"Host(\\`${pango_url#https://}\\`)\"\n      service: next-service\n      entryPoints:\n        - web\n      middlewares:\n        - redirect-to-https\n\n    # Next.js router (handles everything except API and WebSocket paths)\n    next-router:\n      rule: \"Host(\\`${pango_url#https://}\\`) && !PathPrefix(\\`/api/v1\\`)\"\n      service: next-service\n      entryPoints:\n        - websecure\n      tls:\n        certResolver: letsencrypt\n\n    # API router (handles /api/v1 paths)\n    api-router:\n      rule: \"Host(\\`${pango_url#https://}\\`) && PathPrefix(\\`/api/v1\\`)\"\n      service: api-service\n      entryPoints:\n        - websecure\n      tls:\n        certResolver: letsencrypt\n\n    # WebSocket router\n    ws-router:\n      rule: \"Host(\\`${pango_url#https://}\\`)\"\n      service: api-service\n      entryPoints:\n        - websecure\n      tls:\n        certResolver: letsencrypt\n\n  services:\n    next-service:\n      loadBalancer:\n        servers:\n          - url: \"http://$LOCAL_IP:3002\"\n\n    api-service:\n      loadBalancer:\n        servers:\n          - url: \"http://$LOCAL_IP:3000\"\nEOF\n$STD npm run db:push\n\n. /etc/os-release\nif [ \"$VERSION_CODENAME\" = \"trixie\" ]; then\n  echo \"net.ipv4.ip_forward=1\" >>/etc/sysctl.d/sysctl.conf\n  $STD sysctl -p /etc/sysctl.d/sysctl.conf\nelse\n  echo \"net.ipv4.ip_forward=1\" >>/etc/sysctl.conf\n  $STD sysctl -p /etc/sysctl.conf\nfi\nmsg_ok \"Setup Pangolin\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/pangolin.service\n[Unit]\nDescription=Pangolin Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nEnvironment=NODE_ENV=production\nEnvironment=ENVIRONMENT=prod\nWorkingDirectory=/opt/pangolin\nExecStartPre=/usr/bin/node dist/migrations.mjs\nExecStart=/usr/bin/node --enable-source-maps dist/server.mjs\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now pangolin\n\ncat <<EOF >/etc/systemd/system/gerbil.service\n[Unit]\nDescription=Gerbil Service\nAfter=network.target\nRequires=pangolin.service\n\n[Service]\nType=simple\nUser=root\nExecStart=/usr/bin/gerbil --reachableAt=http://$LOCAL_IP:3004 --generateAndSaveKeyTo=/var/config/key --remoteConfig=http://$LOCAL_IP:3001/api/v1/\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now gerbil\n\ncat <<'EOF' >/etc/systemd/system/traefik.service\n[Unit]\nDescription=Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=notify\nExecStart=/usr/bin/traefik --configFile=/opt/pangolin/config/traefik/traefik_config.yml\nRestart=on-failure\nExecReload=/bin/kill -USR1 \\$MAINPID\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now traefik\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/paperless-ai-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/clusterzx/paperless-ai\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Python3\"\n$STD apt install -y \\\n  python3-pip \\\n  python3-dev \\\n  python3-venv\nmkdir -p ~/.config/pip\ncat >~/.config/pip/pip.conf <<EOF\n[global]\nbreak-system-packages = true\nEOF\nmsg_ok \"Installed Python3\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"paperless-ai\" \"clusterzx/paperless-ai\" \"tarball\"\n\nmsg_info \"Setup Paperless-AI\"\ncd /opt/paperless-ai\n$STD python3 -m venv /opt/paperless-ai/venv\nsource /opt/paperless-ai/venv/bin/activate\n# TMPDIR to use container disk instead of tmpfs for large pip downloads (https://github.com/community-scripts/ProxmoxVE/issues/10338)\nexport TMPDIR=/opt/paperless-ai/tmp\nmkdir -p \"$TMPDIR\"\n$STD pip install --upgrade pip\n$STD pip install --no-cache-dir -r requirements.txt\nrm -rf \"$TMPDIR\"\nmkdir -p data/chromadb\n$STD npm ci --only=production\nmkdir -p /opt/paperless-ai/data\ncat <<EOF >/opt/paperless-ai/data/.env\nPAPERLESS_API_URL=\nPAPERLESS_API_TOKEN=\nPAPERLESS_USERNAME=\nAI_PROVIDER=openai\nOPENAI_API_KEY=\nOPENAI_MODEL=gpt-4o-mini\nOLLAMA_API_URL=\nOLLAMA_MODEL=\nSCAN_INTERVAL=*/10 * * * *\nSYSTEM_PROMPT=\"\"\nPROCESS_PREDEFINED_DOCUMENTS=no\nTAGS=\nADD_AI_PROCESSED_TAG=no\nAI_PROCESSED_TAG_NAME=ki-gen\nUSE_PROMPT_TAGS=no\nPROMPT_TAGS=\nUSE_EXISTING_DATA=no\nAPI_KEY=\nCUSTOM_API_KEY=\nCUSTOM_BASE_URL=\nCUSTOM_MODEL=\nRAG_SERVICE_URL=http://localhost:8000\nRAG_SERVICE_ENABLED=true\nEOF\nmsg_ok \"Setup Paperless-AI\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/paperless-ai.service\n[Unit]\nDescription=PaperlessAI Service\nAfter=network.target paperless-rag.service\nRequires=paperless-rag.service\n\n[Service]\nWorkingDirectory=/opt/paperless-ai\nEnvironment=\"NODE_ENV=production\"\nEnvironmentFile=/opt/paperless-ai/data/.env\nExecStart=/usr/bin/node server.js\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/paperless-rag.service\n[Unit]\nDescription=PaperlessAI-RAG Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/paperless-ai\nEnvironmentFile=/opt/paperless-ai/data/.env\nExecStart=/opt/paperless-ai/venv/bin/python3 main.py --host 0.0.0.0 --port 8000 --initialize\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now paperless-rag\nsleep 5\nsystemctl enable -q --now paperless-ai\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/paperless-gpt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/icereed/paperless-gpt\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  gcc \\\n  musl-dev \\\n  mupdf \\\n  libc6-dev \\\n  musl-tools\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nsetup_go\nfetch_and_deploy_gh_release \"paperless-gpt\" \"icereed/paperless-gpt\" \"tarball\"\n\nmsg_info \"Setup Paperless-GPT\"\ncd /opt/paperless-gpt/web-app\n$STD npm install\n$STD npm run build\ncd /opt/paperless-gpt\ngo mod download\nexport CC=musl-gcc\nCGO_ENABLED=1 go build -tags musl -o /dev/null github.com/mattn/go-sqlite3\nCGO_ENABLED=1 go build -tags musl -o paperless-gpt .\nmsg_ok \"Setup Paperless-GPT\"\n\nmkdir -p /opt/paperless-gpt-data\nread -rp \"${TAB3}Do you want to enter the Paperless local URL now? (y/n) \" input_url\nif [[ $input_url =~ ^[Yy]$ ]]; then\n  read -rp \"${TAB3}Enter your Paperless-NGX instance URL (e.g., http://192.168.1.100:8000): \" PAPERLESS_BASE_URL\nelse\n  PAPERLESS_BASE_URL=\"http://your_paperless_ngx_url\"\nfi\n\nread -rp \"${TAB3}Do you want to enter the Paperless API token now? (y/n) \" input_token\nif [[ $input_token =~ ^[Yy]$ ]]; then\n  read -rp \"${TAB3}Enter your Paperless API token: \" PAPERLESS_API_TOKEN\nelse\n  PAPERLESS_API_TOKEN=\"your_paperless_api_token\"\nfi\n\nmsg_info \"Setup Environment\"\ncat <<EOF >/opt/paperless-gpt-data/.env\nPAPERLESS_BASE_URL=$PAPERLESS_BASE_URL\nPAPERLESS_API_TOKEN=$PAPERLESS_API_TOKEN\n\nLLM_PROVIDER=openai\nLLM_MODEL=gpt-4o\nOPENAI_API_KEY=your_openai_api_key\n\n#VISION_LLM_PROVIDER=ollama\n#VISION_LLM_MODEL=minicpm-v\n\nLLM_LANGUAGE=English\nLOG_LEVEL=info\n\nLISTEN_INTERFACE=:8080\n\nAUTO_TAG=paperless-gpt-auto\nMANUAL_TAG=paperless-gpt\nAUTO_OCR_TAG=paperless-gpt-ocr-auto\n\nOCR_LIMIT_PAGES=5\nEOF\nmsg_ok \"Setup Environment\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/paperless-gpt.service\n[Unit]\nDescription=Paperless-GPT\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/paperless-gpt\nExecStart=/opt/paperless-gpt/paperless-gpt\nRestart=always\nUser=root\nEnvironmentFile=/opt/paperless-gpt-data/.env\nStandardOutput=append:/var/log/paperless-gpt.log\nStandardError=append:/var/log/paperless-gpt.log\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now paperless-gpt\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/paperless-ngx-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.paperless-ngx.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  redis \\\n  build-essential \\\n  imagemagick \\\n  fonts-liberation \\\n  optipng \\\n  libpq-dev \\\n  libmagic-dev \\\n  libzbar0t64 \\\n  poppler-utils \\\n  default-libmysqlclient-dev \\\n  automake \\\n  libtool \\\n  pkg-config \\\n  libtiff-dev \\\n  libpng-dev \\\n  libleptonica-dev \\\n  unpaper \\\n  icc-profiles-free \\\n  qpdf \\\n  libleptonica6 \\\n  libxml2 \\\n  pngquant \\\n  zlib1g \\\n  tesseract-ocr \\\n  tesseract-ocr-eng \\\n  ghostscript\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"paperlessdb\" PG_DB_USER=\"paperless\" setup_postgresql_db\nPYTHON_VERSION=\"3.13\" setup_uv\nfetch_and_deploy_gh_release \"paperless\" \"paperless-ngx/paperless-ngx\" \"prebuild\" \"latest\" \"/opt/paperless\" \"paperless*tar.xz\"\n\nmsg_info \"Setup Paperless-ngx\"\ncd /opt/paperless\nrm -rf /opt/paperless/docker\n$STD uv sync --all-extras\ncurl -fsSL \"https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/paperless.conf.example\" -o /opt/paperless/paperless.conf\nmkdir -p /opt/paperless_data/{consume,data,media,trash}\nmkdir -p /opt/paperless/static\nSECRET_KEY=\"$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)\"\n{\n  echo \"\"\n  echo \"Paperless-ngx Secret Key: $SECRET_KEY\"\n  echo \"Paperless-ngx WebUI User: admin\"\n  echo \"Paperless-ngx WebUI Password: $PG_DB_PASS\"\n} >>~/paperless-ngx.creds\nsed -i \\\n  -e 's|#PAPERLESS_REDIS=redis://localhost:6379|PAPERLESS_REDIS=redis://localhost:6379|' \\\n  -e \"s|#PAPERLESS_CONSUMPTION_DIR=../consume|PAPERLESS_CONSUMPTION_DIR=/opt/paperless_data/consume|\" \\\n  -e \"s|#PAPERLESS_DATA_DIR=../data|PAPERLESS_DATA_DIR=/opt/paperless_data/data|\" \\\n  -e \"s|#PAPERLESS_MEDIA_ROOT=../media|PAPERLESS_MEDIA_ROOT=/opt/paperless_data/media|\" \\\n  -e \"s|#PAPERLESS_EMPTY_TRASH_DIR=|PAPERLESS_EMPTY_TRASH_DIR=/opt/paperless_data/trash|\" \\\n  -e \"s|#PAPERLESS_STATICDIR=../static|PAPERLESS_STATICDIR=/opt/paperless/static|\" \\\n  -e 's|#PAPERLESS_DBHOST=localhost|PAPERLESS_DBHOST=localhost|' \\\n  -e 's|#PAPERLESS_DBPORT=5432|PAPERLESS_DBPORT=5432|' \\\n  -e \"s|#PAPERLESS_DBNAME=paperless|PAPERLESS_DBNAME=$PG_DB_NAME|\" \\\n  -e \"s|#PAPERLESS_DBUSER=paperless|PAPERLESS_DBUSER=$PG_DB_USER|\" \\\n  -e \"s|#PAPERLESS_DBPASS=paperless|PAPERLESS_DBPASS=$PG_DB_PASS|\" \\\n  -e \"s|#PAPERLESS_SECRET_KEY=change-me|PAPERLESS_SECRET_KEY=$SECRET_KEY|\" \\\n  /opt/paperless/paperless.conf\ncd /opt/paperless/src\nset -a\n. /opt/paperless/paperless.conf\nset +a\n$STD uv run -- python manage.py migrate\nmsg_ok \"Setup Paperless-ngx\"\n\nmsg_info \"Setting up admin Paperless-ngx User & Password\"\ncat <<EOF | uv run -- python /opt/paperless/src/manage.py shell\nfrom django.contrib.auth import get_user_model\nUserModel = get_user_model()\nuser = UserModel.objects.create_user('admin', password='$PG_DB_PASS')\nuser.is_superuser = True\nuser.is_staff = True\nuser.save()\nEOF\nmsg_ok \"Set up admin Paperless-ngx User & Password\"\n\nsetup_nltk \"snowball_data stopwords punkt_tab\" \"/usr/share/nltk_data\"\nfor policy_file in /etc/ImageMagick-6/policy.xml /etc/ImageMagick-7/policy.xml; do\n  if [[ -f \"$policy_file\" ]]; then\n    sed -i -e 's/rights=\"none\" pattern=\"PDF\"/rights=\"read|write\" pattern=\"PDF\"/' \"$policy_file\"\n  fi\ndone\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/paperless-scheduler.service\n[Unit]\nDescription=Paperless Celery beat\nRequires=redis.service\n\n[Service]\nWorkingDirectory=/opt/paperless/src\nExecStart=uv run -- celery --app paperless beat --loglevel INFO\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/paperless-task-queue.service\n[Unit]\nDescription=Paperless Celery Workers\nRequires=redis.service\nAfter=postgresql.service\n\n[Service]\nWorkingDirectory=/opt/paperless/src\nExecStart=uv run -- celery --app paperless worker --loglevel INFO\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/paperless-consumer.service\n[Unit]\nDescription=Paperless consumer\nRequires=redis.service\n\n[Service]\nWorkingDirectory=/opt/paperless/src\nExecStartPre=/bin/sleep 2\nExecStart=uv run -- python manage.py document_consumer\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/paperless-webserver.service\n[Unit]\nDescription=Paperless webserver\nAfter=network.target\nWants=network.target\nRequires=redis.service\n\n[Service]\nWorkingDirectory=/opt/paperless/src\nExecStart=uv run -- granian --interface asgi --ws \"paperless.asgi:application\"\nEnvironment=GRANIAN_HOST=::\nEnvironment=GRANIAN_PORT=8000\nEnvironment=GRANIAN_WORKERS=1\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now paperless-webserver paperless-scheduler paperless-task-queue paperless-consumer\nmsg_ok \"Created Services\"\n\nread -r -p \"${TAB3}Would you like to add Adminer? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  setup_adminer\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/papra-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/papra-hq/papra\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  tesseract-ocr \\\n  tesseract-ocr-all\nmsg_ok \"Installed Dependencies\"\n\nRELEASE=$(curl -fsSL https://api.github.com/repos/papra-hq/papra/releases | grep -oP '\"tag_name\":\\s*\"\\K@papra/app@[^\"]+' | head -n1)\nfetch_and_deploy_gh_release \"papra\" \"papra-hq/papra\" \"tarball\" \"${RELEASE}\" \"/opt/papra\"\n\npnpm_version=$(grep -oP '\"packageManager\":\\s*\"pnpm@\\K[^\"]+' /opt/papra/package.json)\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm@$pnpm_version\" setup_nodejs\n\nmsg_info \"Installing Papra (Patience)\"\ncd /opt/papra\n$STD pnpm install --frozen-lockfile\n$STD pnpm --filter \"@papra/app-client...\" run build\n$STD pnpm --filter \"@papra/app-server...\" run build\nln -sf /opt/papra/apps/papra-client/dist /opt/papra/apps/papra-server/public\nmsg_ok \"Installed Papra\"\n\nmsg_info \"Configuring Papra\"\nmkdir -p /opt/papra_data/{db,documents,ingestion}\n[[ ! -f /opt/papra_data/.secret ]] && openssl rand -hex 32 >/opt/papra_data/.secret\ncat <<EOF >/opt/papra/apps/papra-server/.env\nNODE_ENV=production\nSERVER_SERVE_PUBLIC_DIR=true\nPORT=1221\nDATABASE_URL=file:/opt/papra_data/db/db.sqlite\nDOCUMENT_STORAGE_FILESYSTEM_ROOT=/opt/papra_data/documents\nPAPRA_CONFIG_DIR=/opt/papra_data\nAUTH_SECRET=$(cat /opt/papra_data/.secret)\nBETTER_AUTH_SECRET=$(cat /opt/papra_data/.secret)\nBETTER_AUTH_TELEMETRY=0\nCLIENT_BASE_URL=http://${LOCAL_IP}:1221\nSERVER_BASE_URL=http://${LOCAL_IP}:1221\nEMAILS_DRY_RUN=true\nINGESTION_FOLDER_IS_ENABLED=true\nINGESTION_FOLDER_ROOT_PATH=/opt/papra_data/ingestion\nEOF\nmsg_ok \"Configured Papra\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/papra.service\n[Unit]\nDescription=Papra Document Management\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/papra/apps/papra-server\nEnvironmentFile=/opt/papra/apps/papra-server/.env\nExecStartPre=/usr/bin/pnpm run migrate:up\nExecStart=/usr/bin/node dist/index.js\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now papra\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/part-db-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.part-db.de/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"partdb\" PG_DB_USER=\"partdb\" setup_postgresql_db\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" PHP_MODULE=\"xsl\" PHP_POST_MAX_SIZE=\"100M\" PHP_UPLOAD_MAX_FILESIZE=\"100M\" setup_php\nsetup_composer\n\nfetch_and_deploy_gh_release \"partdb\" \"Part-DB/Part-DB-server\" \"prebuild\" \"latest\" \"/opt/partdb\" \"partdb_with_assets.zip\"\n\nmsg_info \"Installing Part-DB\"\ncd /opt/partdb/\ncp .env .env.local\nsed -i \"s|DATABASE_URL=\\\"sqlite:///%kernel.project_dir%/var/app.db\\\"|DATABASE_URL=\\\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}?serverVersion=12.19&charset=utf8\\\"|\" .env.local\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD composer install --no-dev -o --no-interaction\n$STD php bin/console cache:clear\nphp bin/console doctrine:migrations:migrate -n >~/database-migration-output\nchown -R www-data:www-data /opt/partdb\nADMIN_PASS=$(grep -oP 'The initial password for the \"admin\" user is: \\K\\w+' ~/database-migration-output)\n{\n  echo \"\"\n  echo \"Part-DB Admin User: admin\"\n  echo \"Part-DB Admin Password: $ADMIN_PASS\"\n} >>~/partdb.creds\nrm -rf ~/database-migration-output\nmsg_ok \"Installed Part-DB\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/partdb.conf\n<VirtualHost *:80>\n    ServerName partdb\n    DocumentRoot /opt/partdb/public\n    <Directory /opt/partdb/public>\n        Options FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/partdb_error.log\n    CustomLog /var/log/apache2/partdb_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite partdb\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/passbolt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.passbolt.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  python3-certbot-nginx \\\n  debconf-utils\nmsg_ok \"Installed dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"passboltdb\" MARIADB_DB_USER=\"passbolt\" setup_mariadb_db\ncreate_self_signed_cert\n\nsetup_deb822_repo \\\n  \"passbolt\" \\\n  \"https://keys.openpgp.org/pks/lookup?op=get&options=mr&search=0x3D1A0346C8E1802F774AEF21DE8B853FC155581D\" \\\n  \"https://download.passbolt.com/ce/debian\" \\\n  \"buster\" \\\n  \"stable\"\n\nmsg_info \"Setting up Passbolt (Patience)\"\nexport DEBIAN_FRONTEND=noninteractive\necho passbolt-ce-server passbolt/mysql-configuration boolean true | debconf-set-selections\necho passbolt-ce-server passbolt/mysql-passbolt-username string $MARIADB_DB_USER | debconf-set-selections\necho passbolt-ce-server passbolt/mysql-passbolt-password password $MARIADB_DB_PASS | debconf-set-selections\necho passbolt-ce-server passbolt/mysql-passbolt-password-repeat password $MARIADB_DB_PASS | debconf-set-selections\necho passbolt-ce-server passbolt/mysql-passbolt-dbname string $MARIADB_DB_NAME | debconf-set-selections\necho passbolt-ce-server passbolt/nginx-configuration boolean true | debconf-set-selections\necho passbolt-ce-server passbolt/nginx-configuration-three-choices select manual | debconf-set-selections\necho passbolt-ce-server passbolt/nginx-domain string $LOCAL_IP | debconf-set-selections\necho passbolt-ce-server passbolt/nginx-certificate-file string /etc/ssl/passbolt/passbolt.crt | debconf-set-selections\necho passbolt-ce-server passbolt/nginx-certificate-key-file string /etc/ssl/passbolt/passbolt.key | debconf-set-selections\n$STD apt install -y --no-install-recommends passbolt-ce-server\nsed -i 's/client_max_body_size[[:space:]]\\+[0-9]\\+M;/client_max_body_size        15M;/' /etc/nginx/sites-enabled/nginx-passbolt.conf\nsystemctl reload nginx\nmsg_ok \"Setup Passbolt\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/patchmon-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/PatcMmon/PatchMon\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y redis-server\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"patchmon_db\" PG_DB_USER=\"patchmon_usr\" setup_postgresql_db\n\nRELEASE=\"v2.0.2\"\nfetch_and_deploy_gh_release \"PatchMon\" \"PatchMon/PatchMon\" \"singlefile\" \"latest\" \"/opt/patchmon\" \"patchmon-server-linux-amd64\"\nmv /opt/patchmon/PatchMon /opt/patchmon/patchmon-server\n\nmsg_info \"Configuring PatchMon\"\ncat <<EOF >/opt/patchmon/.env\nDATABASE_URL=\"postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\"\nJWT_SECRET=\"$(openssl rand -hex 64)\"\nSESSION_SECRET=\"$(openssl rand -hex 64)\"\nAI_ENCRYPTION_KEY=\"$(openssl rand -hex 64)\"\nCORS_ORIGIN=http://${LOCAL_IP}:3000\nPORT=3000\nAPP_ENV=production\n\n# Redis\nREDIS_HOST=localhost\nREDIS_PORT=6379\n\n## OIDC / SSO (when OIDC_ENABLED=true, issuer/client/secret/redirect required)\n# OIDC_ENABLED=false\n# OIDC_ISSUER_URL=\n# OIDC_CLIENT_ID=\n# OIDC_CLIENT_SECRET=\n# OIDC_REDIRECT_URI=\n# OIDC_SCOPES=openid email profile groups\n# OIDC_AUTO_CREATE_USERS=false\n# OIDC_DEFAULT_ROLE=user\n# OIDC_DISABLE_LOCAL_AUTH=false\n# OIDC_BUTTON_TEXT=Login with SSO\n# OIDC_SESSION_TTL=600\n# OIDC_POST_LOGOUT_URI=\n# OIDC_SYNC_ROLES=false\n# OIDC_ADMIN_GROUP=\n# OIDC_SUPERADMIN_GROUP=\n# OIDC_HOST_MANAGER_GROUP=\n# OIDC_READONLY_GROUP=\n# OIDC_USER_GROUP=\n# OIDC_ENFORCE_HTTPS=true\n\nAGENT_BINARIES_DIR=/opt/patchmon/agents\nEOF\nmsg_ok \"Configured PatchMon\"\n\nmsg_info \"Fetching PatchMon agent binaries\"\nRELEASE=$(get_latest_github_release \"PatchMon/PatchMon\")\nmkdir -p /opt/patchmon/agents\nFILE_URL=\"https://github.com/PatchMon/PatchMon/releases/download/v${RELEASE}/patchmon-agent-\"\nAGENT_NAME=(\n  \"linux-amd64\"\n  \"linux-arm64\"\n  \"linux-arm\"\n  \"linux-386\"\n  \"freebsd-amd64\"\n  \"freebsd-arm64\"\n  \"freebsd-arm\"\n  \"freebsd-386\"\n  \"windows-amd64.exe\"\n  \"windows-arm64.exe\"\n)\nfor arch in \"${AGENT_NAME[@]}\"; do\n  curl_with_retry \"${FILE_URL}${arch}\" \"/opt/patchmon/agents/patchmon-agent-${arch}\"\n  [[ \"${arch}\" != *.exe ]] && chmod 755 \"/opt/patchmon/agents/patchmon-agent-${arch}\"\ndone\nmsg_ok \"Fetched PatchMon agent binaries\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/patchmon-server.service\n[Unit]\nDescription=PatchMon Server\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/patchmon\nExecStart=/opt/patchmon/patchmon-server\nRestart=always\nRestartSec=10\nEnvironment=PATH=/usr/bin:/usr/local/bin\nEnvironmentFile=/opt/patchmon/.env\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/opt/patchmon\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now patchmon-server\nmsg_ok \"Created and started service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/paymenter-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.paymenter.org\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  nginx \\\n  redis-server \\\n  cron\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" setup_php\nsetup_composer\nfetch_and_deploy_gh_release \"paymenter\" \"paymenter/paymenter\" \"prebuild\" \"latest\" \"/opt/paymenter\" \"paymenter.tar.gz\"\nchmod -R 755 /opt/paymenter/storage/* /opt/paymenter/bootstrap/cache/\n\nmsg_info \"Setting up database\"\nDB_NAME=paymenter\nDB_USER=paymenter\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb mysql\n$STD mariadb -u root -e \"CREATE DATABASE $DB_NAME;\"\n$STD mariadb -u root -e \"CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';\"\n$STD mariadb -u root -e \"GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost' WITH GRANT OPTION;\"\n{\n  echo \"Paymenter Database Credentials\"\n  echo \"Database: $DB_NAME\"\n  echo \"Username: $DB_USER\"\n  echo \"Password: $DB_PASS\"\n} >>~/paymenter_db.creds\ncd /opt/paymenter\ncp .env.example .env\n$STD composer install --no-dev --optimize-autoloader --no-interaction\n$STD php artisan key:generate --force\n$STD php artisan storage:link\nsed -i \"s/^DB_DATABASE=.*/DB_DATABASE=${DB_NAME}/\" .env\nsed -i \"s/^DB_USERNAME=.*/DB_USERNAME=${DB_USER}/\" .env\nsed -i \"s/^DB_PASSWORD=.*/DB_PASSWORD=${DB_PASS}/\" .env\n$STD php artisan migrate --force --seed\nmsg_ok \"Set up database\"\n\nmsg_info \"Creating Admin User\"\n$STD php artisan app:user:create paymenter admin admin@paymenter.org paymenter 1 -q\nmsg_ok \"Created Admin User\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/paymenter.conf\nserver {\n    listen 80;\n    listen [::]:80;\n    server_name localhost;\n    root /opt/paymenter/public;\n\n    index index.php;\n\n    location / {\n        try_files \\$uri \\$uri/ /index.php?\\$query_string;\n    }\n\n    location ~ \\.php\\$ {\n        include snippets/fastcgi-php.conf;\n        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name;\n        include fastcgi_params;\n    }\n\n    location ~ /\\.ht {\n        deny all;\n    }\n}\nEOF\nln -s /etc/nginx/sites-available/paymenter.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nchown -R www-data:www-data /opt/paymenter/*\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Setting up Cronjob\"\necho \"* * * * * php /opt/paymenter/artisan schedule:run >> /dev/null 2>&1\" | crontab -\nmsg_ok \"Setup Cronjob\"\n\nmsg_info \"Setting up Service\"\ncat <<EOF >/etc/systemd/system/paymenter.service\n[Unit]\nDescription=Paymenter Queue Worker\n\n[Service]\nUser=www-data\nGroup=www-data\nRestart=always\nExecStart=/usr/bin/php /opt/paymenter/artisan queue:work\nStartLimitInterval=180\nStartLimitBurst=30\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now paymenter\nsystemctl enable -q --now redis-server\nmsg_ok \"Setup Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/peanut-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Brandawg93/PeaNUT/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing NUT\"\n$STD apt install -y nut-client\nmsg_ok \"Installed NUT\"\n\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\nfetch_and_deploy_gh_release \"peanut\" \"Brandawg93/PeaNUT\" \"tarball\" \"latest\" \"/opt/peanut\"\n\nmsg_info \"Setup Peanut\"\ncd /opt/peanut\n$STD pnpm i\n$STD pnpm run build:local\ncp -r .next/static .next/standalone/.next/\nmkdir -p /opt/peanut/.next/standalone/config\nmkdir -p /etc/peanut/\nln -sf .next/standalone/server.js server.js\nif [[ ! -f /etc/peanut/settings.yml ]]; then\n  cat <<EOF >/etc/peanut/settings.yml\nNUT_SERVERS: []\nEOF\nfi\nln -sf /etc/peanut/settings.yml /opt/peanut/.next/standalone/config/settings.yml\ncat <<EOF >/etc/peanut/peanut.env\nNODE_ENV=production\n\n#WEB_HOST=0.0.0.0\n#WEB_PORT=8080\n#NUT_HOST=localhost\n#NUT_PORT=3493\n\n# Disable auth entirely:\n#AUTH_DISABLED=true\n\n# Bootstrap initial account on first start (ignored afterwards):\n#WEB_USERNAME=admin\n#WEB_PASSWORD=changeme\nEOF\nchmod 600 /etc/peanut/peanut.env\nmsg_ok \"Setup Peanut\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/peanut.service\n[Unit]\nDescription=Peanut\nAfter=network.target\n[Service]\nSyslogIdentifier=peanut\nRestart=always\nRestartSec=5\nType=simple\nEnvironmentFile=/etc/peanut/peanut.env\nWorkingDirectory=/opt/peanut\nExecStart=node /opt/peanut/entrypoint.mjs\nTimeoutStopSec=30\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now peanut\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pelican-panel-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pelican-dev/panel\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  lsb-release \\\n  cron\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"panel\" MARIADB_DB_USER=\"pelican\" setup_mariadb_db\nfetch_and_deploy_gh_release \"pelican-panel\" \"pelican-dev/panel\" \"prebuild\" \"latest\" \"/opt/pelican-panel\" \"panel.tar.gz\"\n\nmsg_info \"Installing Pelican Panel\"\ncd /opt/pelican-panel\n$STD composer install --no-dev --optimize-autoloader --no-interaction\n$STD php artisan p:environment:setup\n$STD php artisan p:environment:queue-service --no-interaction\necho \"* * * * * php /opt/pelican-panel/artisan schedule:run >> /dev/null 2>&1\" | crontab -u www-data -\nchown -R www-data:www-data /opt/pelican-panel\nchmod -R 755 /opt/pelican-panel/storage /opt/pelican-panel/bootstrap/cache/\nmsg_ok \"Installed Pelican Panel\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/pelican.conf\n<VirtualHost *:80>\n    ServerName pelican\n    DocumentRoot /opt/pelican-panel/public\n    AllowEncodedSlashes On\n    php_value upload_max_filesize 100M\n    php_value post_max_size 100M\n\n    <Directory /opt/pelican-panel/public>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/pelican_error.log\n    CustomLog /var/log/apache2/pelican_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite pelican\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pelican-wings-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pelican-dev/wings\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Docker\"\nDOCKER_CONFIG_PATH='/etc/docker/daemon.json'\nmkdir -p \"$(dirname $DOCKER_CONFIG_PATH)\"\necho -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n$STD sh <(curl -fsSL https://get.docker.com)\nsystemctl enable -q --now docker\nmsg_ok \"Installed Docker\"\n\nfetch_and_deploy_gh_release \"wings\" \"pelican-dev/wings\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"wings_linux_arm64\"\nmkdir -p /etc/pelican /var/run/wings\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wings.service\n[Unit]\nDescription=Wings Daemon\nAfter=docker.service\nRequires=docker.service\nPartOf=docker.service\n\n[Service]\nUser=root\nWorkingDirectory=/etc/pelican\nLimitNOFILE=4096\nPIDFile=/var/run/wings/daemon.pid\nExecStart=/usr/local/bin/wings\nRestart=on-failure\nStartLimitInterval=180\nStartLimitBurst=30\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now wings\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/photoprism-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.photoprism.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  exiftool \\\n  ffmpeg \\\n  libheif1 \\\n  libpng-dev \\\n  libjpeg-dev \\\n  libtiff-dev \\\n  imagemagick \\\n  darktable \\\n  rawtherapee \\\n  libvips42 \\\n  lsb-release\n\necho 'export PATH=/usr/local:$PATH' >>~/.bashrc\necho '# Load PhotoPrism environment variables for CLI tools' >>~/.bashrc\necho 'export $(grep -v \"^#\" /opt/photoprism/config/.env | xargs)' >>~/.bashrc\nexport PATH=/usr/local:$PATH\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"photoprism\" \"photoprism/photoprism\" \"prebuild\" \"latest\" \"/opt/photoprism\" \"*linux-arm64.tar.gz\"\n\nmsg_info \"Installing PhotoPrism (Patience)\"\nmkdir -p /opt/photoprism/{cache,config,photos,storage,temp}\nmkdir -p /opt/photoprism/photos/{originals,import}\nmkdir -p /opt/photoprism_backups\nLIBHEIF_URL=$(curl -fsSL \"https://dl.photoprism.app/dist/libheif/\" | grep -oP \"libheif-bookworm-arm64-v[0-9\\.]+\\.tar\\.gz\" | sort -V | tail -n 1)\ncurl -fsSL \"https://dl.photoprism.app/dist/libheif/$LIBHEIF_URL\" -o /tmp/libheif.tar.gz\ntar -xzf /tmp/libheif.tar.gz -C /usr/local\nldconfig\necho \"${LIBHEIF_URL}\" >~/.photoprism_libheif\nchmod -R 755 /opt/photoprism/photos/originals\ncat <<EOF >/opt/photoprism/config/.env\n# Authentication\nPHOTOPRISM_ADMIN_USER='admin'\nPHOTOPRISM_ADMIN_PASSWORD='changeme'\nPHOTOPRISM_AUTH_MODE='password'\nPHOTOPRISM_PUBLIC='false'\n\n# Network / HTTP\nPHOTOPRISM_HTTP_HOST='0.0.0.0'\nPHOTOPRISM_HTTP_PORT='2342'\nPHOTOPRISM_SITE_URL='http://localhost:2342/'\nPHOTOPRISM_DISABLE_TLS='true'\nPHOTOPRISM_DEFAULT_TLS='false'\nPHOTOPRISM_HTTP_COMPRESSION='gzip'\n\n# Features & AI\nPHOTOPRISM_DISABLE_TENSORFLOW='false'\nPHOTOPRISM_DISABLE_FACES='false'\nPHOTOPRISM_DISABLE_CLASSIFICATION='false'\nPHOTOPRISM_DISABLE_VECTORS='false'\nPHOTOPRISM_DETECT_NSFW='false'\nPHOTOPRISM_UPLOAD_NSFW='true'\n\n# Paths & Storage\nPHOTOPRISM_STORAGE_PATH='/opt/photoprism/storage'\nPHOTOPRISM_ORIGINALS_PATH='/opt/photoprism/photos/originals'\nPHOTOPRISM_IMPORT_PATH='/opt/photoprism/photos/import'\nPHOTOPRISM_BACKUP_PATH='/opt/photoprism_backups'\n\n# Database\nPHOTOPRISM_DATABASE_DRIVER='sqlite'\n\n# Behavior & Options\nPHOTOPRISM_AUTO_INDEX='300'\nPHOTOPRISM_AUTO_IMPORT='-1'\nPHOTOPRISM_DISABLE_WEBDAV='false'\nPHOTOPRISM_READONLY='false'\nPHOTOPRISM_DISABLE_SETTINGS='false'\nPHOTOPRISM_DISABLE_CHOWN='false'\nPHOTOPRISM_EXPERIMENTAL='false'\nPHOTOPRISM_INIT='https tensorflow'\n\n# Image Processing\nPHOTOPRISM_ORIGINALS_LIMIT='5000'\nPHOTOPRISM_JPEG_QUALITY='85'\nPHOTOPRISM_RAW_PRESETS='false'\nPHOTOPRISM_DISABLE_RAW='false'\n\n# Debug & Logging\nPHOTOPRISM_DEBUG='false'\nPHOTOPRISM_LOG_LEVEL='info'\n\n# Site Info\nPHOTOPRISM_SITE_CAPTION='https://community-scripts.org'\nPHOTOPRISM_SITE_DESCRIPTION=''\nPHOTOPRISM_SITE_AUTHOR=''\nEOF\nln -sf /opt/photoprism/bin/photoprism /usr/local/bin/photoprism\n\nmkdir -p /etc/photoprism/\ncat <<EOF >/etc/photoprism/defaults.yml\nConfigPath: \"~/.config/photoprism\"\nStoragePath: \"/opt/photoprism/storage\"\nOriginalsPath: \"/opt/photoprism/photos/originals\"\nImportPath: \"/media\"\nAdminUser: \"admin\"\nAdminPassword: \"changeme\"\nAuthMode: \"password\"\nDatabaseDriver: \"sqlite\"\nHttpHost: \"0.0.0.0\"\nHttpPort: 2342\nHttpCompression: \"gzip\"\nDisableTLS: false\nDefaultTLS: true\nExperimental: false\nDisableWebDAV: false\nDisableSettings: false\nDisableTensorFlow: false\nDisableFaces: false\nDisableClassification: false\nDisableVectors: false\nDisableRaw: false\nRawPresets: false\nJpegQuality: 85\nDetectNSFW: false\nUploadNSFW: true\nEOF\nmsg_ok \"Installed PhotoPrism\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/photoprism.service\n[Unit]\nDescription=PhotoPrism service\nAfter=network.target\n\n[Service]\nType=forking\nUser=root\nWorkingDirectory=/opt/photoprism\nEnvironmentFile=/opt/photoprism/config/.env\nExecStart=/opt/photoprism/bin/photoprism up -d\nExecStop=/opt/photoprism/bin/photoprism down\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now photoprism\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pialert-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/leiweibau/Pi.Alert/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt -y install \\\n  apt-utils \\\n  avahi-utils \\\n  lighttpd \\\n  sqlite3 \\\n  mmdb-bin \\\n  arp-scan \\\n  dnsutils \\\n  net-tools \\\n  nbtscan \\\n  libwww-perl \\\n  nmap \\\n  aria2 \\\n  wakeonlan \\\n  fping \\\n  zip \\\n  libtext-csv-perl \\\n\tcron\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing PHP Dependencies\"\n$STD apt -y install \\\n  php \\\n  php-cgi \\\n  php-fpm \\\n  php-curl \\\n  php-xml \\\n  php-sqlite3\n$STD lighttpd-enable-mod fastcgi-php\nservice lighttpd force-reload\nmsg_ok \"Installed PHP Dependencies\"\n\nmsg_info \"Installing Python Dependencies\"\n$STD apt -y install \\\n  python3-pip \\\n  python3-requests \\\n  python3-tz \\\n  python3-tzlocal \\\n  python3-aiohttp \\\n  python3-cryptography\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\n$STD pip3 install mac-vendor-lookup\n$STD pip3 install fritzconnection\n$STD pip3 install cryptography\n$STD pip3 install pyunifi\n$STD pip3 install openwrt-luci-rpc\n$STD pip3 install asusrouter\n$STD pip3 install paho-mqtt\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Installing Pi.Alert\"\ncurl -fsSL https://github.com/leiweibau/Pi.Alert/raw/main/tar/pialert_latest.tar | tar xvf - -C /opt >/dev/null 2>&1\nrm -rf /var/lib/ieee-data /var/www/html/index.html\nsed -i -e 's#^sudo cp -n /usr/share/ieee-data/.* /var/lib/ieee-data/#\\# &#' -e '/^sudo mkdir -p 2_backup$/s/^/# /' -e '/^sudo cp \\*.txt 2_backup$/s/^/# /' -e '/^sudo cp \\*.csv 2_backup$/s/^/# /' /opt/pialert/back/update_vendors.sh\nmv /var/www/html/index.lighttpd.html /var/www/html/index.lighttpd.html.old\nln -s /usr/share/ieee-data/ /var/lib/\nln -s /opt/pialert/install/index.html /var/www/html/index.html\nln -s /opt/pialert/front /var/www/html/pialert\nchmod go+x /opt/pialert /opt/pialert/back/shoutrrr/arm64/shoutrrr\nchgrp -R www-data /opt/pialert/db /opt/pialert/front/reports /opt/pialert/config /opt/pialert/config/pialert.conf\nchmod -R 775 /opt/pialert/db /opt/pialert/db/temp /opt/pialert/config /opt/pialert/front/reports\ntouch /opt/pialert/log/pialert.vendors.log /opt/pialert/log/pialert.IP.log /opt/pialert/log/pialert.1.log /opt/pialert/log/pialert.cleanup.log /opt/pialert/log/pialert.webservices.log\nsrc_dir=\"/opt/pialert/log\"\ndest_dir=\"/opt/pialert/front/php/server\"\nfor file in pialert.vendors.log pialert.IP.log pialert.1.log pialert.cleanup.log pialert.webservices.log; do\n  ln -s \"$src_dir/$file\" \"$dest_dir/$file\"\ndone\nsed -i 's#PIALERT_PATH\\s*=\\s*'\\''/home/pi/pialert'\\''#PIALERT_PATH           = '\\''/opt/pialert'\\''#' /opt/pialert/config/pialert.conf\nsed -i 's/$HOME/\\/opt/g' /opt/pialert/install/pialert.cron\ncrontab /opt/pialert/install/pialert.cron\necho \"python3 /opt/pialert/back/pialert.py 1\" >/usr/bin/scan\nchmod +x /usr/bin/scan\necho \"/opt/pialert/back/pialert-cli set_permissions --lxc\" >/usr/bin/permissions\nchmod +x /usr/bin/permissions\necho \"/opt/pialert/back/pialert-cli set_sudoers --lxc\" >/usr/bin/sudoers\nchmod +x /usr/bin/sudoers\nmsg_ok \"Installed Pi.Alert\"\n\nmsg_info \"Start Pi.Alert Scan (Patience)\"\n$STD python3 /opt/pialert/back/pialert.py update_vendors\n$STD python3 /opt/pialert/back/pialert.py internet_IP\n$STD python3 /opt/pialert/back/pialert.py 1\nmsg_ok \"Finished Pi.Alert Scan\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pihole-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pi-hole.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://pi-hole.net/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://install.pi-hole.net\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ufw\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Pi-hole\"\nmkdir -p /etc/pihole\ntouch /etc/pihole/pihole.toml\n$STD bash <(curl -fsSL https://install.pi-hole.net) --unattended\nsed -i -E '\n/^\\s*upstreams =/ s|=.*|= [\"8.8.8.8\", \"8.8.4.4\"]|\n/^\\s*interface =/ s|=.*|= \"eth0\"|\n/^\\s*queryLogging =/ s|=.*|= true|\n/^\\s*size =/ s|=.*|= 10000|\n/^\\s*active =/ s|=.*|= true|\n/^\\s*listeningMode =/ s|=.*|= \"LOCAL\"|\n/^\\s*port =/ s|=.*|= \"80o,443os,[::]:80o,[::]:443os\"|\n/^\\s*pwhash =/ s|=.*|= \"\"|\n\n# DHCP Disable\n/^\\s*\\[dhcp\\]/,/^\\s*\\[/{s/^\\s*active = true/  active = false/}\n\n# NTP Disable\n/^\\s*\\[ntp.ipv4\\]/,/^\\s*\\[/{s/^\\s*active = true/  active = false/}\n/^\\s*\\[ntp.ipv6\\]/,/^\\s*\\[/{s/^\\s*active = true/  active = false/}\n/^\\s*\\[ntp.sync\\]/,/^\\s*\\[/{s/^\\s*active = true/  active = false/}\n/^\\s*\\[ntp.sync\\]/,/^\\s*\\[/{s/^\\s*interval = [0-9]+/  interval = 0/}\n/^\\s*\\[ntp.sync.rtc\\]/,/^\\s*\\[/{s/^\\s*set = true/  set = false/}\n\n# set domainNeeded und expandHosts\n/^\\s*domainNeeded =/ s|=.*|= true|\n/^\\s*expandHosts =/ s|=.*|= true|\n' /etc/pihole/pihole.toml\n\ncat <<EOF >/etc/dnsmasq.d/01-pihole.conf\nserver=8.8.8.8\nserver=8.8.4.4\nEOF\n$STD pihole-FTL --config ntp.sync.interval 0\nsystemctl restart pihole-FTL.service\nmsg_ok \"Installed Pi-hole\"\n\nread -r -p \"${TAB3}Would you like to add Unbound? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  read -r -p \"${TAB3}Unbound is configured as a recursive DNS server by default, would you like it to be configured as a forwarding DNS server (using DNS-over-TLS (DoT)) instead? <y/N> \" prompt\n  msg_info \"Installing Unbound\"\n  mkdir -p /etc/unbound/unbound.conf.d\n  cat <<EOF >/etc/unbound/unbound.conf.d/pi-hole.conf\nserver:\n  verbosity: 0\n  interface: 127.0.0.1\n  port: 5335\n  do-ip6: no\n  do-ip4: yes\n  do-udp: yes\n  do-tcp: yes\n  num-threads: 1\n  hide-identity: yes\n  hide-version: yes\n  harden-glue: yes\n  harden-dnssec-stripped: yes\n  harden-referral-path: yes\n  use-caps-for-id: no\n  harden-algo-downgrade: no\n  qname-minimisation: yes\n  aggressive-nsec: yes\n  rrset-roundrobin: yes\n  cache-min-ttl: 300\n  cache-max-ttl: 14400\n  msg-cache-slabs: 8\n  rrset-cache-slabs: 8\n  infra-cache-slabs: 8\n  key-cache-slabs: 8\n  serve-expired: yes\n  serve-expired-ttl: 3600\n  edns-buffer-size: 1232\n  prefetch: yes\n  prefetch-key: yes\n  target-fetch-policy: \"3 2 1 1 1\"\n  unwanted-reply-threshold: 10000000\n  rrset-cache-size: 256m\n  msg-cache-size: 128m\n  so-rcvbuf: 1m\n  private-address: 192.168.0.0/16\n  private-address: 169.254.0.0/16\n  private-address: 172.16.0.0/12\n  private-address: 10.0.0.0/8\n  private-address: fd00::/8\n  private-address: fe80::/10\nEOF\n  mkdir -p /etc/dnsmasq.d/\n  cat <<EOF >/etc/dnsmasq.d/99-edns.conf\nedns-packet-max=1232\nEOF\n\n  if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n    cat <<EOF >>/etc/unbound/unbound.conf.d/pi-hole.conf\n  tls-cert-bundle: \"/etc/ssl/certs/ca-certificates.crt\"\nforward-zone:\n  name: \".\"\n  forward-tls-upstream: yes\n  forward-first: no\n\n  forward-addr: 8.8.8.8@853#dns.google\n  forward-addr: 8.8.4.4@853#dns.google\n  forward-addr: 2001:4860:4860::8888@853#dns.google\n  forward-addr: 2001:4860:4860::8844@853#dns.google\n\n  #forward-addr: 1.1.1.1@853#cloudflare-dns.com\n  #forward-addr: 1.0.0.1@853#cloudflare-dns.com\n  #forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com\n  #forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com\n\n  #forward-addr: 9.9.9.9@853#dns.quad9.net\n  #forward-addr: 149.112.112.112@853#dns.quad9.net\n  #forward-addr: 2620:fe::fe@853#dns.quad9.net\n  #forward-addr: 2620:fe::9@853#dns.quad9.net\nEOF\n  fi\n  $STD apt install -y unbound\n  cat <<EOF >/etc/dnsmasq.d/01-pihole.conf\nserver=127.0.0.1#5335\nserver=8.8.8.8\nserver=8.8.4.4\nEOF\n\n  sed -i -E '/^\\s*upstreams\\s*=\\s*\\[/,/^\\s*\\]/c\\  upstreams = [\\n    \"127.0.0.1#5335\",\\n    \"8.8.4.4\"\\n  ]' /etc/pihole/pihole.toml\n  systemctl restart unbound\n  systemctl restart pihole-FTL.service\n  msg_ok \"Installed Unbound\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/planka-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/plankanban/planka\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  unzip \\\n  build-essential \\\n  python3-venv\nmsg_ok \"Installed dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\n\nmsg_info \"Setting up PostgreSQL Database\"\nDB_NAME=planka\nDB_USER=planka\nDB_PASS=$(openssl rand -base64 16 | tr -d '/+=')\n$STD sudo -u postgres psql -c \"CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME WITH OWNER $DB_USER ENCODING 'UTF8' TEMPLATE template0;\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET client_encoding TO 'utf8';\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';\"\n$STD sudo -u postgres psql -c \"ALTER ROLE $DB_USER SET timezone TO 'UTC'\"\n{\n  echo \"PLANKA DB Credentials\"\n  echo \"PLANKA Database User: $DB_USER\"\n  echo \"PLANKA Database Password: $DB_PASS\"\n  echo \"PLANKA Database Name: $DB_NAME\"\n} >>~/planka.creds\nmsg_ok \"Set up PostgreSQL Database\"\n\nfetch_and_deploy_gh_release \"planka\" \"plankanban/planka\" \"prebuild\" \"latest\" \"/opt/planka\" \"planka-prebuild.zip\"\n\nmsg_info \"Configuring PLANKA\"\nSECRET_KEY=$(openssl rand -hex 64)\ncd /opt/planka\n$STD npm install\ncp .env.sample .env\nsed -i \"s#http://localhost:1337#http://$LOCAL_IP:1337#g\" /opt/planka/.env\nsed -i \"s#postgres@localhost#planka:$DB_PASS@localhost#g\" /opt/planka/.env\nsed -i \"s#notsecretkey#$SECRET_KEY#g\" /opt/planka/.env\nmkdir -p /opt/planka/data/protected/{favicons,user-avatars,background-images} /opt/planka/data/private/attachments\n$STD npm run db:init\nmsg_ok \"Configured PLANKA\"\n\nmsg_info \"Creating Admin User\"\nADMIN_EMAIL=\"admin@planka.local\"\nADMIN_PASSWORD=\"$(openssl rand -base64 12)\"\nADMIN_NAME=\"Administrator\"\nADMIN_USERNAME=\"admin\"\necho \"\" >>.env\necho \"# Temporary admin user creation settings\" >>.env\necho \"DEFAULT_ADMIN_EMAIL=$ADMIN_EMAIL\" >>.env\necho \"DEFAULT_ADMIN_PASSWORD=$ADMIN_PASSWORD\" >>.env\necho \"DEFAULT_ADMIN_NAME=$ADMIN_NAME\" >>.env\necho \"DEFAULT_ADMIN_USERNAME=$ADMIN_USERNAME\" >>.env\n$STD npm run db:seed\nsed -i '/# Temporary admin user creation settings/,$d' .env\n{\n  echo \"\"\n  echo \"PLANKA Admin Credentials\"\n  echo \"Admin Email: $ADMIN_EMAIL\"\n  echo \"Admin Password: $ADMIN_PASSWORD\"\n  echo \"Admin Name: $ADMIN_NAME\"\n  echo \"Admin Username: $ADMIN_USERNAME\"\n} >>~/planka.creds\nmsg_ok \"Created Admin User\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/planka.service\n[Unit]\nDescription=planka Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/planka\nExecStart=/usr/bin/npm start --prod\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now planka\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/plant-it-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://plant-it.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  redis \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"plantit\" MARIADB_DB_USER=\"plantit_usr\" setup_mariadb_db\nJAVA_VERSION=\"21\" setup_java\nUSE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"plant-it\" \"MDeLuise/plant-it\" \"singlefile\" \"0.10.0\" \"/opt/plant-it/backend\" \"server.jar\"\nfetch_and_deploy_gh_release \"plant-it-front\" \"MDeLuise/plant-it\" \"prebuild\" \"0.10.0\" \"/opt/plant-it/frontend\" \"client.tar.gz\"\n\nmsg_info \"Configured Plant-it\"\nJWT_SECRET=$(openssl rand -base64 24 | tr -d '/+=')\nmkdir -p /opt/plant-it-data\ncat <<EOF >/opt/plant-it/backend/server.env\nMYSQL_HOST=localhost\nMYSQL_PORT=3306\nMYSQL_USERNAME=$MARIADB_DB_USER\nMYSQL_PSW=$MARIADB_DB_PASS\nMYSQL_DATABASE=$MARIADB_DB_NAME\nMYSQL_ROOT_PASSWORD=$MARIADB_DB_PASS\n\nJWT_SECRET=$JWT_SECRET\nJWT_EXP=1\n\nUSERS_LIMIT=-1\nUPLOAD_DIR=/opt/plant-it-data\nAPI_PORT=8080\nFLORACODEX_KEY=\nLOG_LEVEL=DEBUG\nALLOWED_ORIGINS=*\n\nCACHE_TYPE=redis\nCACHE_TTL=86400\nCACHE_HOST=localhost\nCACHE_PORT=6379\nEOF\nmsg_ok \"Configured Plant-it\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/plant-it.service\n[Unit]\nDescription=Plant-it Backend Service\nAfter=syslog.target network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/plant-it/backend\nEnvironmentFile=/opt/plant-it/backend/server.env\nExecStart=/usr/bin/java -jar -Xmx2g server.jar\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now plant-it\n\ncat <<EOF >/etc/nginx/nginx.conf\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    server {\n        listen 3000;\n        server_name localhost;\n\n        root /opt/plant-it/frontend;\n        index index.html;\n\n        location / {\n            try_files \\$uri \\$uri/ /index.html;\n        }\n\n        error_page 404 /404.html;\n        location = /404.html {\n            internal;\n        }\n    }\n}\nEOF\nsystemctl restart nginx\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/plex-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.plex.tv/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting Up Plex Media Server Repository\"\nsetup_deb822_repo \\\n  \"plexmediaserver\" \\\n  \"https://downloads.plex.tv/plex-keys/PlexSign.v2.key\" \\\n  \"https://repo.plex.tv/deb/\" \\\n  \"public\" \\\n  \"main\"\nmsg_ok \"Set Up Plex Media Server Repository\"\n\nmsg_info \"Installing Plex Media Server\"\n$STD apt install -y plexmediaserver\nmsg_ok \"Installed Plex Media Server\"\n\nsetup_hwaccel \"plex\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pocketbase-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pocketbase.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"pocketbase\" \"pocketbase/pocketbase\" \"prebuild\" \"latest\" \"/opt/pocketbase\" \"pocketbase*linux_arm64.zip\"\n\nmsg_info \"Configuring Pocketbase\"\nmkdir -p /opt/pocketbase/{pb_public,pb_migrations,pb_hooks}\nmsg_ok \"Configured Pocketbase\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/pocketbase.service\n[Unit]\nDescription = pocketbase\n\n[Service]\nType           = simple\nLimitNOFILE    = 4096\nRestart        = always\nRestartSec     = 5s\nStandardOutput = append:/opt/pocketbase/errors.log\nStandardError  = append:/opt/pocketbase/errors.log\nExecStart      = /opt/pocketbase/pocketbase serve --http=0.0.0.0:8080\n\n[Install]\nWantedBy = multi-user.target\nEOF\nsystemctl enable -q --now pocketbase\nmsg_ok \"Service created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pocketid-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Snarkenfaugister\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pocket-id/pocket-id\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nread -r -p \"${TAB3}What public URL do you want to use (e.g. pocketid.mydomain.com)? \" public_url\nfetch_and_deploy_gh_release \"pocket-id\" \"pocket-id/pocket-id\" \"singlefile\" \"latest\" \"/opt/pocket-id/\" \"pocket-id-linux-arm64\"\n\nmsg_info \"Configuring Pocket ID\"\nENCRYPTION_KEY=$(openssl rand -base64 32)\n\ncat <<EOF >/opt/pocket-id/.env\nAPP_ENV=production\nAPP_URL=https://${public_url}\nTRUST_PROXY=false\n# MAXMIND_LICENSE_KEY=\nPORT=1411\nHOST=0.0.0.0\nENCRYPTION_KEY=${ENCRYPTION_KEY}\nEOF\nmsg_ok \"Configured Pocket ID\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/pocketid.service\n[Unit]\nDescription=Pocket ID Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nWorkingDirectory=/opt/pocket-id\nEnvironmentFile=/opt/pocket-id/.env\nExecStart=/opt/pocket-id/pocket-id\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nmsg_ok \"Created Service\"\n\nmsg_info \"Starting Service\"\nsystemctl enable -q --now pocketid\nmsg_ok \"Started Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/podman-homeassistant-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.home-assistant.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPORTAINER_LATEST_VERSION=$(get_latest_github_release \"portainer/portainer\")\nPORTAINER_AGENT_LATEST_VERSION=$(get_latest_github_release \"portainer/agent\")\n\nif $STD mount | grep 'on / type zfs' >null && echo \"ZFS\"; then\n  msg_info \"Enabling ZFS support.\"\n  mkdir -p /etc/containers\n  cat <<'EOF' >/usr/local/bin/overlayzfsmount\n#!/bin/sh\nexec /bin/mount -t overlay overlay \"$@\"\nEOF\n  chmod +x /usr/local/bin/overlayzfsmount\n  cat <<'EOF' >/etc/containers/storage.conf\n[storage]\ndriver = \"overlay\"\nrunroot = \"/run/containers/storage\"\ngraphroot = \"/var/lib/containers/storage\"\n\n[storage.options]\npull_options = {enable_partial_images = \"false\", use_hard_links = \"false\", ostree_repos=\"\"}\nmount_program = \"/usr/local/bin/overlayzfsmount\"\n\n[storage.options.overlay]\nmountopt = \"nodev\"\nEOF\nfi\n\nmsg_info \"Installing Podman\"\n$STD apt install -y podman\nsystemctl enable -q --now podman.socket\necho -e 'unqualified-search-registries=[\"docker.io\"]' >>/etc/containers/registries.conf\nmsg_ok \"Installed Podman\"\n\nmkdir -p /etc/containers/systemd\n\nread -r -p \"${TAB3}Would you like to add Portainer? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Portainer $PORTAINER_LATEST_VERSION\"\n  podman volume create portainer_data >/dev/null\n  cat <<EOF >/etc/containers/systemd/portainer.container\n[Unit]\nDescription=Portainer Container\nAfter=network-online.target\n\n[Container]\nImage=docker.io/portainer/portainer-ce:latest\nContainerName=portainer\nPublishPort=8000:8000\nPublishPort=9443:9443\nVolume=/run/podman/podman.sock:/var/run/docker.sock\nVolume=portainer_data:/data\n\n[Service]\nRestart=always\n\n[Install]\nWantedBy=default.target multi-user.target\nEOF\n  systemctl daemon-reload\n  $STD systemctl start portainer\n  msg_ok \"Installed Portainer $PORTAINER_LATEST_VERSION\"\nelse\n  read -r -p \"${TAB3}Would you like to add the Portainer Agent? <y/N> \" prompt\n  if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n    msg_info \"Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION\"\n    cat <<EOF >/etc/containers/systemd/portainer-agent.container\n[Unit]\nDescription=Portainer Agent Container\nAfter=network-online.target\n\n[Container]\nImage=docker.io/portainer/agent:latest\nContainerName=portainer_agent\nPublishPort=9001:9001\nVolume=/run/podman/podman.sock:/var/run/docker.sock\nVolume=/var/lib/containers/storage/volumes:/var/lib/docker/volumes\n\n[Service]\nRestart=always\n\n[Install]\nWantedBy=default.target multi-user.target\nEOF\n    systemctl daemon-reload\n    $STD systemctl start portainer-agent\n    msg_ok \"Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION\"\n  fi\nfi\n\nmsg_info \"Pulling Home Assistant Image\"\n$STD podman pull docker.io/homeassistant/home-assistant:stable\nmsg_ok \"Pulled Home Assistant Image\"\n\nmsg_info \"Installing Home Assistant\"\n$STD podman volume create hass_config\ncat <<EOF >/etc/containers/systemd/homeassistant.container\n[Unit]\nDescription=Home Assistant Container\nAfter=network-online.target\n\n[Container]\nImage=docker.io/homeassistant/home-assistant:stable\nContainerName=homeassistant\nVolume=/dev:/dev\nVolume=hass_config:/config\nVolume=/etc/localtime:/etc/localtime:ro\nVolume=/etc/timezone:/etc/timezone:ro\nNetwork=host\n\n[Service]\nRestart=always\nTimeoutStartSec=300\n\n[Install]\nWantedBy=default.target multi-user.target\nEOF\nsystemctl daemon-reload\n$STD systemctl start homeassistant\nmsg_ok \"Installed Home Assistant\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/podman-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://podman.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPORTAINER_LATEST_VERSION=$(get_latest_github_release \"portainer/portainer\")\nPORTAINER_AGENT_LATEST_VERSION=$(get_latest_github_release \"portainer/agent\")\n\nif $STD mount | grep 'on / type zfs' >null && echo \"ZFS\"; then\n  msg_info \"Enabling ZFS support.\"\n  mkdir -p /etc/containers\n  cat <<'EOF' >/usr/local/bin/overlayzfsmount\n#!/bin/sh\nexec /bin/mount -t overlay overlay \"$@\"\nEOF\n  chmod +x /usr/local/bin/overlayzfsmount\n  cat <<'EOF' >/etc/containers/storage.conf\n[storage]\ndriver = \"overlay\"\nrunroot = \"/run/containers/storage\"\ngraphroot = \"/var/lib/containers/storage\"\n\n[storage.options]\npull_options = {enable_partial_images = \"false\", use_hard_links = \"false\", ostree_repos=\"\"}\nmount_program = \"/usr/local/bin/overlayzfsmount\"\n\n[storage.options.overlay]\nmountopt = \"nodev\"\nEOF\nfi\n\nmsg_info \"Installing Podman\"\n$STD apt install -y podman\nsystemctl enable -q --now podman.socket\necho -e 'unqualified-search-registries=[\"docker.io\"]' >>/etc/containers/registries.conf\nmsg_ok \"Installed Podman\"\n\nmkdir -p /etc/containers/systemd\n\nread -r -p \"${TAB3}Would you like to add Portainer? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Portainer $PORTAINER_LATEST_VERSION\"\n  podman volume create portainer_data >/dev/null\n  cat <<EOF >/etc/containers/systemd/portainer.container\n[Unit]\nDescription=Portainer Container\nAfter=network-online.target\n\n[Container]\nImage=docker.io/portainer/portainer-ce:latest\nContainerName=portainer\nPublishPort=8000:8000\nPublishPort=9443:9443\nVolume=/run/podman/podman.sock:/var/run/docker.sock\nVolume=portainer_data:/data\n\n[Service]\nRestart=always\n\n[Install]\nWantedBy=default.target multi-user.target\nEOF\n  systemctl daemon-reload\n  $STD systemctl start portainer\n  msg_ok \"Installed Portainer $PORTAINER_LATEST_VERSION\"\nelse\n  read -r -p \"${TAB3}Would you like to add the Portainer Agent? <y/N> \" prompt\n  if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n    msg_info \"Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION\"\n    cat <<EOF >/etc/containers/systemd/portainer-agent.container\n[Unit]\nDescription=Portainer Agent Container\nAfter=network-online.target\n\n[Container]\nImage=docker.io/portainer/agent:latest\nContainerName=portainer_agent\nPublishPort=9001:9001\nVolume=/run/podman/podman.sock:/var/run/docker.sock\nVolume=/var/lib/containers/storage/volumes:/var/lib/docker/volumes\n\n[Service]\nRestart=always\n\n[Install]\nWantedBy=default.target multi-user.target\nEOF\n    systemctl daemon-reload\n    $STD systemctl start portainer-agent\n    msg_ok \"Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION\"\n  fi\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/postgresql-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.postgresql.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nread -r -p \"${TAB3}Enter PostgreSQL version (15/16/17/18): \" ver\n[[ $ver =~ ^(15|16|17|18)$ ]] || {\n  echo \"Invalid version\"\n  exit 64\n}\nPG_VERSION=$ver setup_postgresql\n\ncat <<EOF >/etc/postgresql/$ver/main/pg_hba.conf\n# PostgreSQL Client Authentication Configuration File\nlocal   all             postgres                                peer\n# TYPE  DATABASE        USER            ADDRESS                 METHOD\n# \"local\" is for Unix domain socket connections only\nlocal   all             all                                     md5\n# IPv4 local connections:\nhost    all             all             127.0.0.1/32            scram-sha-256\nhost    all             all             0.0.0.0/24              md5\n# IPv6 local connections:\nhost    all             all             ::1/128                 scram-sha-256\nhost    all             all             0.0.0.0/0               md5\n# Allow replication connections from localhost, by a user with the\n# replication privilege.\nlocal   replication     all                                     peer\nhost    replication     all             127.0.0.1/32            scram-sha-256\nhost    replication     all             ::1/128                 scram-sha-256\nEOF\n\ncat <<EOF >/etc/postgresql/$ver/main/postgresql.conf\n# -----------------------------\n# PostgreSQL configuration file\n# -----------------------------\n\n#------------------------------------------------------------------------------\n# FILE LOCATIONS\n#------------------------------------------------------------------------------\n\ndata_directory = '/var/lib/postgresql/$ver/main'       \nhba_file = '/etc/postgresql/$ver/main/pg_hba.conf'     \nident_file = '/etc/postgresql/$ver/main/pg_ident.conf'   \nexternal_pid_file = '/var/run/postgresql/$ver-main.pid'                   \n\n#------------------------------------------------------------------------------\n# CONNECTIONS AND AUTHENTICATION\n#------------------------------------------------------------------------------\n\n# - Connection Settings -\n\nlisten_addresses = '*'                 \nport = 5432                             \nmax_connections = 100                  \nunix_socket_directories = '/var/run/postgresql' \n\n# - SSL -\n\nssl = on\nssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'\nssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'\n\n#------------------------------------------------------------------------------\n# RESOURCE USAGE (except WAL)\n#------------------------------------------------------------------------------\n\nshared_buffers = 128MB                \ndynamic_shared_memory_type = posix      \n\n#------------------------------------------------------------------------------\n# WRITE-AHEAD LOG\n#------------------------------------------------------------------------------\n\nmax_wal_size = 1GB\nmin_wal_size = 80MB\n\n#------------------------------------------------------------------------------\n# REPORTING AND LOGGING\n#------------------------------------------------------------------------------\n\n# - What to Log -\n\nlog_line_prefix = '%m [%p] %q%u@%d '           \nlog_timezone = 'Etc/UTC'\n\n#------------------------------------------------------------------------------\n# PROCESS TITLE\n#------------------------------------------------------------------------------\n\ncluster_name = '$ver/main'                \n\n#------------------------------------------------------------------------------\n# CLIENT CONNECTION DEFAULTS\n#------------------------------------------------------------------------------\n\n# - Locale and Formatting -\n\ndatestyle = 'iso, mdy'\ntimezone = 'Etc/UTC'\nlc_messages = 'C'                      \nlc_monetary = 'C'                       \nlc_numeric = 'C'                        \nlc_time = 'C'                           \ndefault_text_search_config = 'pg_catalog.english'\n\n#------------------------------------------------------------------------------\n# CONFIG FILE INCLUDES\n#------------------------------------------------------------------------------\n\ninclude_dir = 'conf.d'                  \nEOF\n\nsystemctl restart postgresql\nmsg_ok \"Installed PostgreSQL\"\n\nread -r -p \"${TAB3}Would you like to add Adminer? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Adminer\"\n  $STD apt install -y adminer\n  $STD a2enconf adminer\n  systemctl reload apache2\n  msg_ok \"Installed Adminer\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/powerdns-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.powerdns.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.3\" PHP_APACHE=\"YES\" PHP_FPM=\"YES\" PHP_MODULE=\"gettext,tokenizer,sqlite3,ldap\" setup_php\nsetup_deb822_repo \\\n  \"pdns\" \\\n  \"https://repo.powerdns.com/FD380FBB-pub.asc\" \\\n  \"http://repo.powerdns.com/debian\" \\\n  \"trixie-auth-50\"\n\ncat <<EOF >/etc/apt/preferences.d/auth-50\nPackage: pdns-*\nPin: origin repo.powerdns.com\nPin-Priority: 600\nEOF\n\nescape_sql() {\n  printf '%s' \"$1\" | sed \"s/'/''/g\"\n}\n\nmsg_info \"Setting up PowerDNS\"\n$STD apt install -y \\\n  pdns-server \\\n  pdns-backend-sqlite3\nsed -i 's/^launch=$/# launch=/' /etc/powerdns/pdns.conf\nrm -f /etc/powerdns/pdns.d/bind.conf\ncat <<EOF >/etc/powerdns/pdns.d/gsqlite3.conf\nlaunch=gsqlite3\ngsqlite3-database=/opt/poweradmin/powerdns.db\nEOF\nmsg_ok \"Setup PowerDNS\"\n\nfetch_and_deploy_gh_release \"poweradmin\" \"poweradmin/poweradmin\" \"tarball\"\n\nmsg_info \"Setting up Poweradmin\"\nsqlite3 /opt/poweradmin/powerdns.db </opt/poweradmin/sql/poweradmin-sqlite-db-structure.sql\nsqlite3 /opt/poweradmin/powerdns.db </opt/poweradmin/sql/pdns/49/schema.sqlite3.sql\nPA_ADMIN_USERNAME=\"admin\"\nPA_ADMIN_EMAIL=\"admin@example.com\"\nPA_ADMIN_FULLNAME=\"Administrator\"\nPA_ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -d \"=+/\" | cut -c1-16)\nPA_SESSION_KEY=$(openssl rand -base64 75 | tr -dc 'A-Za-z0-9^@#!(){}[]%_\\-+=~' | head -c 50)\nPASSWORD_HASH=$(php -r \"echo password_hash(\\$argv[1], PASSWORD_DEFAULT);\" -- \"${PA_ADMIN_PASSWORD}\" 2>/dev/null)\nsqlite3 /opt/poweradmin/powerdns.db \"INSERT INTO users (username, password, fullname, email, description, perm_templ, active, use_ldap) \\\n  VALUES ('$(escape_sql \"${PA_ADMIN_USERNAME}\")', '$(escape_sql \"${PASSWORD_HASH}\")', '$(escape_sql \"${PA_ADMIN_FULLNAME}\")', \\\n  '$(escape_sql \"${PA_ADMIN_EMAIL}\")', 'System Administrator', 1, 1, 0);\"\n\ncat <<EOF >~/poweradmin.creds\nAdmin Username: ${PA_ADMIN_USERNAME}\nAdmin Password: ${PA_ADMIN_PASSWORD}\nEOF\n\ncat <<EOF >/opt/poweradmin/config/settings.php\n<?php\n\n/**\n * Poweradmin Settings Configuration File\n *\n * Generated by the installer on 2026-02-02 21:01:40\n */\n\nreturn [\n    /**\n     * Database Settings\n     */\n    'database' => [\n        'type' => 'sqlite',\n        'file' => '/opt/poweradmin/powerdns.db',\n    ],\n\n    /**\n     * Security Settings\n     */\n    'security' => [\n        'session_key' => '${PA_SESSION_KEY}',\n    ],\n\n    /**\n     * Interface Settings\n     */\n    'interface' => [\n        'language' => 'en_EN',\n    ],\n\n    /**\n     * DNS Settings\n     */\n    'dns' => [\n        'hostmaster' => 'localhost.lan',\n        'ns1' => '8.8.8.8',\n        'ns2' => '9.9.9.9',\n    ]\n];\nEOF\nrm -rf /opt/poweradmin/install\nmsg_ok \"Setup Poweradmin\"\n\nmsg_info \"Creating Service\"\nrm /etc/apache2/sites-enabled/000-default.conf\ncat <<EOF >/etc/apache2/sites-enabled/poweradmin.conf\n<VirtualHost *:80>\n    ServerName localhost\n    DocumentRoot /opt/poweradmin\n\n    <Directory /opt/poweradmin>\n        Options -Indexes +FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    # For DDNS update functionality\n    RewriteEngine On\n    RewriteRule ^/update(.*)\\$ /dynamic_update.php [L]\n    RewriteRule ^/nic/update(.*)\\$ /dynamic_update.php [L]\n</VirtualHost>\nEOF\n$STD a2enmod rewrite headers\nchown -R www-data:pdns /opt/poweradmin\nchmod 775 /opt/poweradmin\nchown pdns:pdns /opt/poweradmin/powerdns.db\nchmod 664 /opt/poweradmin/powerdns.db\nusermod -aG pdns www-data\n$STD systemctl restart pdns apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/privatebin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://privatebin.info/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  openssl\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.2\" PHP_FPM=\"YES\" setup_php\ncreate_self_signed_cert\nfetch_and_deploy_gh_release \"privatebin\" \"PrivateBin/PrivateBin\" \"tarball\"\n\nmsg_info \"Configuring Environment\"\nmkdir -p /opt/privatebin/data\ncp /opt/privatebin/cfg/conf.sample.php /opt/privatebin/cfg/conf.php\nsed -i \"s|// 'traffic'|'traffic'|g\" /opt/privatebin/cfg/conf.php\nchown -R www-data:www-data /opt/privatebin\nchmod -R 0755 /opt/privatebin/data\nmsg_ok \"Configured Environment\"\n\nmsg_info \"Configuring PHP\"\nsed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php/8.2/fpm/php.ini\nsystemctl restart php8.2-fpm\nmsg_ok \"Configured PHP\"\n\nmsg_info \"Configuring Universal Nginx\"\ncat <<EOF >/etc/nginx/sites-available/privatebin.conf\nserver {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n    return 301 https://\\$host\\$request_uri;\n}\n\nserver {\n    listen 443 ssl default_server;\n    listen [::]:443 ssl default_server;\n    \n    ssl_certificate /etc/ssl/privatebin/privatebin.crt;\n    ssl_certificate_key /etc/ssl/privatebin/privatebin.key;\n    \n    root /opt/privatebin;\n    index index.php;\n\n    location / {\n        try_files \\$uri \\$uri/ /index.php\\$is_args\\$args;\n    }\n\n    location ~ \\.php\\$ {\n        include snippets/fastcgi-php.conf;\n        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name;\n        include fastcgi_params;\n    }\n\n    location ~ /\\.ht {\n        deny all;\n    }\n\n    add_header Strict-Transport-Security \"max-age=63072000; includeSubdomains; preload\";\n    add_header X-Content-Type-Options nosniff;\n    add_header X-Frame-Options \"SAMEORIGIN\";\n    add_header X-XSS-Protection \"1; mode=block\";\n}\nEOF\nln -s /etc/nginx/sites-available/privatebin.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\nsystemctl reload nginx\nmsg_ok \"Nginx Configured\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/profilarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dictionarry-Hub/profilarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3-dev \\\n  libffi-dev \\\n  libssl-dev \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nNODE_VERSION=\"22\" setup_nodejs\n\nmsg_info \"Creating directories\"\nmkdir -p /opt/profilarr \\\n  /config\nmsg_ok \"Created directories\"\n\nfetch_and_deploy_gh_release \"profilarr\" \"Dictionarry-Hub/profilarr\" \"tarball\"\n\nmsg_info \"Installing Python Dependencies\"\ncd /opt/profilarr/backend\n$STD uv venv /opt/profilarr/backend/.venv\nsed 's/==/>=/g' requirements.txt >requirements-relaxed.txt\n$STD uv pip install --python /opt/profilarr/backend/.venv/bin/python -r requirements-relaxed.txt\nrm -f requirements-relaxed.txt\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Building Frontend\"\ncd /opt/profilarr/frontend\n$STD npm install\n$STD npm run build\ncp -r dist /opt/profilarr/backend/app/static\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/profilarr.service\n[Unit]\nDescription=Profilarr - Configuration Management Platform for Radarr/Sonarr\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/profilarr/backend\nEnvironment=\"PATH=/opt/profilarr/backend/.venv/bin:/usr/local/bin:/usr/bin:/bin\"\nEnvironment=\"PYTHONPATH=/opt/profilarr/backend\"\nExecStart=/opt/profilarr/backend/.venv/bin/gunicorn --bind 0.0.0.0:6868 --timeout 600 app.main:create_app()\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now profilarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/projectsend-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.projectsend.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\nsetup_mariadb\nMARIADB_DB_NAME=\"projectsend\" MARIADB_DB_USER=\"projectsend\" setup_mariadb_db\nfetch_and_deploy_gh_release \"projectsend\" \"projectsend/projectsend\" \"prebuild\" \"latest\" \"/opt/projectsend\" \"projectsend-r*.zip\"\n\nmsg_info \"Installing ProjectSend\"\nmv /opt/projectsend/includes/sys.config.sample.php /opt/projectsend/includes/sys.config.php\nchown -R www-data:www-data /opt/projectsend\nchmod -R 775 /opt/projectsend\nchmod 644 /opt/projectsend/includes/sys.config.php\nsed -i -e \"s/\\(define('DB_NAME', \\).*/\\1'$MARIADB_DB_NAME');/\" \\\n  -e \"s/\\(define('DB_USER', \\).*/\\1'$MARIADB_DB_USER');/\" \\\n  -e \"s/\\(define('DB_PASSWORD', \\).*/\\1'$MARIADB_DB_PASS');/\" \\\n  /opt/projectsend/includes/sys.config.php\nsed -i -e \"s/^\\(memory_limit = \\).*/\\1 256M/\" \\\n  -e \"s/^\\(post_max_size = \\).*/\\1 256M/\" \\\n  -e \"s/^\\(upload_max_filesize = \\).*/\\1 256M/\" \\\n  -e \"s/^\\(max_execution_time = \\).*/\\1 300/\" \\\n  /etc/php/8.4/apache2/php.ini\nmsg_ok \"Installed projectsend\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/projectsend.conf\n<VirtualHost *:80>\n    ServerName projectsend\n    DocumentRoot /opt/projectsend\n    <Directory /opt/projectsend>\n        Options FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/projectsend_error.log\n    CustomLog /var/log/apache2/projectsend_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite projectsend\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/prometheus-alertmanager-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prometheus.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"alertmanager\" \"prometheus/alertmanager\" \"prebuild\" \"latest\" \"/usr/local/bin/\" \"alertmanager*linux-arm64.tar.gz\"\n\nmsg_info \"Configuring Prometheus Alertmanager\"\nmkdir -p /etc/alertmanager /var/lib/alertmanager\nmv /usr/local/bin/alertmanager.yml /etc/alertmanager/alertmanager.yml\nmsg_ok \"Configured Prometheus Alertmanager\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/prometheus-alertmanager.service\n[Unit]\nDescription=Prometheus Alertmanager\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nUser=root\nRestart=always\nType=simple\nExecStart=/usr/local/bin/alertmanager \\\n    --config.file=/etc/alertmanager/alertmanager.yml \\\n    --storage.path=/var/lib/alertmanager/ \\\n    --web.listen-address=0.0.0.0:9093\nExecReload=/bin/kill -HUP \\$MAINPID\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now prometheus-alertmanager\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/prometheus-blackbox-exporter-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Marfnl\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/prometheus/blackbox_exporter\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"blackbox-exporter\" \"prometheus/blackbox_exporter\" \"prebuild\" \"latest\" \"/opt/blackbox-exporter\" \"blackbox_exporter-*.linux-arm64.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/blackbox-exporter.service\n[Unit]\nDescription=Blackbox Exporter Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/blackbox-exporter\nExecStart=/opt/blackbox-exporter/blackbox_exporter\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now blackbox-exporter\nmsg_ok \"Service Created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/prometheus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prometheus.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"prometheus\" \"prometheus/prometheus\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"*linux-arm64.tar.gz\"\n\nmsg_info \"Installing Prometheus\"\nmkdir -p /etc/prometheus\nmkdir -p /var/lib/prometheus\nmv /usr/local/bin/prometheus.yml /etc/prometheus/prometheus.yml\nmsg_ok \"Installed Prometheus\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/etc/systemd/system/prometheus.service\n[Unit]\nDescription=Prometheus\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nUser=root\nRestart=always\nType=simple\nExecStart=/usr/local/bin/prometheus \\\n    --config.file=/etc/prometheus/prometheus.yml \\\n    --storage.tsdb.path=/var/lib/prometheus/ \\\n    --web.listen-address=0.0.0.0:9090\n\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now prometheus\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/prometheus-pve-exporter-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/prometheus-pve/prometheus-pve-exporter\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPYTHON_VERSION=\"3.12\" setup_uv\n\nmsg_info \"Installing Prometheus Proxmox VE Exporter\"\nmkdir -p /opt/prometheus-pve-exporter\ncd /opt/prometheus-pve-exporter\n\n$STD uv venv --clear /opt/prometheus-pve-exporter/.venv\n$STD /opt/prometheus-pve-exporter/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/prometheus-pve-exporter/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/prometheus-pve-exporter/.venv/bin/python -m pip install prometheus-pve-exporter\ncat <<EOF >/opt/prometheus-pve-exporter/pve.yml\ndefault:\n    user: prometheus@pve\n    password: sEcr3T!\n    verify_ssl: false\nEOF\nmsg_ok \"Installed Prometheus Proxmox VE Exporter\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/prometheus-pve-exporter.service\n[Unit]\nDescription=Prometheus Proxmox VE Exporter\nDocumentation=https://github.com/znerol/prometheus-pve-exporter\nAfter=syslog.target network.target\n\n[Service]\nUser=root\nRestart=always\nType=simple\nExecStart=/opt/prometheus-pve-exporter/.venv/bin/pve_exporter \\\n    --config.file=/opt/prometheus-pve-exporter/pve.yml \\\n    --web.listen-address=0.0.0.0:9221\nExecReload=/bin/kill -HUP \\$MAINPID\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now prometheus-pve-exporter\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/protonmail-bridge-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Stephen Chin (steveonjava)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ProtonMail/proton-bridge\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y pass\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Creating Service User\"\nuseradd -r -m -d /home/protonbridge -s /usr/sbin/nologin protonbridge\ninstall -d -m 0750 -o protonbridge -g protonbridge /home/protonbridge\nmsg_ok \"Created Service User\"\n\nfetch_and_deploy_gh_release \"protonmail-bridge\" \"ProtonMail/proton-bridge\" \"binary\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/protonmail-bridge.service\n[Unit]\nDescription=Proton Mail Bridge (noninteractive)\nAfter=network-online.target\nWants=network-online.target\nConditionPathExists=/home/protonbridge/.protonmailbridge-initialized\n\n[Service]\nType=simple\nUser=protonbridge\nGroup=protonbridge\nWorkingDirectory=/home/protonbridge\nEnvironment=HOME=/home/protonbridge\nExecStart=/usr/bin/protonmail-bridge --noninteractive\nRestart=always\nRestartSec=3\nNoNewPrivileges=yes\nPrivateTmp=yes\nProtectSystem=full\nProtectKernelTunables=yes\nProtectKernelModules=yes\nProtectControlGroups=yes\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<'EOF' >/etc/systemd/system/protonmail-bridge-imap.socket\n[Unit]\nDescription=Proton Mail Bridge IMAP Socket (143)\nConditionPathExists=/home/protonbridge/.protonmailbridge-initialized\n\n[Socket]\nListenStream=143\nAccept=no\nService=protonmail-bridge-imap-proxy.service\n\n[Install]\nWantedBy=sockets.target\nEOF\ncat <<'EOF' >/etc/systemd/system/protonmail-bridge-imap-proxy.service\n[Unit]\nDescription=Proton Mail Bridge IMAP Proxy (143 -> 127.0.0.1:1143)\nAfter=protonmail-bridge.service\nRequires=protonmail-bridge.service\nConditionPathExists=/home/protonbridge/.protonmailbridge-initialized\n\n[Service]\nType=simple\nSockets=protonmail-bridge-imap.socket\nExecStart=/usr/lib/systemd/systemd-socket-proxyd 127.0.0.1:1143\nNoNewPrivileges=yes\nPrivateTmp=yes\nEOF\ncat <<'EOF' >/etc/systemd/system/protonmail-bridge-smtp.socket\n[Unit]\nDescription=Proton Mail Bridge SMTP Socket (587)\nConditionPathExists=/home/protonbridge/.protonmailbridge-initialized\n\n[Socket]\nListenStream=587\nAccept=no\nService=protonmail-bridge-smtp-proxy.service\n\n[Install]\nWantedBy=sockets.target\nEOF\ncat <<'EOF' >/etc/systemd/system/protonmail-bridge-smtp-proxy.service\n[Unit]\nDescription=Proton Mail Bridge SMTP Proxy (587 -> 127.0.0.1:1025)\nAfter=protonmail-bridge.service\nRequires=protonmail-bridge.service\nConditionPathExists=/home/protonbridge/.protonmailbridge-initialized\n\n[Service]\nType=simple\nSockets=protonmail-bridge-smtp.socket\nExecStart=/usr/lib/systemd/systemd-socket-proxyd 127.0.0.1:1025\nNoNewPrivileges=yes\nPrivateTmp=yes\nEOF\nmsg_ok \"Created Services\"\n\nmsg_info \"Creating Helper Commands\"\n\ncat <<'EOF' >/usr/local/bin/protonmailbridge-configure\n#!/usr/bin/env bash\nset -euo pipefail\n\nBRIDGE_USER=\"protonbridge\"\nBRIDGE_HOME=\"/home/${BRIDGE_USER}\"\nGNUPG_HOME=\"${BRIDGE_HOME}/.gnupg\"\nMARKER=\"${BRIDGE_HOME}/.protonmailbridge-initialized\"\n\nFIRST_TIME=0\nif [[ ! -f \"${MARKER}\" ]]; then\n  FIRST_TIME=1\nfi\n\n# Stop sockets/proxies/bridge daemon before configuration\nsystemctl stop protonmail-bridge-imap.socket protonmail-bridge-smtp.socket\nsystemctl stop protonmail-bridge-imap-proxy protonmail-bridge-smtp-proxy protonmail-bridge\n\nif [[ \"${FIRST_TIME}\" == \"1\" ]]; then\n  echo \"First-time setup: initializing pass keychain for ${BRIDGE_USER} (required by Proton Mail Bridge on Linux).\"\n\n  install -d -m 0700 -o \"${BRIDGE_USER}\" -g \"${BRIDGE_USER}\" \"${GNUPG_HOME}\"\n\n  FPR=\"$(runuser -u \"${BRIDGE_USER}\" -- env HOME=\"${BRIDGE_HOME}\" GNUPGHOME=\"${GNUPG_HOME}\" \\\n    gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: '$1==\"fpr\"{print $10; exit}')\"\n\n  if [[ -z \"${FPR}\" ]]; then\n    runuser -u \"${BRIDGE_USER}\" -- env HOME=\"${BRIDGE_HOME}\" GNUPGHOME=\"${GNUPG_HOME}\" \\\n      gpg --batch --pinentry-mode loopback --passphrase '' \\\n      --quick-gen-key 'ProtonMail Bridge' default default never\n\n    FPR=\"$(runuser -u \"${BRIDGE_USER}\" -- env HOME=\"${BRIDGE_HOME}\" GNUPGHOME=\"${GNUPG_HOME}\" \\\n      gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: '$1==\"fpr\"{print $10; exit}')\"\n  fi\n\n  if [[ -z \"${FPR}\" ]]; then\n    echo \"Failed to detect a GPG key fingerprint for ${BRIDGE_USER}.\" >&2\n    exit 1\n  fi\n\n  runuser -u \"${BRIDGE_USER}\" -- env HOME=\"${BRIDGE_HOME}\" GNUPGHOME=\"${GNUPG_HOME}\" \\\n    pass init \"${FPR}\"\n\n  echo\n  echo \"To do initial configuration of the Proton Mail Bridge:\"\n  echo \"Run: login\"\n  echo \"Run: info\"\n  echo \"Run: exit\"\n  echo\nelse\n  echo\n  echo \"Launching Proton Mail Bridge CLI for configuration.\"\n  echo \"External access is disabled until you exit.\"\n  echo \"Run: exit\"\n  echo\nfi\n\nrunuser -u \"${BRIDGE_USER}\" -- env HOME=\"${BRIDGE_HOME}\" \\\n  protonmail-bridge -c\n\nif [[ \"${FIRST_TIME}\" == \"1\" ]]; then\n  touch \"${MARKER}\"\n  chown \"${BRIDGE_USER}:${BRIDGE_USER}\" \"${MARKER}\"\n  chmod 0644 \"${MARKER}\"\nfi\n\nsystemctl enable -q --now protonmail-bridge.service protonmail-bridge-imap.socket protonmail-bridge-smtp.socket\n\nif [[ \"${FIRST_TIME}\" == \"1\" ]]; then\n  echo \"Initialization complete. Services enabled and started.\"\nelse\n  echo \"Configuration complete. Services enabled and started.\"\nfi\nEOF\nchmod +x /usr/local/bin/protonmailbridge-configure\nln -sf /usr/local/bin/protonmailbridge-configure /usr/bin/protonmailbridge-configure\nmsg_ok \"Created Helper Commands\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/prowlarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://prowlarr.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3 libicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"prowlarr\" \"Prowlarr/Prowlarr\" \"prebuild\" \"latest\" \"/opt/Prowlarr\" \"Prowlarr.master*linux-core-arm64.tar.gz\"\n\nmsg_info \"Configuring Prowlarr\"\nmkdir -p /var/lib/prowlarr/\nchmod 775 /var/lib/prowlarr/ /opt/Prowlarr\nmsg_ok \"Configured Prowlarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/prowlarr.service\n[Unit]\nDescription=Prowlarr Daemon\nAfter=syslog.target network.target\n\n[Service]\nUMask=0002\nType=simple\nExecStart=/opt/Prowlarr/Prowlarr -nobrowser -data=/var/lib/prowlarr/\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now prowlarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/proxmox-backup-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.proxmox.com/en/proxmox-backup-server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y git\n$STD apt-get install -y dpkg-dev\n\nmsg_info \"Installing Proxmox Backup Server (Patience)\"\nexport DEBIAN_FRONTEND=noninteractive\ngit clone https://github.com/wofferl/proxmox-backup-arm64\ncd ./proxmox-backup-arm64\n$STD ./build.sh install\n$STD apt install -y -f ./packages/*\nmsg_ok \"Installed Proxmox Backup Server\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/proxmox-datacenter-manager-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: Proxmox Server Solution GmbH\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y rsyslog\nsystemctl enable -q --now rsyslog\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Proxmox Datacenter Manager\"\ncurl -fsSL https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg -o /usr/share/keyrings/proxmox-archive-keyring.gpg\nsetup_deb822_repo \\\n  \"pdm\" \\\n  \"https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg\" \\\n  \"http://download.proxmox.com/debian/pdm\" \\\n  \"trixie\" \\\n  \"pdm-no-subscription\"\n\nsetup_deb822_repo \\\n  \"pdm-test\" \\\n  \"https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg\" \\\n  \"http://download.proxmox.com/debian/pdm\" \\\n  \"trixie\" \\\n  \"pdm-test\" \\\n  \"\" \\\n  \"false\"\n\ncat <<'EOF' > /etc/apt/preferences.d/99-pdm-unneeded-packages\nPackage: proxmox-default-kernel proxmox-kernel-* pve-firmware\nPin: release *\nPin-Priority: -1\nEOF\n\nDEBIAN_FRONTEND=noninteractive\n$STD apt -o Dpkg::Options::=\"--force-confdef\" \\\n  -o Dpkg::Options::=\"--force-confold\" \\\n  install -y proxmox-datacenter-manager \\\n  proxmox-datacenter-manager-ui \\\n  proxmox-mail-forward \\\n  proxmox-offline-mirror-helper\nmsg_ok \"Installed Proxmox Datacenter Manager\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/proxmox-mail-gateway-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.proxmox.com/en/products/proxmox-mail-gateway\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Proxmox Mail Gateway\"\nsetup_deb822_repo \\\n  \"pmg\" \\\n  \"https://enterprise.proxmox.com/debian/proxmox-release-trixie.gpg\" \\\n  \"http://download.proxmox.com/debian/pmg\" \\\n  \"trixie\" \\\n  \"pmg-no-subscription\"\n$STD apt install -y proxmox-mailgateway-container\nmsg_ok \"Installed Proxmox Mail Gateway\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ps5-mqtt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: liecno\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/FunkeyFlo/ps5-mqtt/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  jq \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"playactor\" setup_nodejs\nfetch_and_deploy_gh_release \"ps5-mqtt\" \"FunkeyFlo/ps5-mqtt\" \"tarball\"\n\nmsg_info \"Configuring PS5-MQTT\"\ncd /opt/ps5-mqtt/ps5-mqtt/\n$STD npm install\n$STD npm run build\nmkdir -p /opt/.config/ps5-mqtt/\nmkdir -p /opt/.config/ps5-mqtt/playactor\ncat <<EOF >/opt/.config/ps5-mqtt/config.json\n{\n  \"mqtt\": {\n      \"host\": \"\",\n      \"port\": \"\",\n      \"user\": \"\",\n      \"pass\": \"\",\n      \"discovery_topic\": \"homeassistant\"\n  },\n\n  \"device_check_interval\": 5000,\n  \"device_discovery_interval\": 60000,\n  \"device_discovery_broadcast_address\": \"\",\n\n  \"include_ps4_devices\": false,\n\n  \"psn_accounts\": [\n    {\n      \"username\": \"\",\n      \"npsso\":\"\"\n    }\n  ],\n\n  \"account_check_interval\": 5000,\n\n  \"credentialsStoragePath\": \"/opt/.config/ps5-mqtt/credentials.json\",\n  \"frontendPort\": \"8645\"\n}\nEOF\nmsg_ok \"Configured PS5-MQTT\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/ps5-mqtt.service\n[Unit]\nDescription=PS5-MQTT Daemon\nAfter=syslog.target network.target\n\n[Service]\nWorkingDirectory=/opt/ps5-mqtt/ps5-mqtt\nEnvironment=\"CONFIG_PATH=/opt/.config/ps5-mqtt/config.json\"\nEnvironment=\"DEBUG='@ha:ps5:*'\"\nRestart=always\nRestartSec=5\nType=simple\nExecStart=node server/dist/index.js\nKillMode=process\nSyslogIdentifier=ps5-mqtt\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now ps5-mqtt\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pterodactyl-panel-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pterodactyl/panel\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  lsb-release \\\n  redis \\\n  apache2 \\\n  composer \\\n  cron\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\n\nmsg_info \"Adding PHP Repository\"\n$STD curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb\n$STD dpkg -i /tmp/debsuryorg-archive-keyring.deb\ncat <<EOF >/etc/apt/sources.list.d/php.sources\nTypes: deb\nURIs: https://packages.sury.org/php/\nSuites: $(lsb_release -sc)\nComponents: main\nSigned-By: /usr/share/keyrings/deb.sury.org-php.gpg\nEOF\n$STD apt update\nmsg_ok \"Added PHP Repository\"\n\nmsg_info \"Installing PHP\"\n$STD apt remove -y php8.2*\n$STD apt install -y \\\n  php8.4 \\\n  php8.4-{gd,mysql,mbstring,bcmath,xml,curl,zip,intl,fpm} \\\n  libapache2-mod-php8.4\nmsg_ok \"Installed PHP\"\n\nmsg_info \"Setting up MariaDB\"\nDB_NAME=panel\nDB_USER=pterodactyl\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD mariadb -u root -e \"CREATE DATABASE $DB_NAME;\"\n$STD mariadb -u root -e \"CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';\"\n$STD mariadb -u root -e \"GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;\"\n{\n  echo \"pterodactyl Panel-Credentials\"\n  echo \"pterodactyl Panel Database User: $DB_USER\"\n  echo \"pterodactyl Panel Database Password: $DB_PASS\"\n  echo \"pterodactyl Panel Database Name: $DB_NAME\"\n} >>~/pterodactyl-panel.creds\nmsg_ok \"Set up MariaDB\"\n\nread -p \"${TAB3}Provide an email address for admin login, this should be a valid email address: \" ADMIN_EMAIL\nread -p \"${TAB3}Enter your First Name: \" NAME_FIRST\nread -p \"${TAB3}Enter your Last Name: \" NAME_LAST\n\nmsg_info \"Installing pterodactyl Panel\"\nRELEASE=$(curl -fsSL https://api.github.com/repos/pterodactyl/panel/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\nmkdir /opt/pterodactyl-panel\ncd /opt/pterodactyl-panel\ncurl -fsSL \"https://github.com/pterodactyl/panel/releases/download/v${RELEASE}/panel.tar.gz\" -o \"panel.tar.gz\"\ntar -xzf \"panel.tar.gz\"\ncp .env.example .env\nADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD composer install --no-dev --optimize-autoloader --no-interaction\n$STD php artisan key:generate --force\n$STD php artisan p:environment:setup --no-interaction --author \"$ADMIN_EMAIL\" --url \"http://$LOCAL_IP\"\n$STD php artisan p:environment:database --no-interaction --database $DB_NAME --username $DB_USER --password \"$DB_PASS\"\n$STD php artisan migrate --seed --force --no-interaction\n$STD php artisan p:user:make --no-interaction --admin=1 --email \"$ADMIN_EMAIL\" --password \"$ADMIN_PASS\" --name-first \"$NAME_FIRST\" --name-last \"$NAME_LAST\" --username \"admin\"\necho \"* * * * * php /opt/pterodactyl-panel/artisan schedule:run >> /dev/null 2>&1\" | crontab -u www-data -\nchown -R www-data:www-data /opt/pterodactyl-panel/*\nchmod -R 755 /opt/pterodactyl-panel/storage/* /opt/pterodactyl-panel/bootstrap/cache/\nln -s /opt/pterodactyl-panel /var/www/pterodactyl\n{\n  echo \"\"\n  echo \"pterodactyl Admin Username: admin\"\n  echo \"pterodactyl Admin Email: $ADMIN_EMAIL\"\n  echo \"pterodactyl Admin Password: $ADMIN_PASS\"\n} >>~/pterodactyl-panel.creds\nrm -rf \"/opt/pterodactyl-panel/panel.tar.gz\"\nrm -rf \"/tmp/debsuryorg-archive-keyring.deb\"\necho \"${RELEASE}\" >/opt/\"${APPLICATION}\"_version.txt\nmsg_ok \"Installed pterodactyl Panel\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/pteroq.service\n[Unit]\nDescription=Pterodactyl Queue Worker\nAfter=redis-server.service\n\n[Service]\nUser=www-data\nGroup=www-data\nRestart=always\nExecStart=/usr/bin/php /opt/pterodactyl-panel/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3\nStartLimitInterval=180\nStartLimitBurst=30\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now pteroq\ncat <<EOF >/etc/apache2/sites-available/pterodactyl.conf\n<VirtualHost *:80>\n    ServerName pterodactyl\n    DocumentRoot /opt/pterodactyl-panel/public\n\n    AllowEncodedSlashes On\n    \n    php_value upload_max_filesize 100M\n    php_value post_max_size 100M\n\n    <Directory /opt/pterodactyl-panel/public>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/pterodactyl_error.log\n    CustomLog /var/log/apache2/pterodactyl_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite pterodactyl\n$STD a2enmod rewrite\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pterodactyl-wings-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/pterodactyl/wings\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Docker\"\nDOCKER_CONFIG_PATH='/etc/docker/daemon.json'\nmkdir -p $(dirname $DOCKER_CONFIG_PATH)\necho -e '{\\n  \"log-driver\": \"journald\"\\n}' >/etc/docker/daemon.json\n$STD sh <(curl -fsSL https://get.docker.com)\nsystemctl enable -q --now docker\nmsg_ok \"Installed Docker\"\n\nfetch_and_deploy_gh_release \"wings\" \"pterodactyl/wings\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"wings_linux_arm64\"\nmkdir -p /etc/pterodactyl\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wings.service\n[Unit]\nDescription=Pterodactyl Wings Daemon\nAfter=docker.service\nRequires=docker.service\nPartOf=docker.service\n\n[Service]\nUser=root\nWorkingDirectory=/etc/pterodactyl\nLimitNOFILE=4096\nPIDFile=/var/run/wings/daemon.pid\nExecStart=/usr/local/bin/wings\nRestart=on-failure\nStartLimitInterval=180\nStartLimitBurst=30\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now wings\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pulse-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rcourtman & vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rcourtman/Pulse\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  diffutils \\\n  polkitd\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Creating User\"\nif useradd -r -m -d /opt/pulse-home -s /usr/sbin/nologin pulse; then\n  msg_ok \"Created User\"\nelse\n  msg_error \"User creation failed\"\n  exit 71\nfi\n\nmkdir -p /etc/pulse\nfetch_and_deploy_gh_release \"pulse\" \"rcourtman/Pulse\" \"prebuild\" \"latest\" \"/opt/pulse\" \"pulse-v*-linux-arm64.tar.gz\"\nln -sf /opt/pulse/bin/pulse /usr/local/bin/pulse\nchown -R pulse:pulse /etc/pulse /opt/pulse\nmsg_ok \"Installed Pulse\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/pulse.service\n[Unit]\nDescription=Pulse Monitoring Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=pulse\nGroup=pulse\nWorkingDirectory=/opt/pulse\nExecStart=/opt/pulse/bin/pulse\nRestart=always\nRestartSec=3\nStandardOutput=journal\nStandardError=journal\nEnvironment=\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\nEnvironment=\"PULSE_DATA_DIR=/etc/pulse\"\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now pulse\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/pve-scripts-local-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  sshpass \\\n  rsync \\\n  expect\nmsg_ok \"Dependencies installed.\"\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"ProxmoxVE-Local\" \"community-scripts/ProxmoxVE-Local\" \"tarball\"\n\nmsg_info \"Installing PVE Scripts local\"\ncd /opt/ProxmoxVE-Local\n$STD npm install\ncp .env.example .env\nmkdir -p data\nchmod 755 data\n\n$STD npx prisma generate\n$STD npx prisma migrate deploy\n\n$STD npm run build\nmsg_ok \"Installed PVE Scripts local\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/pvescriptslocal.service\n[Unit]\nDescription=PVEScriptslocal Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/ProxmoxVE-Local\nExecStart=/usr/bin/npm start\nRestart=always\nRestartSec=10\nEnvironment=NODE_ENV=production\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now pvescriptslocal\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/qbittorrent-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.qbittorrent.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"qbittorrent\" \"userdocs/qbittorrent-nox-static\" \"singlefile\" \"latest\" \"/opt/qbittorrent\" \"aarch64-qbittorrent-nox\"\n\nmsg_info \"Setup qBittorrent-nox\"\nmv /opt/qbittorrent/qbittorrent /opt/qbittorrent/qbittorrent-nox\nmkdir -p ~/.config/qBittorrent/\ncat <<EOF >~/.config/qBittorrent/qBittorrent.conf\n[LegalNotice]\nAccepted=true\n\n[Preferences]\nWebUI\\Password_PBKDF2=\"@ByteArray(amjeuVrF3xRbgzqWQmes5A==:XK3/Ra9jUmqUc4RwzCtrhrkQIcYczBl90DJw2rT8DFVTss4nxpoRhvyxhCf87ahVE3SzD8K9lyPdpyUCfmVsUg==)\"\nWebUI\\Port=8090\nWebUI\\UseUPnP=false\nWebUI\\Username=admin\n\n[Network]\nPortForwardingEnabled=false\nEOF\nmsg_ok \"Setup qBittorrent-nox\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/qbittorrent-nox.service\n[Unit]\nDescription=qBittorrent client\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nExecStart=/opt/qbittorrent/qbittorrent-nox\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now qbittorrent-nox\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/qdrant-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/qdrant/qdrant\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"qdrant\" \"qdrant/qdrant\" \"prebuild\" \"latest\" \"/opt/qdrant\" \"qdrant-aarch64-unknown-linux-musl.tar.gz\"\n$STD mv /opt/qdrant/qdrant /usr/bin/qdrant\n$STD rm -rf /opt/qdrant\n\nmsg_info \"Creating Qdrant Configuration\"\nmkdir -p /etc/qdrant\nmkdir -p /var/lib/qdrant/{storage,snapshots}\nchown -R root:root /var/lib/qdrant\nchmod -R 755 /var/lib/qdrant\n\ncat <<EOF >/etc/qdrant/config.yaml\nlog_level: INFO\n\nstorage:\n  storage_path: /var/lib/qdrant/storage\n  snapshots_path: /var/lib/qdrant/snapshots\n\nservice:\n  host: 0.0.0.0\n  http_port: 6333\n  grpc_port: 6334\n  enable_cors: true\nEOF\nmsg_ok \"Created Qdrant Configuration\"\n\nmsg_info \"Creating Qdrant Service\"\ncat <<EOF >/etc/systemd/system/qdrant.service\n[Unit]\nDescription=Qdrant Vector Search Engine\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/qdrant --config-path /etc/qdrant/config.yaml\nWorkingDirectory=/var/lib/qdrant\nRestart=on-failure\nRestartSec=5\nUser=root\nLimitNOFILE=1048576\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now qdrant\nmsg_ok \"Created Qdrant Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/qui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/autobrr/qui\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"qui\" \"autobrr/qui\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"qui_*_linux_aarch64.tar.gz\"\nchmod +x /usr/local/bin/qui\nln -sf /usr/local/bin/qui /usr/bin/qui\nln -sf /usr/local/bin/qui /opt/qui\n\nmsg_info \"Creating Qui Service\"\ncat <<EOF >/etc/systemd/system/qui.service\n[Unit]\nDescription=Qui - qBittorrent Web UI\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/qui serve\nRestart=on-failure\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now qui\nmsg_ok \"Created Qui Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/rabbitmq-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.rabbitmq.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  lsb-release \\\n  apt-transport-https \\\n  make \\\n  software-properties-common\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Adding RabbitMQ signing key\"\n# primary RabbitMQ signing key\ncurl -1sLf \"https://github.com/rabbitmq/signing-keys/releases/download/3.0/rabbitmq-release-signing-key.asc\" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.github.rabbitmq.signing.gpg > /dev/null\n\n# Launchpad PPA signing key for apt\ncurl -1sLf \"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xf77f1eda57ebb1cc\" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg > /dev/null\nmsg_ok \"Signing keys added\"\n\nmsg_info \"Adding RabbitMQ repository\"\ncat <<EOF >/etc/apt/sources.list.d/rabbitmq.list\ndeb [arch=arm64 signed-by=/usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg] http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu noble main\ndeb-src [signed-by=/usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg] http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu noble main\nEOF\n$STD add-apt-repository -y ppa:rabbitmq/rabbitmq-erlang\nmsg_ok \"RabbitMQ repository added\"\n\nmsg_info \"Updating package list\"\n$STD apt update -y\nmsg_ok \"Package list updated\"\n\nmsg_info \"Installing Erlang & RabbitMQ server\"\n$STD apt install -y erlang-base \\\n  erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \\\n  erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \\\n  erlang-runtime-tools erlang-snmp erlang-ssl \\\n  erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl \\\n  rabbitmq-server\nmsg_ok \"RabbitMQ server installed\"\n\nmsg_info \"Starting RabbitMQ service\"\nsystemctl enable -q --now rabbitmq-server\nmsg_ok \"Started Service\"\n\nmsg_info \"Enabling RabbitMQ Management Plugin\"\n$STD rabbitmq-plugins enable rabbitmq_management\n$STD rabbitmqctl enable_feature_flag all\nmsg_ok \"Enabled RabbitMQ Management Plugin\"\n\nmsg_info \"Creating User\"\n$STD rabbitmqctl add_user proxmox proxmox\n$STD rabbitmqctl set_user_tags proxmox administrator\n$STD rabbitmqctl set_permissions -p / proxmox \".*\" \".*\" \".*\"\nmsg_ok \"Created User\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/radarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://radarr.video/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3 libicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"Radarr\" \"Radarr/Radarr\" \"prebuild\" \"latest\" \"/opt/Radarr\" \"Radarr.master*linux-core-arm64.tar.gz\"\n\nmsg_info \"Configuring Radarr\"\nmkdir -p /var/lib/radarr/\nchmod 775 /var/lib/radarr/ /opt/Radarr/\nmsg_ok \"Configured Radarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/radarr.service\n[Unit]\nDescription=Radarr Daemon\nAfter=syslog.target network.target\n\n[Service]\nUMask=0002\nType=simple\nExecStart=/opt/Radarr/Radarr -nobrowser -data=/var/lib/radarr/\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now radarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/radicale-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://radicale.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apache2-utils\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nfetch_and_deploy_gh_release \"Radicale\" \"Kozea/Radicale\" \"tarball\" \"latest\" \"/opt/radicale\"\n\nmsg_info \"Setting up Radicale\"\ncd /opt/radicale\nRNDPASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD htpasswd -c -b -5 /opt/radicale/users admin \"$RNDPASS\"\n{\n  echo \"Radicale Credentials\"\n  echo \"Admin User: admin\"\n  echo \"Admin Password: $RNDPASS\"\n} >>~/radicale.creds\n\nmkdir -p /etc/radicale\ncat <<EOF >/etc/radicale/config\n[server]\nhosts = 0.0.0.0:5232\n\n[auth]\ntype = htpasswd\nhtpasswd_filename = /opt/radicale/users\nhtpasswd_encryption = sha512\n\n[storage]\ntype = multifilesystem\nfilesystem_folder = /var/lib/radicale/collections\n\n[web]\ntype = internal\nEOF\nmsg_ok \"Set up Radicale\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/radicale.service\n[Unit]\nDescription=A simple CalDAV (calendar) and CardDAV (contact) server\nAfter=network.target\nRequires=network.target\n\n[Service]\nWorkingDirectory=/opt/radicale\nExecStart=/usr/local/bin/uv run -m radicale --config /etc/radicale/config\nRestart=on-failure\n# User=radicale\n# Deny other users access to the calendar data\n# UMask=0027\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now radicale\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/rclone-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rclone/rclone\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apache2-utils fuse3\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"rclone\" \"rclone/rclone\" \"prebuild\" \"latest\" \"/opt/rclone\" \"rclone*linux-arm64.zip\"\n\nmsg_info \"Installing rclone\"\ncd /opt/rclone\nRCLONE_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD htpasswd -cb -B /opt/login.pwd admin \"$RCLONE_PASSWORD\"\n{\n  echo \"rclone-Credentials\"\n  echo \"rclone User Name: admin\"\n  echo \"rclone Password: $RCLONE_PASSWORD\"\n} >>~/rclone.creds\nmsg_ok \"Installed rclone\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/rclone-web.service\n[Unit]\nDescription=Rclone Web GUI\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/rclone\nExecStart=/opt/rclone/rclone rcd --rc-web-gui --rc-web-gui-no-open-browser --rc-addr :3000 --rc-htpasswd /opt/login.pwd\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now rclone-web\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/rdtclient-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rogerfar/rdt-client\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y unzip\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing ASP.NET Core Runtime\"\n$STD apt-get install -y libc6\n$STD apt-get install -y libgcc1\n$STD apt-get install -y libgssapi-krb5-2\n$STD apt-get install -y libicu72\n$STD apt-get install -y liblttng-ust1\n$STD apt-get install -y libssl3\n$STD apt-get install -y libstdc++6\n$STD apt-get install -y zlib1g\n\ncurl -SL -o dotnet.tar.gz https://download.visualstudio.microsoft.com/download/pr/6f79d99b-dc38-4c44-a549-32329419bb9f/a411ec38fb374e3a4676647b236ba021/dotnet-sdk-9.0.100-linux-arm64.tar.gz\nmkdir -p /usr/share/dotnet\n$STD tar -zxf dotnet.tar.gz -C /usr/share/dotnet\n$STD ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet\nmsg_ok \"Installed ASP.NET Core Runtime\"\n\nfetch_and_deploy_gh_release \"rdt-client\" \"rogerfar/rdt-client\" \"prebuild\" \"latest\" \"/opt/rdtc\" \"RealDebridClient.zip\"\n\nmsg_info \"Setting up rdtclient\"\ncd /opt/rdtc\nmkdir -p data/{db,downloads}\nsed -i 's#/data/db/#/opt/rdtc&#g' /opt/rdtc/appsettings.json\nmsg_ok \"Configured rdtclient\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/rdtc.service\n[Unit]\nDescription=RdtClient Service\n\n[Service]\nWorkingDirectory=/opt/rdtc\nExecStart=/usr/bin/dotnet RdtClient.Web.dll\nSyslogIdentifier=RdtClient\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now rdtc\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/reactive-resume-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://rxresume.org\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"reactive_resume\" PG_DB_USER=\"reactive_resume\" setup_postgresql_db\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Installing Dependencies\"\ncd /tmp\ncurl -fsSL https://dl.min.io/server/minio/release/linux-arm64/minio.deb -o minio.deb\n$STD dpkg -i minio.deb\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"reactive-resume\" \"amruthpillai/reactive-resume\" \"tarball\"\n\nmsg_info \"Building Reactive Resume (Patience)\"\ncd /opt/reactive-resume\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\ncorepack enable\ncorepack prepare --activate\nexport NODE_ENV=\"production\"\nexport CI=\"true\"\n$STD pnpm install --frozen-lockfile\n$STD pnpm run build\nmkdir -p /opt/reactive-resume/data\nmsg_ok \"Built Reactive Resume\"\n\nmsg_info \"Installing Browserless (Patience)\"\ncd /tmp\ncurl -fsSL https://github.com/browserless/browserless/archive/refs/tags/v\"$TAG\".zip -o v\"$TAG\".zip\n$STD unzip v\"$TAG\".zip\nmv browserless-\"$TAG\" /opt/browserless\ncd /opt/browserless\n$STD npm install\nrm -rf src/routes/{chrome,edge,firefox,webkit}\n$STD npm install typescript -g\n$STD node_modules/playwright-core/cli.js install --with-deps chromium\n$STD npm install typescript --save-dev\n$STD npm install esbuild --save-dev\n$STD npm run build\n$STD npm run build:function\n$STD npm prune production\nmsg_ok \"Installed Browserless\"\n\nAUTH_SECRET=$(openssl rand -hex 32)\n\ncat <<EOF >/opt/reactive-resume/.env\n# Reactive Resume v5 Configuration\nNODE_ENV=production\nPORT=3000\n\n# Public URL (change to your FQDN when using a reverse proxy)\nAPP_URL=http://${LOCAL_IP}:3000\n\n# Database\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\n\n# Authentication Secret (do not change after initial setup)\nAUTH_SECRET=${AUTH_SECRET}\n\n# Printer (headless Chromium for PDF generation)\nPRINTER_ENDPOINT=http://localhost:9222\n\n# Storage: uses local filesystem (/opt/reactive-resume/data) when S3 is not configured\n# S3_ACCESS_KEY_ID=\n# S3_SECRET_ACCESS_KEY=\n# S3_REGION=us-east-1\n# S3_ENDPOINT=\n# S3_BUCKET=\n# S3_FORCE_PATH_STYLE=false\n\n# Email (optional, logs to console if not configured)\n# SMTP_HOST=\n# SMTP_PORT=465\n# SMTP_USER=\n# SMTP_PASS=\n# SMTP_FROM=Reactive Resume <noreply@localhost>\n\n# OAuth (optional)\n# GITHUB_CLIENT_ID=\n# GITHUB_CLIENT_SECRET=\n# GOOGLE_CLIENT_ID=\n# GOOGLE_CLIENT_SECRET=\n\n# Feature Flags\n# FLAG_DISABLE_SIGNUPS=false\n# FLAG_DISABLE_EMAIL_AUTH=false\nEOF\nmsg_ok \"Configured Reactive Resume\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/chromium-printer.service\n[Unit]\nDescription=Headless Chromium for Reactive Resume PDF generation\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/chromium --headless --disable-gpu --no-sandbox --disable-dev-shm-usage --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/reactive-resume.service\n[Unit]\nDescription=Reactive Resume\nAfter=network.target postgresql.service chromium-printer.service\nWants=postgresql.service chromium-printer.service\n\n[Service]\nWorkingDirectory=/opt/reactive-resume\nEnvironmentFile=/opt/reactive-resume/.env\nExecStart=/usr/bin/node .output/server/index.mjs\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl daemon-reload\nsystemctl enable -q --now chromium-printer.service reactive-resume.service\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/readarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://readarr.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3 libicu76\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Readarr\"\nmkdir -p /var/lib/readarr/\nchmod 775 /var/lib/readarr/\ncd /var/lib/readarr/\n$STD curl -fsSL 'https://readarr.servarr.com/v1/update/develop/updatefile?os=linux&runtime=netcore&arch=arm64' -o readarr.tar.gz\n$STD tar -xvzf readarr.tar.gz\nmv Readarr /opt\nchmod 775 /opt/Readarr\nrm -rf Readarr.develop.*.tar.gz\nmsg_ok \"Installed Readarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/readarr.service\n[Unit]\nDescription=Readarr Daemon\nAfter=syslog.target network.target\n[Service]\nUMask=0002\nType=simple\nExecStart=/opt/Readarr/Readarr -nobrowser -data=/var/lib/readarr/\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl -q daemon-reload\nsystemctl enable --now -q readarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/readeck-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://readeck.org/en/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_codeberg_release \"readeck\" \"readeck/readeck\" \"singlefile\" \"latest\" \"/opt/readeck\" \"readeck-*-linux-arm64\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/readeck.service\n[Unit]\nDescription=Readeck Service\nAfter=network.target\n\n[Service]\nEnvironment=READECK_SERVER_HOST=0.0.0.0\nEnvironment=READECK_SERVER_PORT=8000\nExecStart=/opt/readeck/./readeck serve\nWorkingDirectory=/opt/readeck\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now readeck\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/recyclarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MrYadro\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://recyclarr.dev/wiki/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git libicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"recyclarr\" \"recyclarr/recyclarr\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"recyclarr-linux-arm64.tar.xz\"\n\nmsg_info \"Configuring Recyclarr\"\nmkdir -p /root/.config/recyclarr/{configs,includes}\n$STD recyclarr config create\nmsg_ok \"Configured Recyclarr\"\n\nmsg_info \"Setting up Daily Sync Cron\"\ncat <<EOF >/etc/cron.d/recyclarr\n# Run recyclarr sync daily\n@daily root /usr/local/bin/recyclarr sync >> /root/.config/recyclarr/sync.log 2>&1\nEOF\nchmod 644 /etc/cron.d/recyclarr\nmsg_ok \"Setup Daily Sync Cron\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/redis-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://redis.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up Redis Repository\"\nsetup_deb822_repo \\\n  \"redis\" \\\n  \"https://packages.redis.io/gpg\" \\\n  \"https://packages.redis.io/deb\" \\\n  \"trixie\"\nmsg_ok \"Setup Redis Repository\"\n\nmsg_info \"Setting up Redis\"\n$STD apt install -y redis\nsed -i 's/^bind .*/bind 0.0.0.0/' /etc/redis/redis.conf\nsystemctl enable -q --now redis-server\nmsg_ok \"Setup Redis\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/reitti-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dedicatedcode/reitti\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  redis-server \\\n  libpq-dev \\\n  zstd \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"25\" setup_java\nPG_VERSION=\"17\" PG_MODULES=\"postgis\" setup_postgresql\nPG_DB_NAME=\"reitti_db\" PG_DB_USER=\"reitti\" PG_DB_EXTENSIONS=\"postgis\" setup_postgresql_db\n\nUSE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"reitti\" \"dedicatedcode/reitti\" \"singlefile\" \"latest\" \"/opt/reitti\" \"reitti-app.jar\"\nmv /opt/reitti/reitti-*.jar /opt/reitti/reitti.jar\n\nmsg_info \"Installing Nginx Tile Cache\"\nmkdir -p /var/cache/nginx/tiles\ncat <<EOF >/etc/nginx/nginx.conf\nuser www-data;\n\nevents {\n  worker_connections 1024;\n}\nhttp {\n  proxy_cache_path /var/cache/nginx/tiles levels=1:2 keys_zone=tiles:10m max_size=1g inactive=30d use_temp_path=off;\n  server {\n    listen 80;\n    location / {\n      proxy_pass https://tile.openstreetmap.org/;\n      proxy_set_header Host tile.openstreetmap.org;\n      proxy_set_header User-Agent \"Reitti/1.0\";\n      proxy_cache tiles;\n      proxy_cache_valid 200 30d;\n      proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;\n    }\n  }\n}\nEOF\nchown -R www-data:www-data /var/cache/nginx\nchmod -R 750 /var/cache/nginx\nsystemctl restart nginx\nmsg_ok \"Installed Nginx Tile Cache\"\n\nmsg_info \"Creating Reitti Configuration-File\"\nmkdir -p /opt/reitti/data\ncat <<EOF >/opt/reitti/application.properties\n# Server configuration\nserver.port=8080\nserver.servlet.context-path=/\nserver.forward-headers-strategy=framework\nserver.compression.enabled=true\nserver.compression.min-response-size=1024\nserver.compression.mime-types=text/plain,application/json\n\n# Logging configuration\nlogging.level.root=INFO\nlogging.level.org.hibernate.engine.jdbc.spi.SqlExceptionHelper=FATAL\nlogging.level.com.dedicatedcode.reitti=INFO\n\n# Internationalization\nspring.messages.basename=messages\nspring.messages.encoding=UTF-8\nspring.messages.cache-duration=3600\nspring.messages.fallback-to-system-locale=false\n\n# PostgreSQL configuration\nspring.datasource.url=jdbc:postgresql://127.0.0.1:5432/$PG_DB_NAME\nspring.datasource.username=$PG_DB_USER\nspring.datasource.password=$PG_DB_PASS\nspring.datasource.hikari.maximum-pool-size=20\n\n# Redis configuration\nspring.data.redis.host=127.0.0.1\nspring.data.redis.port=6379\nspring.data.redis.username=\nspring.data.redis.password=\nspring.data.redis.database=0\nspring.cache.redis.key-prefix=\n\nspring.cache.cache-names=processed-visits,significant-places,users,magic-links,configurations,transport-mode-configs,avatarThumbnails,avatarData,user-settings\nspring.cache.redis.time-to-live=1d\n\n# Upload configuration\nspring.servlet.multipart.max-file-size=5GB\nspring.servlet.multipart.max-request-size=5GB\nserver.tomcat.max-part-count=100\n\n# Rqueue configuration\nrqueue.web.enable=false\nrqueue.job.enabled=false\nrqueue.message.durability.in-terminal-state=0\nrqueue.key.prefix=\\${spring.cache.redis.key-prefix}\nrqueue.message.converter.provider.class=com.dedicatedcode.reitti.config.RQueueCustomMessageConverter\n\n# Application-specific settings\nreitti.server.advertise-uri=\n\nreitti.security.local-login.disable=false\n\n# OIDC / Security Settings\nreitti.security.oidc.enabled=false\nreitti.security.oidc.registration.enabled=false\n\nreitti.import.batch-size=10000\nreitti.import.processing-idle-start-time=10\n\nreitti.geo-point-filter.max-speed-kmh=1000\nreitti.geo-point-filter.max-accuracy-meters=100\nreitti.geo-point-filter.history-lookback-hours=24\nreitti.geo-point-filter.window-size=50\n\nreitti.process-data.schedule=0 */10 * * * *\nreitti.process-data.refresh-views.schedule=0 0 4 * * *\nreitti.imports.schedule=0 5/10 * * * *\nreitti.imports.owntracks-recorder.schedule=\\${reitti.imports.schedule}\n\n# Geocoding service configuration\nreitti.geocoding.max-errors=10\nreitti.geocoding.photon.base-url=\n\n# Tiles Configuration\nreitti.ui.tiles.cache.url=http://127.0.0.1\nreitti.ui.tiles.default.service=https://tile.openstreetmap.org/{z}/{x}/{y}.png\nreitti.ui.tiles.default.attribution=&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors\n\n# Data management configuration\nreitti.data-management.enabled=false\nreitti.data-management.preview-cleanup.cron=0 0 4 * * *\n\nreitti.storage.path=data/\nreitti.storage.cleanup.cron=0 0 4 * * *\n\n# Location data density normalization\nreitti.location.density.target-points-per-minute=4\n\n# Logging buffer\nreitti.logging.buffer-size=1000\nreitti.logging.max-buffer-size=10000\n\nspring.config.import=optional:oidc.properties\nEOF\nmsg_ok \"Created Configuration-File for Reitti\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/reitti.service\n[Unit]\nDescription=Reitti\nAfter=network.target postgresql.service redis-server.service\nWants=postgresql.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/reitti/\nExecStart=/usr/bin/java --enable-native-access=ALL-UNNAMED -jar -Xmx2g reitti.jar\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now reitti\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/resiliosync-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: David Bennett (dbinit)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.resilio.com/sync\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up Resilio Sync Repository\"\nsetup_deb822_repo \\\n  \"resilio\" \\\n  \"https://linux-packages.resilio.com/resilio-sync/key.asc\" \\\n  \"http://linux-packages.resilio.com/resilio-sync/deb\" \\\n  \"resilio-sync\" \\\n  \"non-free\"\nmsg_ok \"Setup Resilio Sync Repository\"\n\nmsg_info \"Installing Resilio Sync\"\n$STD apt install -y resilio-sync\nsed -i \"s/127.0.0.1:8888/0.0.0.0:8888/g\" /etc/resilio-sync/config.json\nsystemctl enable -q resilio-sync\nsystemctl restart resilio-sync\nmsg_ok \"Installed Resilio Sync\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/revealjs-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/hakimel/reveal.js\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"revealjs\" \"hakimel/reveal.js\" \"tarball\"\n\nmsg_info \"Configuring ${APPLICATION}\"\ncd /opt/revealjs\n$STD npm install\nsed -i 's/\"vite\"/\"vite --host\"/g' package.json\nmsg_ok \"Setup ${APPLICATION}\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/revealjs.service\n[Unit]\nDescription=Reveal.js Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/revealjs\nExecStart=/usr/bin/npm start\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now revealjs\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/romm-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ) | DevelopmentCats | AlphaLawless\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://romm.app\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  acl \\\n  git \\\n  build-essential \\\n  libssl-dev \\\n  libffi-dev \\\n  libmagic-dev \\\n  python3-dev \\\n  python3-pip \\\n  python3-venv \\\n  libmariadb3 \\\n  libmariadb-dev \\\n  libpq-dev \\\n  libbz2-dev \\\n  libreadline-dev \\\n  libsqlite3-dev \\\n  zlib1g-dev \\\n  liblzma-dev \\\n  libncurses5-dev \\\n  libncursesw5-dev \\\n  redis-server \\\n  redis-tools \\\n  p7zip-full \\\n  tzdata \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nNODE_VERSION=\"24\" setup_nodejs\nsetup_mariadb\nMARIADB_DB_NAME=\"romm\" MARIADB_DB_USER=\"romm\" setup_mariadb_db\n\nmsg_info \"Creating directories\"\nmkdir -p /opt/romm \\\n  /var/lib/romm/config \\\n  /var/lib/romm/resources \\\n  /var/lib/romm/assets/{saves,states,screenshots} \\\n  /var/lib/romm/library/roms \\\n  /var/lib/romm/library/bios\nmsg_ok \"Created directories\"\n\nmsg_info \"Creating configuration file\"\ncat <<'EOF' >/var/lib/romm/config/config.yml\n# RomM Configuration File\n# Documentation: https://docs.romm.app/latest/Getting-Started/Configuration-File/\n# Only uncomment the lines you want to use/modify\n\n# exclude:\n#   platforms:\n#     - excluded_folder_a\n#   roms:\n#     single_file:\n#       extensions:\n#         - xml\n#         - txt\n#       names:\n#         - '._*'\n#         - '*.nfo'\n#     multi_file:\n#       names:\n#         - downloaded_media\n#         - media\n\n# system:\n#   platforms:\n#     gc: ngc\n#     ps1: psx\n\n# The folder name where your roms are located (relative to library path)\n# filesystem:\n#   roms_folder: 'roms'\n\n# scan:\n#   priority:\n#     metadata:\n#       - \"igdb\"\n#       - \"moby\"\n#       - \"ss\"\n#       - \"ra\"\n#     artwork:\n#       - \"igdb\"\n#       - \"moby\"\n#       - \"ss\"\n#     region:\n#       - \"us\"\n#       - \"eu\"\n#       - \"jp\"\n#     language:\n#       - \"en\"\n#   media:\n#     - box2d\n#     - box3d\n#     - screenshot\n#     - manual\n\n# emulatorjs:\n#   debug: false\n#   cache_limit: null\nEOF\nchmod 644 /var/lib/romm/config/config.yml\nmsg_ok \"Created configuration file\"\n\nfetch_and_deploy_gh_release \"RAHasher\" \"RetroAchievements/RALibretro\" \"prebuild\" \"latest\" \"/opt/RALibretro\" \"RAHasher-x64-Linux-*.zip\"\ncp /opt/RALibretro/RAHasher /usr/bin/RAHasher\nchmod +x /usr/bin/RAHasher\n\nfetch_and_deploy_gh_release \"romm\" \"rommapp/romm\" \"tarball\"\n\nmsg_info \"Creating environment file\"\nsed -i 's/^supervised no/supervised systemd/' /etc/redis/redis.conf\nsystemctl restart redis-server\nsystemctl enable -q --now redis-server\nAUTH_SECRET_KEY=$(openssl rand -hex 32)\n\ncat <<EOF >/opt/romm/.env\nROMM_BASE_PATH=/var/lib/romm\nROMM_CONFIG_PATH=/var/lib/romm/config/config.yml\nWEB_CONCURRENCY=4\n\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_NAME=$MARIADB_DB_NAME\nDB_USER=$MARIADB_DB_USER\nDB_PASSWD=$MARIADB_DB_PASS\n\nREDIS_HOST=127.0.0.1\nREDIS_PORT=6379\n\nROMM_AUTH_SECRET_KEY=$AUTH_SECRET_KEY\nDISABLE_DOWNLOAD_ENDPOINT_AUTH=false\nDISABLE_CSRF_PROTECTION=false\n\nENABLE_RESCAN_ON_FILESYSTEM_CHANGE=true\nRESCAN_ON_FILESYSTEM_CHANGE_DELAY=5\n\nENABLE_SCHEDULED_RESCAN=true\nSCHEDULED_RESCAN_CRON=0 3 * * *\nENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB=true\nSCHEDULED_UPDATE_SWITCH_TITLEDB_CRON=0 4 * * *\n\nLOGLEVEL=INFO\nEOF\n\nchmod 600 /opt/romm/.env\nmsg_ok \"Created environment file\"\n\nmsg_info \"Setting up RomM Backend\"\ncd /opt/romm\nexport UV_CONCURRENT_DOWNLOADS=1\n$STD uv sync --all-extras\ncd /opt/romm/backend\n$STD uv run alembic upgrade head\nmsg_ok \"Set up RomM Backend\"\n\nmsg_info \"Setting up RomM Frontend\"\ncd /opt/romm/frontend\n$STD npm install\n$STD npm run build\n\ncp -rf /opt/romm/frontend/assets/* /opt/romm/frontend/dist/assets/\n\nmkdir -p /opt/romm/frontend/dist/assets/romm\nROMM_BASE=$(grep '^ROMM_BASE_PATH=' /opt/romm/.env | cut -d'=' -f2)\nROMM_BASE=${ROMM_BASE:-/var/lib/romm}\nln -sfn \"$ROMM_BASE\"/resources /opt/romm/frontend/dist/assets/romm/resources\nln -sfn \"$ROMM_BASE\"/assets /opt/romm/frontend/dist/assets/romm/assets\nmsg_ok \"Set up RomM Frontend\"\n\nmsg_info \"Configuring Nginx\"\ncat <<'EOF' >/etc/nginx/sites-available/romm\nupstream romm_backend {\n    server 127.0.0.1:5000;\n}\n\nmap $http_upgrade $connection_upgrade {\n    default upgrade;\n    '' close;\n}\n\nserver {\n    listen 80;\n    server_name _;\n    root /opt/romm/frontend/dist;\n    client_max_body_size 0;\n\n    # Frontend SPA\n    location / {\n        try_files $uri $uri/ /index.html;\n    }\n\n    # Static assets\n    location /assets {\n        alias /opt/romm/frontend/dist/assets;\n        try_files $uri $uri/ =404;\n        expires 1y;\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    # EmulatorJS player - requires COOP/COEP headers for SharedArrayBuffer\n    location ~ ^/rom/.*/ejs$ {\n        add_header Cross-Origin-Embedder-Policy \"require-corp\";\n        add_header Cross-Origin-Opener-Policy \"same-origin\";\n        try_files $uri /index.html;\n    }\n\n    # Backend API\n    location /api {\n        proxy_pass http://romm_backend;\n        proxy_buffering off;\n        proxy_request_buffering off;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    # WebSocket and Netplay\n    location ~ ^/(ws|netplay) {\n        proxy_pass http://romm_backend;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $connection_upgrade;\n        proxy_set_header Host $host;\n        proxy_read_timeout 86400;\n    }\n\n    # OpenAPI docs\n    location = /openapi.json {\n        proxy_pass http://romm_backend;\n    }\n\n    # Internal library file serving\n    location /library/ {\n        internal;\n        alias /var/lib/romm/library/;\n    }\n}\nEOF\n\nsed -i \"s|alias /var/lib/romm/library/;|alias ${ROMM_BASE}/library/;|\" /etc/nginx/sites-available/romm\nrm -f /etc/nginx/sites-enabled/default\nln -sf /etc/nginx/sites-available/romm /etc/nginx/sites-enabled/romm\nsystemctl restart nginx\nsystemctl enable -q --now nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/romm-backend.service\n[Unit]\nDescription=RomM Backend\nAfter=network.target mariadb.service redis-server.service\nRequires=mariadb.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/romm/backend\nEnvironmentFile=/opt/romm/.env\nEnvironment=\"PYTHONPATH=/opt/romm\"\nExecStart=/opt/romm/.venv/bin/python main.py\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/romm-worker.service\n[Unit]\nDescription=RomM RQ Worker\nAfter=network.target mariadb.service redis-server.service romm-backend.service\nRequires=mariadb.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/romm/backend\nEnvironmentFile=/opt/romm/.env\nEnvironment=\"PYTHONPATH=/opt/romm/backend\"\nExecStart=/opt/romm/.venv/bin/rq worker --path /opt/romm/backend --url redis://127.0.0.1:6379/0 high default low\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/romm-scheduler.service\n[Unit]\nDescription=RomM RQ Scheduler\nAfter=network.target mariadb.service redis-server.service romm-backend.service\nRequires=mariadb.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/romm/backend\nEnvironmentFile=/opt/romm/.env\nEnvironment=\"PYTHONPATH=/opt/romm/backend\"\nEnvironment=\"RQ_REDIS_HOST=127.0.0.1\"\nEnvironment=\"RQ_REDIS_PORT=6379\"\nExecStart=/opt/romm/.venv/bin/rqscheduler --path /opt/romm/backend\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/romm-watcher.service\n[Unit]\nDescription=RomM Filesystem Watcher\nAfter=network.target romm-backend.service\nRequires=romm-backend.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/romm/backend\nEnvironmentFile=/opt/romm/.env\nEnvironment=\"PYTHONPATH=/opt/romm/backend\"\nExecStart=/opt/romm/.venv/bin/watchfiles --target-type command '/opt/romm/.venv/bin/python watcher.py' /var/lib/romm/library\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now romm-backend romm-worker romm-scheduler romm-watcher\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/rustdeskserver-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/rustdesk/rustdesk-server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"rustdesk-hbbr\" \"rustdesk/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbr*arm64.deb\"\nfetch_and_deploy_gh_release \"rustdesk-hbbs\" \"rustdesk/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbs*arm64.deb\"\nfetch_and_deploy_gh_release \"rustdesk-utils\" \"rustdesk/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-utils*arm64.deb\"\nfetch_and_deploy_gh_release \"rustdesk-api\" \"lejianwen/rustdesk-api\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-api-server*arm64.deb\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/rustypaste-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: GoldenSpringness | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/orhun/rustypaste\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"rustypaste\" \"orhun/rustypaste\" \"prebuild\" \"latest\" \"/opt/rustypaste\" \"*aarch64-unknown-linux-gnu.tar.gz\"\nfetch_and_deploy_gh_release \"rustypaste-cli\" \"orhun/rustypaste-cli\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"*aarch64-unknown-linux-gnu.tar.gz\"\n\nmsg_info \"Setting up RustyPaste\"\ncd /opt/rustypaste\nsed -i 's|^address = \".*\"|address = \"0.0.0.0:8000\"|' config.toml\nmsg_ok \"Set up RustyPaste\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/rustypaste.service\n[Unit]\nDescription=rustypaste Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/rustypaste\nExecStart=/opt/rustypaste/rustypaste\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now rustypaste\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sabnzbd-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sabnzbd.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  par2 \\\n  p7zip-full\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\n\nmsg_info \"Setup Unrar\"\ncat <<EOF >/etc/apt/sources.list.d/non-free.sources\nTypes: deb\nURIs: http://deb.debian.org/debian/\nSuites: trixie\nComponents: non-free \nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n$STD apt update\n$STD apt install -y unrar\nmsg_ok \"Setup Unrar\"\n\nfetch_and_deploy_gh_release \"sabnzbd-org\" \"sabnzbd/sabnzbd\" \"prebuild\" \"latest\" \"/opt/sabnzbd\" \"SABnzbd-*-src.tar.gz\"\n\nmsg_info \"Installing SABnzbd\"\n$STD uv venv --clear /opt/sabnzbd/venv\n$STD uv pip install -r /opt/sabnzbd/requirements.txt --python=/opt/sabnzbd/venv/bin/python\nmsg_ok \"Installed SABnzbd\"\n\nread -r -p \"Would you like to install par2cmdline-turbo? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  mv /usr/bin/par2 /usr/bin/par2.old\n  fetch_and_deploy_gh_release \"par2cmdline-turbo\" \"animetosho/par2cmdline-turbo\" \"prebuild\" \"latest\" \"/usr/bin/\" \"*-linux-arm64.zip\"\nfi\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/sabnzbd.service\n[Unit]\nDescription=SABnzbd\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/sabnzbd\nExecStart=/opt/sabnzbd/venv/bin/python SABnzbd.py -s 0.0.0.0:7777\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now sabnzbd\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/salt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/saltstack/salt\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up Salt Repo\"\nsetup_deb822_repo \\\n  \"salt\" \\\n  \"https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public\" \\\n  \"https://packages.broadcom.com/artifactory/saltproject-deb\" \\\n  \"stable\"\nmsg_ok \"Setup Salt Repo\"\n\nmsg_info \"Installing Salt\"\nRELEASE=$(get_latest_github_release \"saltstack/salt\")\ncat <<EOF >/etc/apt/preferences.d/salt-pin-1001\nPackage: salt-*\nPin: version ${RELEASE}\nPin-Priority: 1001\nEOF\n$STD apt install -y salt-master\necho \"${RELEASE}\" >/~.salt\nmsg_ok \"Installed Salt\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/scanopy-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/scanopy/scanopy\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  libssl-dev \\\n  pkg-config\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=17 setup_postgresql\nNODE_VERSION=\"24\" setup_nodejs\nPG_DB_NAME=\"scanopy_db\" PG_DB_USER=\"scanopy\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\nfetch_and_deploy_gh_release \"Scanopy\" \"scanopy/scanopy\" \"tarball\" \"latest\" \"/opt/scanopy\"\nTOOLCHAIN=\"$(grep \"channel\" /opt/scanopy/backend/rust-toolchain.toml | awk -F\\\" '{print $2}')\"\nRUST_TOOLCHAIN=$TOOLCHAIN setup_rust\n\nmsg_info \"Building Scanopy Server (patience)\"\ncd /opt/scanopy/backend\n$STD cargo build --release --bin server --bin generate-fixtures\n$STD ./target/release/generate-fixtures --output-dir /opt/scanopy/ui/src/lib/data\nmv ./target/release/server /usr/bin/scanopy-server\nmsg_ok \"Built Scanopy Server\"\n\nmsg_info \"Creating frontend UI\"\nexport PUBLIC_SERVER_HOSTNAME=default\nexport PUBLIC_SERVER_PORT=\"\"\ncd /opt/scanopy/ui\n$STD npm ci --no-fund --no-audit\n$STD npm run build\nmsg_ok \"Created frontend UI\"\n\nmsg_info \"Configuring server for first-run\"\ncat <<EOF >/opt/scanopy/.env\n### - SERVER\nSCANOPY_DATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\nSCANOPY_WEB_EXTERNAL_PATH=\"/opt/scanopy/ui/build\"\nSCANOPY_PUBLIC_URL=http://${LOCAL_IP}:60072\nSCANOPY_SERVER_PORT=60072\nSCANOPY_LOG_LEVEL=info\nSCANOPY_INTEGRATED_DAEMON_URL=http://127.0.0.1:60073\n## - uncomment to disable signups\n# SCANOPY_DISABLE_REGISTRATION=true\n## - uncomment when using TLS\n# SCANOPY_USE_SECURE_SESSION_COOKIES=true\n## - see https://github.com/imbolc/axum-client-ip?tab=readme-ov-file#configurable-vs-specific-extractors\n## - before uncommenting the below\n# SCANOPY_CLIENT_IP_SOURCE=\n\n### - SMTP (password reset and notifications - optional)\n# SCANOPY_SMTP_RELAY=smtp.gmail.com:587\n# SCANOPY_SMTP_USERNAME=your-email@gmail.com\n# SCANOPY_SMTP_PASSWORD=your-app-password\n# SCANOPY_SMTP_EMAIL=scanopy@yourdomain.tld\n\n### - INTEGRATED DAEMON\nSCANOPY_SERVER_URL=http://127.0.0.1:60072\nSCANOPY_BIND_ADDRESS=0.0.0.0\nSCANOPY_NAME=\"scanopy-daemon\"\nSCANOPY_HEARTBEAT_INTERVAL=30\n\n### - see https://github.com/scanopy/scanopy/blob/main/docs/CONFIGURATION.md for more options\nEOF\n\ncat <<EOF >/etc/systemd/system/scanopy-server.service\n[Unit]\nDescription=Scanopy Network Discovery Server\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/scanopy/backend\nEnvironmentFile=/opt/scanopy/.env\nExecStart=/usr/bin/scanopy-server\nRestart=always\nRestartSec=10\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now scanopy-server\n\n# Creating short script to configure scanopy-daemon\n# cat <<EOF >~/configure_daemon.sh\n# #!/usr/bin/env bash\n#\n# echo \"Auto-configuring integrated daemon...\"\n#\n# NETWORK_ID=\"\\$(sudo -u postgres psql -1 -t -d \"${PG_DB_NAME}\" -c 'SELECT id FROM networks;')\"\n# API_KEY=\"\\$(sudo -u postgres psql -1 -t -d \"${PG_DB_NAME}\" -c 'SELECT key FROM api_keys;')\"\n#\n# cat <<END >/etc/systemd/system/scanopy-daemon.service\n# [Unit]\n# Description=Scanopy Network Discovery Daemon\n# After=network-online.target\n# Wants=network-online.target\n#\n# [Service]\n# Type=simple\n# User=root\n# ExecStart=/usr/bin/scanopy-daemon --server-url http://127.0.0.1:60072 --network-id \\${NETWORK_ID} --daemon-api-key \\${API_KEY} --mode push\n# Restart=always\n# RestartSec=10\n# StandardOutput=journal\n# StandardError=journal\n#\n# [Install]\n# WantedBy=multi-user.target\n# END\n#\n# systemctl enable -q --now scanopy-daemon\n# echo \"Scanopy daemon configured and running\"\n#\n# EOF\n# chmod +x ~/configure_daemon.sh\nmsg_ok \"Scanopy server running - please create an account, daemon API key and daemon in the Scanopy UI.\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/scraparr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: JasonGreenC\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/thecfu/scraparr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y libicu76\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nfetch_and_deploy_gh_release \"scrappar\" \"thecfu/scraparr\" \"tarball\" \"latest\" \"/opt/scraparr\"\n\nmsg_info \"Installing Scraparr\"\ncd /opt/scraparr\n$STD uv venv --clear /opt/scraparr/.venv\n$STD /opt/scraparr/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/scraparr/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/scraparr/.venv/bin/python -m pip install -r /opt/scraparr/src/scraparr/requirements.txt\nchmod -R 755 /opt/scraparr\nmkdir -p /scraparr/config\nmv /opt/scraparr/config.yaml /scraparr/config/config.yaml\nchmod -R 755 /scraparr\nmsg_ok \"Installed Scraparr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/scraparr.service\n[Unit]\nDescription=Scraparr\nWants=network-online.target\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/scraparr/src\nExecStart=/opt/scraparr/.venv/bin/python -m scraparr.scraparr\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now scraparr\nmsg_ok \"Configured Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/scrypted-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2024 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/tteck/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<< \"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt-get -y install software-properties-common apt-utils\n$STD apt-get -y update\n$STD apt-get -y upgrade\n$STD apt-get install -y avahi-daemon\n$STD apt-get -y install \\\n    build-essential \\\n    gcc \\\n    gir1.2-gtk-3.0 \\\n    libcairo2-dev \\\n    libgirepository1.0-dev \\\n    libglib2.0-dev \\\n    libjpeg-dev \\\n    libgif-dev \\\n    libopenjp2-7 \\\n    libpango1.0-dev \\\n    librsvg2-dev \\\n    pkg-config \\\n    gpg\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting Up Hardware Acceleration\"\n$STD apt-get -y install {va-driver-all,ocl-icd-libopencl1,vainfo}\nif [[ \"$CTTYPE\" == \"0\" ]]; then\n  chgrp video /dev/dri\n  chmod 755 /dev/dri\n  chmod 660 /dev/dri/*\n  $STD adduser $(id -u -n) video\n  $STD adduser $(id -u -n) render\nfi\nmsg_ok \"Set Up Hardware Acceleration\"\n\nmsg_info \"Installing GStreamer (Patience)\"\n$STD apt-get -y install \\\n    gstreamer1.0-tools \\\n    libgstreamer1.0-dev \\\n    libgstreamer-plugins-base1.0-dev \\\n    libgstreamer-plugins-bad1.0-dev \\\n    gstreamer1.0-plugins-base \\\n    gstreamer1.0-plugins-good \\\n    gstreamer1.0-plugins-bad \\\n    gstreamer1.0-plugins-ugly \\\n    gstreamer1.0-libav \\\n    gstreamer1.0-alsa\nmsg_ok \"Installed GStreamer\"\n\nmsg_info \"Setting up Node.js Repository\"\nmkdir -p /etc/apt/keyrings\ncurl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg\necho \"deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main\" >/etc/apt/sources.list.d/nodesource.list\nmsg_ok \"Set up Node.js Repository\"\n\nmsg_info \"Installing Node.js\"\n$STD apt-get update\n$STD apt-get install -y nodejs\nmsg_ok \"Installed Node.js\"\n\nmsg_info \"Updating Python3\"\n$STD apt-get install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Updated Python3\"\n\nmsg_info \"Installing Python3 Dependencies\"\n$STD apt-get -y install \\\n    python3-gi \\\n    python3-gst-1.0 \\\n    python3-matplotlib \\\n    python3-numpy \\\n    python3-opencv \\\n    python3-pil \\\n    python3-setuptools \\\n    python3-skimage \\\n    python3-wheel\n$STD python3 -m pip install --upgrade pip\n$STD python3 -m pip install aiofiles debugpy typing_extensions typing\nmsg_ok \"Installed Python3 Dependencies\"\n\nmsg_info \"Installing Scrypted\"\n$STD npx -y scrypted@latest install-server\n\nif [[ \"$CTTYPE\" == \"0\" ]]; then\n  sed -i -e 's/^sgx:x:104:$/render:x:104:root/' -e 's/^render:x:106:root$/sgx:x:106:/' /etc/group\nelse\n  sed -i -e 's/^sgx:x:104:$/render:x:104:/' -e 's/^render:x:106:$/sgx:x:106:/' /etc/group\nfi\nmsg_ok \"Installed Scrypted\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/scrypted.service\n[Unit]\nDescription=Scrypted service\nAfter=network.target\n\n[Service]\nUser=root\nGroup=root\nType=simple\nExecStart=/usr/bin/npx -y scrypted serve\nRestart=on-failure\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now scrypted.service\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\n$STD apt-get -y autoremove\n$STD apt-get -y autoclean\nmsg_ok \"Cleaned\"\n"
  },
  {
    "path": "install/searxng-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/searxng/searxng\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing SearXNG dependencies\"\ncat <<EOF >/etc/apt/sources.list.d/backports.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie-backports\nComponents: main\nEOF\n$STD apt update\n$STD apt install -y \\\n  python3-dev python3-babel python3-venv python-is-python3 \\\n  uwsgi uwsgi-plugin-python3 \\\n  git build-essential libxslt-dev zlib1g-dev libffi-dev libssl-dev sudo valkey\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Creating user and preparing directories\"\nuseradd --system --shell /bin/bash --home-dir \"/usr/local/searxng\" --comment 'Privacy-respecting metasearch engine' searxng || true\nmkdir -p /usr/local/searxng\nchown -R searxng:searxng /usr/local/searxng\nmsg_ok \"User and directories ready\"\n\nmsg_info \"Cloning SearXNG source\"\n$STD sudo -H -u searxng git clone https://github.com/searxng/searxng /usr/local/searxng/searxng-src\nmsg_ok \"Cloned SearXNG\"\n\nmsg_info \"Creating Python virtual environment\"\nsudo -H -u searxng bash -c '\n  python3 -m venv /usr/local/searxng/searx-pyenv &&\n  . /usr/local/searxng/searx-pyenv/bin/activate &&\n  pip install -U pip setuptools wheel pyyaml lxml msgspec typing_extensions &&\n  pip install --use-pep517 --no-build-isolation -e /usr/local/searxng/searxng-src\n'\nmsg_ok \"Python environment ready\"\n\nmsg_info \"Configuring SearXNG settings\"\nmkdir -p /etc/searxng\nSECRET_KEY=$(openssl rand -hex 32)\ncat <<EOF >/etc/searxng/settings.yml\n# SearXNG settings\nuse_default_settings: true\ngeneral:\n  debug: false\n  instance_name: \"SearXNG\"\n  privacypolicy_url: false\n  contact_url: false\nserver:\n  bind_address: \"0.0.0.0\"\n  port: 8888\n  secret_key: \"${SECRET_KEY}\"\n  limiter: false\n  image_proxy: true\nvalkey:\n  url: \"valkey://localhost:6379/0\"\nui:\n  static_use_hash: true\nenabled_plugins:\n  - 'Hash plugin'\n  - 'Self Information'\n  - 'Tracker URL remover'\n  - 'Ahmia blacklist'\nsearch:\n  safe_search: 2\n  autocomplete: 'google'\nengines:\n  - name: google\n    engine: google\n    shortcut: gg\n    use_mobile_ui: false\n  - name: duckduckgo\n    engine: duckduckgo\n    shortcut: ddg\n    display_error_messages: true\nEOF\n\nchown searxng:searxng /etc/searxng/settings.yml\nchmod 640 /etc/searxng/settings.yml\nmsg_ok \"Configured settings\"\n\nmsg_info \"Set up web services\"\ncat <<EOF >/etc/systemd/system/searxng.service\n[Unit]\nDescription=SearXNG service\nAfter=network.target valkey-server.service\nWants=valkey-server.service\n\n[Service]\nType=simple\nUser=searxng\nGroup=searxng\nEnvironment=\"SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml\"\nExecStart=/usr/local/searxng/searx-pyenv/bin/python -m searx.webapp\nWorkingDirectory=/usr/local/searxng/searxng-src\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now searxng\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/seaweedfs-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/seaweedfs/seaweedfs\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y fuse3\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"seaweedfs\" \"seaweedfs/seaweedfs\" \"prebuild\" \"latest\" \"/opt/seaweedfs\" \"linux_amd64.tar.gz\"\n\nmsg_info \"Setting up SeaweedFS\"\nmkdir -p /opt/seaweedfs-data\nln -sf /opt/seaweedfs/weed /usr/local/bin/weed\nmsg_ok \"Set up SeaweedFS\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/seaweedfs.service\n[Unit]\nDescription=SeaweedFS Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/seaweedfs\nExecStart=/opt/seaweedfs/weed server -dir=/opt/seaweedfs-data -master.port=9333 -volume.port=8080 -filer -s3\nRestart=on-failure\nRestartSec=5\nLimitNOFILE=65536\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now seaweedfs\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/seelf-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/YuukanOO/seelf\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  make \\\n  gcc\nmsg_ok \"Installed Dependencies\"\n\nsetup_go\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"seelf\" \"YuukanOO/seelf\" \"tarball\"\n\nmsg_info \"Setting up seelf. Patience\"\ncd /opt/seelf\n$STD make build\nPASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmkdir -p /opt/seelf/data\n{\n  echo \"ADMIN_EMAIL=admin@example.com\"\n  echo \"ADMIN_PASSWORD=$PASS\"\n} | tee .env ~/seelf.creds >/dev/null\nSEELF_ADMIN_EMAIL=admin@example.com SEELF_ADMIN_PASSWORD=$PASS ./seelf serve &>/dev/null &\nsleep 5\nkill $!\nmsg_ok \"Done setting up seelf\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/seelf.service\n[Unit]\nDescription=seelf Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nEnvironmentFile=/opt/seelf/.env\nEnvironment=DATA_PATH=/opt/seelf/data\nWorkingDirectory=/opt/seelf\nExecStart=/opt/seelf/./seelf -c data/conf.yml serve\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now seelf\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/seerr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.seerr.dev/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3-setuptools\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"seerr\" \"seerr-team/seerr\" \"tarball\"\npnpm_desired=$(grep -Po '\"pnpm\":\\s*\"\\K[^\"]+' /opt/seerr/package.json)\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$pnpm_desired\" setup_nodejs\n\nmsg_info \"Installing Seerr (Patience)\"\nexport CYPRESS_INSTALL_BINARY=0\ncd /opt/seerr\n$STD pnpm install --frozen-lockfile\nexport NODE_OPTIONS=\"--max-old-space-size=3072\"\n$STD pnpm build\nmkdir -p /etc/seerr/\ncat <<EOF >/etc/seerr/seerr.conf\n## Seerr's default port is 5055, if you want to use both, change this.\n## specify on which port to listen\nPORT=5055\n\n## specify on which interface to listen, by default seerr listens on all interfaces\nHOST=0.0.0.0\n\n## Uncomment if you want to force Node.js to resolve IPv4 before IPv6 (advanced users only)\n# FORCE_IPV4_FIRST=true\nEOF\nmsg_ok \"Installed Seerr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/seerr.service\n[Unit]\nDescription=Seerr Service\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nEnvironmentFile=/etc/seerr/seerr.conf\nEnvironment=NODE_ENV=production\nType=exec\nRestart=on-failure\nWorkingDirectory=/opt/seerr\nExecStart=/usr/bin/node dist/index.js\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now seerr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/semaphore-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://semaphoreui.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  ansible\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"semaphore\" \"semaphoreui/semaphore\" \"binary\" \"latest\" \"/opt/semaphore\" \"semaphore_*_linux_arm64.deb\"\n\nmsg_info \"Configuring Semaphore\"\nmkdir -p /opt/semaphore\ncd /opt/semaphore\nSEM_HASH=$(openssl rand -base64 32)\nSEM_ENCRYPTION=$(openssl rand -base64 32)\nSEM_KEY=$(openssl rand -base64 32)\nSEM_PW=$(openssl rand -base64 12)\ncat <<EOF >/opt/semaphore/config.json\n{\n  \"sqlite\": {\n    \"host\": \"/opt/semaphore/database.sqlite\"\n  },\n  \"dialect\": \"sqlite\",\n  \"tmp_path\": \"/opt/semaphore/tmp\",\n  \"cookie_hash\": \"${SEM_HASH}\", \n  \"cookie_encryption\": \"${SEM_ENCRYPTION}\",\n  \"access_key_encryption\": \"${SEM_KEY}\"\n}\nEOF\n$STD semaphore user add --admin --login admin --email admin@community-scripts.org --name Administrator --password \"${SEM_PW}\" --config /opt/semaphore/config.json\necho \"${SEM_PW}\" >~/semaphore.creds\nmsg_ok \"Setup Semaphore\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/semaphore.service\n[Unit]\nDescription=Semaphore UI\nDocumentation=https://docs.semaphoreui.com/\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nExecStart=/usr/bin/semaphore server --config /opt/semaphore/config.json\nRestart=always\nRestartSec=10s\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now semaphore\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sftpgo-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sftpgo.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3\nmsg_ok \"Installed Dependencies\"\n\nsetup_deb822_repo \\\n  \"sftpgo\" \\\n  \"https://ftp.osuosl.org/pub/sftpgo/apt/gpg.key\" \\\n  \"https://ftp.osuosl.org/pub/sftpgo/apt\" \\\n  \"trixie\"\n\nmsg_info \"Installing SFTPGo\"\n$STD apt install -y sftpgo\nmsg_ok \"Installed SFTPGo\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/shelfmark-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/calibrain/shelfmark\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y unrar-free\nln -sf /usr/bin/unrar-free /usr/bin/unrar\nmsg_ok \"Installed Dependencies\"\n\nmkdir -p /etc/shelfmark\ncat <<EOF >/etc/shelfmark/.env\nDOCKERMODE=false\nURL_BASE=\"\"\nCONFIG_DIR=/etc/shelfmark\nTMP_DIR=/tmp/shelfmark\nENABLE_LOGGING=true\nFLASK_HOST=0.0.0.0\nFLASK_PORT=8084\n# SESSION_COOKIES_SECURE=true\n# CWA_DB_PATH=\nUSE_CF_BYPASS=true\nUSING_EXTERNAL_BYPASSER=false\n# EXT_BYPASSER_URL=\n# EXT_BYPASSER_PATH=/v1\nEOF\n\necho \"\"\necho \"\"\necho -e \"${BL}Shelfmark Deployment Type${CL}\"\necho \"─────────────────────────────────────────\"\necho \"Please choose your deployment type:\"\necho \"\"\necho \" 1) Use Shelfmark's internal captcha bypasser (default)\"\necho \" 2) Install FlareSolverr in this LXC\"\necho \" 3) Use an existing Flaresolverr/Byparr LXC\"\necho \" 4) Disable captcha bypassing altogether (not recommended)\"\necho \"\"\n\nread -r -p \"${TAB3}Select deployment type [1]: \" DEPLOYMENT_TYPE\nDEPLOYMENT_TYPE=\"${DEPLOYMENT_TYPE:-1}\"\n\ncase \"$DEPLOYMENT_TYPE\" in\n1)\n  msg_ok \"Using Shelfmark's internal captcha bypasser\"\n  ;;\n2)\n  msg_ok \"Proceeding with FlareSolverr installation\"\n  ;;\n3)\n  echo \"\"\n  echo -e \"${BL}Use an existing FlareSolverr/Byparr LXC${CL}\"\n  echo \"─────────────────────────────────────────\"\n  echo \"Enter the URL/IP address with port of your Flaresolverr/Byparr instance\"\n  echo \"Example: http://flaresoverr.homelab.lan:8191 or\"\n  echo \"http://192.168.10.99:8191\"\n  echo \"\"\n  read -r -p \"FlareSolverr/Byparr URL: \" BYPASSER_URL\n\n  if [[ -z \"$BYPASSER_URL\" ]]; then\n    msg_warn \"No Flaresolverr/Byparr URL provided. Falling back to Shelfmark's internal bypasser.\"\n  else\n    BYPASSER_URL=\"${BYPASSER_URL%/}\"\n    msg_ok \"FlareSolverr/Byparr URL: ${BYPASSER_URL}\"\n  fi\n  ;;\n4)\n  msg_warn \"Disabling captcha bypass. This may cause the majority of searches and downloads to fail.\"\n  ;;\n*)\n  msg_warn \"Invalid selection. Reverting to default (internal bypasser)!\"\n  ;;\nesac\n\nif [[ \"$DEPLOYMENT_TYPE\" == \"2\" ]]; then\n  fetch_and_deploy_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\" \"prebuild\" \"latest\" \"/opt/flaresolverr\" \"flaresolverr_linux_x64.tar.gz\"\n  msg_info \"Installing FlareSolverr (patience)\"\n  $STD apt install -y xvfb\n  setup_deb822_repo \\\n    \"google-chrome\" \\\n    \"https://dl.google.com/linux/linux_signing_key.pub\" \\\n    \"https://dl.google.com/linux/chrome/deb/\" \\\n    \"stable\"\n  $STD apt install -y google-chrome-stable\n  rm /etc/apt/sources.list.d/google-chrome.list\n  sed -i -e '/BYPASSER=/s/false/true/' \\\n    -e 's/^# EXT_/EXT_/' \\\n    -e \"s|_URL=.*|_URL=http://localhost:8191|\" /etc/shelfmark/.env\n  msg_ok \"Installed FlareSolverr\"\nelif [[ \"$DEPLOYMENT_TYPE\" == \"3\" ]]; then\n  sed -i -e '/BYPASSER=/s/false/true/' \\\n    -e 's/^# EXT_/EXT_/' \\\n    -e \"s|_URL=.*|_URL=${BYPASSER_URL}|\" /etc/shelfmark/.env\nelif [[ \"$DEPLOYMENT_TYPE\" == \"4\" ]]; then\n  sed -i '/_BYPASS=/s/true/false/' /etc/shelfmark/.env\nelse\n  DEPLOYMENT_TYPE=\"1\"\n  msg_info \"Installing internal bypasser dependencies\"\n  $STD apt install -y --no-install-recommends \\\n    xvfb \\\n    ffmpeg \\\n    chromium-common \\\n    chromium \\\n    python3-tk\n  msg_ok \"Installed internal bypasser dependencies\"\nfi\n\nNODE_VERSION=\"24\" setup_nodejs\nPYTHON_VERSION=\"3.14\" setup_uv\n\nfetch_and_deploy_gh_release \"shelfmark\" \"calibrain/shelfmark\" \"tarball\" \"latest\" \"/opt/shelfmark\"\nRELEASE_VERSION=$(cat \"$HOME/.shelfmark\")\n\nmsg_info \"Building Shelfmark frontend\"\ncd /opt/shelfmark/src/frontend\necho \"RELEASE_VERSION=${RELEASE_VERSION}\" >>/etc/shelfmark/.env\n$STD npm ci\n$STD npm run build\nmv /opt/shelfmark/src/frontend/dist /opt/shelfmark/frontend-dist\nmsg_ok \"Built Shelfmark frontend\"\n\nmsg_info \"Configuring Shelfmark\"\nexport VIRTUAL_ENV=/opt/shelfmark/venv\ncd /opt/shelfmark\n$STD uv venv --clear ./venv\n$STD source ./venv/bin/activate\nif [[ \"$DEPLOYMENT_TYPE\" == \"1\" ]]; then\n  $STD uv sync --active --locked --no-default-groups --extra browser\nelse\n  $STD uv sync --active --locked --no-default-groups\nfi\nmkdir -p {/var/log/shelfmark,/tmp/shelfmark}\nmsg_ok \"Configured Shelfmark\"\n\nmsg_info \"Creating Services and start script\"\ncat <<EOF >/etc/systemd/system/shelfmark.service\n[Unit]\nDescription=Shelfmark server\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/shelfmark\nEnvironmentFile=/etc/shelfmark/.env\nExecStart=/usr/bin/bash /opt/shelfmark/start.sh\nRestart=always\nRestartSec=10\nKillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nif [[ \"$DEPLOYMENT_TYPE\" == \"1\" ]]; then\n  cat <<EOF >/etc/systemd/system/chromium.service\n[Unit]\nDescription=Chromium Headless Browser\nAfter=network.target\n\n[Service]\nUser=root\nExecStart=/usr/bin/chromium --headless --no-sandbox --disable-gpu --disable-dev-shm-usage --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 --hide-scrollbars\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now chromium\nfi\nif [[ \"$DEPLOYMENT_TYPE\" == \"2\" ]]; then\n  cat <<EOF >/etc/systemd/system/flaresolverr.service\n[Unit]\nDescription=FlareSolverr\nAfter=network.target\n[Service]\nSyslogIdentifier=flaresolverr\nRestart=always\nRestartSec=5\nType=simple\nEnvironment=\"LOG_LEVEL=info\"\nEnvironment=\"CAPTCHA_SOLVER=none\"\nWorkingDirectory=/opt/flaresolverr\nExecStart=/opt/flaresolverr/flaresolverr\nTimeoutStopSec=30\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now flaresolverr\nfi\n\ncat <<EOF >/opt/shelfmark/start.sh\n#!/usr/bin/env bash\n\nsource /opt/shelfmark/venv/bin/activate\nset -a\nsource /etc/shelfmark/.env\nset +a\n\ngunicorn --worker-class geventwebsocket.gunicorn.workers.GeventWebSocketWorker --workers 1 -t 300 -b 0.0.0.0:8084 shelfmark.main:app\nEOF\nchmod +x /opt/shelfmark/start.sh\n\nsystemctl enable -q --now shelfmark\nmsg_ok \"Created Services and start script\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/shinobi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://shinobi.video/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y make zip net-tools git\n$STD apt-get install -y gcc g++ cmake\n$STD apt-get install -y ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nsetup_mariadb\n\nmsg_info \"Installing FFMPEG\"\n$STD apt-get install -y ffmpeg\nmsg_ok \"Installed FFMPEG\"\n\nmsg_info \"Cloning Shinobi\"\ncd /opt\n$STD git clone https://gitlab.com/Shinobi-Systems/Shinobi.git -b master Shinobi\ncd Shinobi\ngitVersionNumber=$(git rev-parse HEAD)\ntheDateRightNow=$(date)\ntouch version.json\nchmod 644 version.json\necho '{\"Product\" : \"'\"Shinobi\"'\" , \"Branch\" : \"'\"master\"'\" , \"Version\" : \"'\"$gitVersionNumber\"'\" , \"Date\" : \"'\"$theDateRightNow\"'\" , \"Repository\" : \"'\"https://gitlab.com/Shinobi-Systems/Shinobi.git\"'\"}' >version.json\nmsg_ok \"Cloned Shinobi\"\n\nmsg_info \"Installing Database\"\nsqluser=\"root\"\nsqlpass=\"root\"\necho \"mariadb-server mariadb-server/root_password password $sqlpass\" | debconf-set-selections\necho \"mariadb-server mariadb-server/root_password_again password $sqlpass\" | debconf-set-selections\nservice mysql start\n$STD mariadb -u \"$sqluser\" -p\"$sqlpass\" -e \"source sql/user.sql\" || true\nmsg_ok \"Installed Database\"\n\nmsg_info \"Installing Shinobi\"\ncp conf.sample.json conf.json\ncronKey=$(head -c 1024 </dev/urandom | sha256sum | awk '{print substr($1,1,29)}')\nsed -i -e 's/Shinobi/'\"$cronKey\"'/g' conf.json\ncp super.sample.json super.json\n$STD npm i npm -g\n$STD npm install --unsafe-perm\n$STD npm install pm2@latest -g\nchmod -R 755 .\ntouch INSTALL/installed.txt\nln -s /opt/Shinobi/INSTALL/shinobi /usr/bin/shinobi\nnode /opt/Shinobi/tools/modifyConfiguration.js addToConfig=\"{\\\"cron\\\":{\\\"key\\\":\\\"$(head -c 64 </dev/urandom | sha256sum | awk '{print substr($1,1,60)}')\\\"}}\" &>/dev/null\n$STD pm2 start camera.js\n$STD pm2 start cron.js\n$STD pm2 startup\n$STD pm2 save\n$STD pm2 list\nmsg_ok \"Installed Shinobi\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/shlink-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://shlink.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.5\" setup_php\nsetup_mariadb\nMARIADB_DB_NAME=\"shlink\" MARIADB_DB_USER=\"shlink\" setup_mariadb_db\n\nfetch_and_deploy_gh_release \"shlink\" \"shlinkio/shlink\" \"prebuild\" \"latest\" \"/opt/shlink\" \"shlink*_php8.5_dist.zip\"\n\nmsg_info \"Setting up Application\"\ncd /opt/shlink\n$STD php ./vendor/bin/rr get --no-interaction --location bin/\nchmod +x bin/rr\nmkdir -p data/cache data/locks data/log data/proxies data/temp-geolite\nchmod -R 775 data\ncat <<EOF >/opt/shlink/.env\nDEFAULT_DOMAIN=${LOCAL_IP}:8080\nIS_HTTPS_ENABLED=false\nDB_DRIVER=maria\nDB_NAME=${MARIADB_DB_NAME}\nDB_USER=${MARIADB_DB_USER}\nDB_PASSWORD=${MARIADB_DB_PASS}\nDB_HOST=127.0.0.1\nDB_PORT=3306\nEOF\nset -a\nsource /opt/shlink/.env\nset +a\n$STD php vendor/bin/shlink-installer init --no-interaction --clear-db-cache --skip-download-geolite\nAPI_OUTPUT=$(php bin/cli api-key:generate --name=default 2>&1)\nINITIAL_API_KEY=$(echo \"$API_OUTPUT\" | sed -n 's/.*Generated API key: \"\\([^\"]*\\)\".*/\\1/p')\nif [[ -n \"$INITIAL_API_KEY\" ]]; then\n  echo \"INITIAL_API_KEY=${INITIAL_API_KEY}\" >>/opt/shlink/.env\nfi\nmsg_ok \"Set up Application\"\n\nif prompt_confirm \"Install Shlink Web Client?\" \"y\" 60; then\n  msg_info \"Installing Dependencies\"\n  $STD apt install -y nginx\n  msg_ok \"Installed Dependencies\"\n\n  fetch_and_deploy_gh_release \"shlink-web-client\" \"shlinkio/shlink-web-client\" \"prebuild\" \"latest\" \"/opt/shlink-web-client\" \"shlink-web-client_*_dist.zip\"\n\n  msg_info \"Setting up Web Client\"\n  cat <<EOF >/opt/shlink-web-client/servers.json\n[\n  {\n    \"name\": \"Shlink\",\n    \"url\": \"http://${LOCAL_IP}:8080\",\n    \"apiKey\": \"${INITIAL_API_KEY}\"\n  }\n]\nEOF\n  cat <<'EOF' >/etc/nginx/sites-available/shlink-web-client\nserver {\n    listen 3000 default_server;\n    charset utf-8;\n    root /opt/shlink-web-client;\n    index index.html;\n\n    location ~* \\.(?:manifest|appcache|html?|xml|json)$ {\n        expires -1;\n    }\n\n    location ~* \\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {\n        expires 1M;\n        add_header Cache-Control \"public\";\n    }\n\n    location ~* \\.(?:css|js)$ {\n        expires 1y;\n        add_header Cache-Control \"public\";\n    }\n\n    location = /servers.json {\n        try_files /servers.json /conf.d/servers.json;\n    }\n\n    location / {\n        try_files $uri $uri/ /index.html$is_args$args;\n    }\n}\nEOF\n  ln -sf /etc/nginx/sites-available/shlink-web-client /etc/nginx/sites-enabled/shlink-web-client\n  rm -f /etc/nginx/sites-enabled/default\n  systemctl enable -q nginx\n  $STD systemctl restart nginx\n  msg_ok \"Set up Web Client\"\nfi\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/shlink.service\n[Unit]\nDescription=Shlink URL Shortener\nAfter=network.target mariadb.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/shlink\nEnvironmentFile=/opt/shlink/.env\nExecStart=/opt/shlink/bin/rr serve -c config/roadrunner/.rr.yml\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now shlink\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/signoz-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://signoz.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  apt-transport-https \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"21\" setup_java\n\nmsg_info \"Setting up ClickHouse\"\ncurl -fsSL \"https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key\" | gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg\ncat <<EOF >/etc/apt/sources.list.d/clickhouse.sources\nTypes: deb\nURIs: https://packages.clickhouse.com/deb\nSuites: stable\nComponents: main\nArchitectures: arm64\nSigned-By: /usr/share/keyrings/clickhouse-keyring.gpg\nEOF\n$STD apt update\nexport DEBIAN_FRONTEND=noninteractive\n$STD apt install -y clickhouse-server clickhouse-client\nmsg_ok \"Setup ClickHouse\"\n\nmsg_info \"Setting up Zookeeper\"\nZOOURL=$(curl -fsSL https://dlcdn.apache.org/zookeeper/current/ | grep -o 'apache-zookeeper-[0-9.]\\+-bin\\.tar\\.gz' | head -n1)\ncurl -fsSL \"https://dlcdn.apache.org/zookeeper/current/$ZOOURL\" -o ~/zookeeper.tar.gz\ntar -xzf \"$HOME/zookeeper.tar.gz\" -C \"$HOME\"\nmkdir -p /opt/zookeeper\nmkdir -p /var/lib/zookeeper\nmkdir -p /var/log/zookeeper\ncp -r ~/apache-zookeeper-*-bin/* /opt/zookeeper\n\ncat <<EOF >/opt/zookeeper/conf/zoo.cfg\ntickTime=2000\ndataDir=/var/lib/zookeeper\nclientPort=2181\nadmin.serverPort=3181\nEOF\n\ncat <<EOF >/opt/zookeeper/conf/zoo.env\nZOO_LOG_DIR=/var/log/zookeeper\nEOF\n\ncat <<EOF >/etc/systemd/system/zookeeper.service\n[Unit]\nDescription=Zookeeper\nDocumentation=http://zookeeper.apache.org\n\n[Service]\nEnvironmentFile=/opt/zookeeper/conf/zoo.env\nType=forking\nWorkingDirectory=/opt/zookeeper\nExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zoo.cfg\nExecStop=/opt/zookeeper/bin/zkServer.sh stop /opt/zookeeper/conf/zoo.cfg\nExecReload=/opt/zookeeper/bin/zkServer.sh restart /opt/zookeeper/conf/zoo.cfg\nTimeoutSec=30\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zookeeper\nmsg_ok \"Setup Zookeeper\"\n\nmsg_info \"Configuring ClickHouse\"\ncat <<EOF >/etc/clickhouse-server/config.d/cluster.xml\n<clickhouse replace=\"true\">\n    <distributed_ddl>\n        <path>/clickhouse/task_queue/ddl</path>\n    </distributed_ddl>\n    <remote_servers>\n        <cluster>\n            <shard>\n                <replica>\n                    <host>127.0.0.1</host>\n                    <port>9000</port>\n                </replica>\n            </shard>\n        </cluster>\n    </remote_servers>\n    <zookeeper>\n        <node>\n            <host>127.0.0.1</host>\n            <port>2181</port>\n        </node>\n    </zookeeper>\n    <macros>\n        <shard>01</shard>\n        <replica>01</replica>\n    </macros>\n</clickhouse>\nEOF\nsystemctl enable -q --now clickhouse-server\nmsg_ok \"Configured ClickHouse\"\n\nfetch_and_deploy_gh_release \"signoz-schema-migrator\" \"SigNoz/signoz-otel-collector\" \"prebuild\" \"latest\" \"/opt/signoz-schema-migrator\" \"signoz-schema-migrator_linux_arm64.tar.gz\"\n\nmsg_info \"Running ClickHouse migrations\"\ncd /opt/signoz-schema-migrator/bin\n$STD ./signoz-schema-migrator sync --dsn=\"tcp://localhost:9000?password=\" --replication=true --up=\n$STD ./signoz-schema-migrator async --dsn=\"tcp://localhost:9000?password=\" --replication=true --up=\nmsg_ok \"ClickHouse Migrations Completed\"\n\nfetch_and_deploy_gh_release \"signoz\" \"SigNoz/signoz\" \"prebuild\" \"latest\" \"/opt/signoz\" \"signoz-community_linux_arm64.tar.gz\"\n\nmsg_info \"Setting up SigNoz\"\nmkdir -p /var/lib/signoz\ncat <<EOF >/opt/signoz/conf/systemd.env\nSIGNOZ_INSTRUMENTATION_LOGS_LEVEL=info\nINVITE_EMAIL_TEMPLATE=/opt/signoz/templates/invitation_email_template.html\nSIGNOZ_SQLSTORE_SQLITE_PATH=/var/lib/signoz/signoz.db\nSIGNOZ_WEB_ENABLED=true\nSIGNOZ_WEB_DIRECTORY=/opt/signoz/web\nSIGNOZ_JWT_SECRET=secret\nSIGNOZ_ALERTMANAGER_PROVIDER=signoz\nSIGNOZ_TELEMETRYSTORE_PROVIDER=clickhouse\nSIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN=tcp://localhost:9000?password=\nDOT_METRICS_ENABLED=true\nEOF\n\ncat <<EOF >/etc/systemd/system/signoz.service\n[Unit]\nDescription=SigNoz\nDocumentation=https://signoz.io/docs\nAfter=clickhouse-server.service\n\n[Service]\nType=simple\nKillMode=mixed\nRestart=on-failure\nWorkingDirectory=/opt/signoz\nEnvironmentFile=/opt/signoz/conf/systemd.env\nExecStart=/opt/signoz/bin/signoz server\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now signoz\nmsg_ok \"Setup Signoz\"\n\nfetch_and_deploy_gh_release \"signoz-otel-collector\" \"SigNoz/signoz-otel-collector\" \"prebuild\" \"latest\" \"/opt/signoz-otel-collector\" \"signoz-otel-collector_linux_arm64.tar.gz\"\n\nmsg_info \"Setting up SigNoz OTel Collector\"\nmkdir -p /var/lib/signoz-otel-collector\ncat <<EOF >/opt/signoz-otel-collector/conf/config.yaml\nreceivers:\n  otlp:\n    protocols:\n      grpc:\n        endpoint: 0.0.0.0:4317\n        max_recv_msg_size_mib: 16\n      http:\n        endpoint: 0.0.0.0:4318\n  jaeger:\n    protocols:\n      grpc:\n        endpoint: 0.0.0.0:14250\n      thrift_http:\n        endpoint: 0.0.0.0:14268\n  httplogreceiver/heroku:\n    endpoint: 0.0.0.0:8081\n    source: heroku\n  httplogreceiver/json:\n    endpoint: 0.0.0.0:8082\n    source: json\nprocessors:\n  batch:\n    send_batch_size: 50000\n    timeout: 1s\n  signozspanmetrics/delta:\n    metrics_exporter: signozclickhousemetrics\n    latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s]\n    dimensions_cache_size: 100000\n    dimensions:\n      - name: service.namespace\n        default: default\n      - name: deployment.environment\n        default: default\n      - name: signoz.collector.id\n    aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA\nextensions:\n  health_check:\n    endpoint: 0.0.0.0:13133\n  zpages:\n    endpoint: localhost:55679\n  pprof:\n    endpoint: localhost:1777\nexporters:\n  clickhousetraces:\n    datasource: tcp://localhost:9000/signoz_traces?password=\n    use_new_schema: true\n  signozclickhousemetrics:\n    dsn: tcp://localhost:9000/signoz_metrics?password=\n    timeout: 45s\n  clickhouselogsexporter:\n    dsn: tcp://localhost:9000/signoz_logs?password=\n    timeout: 10s\n    use_new_schema: true\n  metadataexporter:\n    dsn: tcp://localhost:9000/signoz_metadata?password=\n    timeout: 10s\n    tenant_id: default\n    cache:\n      provider: in_memory\nservice:\n  telemetry:\n    logs:\n      encoding: json\n  extensions: [health_check, zpages, pprof]\n  pipelines:\n    traces:\n      receivers: [otlp, jaeger]\n      processors: [signozspanmetrics/delta, batch]\n      exporters: [clickhousetraces, metadataexporter]\n    metrics:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [metadataexporter, signozclickhousemetrics]\n    logs:\n      receivers: [otlp, httplogreceiver/heroku, httplogreceiver/json]\n      processors: [batch]\n      exporters: [clickhouselogsexporter, metadataexporter]\nEOF\n\ncat <<EOF >/opt/signoz-otel-collector/conf/opamp.yaml\nserver_endpoint: ws://127.0.0.1:4320/v1/opamp\nEOF\n\ncat <<EOF >/etc/systemd/system/signoz-otel-collector.service\n[Unit]\nDescription=SigNoz OTel Collector\nDocumentation=https://signoz.io/docs\nAfter=clickhouse-server.service\n\n[Service]\nType=simple\nKillMode=mixed\nRestart=on-failure\nWorkingDirectory=/opt/signoz-otel-collector\nExecStart=/opt/signoz-otel-collector/bin/signoz-otel-collector --config=/opt/signoz-otel-collector/conf/config.yaml --manager-config=/opt/signoz-otel-collector/conf/opamp.yaml --copy-path=/var/lib/signoz-otel-collector/config.yaml\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now signoz-otel-collector\nrm -rf ~/zookeeper.tar.gz\nrm -rf ~/apache-zookeeper-*-bin\nmsg_ok \"Setup Signoz OTel Collector\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/silverbullet-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dominik Siebel (dsiebel)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://silverbullet.md\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"silverbullet\" \"silverbulletmd/silverbullet\" \"prebuild\" \"latest\" \"/opt/silverbullet/bin\" \"silverbullet-server-linux-aarch64.zip\"\nmkdir -p /opt/silverbullet/space\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/silverbullet.service\n[Unit]\nDescription=Silverbullet Daemon\nAfter=syslog.target network.target\n\n[Service]\nUser=root\nType=simple\nExecStart=/opt/silverbullet/bin/silverbullet --hostname 0.0.0.0 --port 3000 /opt/silverbullet/space\nWorkingDirectory=/opt/silverbullet\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q silverbullet\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/slskd-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/slskd/slskd/, https://github.com/mrusse/soularr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"Slskd\" \"slskd/slskd\" \"prebuild\" \"latest\" \"/opt/slskd\" \"slskd-*-linux-arm64.zip\"\n\nmsg_info \"Configuring Slskd\"\nJWT_KEY=$(openssl rand -base64 44)\nSLSKD_API_KEY=$(openssl rand -base64 44)\ncp /opt/slskd/config/slskd.example.yml /opt/slskd/config/slskd.yml\nsed -i \\\n  -e '/web:/,/cidr/s/^# //' \\\n  -e '/https:/,/port: 5031/s/false/true/' \\\n  -e '/port: 5030/,/socket/s/,.*$//' \\\n  -e '/content_path:/,/authentication/s/false/true/' \\\n  -e \"\\|api_keys|,\\|cidr|s|<some.*$|$SLSKD_API_KEY|; \\\n    s|role: readonly|role: readwrite|; \\\n    s|0.0.0.0/0,::/0|& # Replace this with your subnet|\" \\\n  -e \"\\|jwt:|,\\|ttl|s|key: ~|key: $JWT_KEY|\" \\\n  -e '/soulseek/,/write_queue/s/^# //' \\\n  -e 's/^.*picture/#&/' /opt/slskd/config/slskd.yml\nmsg_ok \"Configured Slskd\"\n\nread -rp \"${TAB3}Do you want to install Soularr? y/N \" soularr\nif [[ ${soularr,,} =~ ^(y|yes)$ ]]; then\n  PYTHON_VERSION=\"3.11\" setup_uv\n  fetch_and_deploy_gh_release \"Soularr\" \"mrusse/soularr\" \"tarball\" \"latest\" \"/opt/soularr\"\n  cd /opt/soularr\n  $STD uv venv venv\n  $STD source venv/bin/activate\n  $STD uv pip install -r requirements.txt\n  sed -i \\\n    -e \"\\|[Slskd]|,\\|host_url|s|yourslskdapikeygoeshere|$SLSKD_API_KEY|\" \\\n    -e \"/host_url/s/slskd/localhost/\" \\\n    /opt/soularr/config.ini\n  cat <<EOF >/opt/soularr/run.sh\n#!/usr/bin/env bash\n\nif ps aux | grep \"[s]oularr.py\" >/dev/null; then\n  echo \"Soularr is already running. Exiting...\" >&2\n  exit 1\nfi\n\n# Remove stale lock file from previous ungraceful exit\nrm -f \"/opt/soularr/.soularr.lock\"\n\nsource /opt/soularr/venv/bin/activate\nuv run python3 -u /opt/soularr/soularr.py --config-dir /opt/soularr 2>&1\nEOF\n  chmod +x /opt/soularr/run.sh\n  deactivate\n  msg_ok \"Installed Soularr\"\nfi\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/slskd.service\n[Unit]\nDescription=Slskd Service\nAfter=network.target\nWants=network.target\n\n[Service]\nWorkingDirectory=/opt/slskd\nExecStart=/opt/slskd/slskd --config /opt/slskd/config/slskd.yml\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nif [[ -d /opt/soularr ]]; then\n  cat <<EOF >/etc/systemd/system/soularr.timer\n[Unit]\nDescription=Soularr service timer\nRefuseManualStart=no\nRefuseManualStop=no\n\n[Timer]\nPersistent=true\n# run every 10 minutes\nOnCalendar=*-*-* *:0/10:00\nUnit=soularr.service\n\n[Install]\nWantedBy=timers.target\nEOF\n\n  cat <<EOF >/etc/systemd/system/soularr.service\n[Unit]\nDescription=Soularr service\nAfter=network.target slskd.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/soularr\nExecStart=/bin/bash -c /opt/soularr/run.sh\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  msg_warn \"Add your Lidarr API key to Soularr in '/opt/soularr/config.ini', then run 'systemctl enable --now soularr.timer'\"\nfi\nsystemctl enable -q --now slskd\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/smokeping-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://oss.oetiker.ch/smokeping/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing SmokePing\"\n$STD apt install -y smokeping\ncat <<EOF >/etc/smokeping/config.d/Targets\n*** Targets ***\nprobe = FPing\nmenu = Top\ntitle = Network Latency Grapher\nremark = Welcome to SmokePing.\n+ Local\nmenu = Local\ntitle = Local Network (ICMP)\n++ LocalMachine\nmenu = Local Machine\ntitle = This host\nhost = localhost\n+ DNS\nmenu = DNS latency\ntitle = DNS latency (ICMP)\n++ Google\ntitle = Google\nhost = 8.8.8.8\n++ Cloudflare\ntitle = Cloudflare\nhost = 1.1.1.1\n++ Quad9  \ntitle = Quad9 \nhost = 9.9.9.9\n++ OpenDNS\ntitle = OpenDNS\nhost = 208.67.222.222\n+ HTTP\nmenu = HTTP latency\ntitle = HTTP latency (ICMP)\n++ Github\nhost = github.com\n++ Discord\nhost = discord.com\n++ Google\nhost = google.com\n++ Cloudflare\nhost = cloudflare.com\n++ Amazon\nhost = amazon.com\n++ Netflix\nhost = netflix.com\nEOF\nsystemctl restart smokeping\nmsg_ok \"Installed SmokePing\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/snipeit-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://snipeitapp.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  nginx\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULE=\"ldap,soap,xsl\" setup_php\nsetup_composer\nfetch_and_deploy_gh_release \"snipe-it\" \"grokability/snipe-it\" \"tarball\"\nsetup_mariadb\nMARIADB_DB_NAME=\"snipeit_db\" MARIADB_DB_USER=\"snipeit\" setup_mariadb_db\n\nmsg_info \"Configuring Snipe-IT\"\ncd /opt/snipe-it\ncp .env.example .env\nsed -i -e \"s|^APP_URL=.*|APP_URL=http://$LOCAL_IP|\" \\\n  -e \"s|^DB_DATABASE=.*|DB_DATABASE=$MARIADB_DB_NAME|\" \\\n  -e \"s|^DB_USERNAME=.*|DB_USERNAME=$MARIADB_DB_USER|\" \\\n  -e \"s|^DB_PASSWORD=.*|DB_PASSWORD=$MARIADB_DB_PASS|\" .env\nchown -R www-data: /opt/snipe-it\nchmod -R 755 /opt/snipe-it\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD composer install --no-dev --optimize-autoloader --no-interaction\n$STD php artisan key:generate --force\nmsg_ok \"Configured Snipe-IT\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/nginx/conf.d/snipeit.conf\nserver {\n        listen 80;\n        root /opt/snipe-it/public;\n        server_name $LOCAL_IP;\n        client_max_body_size 100M;\n        index index.php;\n\n        location / {\n                try_files \\$uri \\$uri/ /index.php?\\$query_string;\n        }\n\n        location ~ \\.php\\$ {\n                include fastcgi.conf;\n                include snippets/fastcgi-php.conf;\n                fastcgi_pass unix:/run/php/php8.3-fpm.sock;\n                fastcgi_split_path_info ^(.+\\.php)(/.+)\\$;\n                fastcgi_param SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name;\n                include fastcgi_params;\n        }\n}\nEOF\nsystemctl reload nginx\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/snowshare-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: TuroYT\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TuroYT/snowshare\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_USER=\"snowshare\" PG_DB_NAME=\"snowshare\" setup_postgresql_db\nfetch_and_deploy_gh_release \"snowshare\" \"TuroYT/snowshare\" \"tarball\"\n\nmsg_info \"Installing SnowShare\"\ncd /opt/snowshare\n$STD npm ci\ncat <<EOF >/opt/snowshare.env\nDATABASE_URL=\"postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\"\nNEXTAUTH_URL=\"http://localhost:3000\"\nNEXTAUTH_SECRET=\"$(openssl rand -base64 32)\"\nALLOW_SIGNUP=true\nNODE_ENV=production\nEOF\nset -a\nsource /opt/snowshare.env\nset +a\n$STD npx prisma generate\n$STD npx prisma migrate deploy\n$STD npm run build\ncat <<EOF >/etc/systemd/system/snowshare.service\n[Unit]\nDescription=SnowShare - Modern File Sharing Platform\nAfter=network.target postgresql.service\nRequires=postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/snowshare\nEnvironmentFile=/opt/snowshare.env\nExecStart=/usr/bin/npm start\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now snowshare\nmsg_ok \"Installed SnowShare\"\n\nmsg_info \"Setting up Cleanup Cron Job\"\ncat <<EOF >/etc/cron.d/snowshare-cleanup\n0 2 * * * root cd /opt/snowshare && /usr/bin/npm run cleanup:expired >> /var/log/snowshare-cleanup.log 2>&1\nEOF\nmsg_ok \"Set up Cleanup Cron Job\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/solidtime-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.solidtime.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y caddy\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULES=\"bcmath,gd,intl,xml,zip,pdo_pgsql,redis,mbstring,curl\" setup_php\nsetup_composer\nNODE_VERSION=\"22\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"solidtime\" PG_DB_USER=\"solidtime\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"solidtime\" \"solidtime-io/solidtime\" \"tarball\"\n\nmsg_info \"Setting up SolidTime\"\ncd /opt/solidtime\ncp .env.example .env\nsed -i \"s|^APP_ENV=.*|APP_ENV=production|\" .env\nsed -i \"s|^APP_DEBUG=.*|APP_DEBUG=false|\" .env\nsed -i \"s|^APP_URL=.*|APP_URL=http://${LOCAL_IP}|\" .env\nsed -i \"s|^APP_ENABLE_REGISTRATION=.*|APP_ENABLE_REGISTRATION=true|\" .env\nsed -i \"s|^DB_CONNECTION=.*|DB_CONNECTION=pgsql|\" .env\nsed -i \"s|^DB_HOST=.*|DB_HOST=127.0.0.1|\" .env\nsed -i \"s|^DB_PORT=.*|DB_PORT=5432|\" .env\nsed -i \"s|^DB_DATABASE=.*|DB_DATABASE=${PG_DB_NAME}|\" .env\nsed -i \"s|^DB_USERNAME=.*|DB_USERNAME=${PG_DB_USER}|\" .env\nsed -i \"s|^DB_PASSWORD=.*|DB_PASSWORD=${PG_DB_PASS}|\" .env\nsed -i \"s|^FILESYSTEM_DISK=.*|FILESYSTEM_DISK=local|\" .env\nsed -i \"s|^PUBLIC_FILESYSTEM_DISK=.*|PUBLIC_FILESYSTEM_DISK=public|\" .env\nsed -i \"s|^MAIL_MAILER=.*|MAIL_MAILER=log|\" .env\nsed -i \"s|^SESSION_SECURE_COOKIE=.*|SESSION_SECURE_COOKIE=false|\" .env\ngrep -q \"^SESSION_SECURE_COOKIE=\" .env || echo \"SESSION_SECURE_COOKIE=false\" >>.env\nsed -i \"s|^APP_FORCE_HTTPS=.*|APP_FORCE_HTTPS=false|\" .env\ngrep -q \"^APP_FORCE_HTTPS=\" .env || echo \"APP_FORCE_HTTPS=false\" >>.env\n$STD composer install --no-dev --optimize-autoloader\nphp artisan self-host:generate-keys >/tmp/solidtime.keys 2>/dev/null\nwhile IFS= read -r line; do\n  KEY=\"${line%%=*}\"\n  [[ -z \"$KEY\" || \"${KEY:0:1}\" == \"#\" ]] && continue\n  sed -i \"/^${KEY}=/d\" .env\n  echo \"$line\" >>.env\ndone </tmp/solidtime.keys\nrm -f /tmp/solidtime.keys\n$STD npm install\n$STD npm run build\nrm -rf node_modules\nmkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache\nchown -R www-data:www-data /opt/solidtime\nchmod -R 775 storage bootstrap/cache\n$STD php artisan storage:link\n$STD php artisan migrate --force\n$STD php artisan passport:client --personal --name=\"API\" -n\n$STD php artisan optimize:clear\nmsg_ok \"Set up SolidTime\"\n\nmsg_info \"Configuring Caddy\"\nPHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . \".\" . PHP_MINOR_VERSION;')\ncat <<EOF >/etc/caddy/Caddyfile\n:80 {\n    root * /opt/solidtime/public\n    php_fastcgi unix//run/php/php${PHP_VER}-fpm.sock\n    file_server\n    encode gzip\n}\nEOF\nusermod -aG www-data caddy\nsystemctl enable -q --now php${PHP_VER}-fpm\nsystemctl restart caddy\nmsg_ok \"Configured Caddy\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sonarqube-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: prop4n\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.sonarsource.com/sonarqube-server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nJAVA_VERSION=\"21\" setup_java\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"sonarqube\" PG_DB_USER=\"sonarqube\" setup_postgresql_db\n\nmsg_info \"Setting up SonarQube\"\ntemp_file=$(mktemp)\nRELEASE=$(get_latest_github_release \"SonarSource/sonarqube\")\ncurl -fsSL \"https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${RELEASE}.zip\" -o $temp_file\nunzip -q \"$temp_file\" -d /opt\nrm -f \"$temp_file\"\nmv /opt/sonarqube-* /opt/sonarqube\n$STD useradd -r -m -U -d /opt/sonarqube -s /bin/bash sonarqube\nchown -R sonarqube:sonarqube /opt/sonarqube\nchmod -R 755 /opt/sonarqube\nmkdir -p /opt/sonarqube/conf\ncat <<EOF >/opt/sonarqube/conf/sonar.properties\nsonar.jdbc.username=${PG_DB_USER}\nsonar.jdbc.password=${PG_DB_PASS}\nsonar.jdbc.url=jdbc:postgresql://localhost/${PG_DB_NAME}\nsonar.web.host=0.0.0.0\nsonar.web.port=9000\nEOF\nchmod +x /opt/sonarqube/bin/linux-aarch64/sonar.sh\necho ${RELEASE} >>~/.sonarqube\nmsg_ok \"Configured SonarQube\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/sonarqube.service\n[Unit]\nDescription=SonarQube service\nAfter=postgresql.service\n\n[Service]\nType=forking\nExecStart=/opt/sonarqube/bin/linux-aarch64/sonar.sh start\nExecStop=/opt/sonarqube/bin/linux-aarch64/sonar.sh stop\nUser=sonarqube\nGroup=sonarqube\nRestart=on-failure\nLimitNOFILE=131072\nLimitNPROC=8192\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now sonarqube\nmsg_ok \"Service Created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sonarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sonarr.tv/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3 libicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"Sonarr\" \"Sonarr/Sonarr\" \"prebuild\" \"latest\" \"/opt/Sonarr\" \"Sonarr.main.*.linux-arm64.tar.gz\"\nmkdir -p /var/lib/sonarr/\nchmod 775 /var/lib/sonarr/\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/sonarr.service\n[Unit]\nDescription=Sonarr Daemon\nAfter=syslog.target network.target\n\n[Service]\nType=simple\nExecStart=/opt/Sonarr/Sonarr -nobrowser -data=/var/lib/sonarr/\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now sonarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sonobarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: GoldenSpringness\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Dodelidoo-Labs/sonobarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"sonobarr\" \"Dodelidoo-Labs/sonobarr\" \"tarball\"\nPYTHON_VERSION=\"3.12\" setup_uv\n\nmsg_info \"Setting up sonobarr\"\n$STD uv venv -c /opt/sonobarr/venv\nsource /opt/sonobarr/venv/bin/activate\n$STD uv pip install --no-cache-dir -r /opt/sonobarr/requirements.txt\nmkdir -p /etc/sonobarr\nmv /opt/sonobarr/.sample-env /etc/sonobarr/.env\nsed -i \"s/^secret_key=.*/secret_key=$(openssl rand -hex 16)/\" /etc/sonobarr/.env\nsed -i \"s/^sonobarr_superadmin_password=.*/sonobarr_superadmin_password=$(openssl rand -hex 16)/\" /etc/sonobarr/.env\necho \"release_version=$(cat ~/.sonobarr)\" >>/etc/sonobarr/.env\necho \"sonobarr_config_dir=/etc/sonobarr\" >>/etc/sonobarr.env\nmsg_ok \"Set up sonobarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/sonobarr.service\n[Unit]\nDescription=sonobarr Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/sonobarr/src\nEnvironmentFile=/etc/sonobarr/.env\nEnvironment=\"PATH=/opt/sonobarr/venv/bin\"\nExecStart=/bin/bash -c 'gunicorn Sonobarr:app -c ../gunicorn_config.py'\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now sonobarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/soulsync-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/Nezreka/SoulSync\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  gcc \\\n  libffi-dev \\\n  libssl-dev \\\n  libchromaprint-tools \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nUV_PYTHON=\"3.11\" setup_uv\n\nfetch_and_deploy_gh_release \"soulsync\" \"Nezreka/SoulSync\" \"tarball\"\n\nmsg_info \"Setting up Application\"\ncd /opt/soulsync\n$STD uv venv /opt/soulsync/.venv --python 3.11\n$STD uv pip install -r requirements.txt --python /opt/soulsync/.venv/bin/python\nmkdir -p /opt/soulsync/{config,data,logs}\nmsg_ok \"Set up Application\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/soulsync.service\n[Unit]\nDescription=SoulSync Music Discovery\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/soulsync\nExecStart=/opt/soulsync/.venv/bin/python web_server.py\nEnvironment=PYTHONPATH=/opt/soulsync PYTHONUNBUFFERED=1 DATABASE_PATH=/opt/soulsync/data/music_library.db\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now soulsync\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sparkyfitness-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Tom Frenzel (tomfrenzel)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/CodeWithCJ/SparkyFitness\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y nginx\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"18\" setup_postgresql\nPG_DB_NAME=\"sparkyfitness\" PG_DB_USER=\"sparky\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\n\nfetch_and_deploy_gh_release sparkyfitness \"CodeWithCJ/SparkyFitness\" \"tarball\" \"latest\"\n\nPNPM_VERSION=\"$(jq -r '.packageManager | split(\"@\")[1]' /opt/sparkyfitness/package.json)\"\nNODE_VERSION=\"25\" NODE_MODULE=\"pnpm@${PNPM_VERSION}\" setup_nodejs\n\nmsg_info \"Configuring Sparky Fitness\"\nmkdir -p \"/etc/sparkyfitness\" \"/var/lib/sparkyfitness/uploads\" \"/var/lib/sparkyfitness/backup\" \"/var/www/sparkyfitness\"\ncp \"/opt/sparkyfitness/docker/.env.example\" \"/etc/sparkyfitness/.env\"\nsed \\\n  -i \\\n  -e \"s|^#\\?SPARKY_FITNESS_DB_HOST=.*|SPARKY_FITNESS_DB_HOST=localhost|\" \\\n  -e \"s|^#\\?SPARKY_FITNESS_DB_PORT=.*|SPARKY_FITNESS_DB_PORT=5432|\" \\\n  -e \"s|^SPARKY_FITNESS_DB_NAME=.*|SPARKY_FITNESS_DB_NAME=sparkyfitness|\" \\\n  -e \"s|^SPARKY_FITNESS_DB_USER=.*|SPARKY_FITNESS_DB_USER=sparky|\" \\\n  -e \"s|^SPARKY_FITNESS_DB_PASSWORD=.*|SPARKY_FITNESS_DB_PASSWORD=${PG_DB_PASS}|\" \\\n  -e \"s|^SPARKY_FITNESS_APP_DB_USER=.*|SPARKY_FITNESS_APP_DB_USER=sparky_app|\" \\\n  -e \"s|^SPARKY_FITNESS_APP_DB_PASSWORD=.*|SPARKY_FITNESS_APP_DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c20)|\" \\\n  -e \"s|^SPARKY_FITNESS_SERVER_HOST=.*|SPARKY_FITNESS_SERVER_HOST=localhost|\" \\\n  -e \"s|^SPARKY_FITNESS_SERVER_PORT=.*|SPARKY_FITNESS_SERVER_PORT=3010|\" \\\n  -e \"s|^SPARKY_FITNESS_FRONTEND_URL=.*|SPARKY_FITNESS_FRONTEND_URL=http://${LOCAL_IP}:80|\" \\\n  -e \"s|^GARMIN_MICROSERVICE_URL=.*|GARMIN_MICROSERVICE_URL=http://${LOCAL_IP}:8000|\" \\\n  -e \"s|^SPARKY_FITNESS_API_ENCRYPTION_KEY=.*|SPARKY_FITNESS_API_ENCRYPTION_KEY=$(openssl rand -hex 32)|\" \\\n  -e \"s|^BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$(openssl rand -hex 32)|\" \\\n  \"/etc/sparkyfitness/.env\"\nmsg_ok \"Configured Sparky Fitness\"\n\nmsg_info \"Building Backend\"\ncd /opt/sparkyfitness/SparkyFitnessServer\n$STD pnpm install\nmsg_ok \"Built Backend\"\n\nmsg_info \"Building Frontend (Patience)\"\ncd /opt/sparkyfitness\n$STD pnpm install\ncd /opt/sparkyfitness/SparkyFitnessFrontend\n$STD pnpm run build\ncp -a /opt/sparkyfitness/SparkyFitnessFrontend/dist/. /var/www/sparkyfitness/\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Creating SparkyFitness Service\"\ncat <<EOF >/etc/systemd/system/sparkyfitness-server.service\n[Unit]\nDescription=SparkyFitness Backend Service\nAfter=network.target postgresql.service\nRequires=postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/sparkyfitness/SparkyFitnessServer\nEnvironmentFile=/etc/sparkyfitness/.env\nExecStart=/opt/sparkyfitness/SparkyFitnessServer/node_modules/.bin/tsx SparkyFitnessServer.js\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now sparkyfitness-server\nmsg_ok \"Created SparkyFitness Service\"\n\nmsg_info \"Configuring Nginx\"\nsed \\\n  -e 's|${SPARKY_FITNESS_SERVER_HOST}|127.0.0.1|g' \\\n  -e 's|${SPARKY_FITNESS_SERVER_PORT}|3010|g' \\\n  -e 's|root /usr/share/nginx/html;|root /var/www/sparkyfitness;|g' \\\n  -e 's|server_name localhost;|server_name _;|g' \\\n  \"/opt/sparkyfitness/docker/nginx.conf\" >/etc/nginx/sites-available/sparkyfitness\nln -sf /etc/nginx/sites-available/sparkyfitness /etc/nginx/sites-enabled/sparkyfitness\nrm -f /etc/nginx/sites-enabled/default\n$STD nginx -t\n$STD systemctl enable -q --now nginx\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/speedtest-tracker-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: AlphaLawless\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alexjustesen/speedtest-tracker\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  sqlite3\nsetcap cap_net_raw+ep /bin/ping\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" setup_php\nsetup_composer\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"speedtest-tracker\" \"alexjustesen/speedtest-tracker\" \"tarball\"\n\nmsg_info \"Installing Speedtest CLI\"\nsetup_deb822_repo \\\n  \"speedtest-cli\" \\\n  \"https://packagecloud.io/ookla/speedtest-cli/gpgkey\" \\\n  \"https://packagecloud.io/ookla/speedtest-cli/debian\" \\\n  \"$(get_os_info codename)\" \\\n  \"main\"\n$STD apt install -y speedtest\nmsg_ok \"Installed Speedtest CLI\"\n\nmsg_info \"Configuring PHP-FPM runtime directory\"\nmkdir -p /etc/systemd/system/php8.4-fpm.service.d/\ncat <<EOF >/etc/systemd/system/php8.4-fpm.service.d/override.conf\n[Service]\nRuntimeDirectory=php\nRuntimeDirectoryMode=0755\nEOF\nmsg_ok \"Configured PHP-FPM runtime directory\"\n\nmsg_info \"Setting up Speedtest Tracker\"\ncd /opt/speedtest-tracker\nAPP_KEY=$(php -r \"echo bin2hex(random_bytes(16));\")\nTIMEZONE=$(timedatectl | grep \"Time zone\" | awk '{print $3}')\ncat <<EOF >/opt/speedtest-tracker/.env\nAPP_NAME=\"Speedtest Tracker\"\nAPP_ENV=production\nAPP_TIMEZONE=${TIMEZONE}\nAPP_KEY=base64:$(echo -n $APP_KEY | base64)\nAPP_DEBUG=false\nAPP_URL=http://${LOCAL_IP}\n\nLOG_CHANNEL=stack\nLOG_LEVEL=debug\n\nDB_CONNECTION=sqlite\nDB_DATABASE=/opt/speedtest-tracker/database/database.sqlite\n\nBROADCAST_DRIVER=log\nCACHE_DRIVER=file\nFILESYSTEM_DISK=local\nQUEUE_CONNECTION=sync\nSESSION_DRIVER=file\nSESSION_LIFETIME=120\n\nSPEEDTEST_SCHEDULE=\"0 */6 * * *\"\nSPEEDTEST_SERVERS=\nSPEEDTEST_EXTERNAL_IP_URL=https://ip.me \nSPEEDTEST_INTERNET_CHECK_HOSTNAME=1.1.1.1\nPRUNE_RESULTS_OLDER_THAN=0\n\nDISPLAY_TIMEZONE=${TIMEZONE}\nEOF\nmkdir -p /opt/speedtest-tracker/database\ntouch /opt/speedtest-tracker/database/database.sqlite\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD composer install --optimize-autoloader --no-dev\n$STD npm ci\n$STD npm run build\n$STD php artisan key:generate --force\n$STD php artisan migrate --force --seed\n$STD php artisan config:clear\n$STD php artisan cache:clear\n$STD php artisan view:clear\nchown -R www-data:www-data /opt/speedtest-tracker\nchmod -R 755 /opt/speedtest-tracker/storage\nchmod -R 755 /opt/speedtest-tracker/bootstrap/cache\nmsg_ok \"Set up Speedtest Tracker\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/speedtest-tracker.service\n[Unit]\nDescription=Speedtest Tracker Queue Worker\nAfter=network.target\n\n[Service]\nType=simple\nUser=www-data\nGroup=www-data\nRestart=always\nExecStart=/usr/bin/php /opt/speedtest-tracker/artisan queue:work --sleep=3 --tries=3 --max-time=3600\nWorkingDirectory=/opt/speedtest-tracker\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now speedtest-tracker\nmsg_ok \"Created Service\"\n\nmsg_info \"Setting up Scheduler\"\ncat <<EOF >/etc/cron.d/speedtest-tracker\n* * * * * www-data cd /opt/speedtest-tracker && php artisan schedule:run >> /dev/null 2>&1\nEOF\nmsg_ok \"Set up Scheduler\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/speedtest-tracker\nserver {\n    listen 80;\n    server_name _;\n    root /opt/speedtest-tracker/public;\n\n    add_header X-Frame-Options \"SAMEORIGIN\";\n    add_header X-Content-Type-Options \"nosniff\";\n\n    index index.php;\n\n    charset utf-8;\n\n    location / {\n        try_files \\$uri \\$uri/ /index.php?\\$query_string;\n    }\n\n    location = /favicon.ico { access_log off; log_not_found off; }\n    location = /robots.txt  { access_log off; log_not_found off; }\n\n    error_page 404 /index.php;\n\n    location ~ \\.php$ {\n        fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME \\$realpath_root\\$fastcgi_script_name;\n        include fastcgi_params;\n    }\n\n    location ~ /\\.(?!well-known).* {\n        deny all;\n    }\n}\nEOF\n\nln -sf /etc/nginx/sites-available/speedtest-tracker /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\nsystemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/split-pro-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: johanngrobe\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/oss-apps/split-pro\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\nPG_VERSION=\"17\" PG_MODULES=\"cron\" setup_postgresql\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y openssl\nmsg_ok \"Installed Dependencies\"\n\nPG_DB_NAME=\"splitpro\" PG_DB_USER=\"splitpro\" PG_DB_EXTENSIONS=\"pg_cron\" setup_postgresql_db\nfetch_and_deploy_gh_release \"split-pro\" \"oss-apps/split-pro\" \"tarball\"\n\nmsg_info \"Installing Dependencies\"\ncd /opt/split-pro\n$STD pnpm install --frozen-lockfile\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Building Split Pro\"\ncd /opt/split-pro\nmkdir -p /opt/split-pro_data/uploads\nln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads\nNEXTAUTH_SECRET=$(openssl rand -base64 32)\ncp .env.example .env\nsed -i \"s|^DATABASE_URL=.*|DATABASE_URL=\\\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\\\"|\" .env\nsed -i \"s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=\\\"${NEXTAUTH_SECRET}\\\"|\" .env\nsed -i \"s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=\\\"http://${LOCAL_IP}:3000\\\"|\" .env\nsed -i \"s|^NEXTAUTH_URL_INTERNAL=.*|NEXTAUTH_URL_INTERNAL=\\\"http://localhost:3000\\\"|\" .env\nsed -i \"/^POSTGRES_CONTAINER_NAME=/d\" .env\nsed -i \"/^POSTGRES_USER=/d\" .env\nsed -i \"/^POSTGRES_PASSWORD=/d\" .env\nsed -i \"/^POSTGRES_DB=/d\" .env\nsed -i \"/^POSTGRES_PORT=/d\" .env\n$STD pnpm build\n$STD pnpm exec prisma migrate deploy\nmsg_ok \"Built Split Pro\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/split-pro.service\n[Unit]\nDescription=Split Pro\nAfter=network.target postgresql.service\nRequires=postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/split-pro\nEnvironmentFile=/opt/split-pro/.env\nExecStart=/usr/bin/pnpm start\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now split-pro\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/splunk-enterprise-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rcastley\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.splunk.com/en_us/download.html\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\necho -e \"${TAB3}┌─────────────────────────────────────────────────────────────────────────┐\"\necho -e \"${TAB3}│                          SPLUNK GENERAL TERMS                           │\"\necho -e \"${TAB3}└─────────────────────────────────────────────────────────────────────────┘\"\necho \"\"\necho -e \"${TAB3}Before proceeding with the Splunk Enterprise installation, you must\"\necho -e \"${TAB3}review and accept the Splunk General Terms.\"\necho \"\"\necho -e \"${TAB3}Please review the terms at:\"\necho -e \"${TAB3}${GATEWAY}${BGN}https://www.splunk.com/en_us/legal/splunk-general-terms.html${CL}\"\necho \"\"\n\nwhile true; do\n    echo -e \"${TAB3}Do you accept the Splunk General Terms? (y/N): \\c\"\n    read -r response\n    case $response in\n        [Yy]|[Yy][Ee][Ss])\n            msg_ok \"Terms accepted. Proceeding with installation...\"\n            break\n            ;;\n        [Nn]|[Nn][Oo]|\"\")\n            msg_error \"Terms not accepted. Installation cannot proceed.\"\n            msg_error \"Please review the terms and run the script again if you wish to proceed.\"\n            exit 254\n            ;;\n        *)\n            msg_error \"Invalid response. Please enter 'y' for yes or 'n' for no.\"\n            ;;\n    esac\ndone\n\nmsg_info \"Setup Splunk Enterprise\"\nDOWNLOAD_URL=$(curl -s \"https://www.splunk.com/en_us/download/splunk-enterprise.html\" | grep -o 'data-link=\"[^\"]*' | sed 's/data-link=\"//' | grep \"https.*products/splunk/releases\" | grep \"linux-amd64\\.tgz$\")\nRELEASE=$(echo \"$DOWNLOAD_URL\" | sed 's|.*/releases/\\([^/]*\\)/.*|\\1|')\n$STD curl -fsSL -o \"splunk-enterprise.tgz\" \"$DOWNLOAD_URL\" || {\n    msg_error \"Failed to download Splunk Enterprise from the provided link.\"\n    exit 250\n}\n$STD tar -xzf \"splunk-enterprise.tgz\" -C /opt\nrm -f \"splunk-enterprise.tgz\"\naddgroup --system splunk\nadduser --system --home /opt/splunk --shell /bin/bash --ingroup splunk --no-create-home splunk\nchown -R splunk:splunk /opt/splunk\nmsg_ok \"Setup Splunk Enterprise v${RELEASE}\"\n\nmsg_info \"Creating Splunk admin user\"\nADMIN_USER=\"admin\"\nADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n{\n    echo \"Splunk-Credentials\"\n    echo \"Username: $ADMIN_USER\"\n    echo \"Password: $ADMIN_PASS\"\n} >> ~/splunk.creds\n\ncat << EOF > \"/opt/splunk/etc/system/local/user-seed.conf\"\n[user_info]\nUSERNAME = $ADMIN_USER\nPASSWORD = $ADMIN_PASS\nEOF\nmsg_ok \"Created Splunk admin user\"\n\nmsg_info \"Starting Service\"\n$STD sudo -u splunk /opt/splunk/bin/splunk start --accept-license --answer-yes --no-prompt\n$STD /opt/splunk/bin/splunk enable boot-start -user splunk\nmsg_ok \"Started Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/spoolman-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Donkie/Spoolman\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  libpq-dev \\\n  libffi-dev\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"spoolman\" \"Donkie/Spoolman\" \"prebuild\" \"latest\" \"/opt/spoolman\" \"spoolman.zip\"\nPYTHON_VERSION=\"3.14\" setup_uv\n\nmsg_info \"Setting up Spoolman\"\ncd /opt/spoolman\n$STD uv sync --locked --no-install-project\n$STD uv sync --locked\ncp .env.example .env\nmsg_ok \"Setup Spoolman\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/spoolman.service\n[Unit]\nDescription=Spoolman\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/spoolman\nEnvironmentFile=/opt/spoolman/.env\nExecStart=/usr/bin/bash /opt/spoolman/scripts/start.sh\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now spoolman\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sportarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Sportarr/Sportarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ffmpeg \\\n  gosu \\\n  sqlite3\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"sportarr\" \"Sportarr/Sportarr\" \"prebuild\" \"latest\" \"/opt/sportarr\" \"Sportarr-linux-arm64-*.tar.gz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/sportarr/.env\nSportarr__DataPath=\"/opt/sportarr-data/config\"\nASPNETCORE_URLS=\"http://*:1867\"\nASPNETCORE_ENVIRONMENT=\"Production\"\nDOTNET_CLI_TELEMETRY_OPTOUT=1\nDOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false\nLIBVA_DRIVER_NAME=iHD\nEOF\ncat <<EOF >/etc/systemd/system/sportarr.service\n[Unit]\nDescription=Sportarr Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/sportarr/.env\nWorkingDirectory=/opt/sportarr\nExecStart=/opt/sportarr/Sportarr\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now sportarr\nmsg_info \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sqlserver2022-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.microsoft.com/en-us/sql-server/sql-server-2022\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y coreutils\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up SQL Server 2022 Repository\"\nsetup_deb822_repo \\\n  \"mssql-server-2022\" \\\n  \"https://packages.microsoft.com/keys/microsoft.asc\" \\\n  \"https://packages.microsoft.com/ubuntu/22.04/mssql-server-2022\" \\\n  \"jammy\" \\\n  \"main\"\nmsg_ok \"Repository configured\"\n\nmsg_info \"Installing SQL Server 2022\"\n$STD apt install -y mssql-server\nmsg_ok \"Installed SQL Server 2022\"\n\nmsg_info \"Installing SQL Server Tools\"\nexport DEBIAN_FRONTEND=noninteractive\nexport ACCEPT_EULA=Y\nsetup_deb822_repo \\\n  \"mssql-release\" \\\n  \"https://packages.microsoft.com/keys/microsoft.asc\" \\\n  \"https://packages.microsoft.com/ubuntu/22.04/prod\" \\\n  \"jammy\" \\\n  \"main\"\n$STD apt-get install -y \\\n  mssql-tools18 \\\n  unixodbc-dev\necho 'export PATH=\"$PATH:/opt/mssql-tools18/bin\"' >>~/.bash_profile\nsource ~/.bash_profile\nmsg_ok \"Installed SQL Server Tools\"\n\nread -r -p \"${TAB3}Do you want to run the SQL server setup now? (Later is also possible) <y/N>\" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  /opt/mssql/bin/mssql-conf setup\nelse\n  msg_ok \"Skipping SQL Server setup. You can run it later with '/opt/mssql/bin/mssql-conf setup'.\"\nfi\n\nmsg_info \"Start Service\"\nsystemctl enable -q --now mssql-server\nmsg_ok \"Service started\"\n\nmsg_info \"Cleaning up\"\nrm -f /etc/profile.d/debuginfod.sh\nrm -f /etc/profile.d/debuginfod.csh\nmsg_ok \"Cleaned up\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sqlserver2025-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.microsoft.com/en-us/sql-server/sql-server-2025\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y coreutils\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up SQL Server 2025 Repository\"\nsetup_deb822_repo \\\n  \"mssql-server-2025\" \\\n  \"https://packages.microsoft.com/keys/microsoft.asc\" \\\n  \"https://packages.microsoft.com/ubuntu/24.04/mssql-server-2025\" \\\n  \"noble\" \\\n  \"main\"\nmsg_ok \"Repository configured\"\n\nmsg_info \"Installing SQL Server 2025\"\n$STD apt install -y mssql-server\nmsg_ok \"Installed SQL Server 2025\"\n\nmsg_info \"Installing SQL Server Tools\"\nexport DEBIAN_FRONTEND=noninteractive\nexport ACCEPT_EULA=Y\nsetup_deb822_repo \\\n  \"mssql-release\" \\\n  \"https://packages.microsoft.com/keys/microsoft.asc\" \\\n  \"https://packages.microsoft.com/ubuntu/24.04/prod\" \\\n  \"noble\" \\\n  \"main\"\n$STD apt-get install -y \\\n  mssql-tools18 \\\n  unixodbc-dev\necho 'export PATH=\"$PATH:/opt/mssql-tools18/bin\"' >>~/.bash_profile\nsource ~/.bash_profile\nmsg_ok \"Installed SQL Server Tools\"\n\nread -r -p \"${TAB3}Do you want to run the SQL Server setup now? (Later is also possible) <y/N>\" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  /opt/mssql/bin/mssql-conf setup\nelse\n  msg_ok \"Skipping SQL Server setup. You can run it later with '/opt/mssql/bin/mssql-conf setup'.\"\nfi\n\nmsg_info \"Starting SQL Server Service\"\nsystemctl enable -q --now mssql-server\nmsg_ok \"Service started\"\n\nmsg_info \"Cleaning up\"\nrm -f /etc/profile.d/debuginfod.sh /etc/profile.d/debuginfod.csh\nmsg_ok \"Cleaned up\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/step-ca-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joerg Heinemann (heinemannj)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/smallstep/certificates\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_deb822_repo \\\n  \"smallstep\" \\\n  \"https://packages.smallstep.com/keys/apt/repo-signing-key.gpg\" \\\n  \"https://packages.smallstep.com/stable/debian\" \\\n  \"debs\" \\\n  \"main\"\n\nmsg_info \"Installing step-ca and step-cli\"\n$STD apt install -y step-ca step-cli\n\nSTEPPATH=\"/etc/step-ca\"\nSTEPHOME=\"/etc/step\"\n\nexport STEPPATH=$STEPPATH\necho \"export STEPPATH=${STEPPATH}\" >> /etc/profile\nexport STEPHOME=$STEPHOME\necho \"export STEPHOME=${STEPHOME}\" >> /etc/profile\n\nmkdir -p \"$STEPHOME\"\n\n# Patch for making $STD happy (/usr/bin/step is a symlink to /usr/bin/step-cli)\nSTEPBIN=\"$(which step)\"\nrm -f \"$STEPBIN\"\ncp -f \"$(which step-cli)\" \"$STEPBIN\"\n\n# Low port-binding capabilities (ports < 1024)\n# - Default step-ca listener port: 443\nsetcap CAP_NET_BIND_SERVICE=+eip \"$(which step-ca)\"\n\n# Service User used by systemd step-ca.service\n$STD useradd --user-group --system --home \"$(step path)\" --shell /bin/false step\nmsg_ok \"Installed step-ca and step-cli\"\n\nDomainName=\"$(hostname -d)\"\n\nPKIName=\"$(prompt_input \"Enter PKIName\" \"MyHomePKI\" 30)\"\nPKICountry=\"$(prompt_input \"Enter PKICountry\" \"DE\" 30)\"\nPKIOrganizationalUnit=\"$(prompt_input \"Enter PKIOrganizationalUnit\" \"MyHomeLab\" 30)\"\nPKIProvisioner=\"$(prompt_input \"Enter PKIProvisioner\" \"pki@$DomainName\" 30)\"\nAcmeProvisioner=\"$(prompt_input \"Enter AcmeProvisioner\" \"acme@$DomainName\" 30)\"\nX509MinDur=\"$(prompt_input \"Enter X509MinDur\" \"48h\" 30)\"\nX509MaxDur=\"$(prompt_input \"Enter X509MaxDur\" \"87600h\" 30)\"\nX509DefaultDur=\"$(prompt_input \"Enter X509DefaultDur\" \"168h\" 30)\"\n\nmsg_info \"Initializing step-ca\"\n\n# Initialize step-ca\nDeploymentType=\"standalone\"\nFQDN=\"$(hostname -f)\"\nIP=\"${LOCAL_IP}\"\nLISTENER=\":443\"\nLISTENER_INSECURE=\":80\"\n\n# Set different signing CA and Provisioner Passwords\nEncryptionPwdDir=\"$(step path)/encryption\"\nPwdFile=\"$EncryptionPwdDir/ca.pwd\"\nProvisionerPwdFile=\"$EncryptionPwdDir/provisioner.pwd\"\nmkdir -p \"$EncryptionPwdDir\"\ngpg -q --gen-random --armor 2 32 >\"$PwdFile\"\ngpg -q --gen-random --armor 2 32 >\"$ProvisionerPwdFile\"\n\n# Used by systemd step-ca.service\nln -s \"$PwdFile\" \"$(step path)/password.txt\"\n\n# Usage of:\n# - SSH feature of step-ca\n# - BadgerDB (badgerv2) => Default DB backend of step-ca\n# - badgerFileLoadingMode: FileIO (instead of MemoryMap) for LXC with low RAM\n$STD step ca init \\\n  --deployment-type=\"$DeploymentType\" \\\n  --ssh \\\n  --name=\"$PKIName\" \\\n  --dns=\"$FQDN\" \\\n  --dns=\"$IP\" \\\n  --address=\"$LISTENER\" \\\n  --provisioner=\"$PKIProvisioner\" \\\n  --password-file=\"$PwdFile\" \\\n  --provisioner-password-file=\"$ProvisionerPwdFile\"\n\n# Define enhanced x509 CA and Certificate Templates\nmkdir -p \"$(step path)/templates/ca\"\nmkdir -p \"$(step path)/templates/x509\"\n\nCARootTemplate=\"$(step path)/templates/ca/root.tpl\"\nCAIntermediateTemplate=\"$(step path)/templates/ca/intermediate.tpl\"\nX509LeafTemplate=\"$(step path)/templates/x509/leaf.tpl\"\nX509LeafTemplateData=\"$(step path)/templates/x509/leaf_data.tpl\"\n\ncat <<'EOF' >\"$CARootTemplate\"\n{\n\t\"subject\": {\n\t\t\"country\": {{ toJson .Insecure.User.country }},\n\t\t\"organization\": {{ toJson .Insecure.User.organization }},\n\t\t\"organizationalUnit\": {{ toJson .Insecure.User.organizationalUnit }},\n\t\t\"commonName\": {{ toJson .Subject.CommonName }}\n\t},\n  \"issuer\": {{ toJson .Subject }},\n\t\"keyUsage\": [\"certSign\", \"crlSign\"],\n\t\"basicConstraints\": {\n\t\t\"isCA\": true,\n\t\t\"maxPathLen\": 1\n\t},\n\t\"issuingCertificateURL\": [{{ toJson .Insecure.User.issuingCertificateURL }}],\n\t\"crlDistributionPoints\": [{{ toJson .Insecure.User.crlDistributionPoints }}]\n}\nEOF\n\ncat <<'EOF' >\"$CAIntermediateTemplate\"\n{\n\t\"subject\": {\n\t\t\"country\": {{ toJson .Insecure.User.country }},\n\t\t\"organization\": {{ toJson .Insecure.User.organization }},\n\t\t\"organizationalUnit\": {{ toJson .Insecure.User.organizationalUnit }},\n\t\t\"commonName\": {{ toJson .Subject.CommonName }}\n\t},\n\t\"keyUsage\": [\"certSign\", \"crlSign\"],\n\t\"basicConstraints\": {\n\t\t\"isCA\": true,\n\t\t\"maxPathLen\": 0\n\t},\n\t\"issuingCertificateURL\": [{{ toJson .Insecure.User.issuingCertificateURL }}],\n\t\"crlDistributionPoints\": [{{ toJson .Insecure.User.crlDistributionPoints }}]\n}\nEOF\n\ncat <<'EOF' >\"$X509LeafTemplate\"\n{\n\t\"subject\": {\n{{- if .Insecure.User.Country }}\n\t\t\"country\": {{ toJson .Insecure.User.country }},\n{{- else }}\n\t\t\"country\": {{ toJson .country }},\n{{- end }}\n{{- if .Insecure.User.organization }}\n\t\t\"organization\": {{ toJson .Insecure.User.organization }},\n{{- else }}\n\t\t\"organization\": {{ toJson .organization }},\n{{- end }}\n{{- if .Insecure.User.organizationalUnit }}\n\t\t\"organizationalUnit\": {{ toJson .Insecure.User.organizationalUnit }},\n{{- else }}\n\t\t\"organizationalUnit\": {{ toJson .organizationalUnit }},\n{{- end }}\n\t\t\"commonName\": {{ toJson .Subject.CommonName }}\n\t},\n\t\"sans\": {{ toJson .SANs }},\n{{- if typeIs \"*rsa.PublicKey\" .Insecure.CR.PublicKey }}\n\t\"keyUsage\": [\"keyEncipherment\", \"digitalSignature\"],\n{{- else }}\n\t\"keyUsage\": [\"digitalSignature\"],\n{{- end }}\n\t\"extKeyUsage\": [\"serverAuth\", \"clientAuth\"],\n{{- if .Insecure.User.issuingCertificateURL }}\n\t\"issuingCertificateURL\": [{{ toJson .Insecure.User.issuingCertificateURL }}],\n{{- else }}\n\t\"issuingCertificateURL\": [{{ toJson .issuingCertificateURL }}],\n{{- end }}\n{{- if .Insecure.User.crlDistributionPoints }}\n\t\"crlDistributionPoints\": [{{ toJson .Insecure.User.crlDistributionPoints }}]\n{{- else }}\n\t\"crlDistributionPoints\": [{{ toJson .crlDistributionPoints }}]\n{{- end }}\n}\nEOF\n\ncat <<EOF >\"$X509LeafTemplateData\"\n{\n\t\"country\": \"${PKICountry}\",\n\t\"organization\": \"${PKIName}\",\n\t\"organizationalUnit\": \"${PKIOrganizationalUnit}\",\n\t\"issuingCertificateURL\": [\"https://${FQDN}${LISTENER}/intermediates.pem\"],\n\t\"crlDistributionPoints\": [\"https://${FQDN}${LISTENER}/crl\"]\n}\nEOF\n\n# Configure CA Provisioners, DB and CRL settings\n$STD step ca provisioner add \"$AcmeProvisioner\" \\\n  --type ACME \\\n  --admin-name \"$AcmeProvisioner\"\n\n$STD step ca provisioner update \"$PKIProvisioner\" \\\n  --x509-min-dur=\"$X509MinDur\" \\\n  --x509-max-dur=\"$X509MaxDur\" \\\n  --x509-default-dur=\"$X509DefaultDur\" \\\n  --x509-template=\"$X509LeafTemplate\" \\\n  --x509-template-data=\"$X509LeafTemplateData\" \\\n  --allow-renewal-after-expiry\n\n$STD step ca provisioner update \"$AcmeProvisioner\" \\\n  --x509-min-dur=\"$X509MinDur\" \\\n  --x509-max-dur=\"$X509MaxDur\" \\\n  --x509-default-dur=\"$X509DefaultDur\" \\\n  --x509-template=\"$X509LeafTemplate\" \\\n  --x509-template-data=\"$X509LeafTemplateData\" \\\n  --allow-renewal-after-expiry\n\nCAConfig=\"$(step path)/config/ca.json\"\njq --arg a \"${PKICountry}\" '.country = $a' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq --arg a \"${PKIName}\" '.organization = $a' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq --arg a \"${PKIOrganizationalUnit}\" '.organizationalUnit = $a' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq --arg a \"${PKIName} Online CA\" '.commonName = $a' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq '.db.badgerFileLoadingMode = \"FileIO\"' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq '.crl.enabled = true' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq '.crl.generateOnRevoke = true' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq '.crl.cacheDuration = \"24h0m0s\"' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq '.crl.renewPeriod = \"16h0m0s\"' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq --arg a \"https://${FQDN}${LISTENER}/crl\" '.crl.idpURL = $a' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\njq --arg a \"$LISTENER_INSECURE\" '.insecureAddress = $a' \"${CAConfig}\" > \"${CAConfig}_tmp\" && mv \"${CAConfig}_tmp\" \"${CAConfig}\"\n\n# Generate Root CA Certificate and Key\n# - Validity: 219168h (~25 Years)\n# - maxPathLen: 1 (Root -> Intermediate -> Leaf) => Only one Intermediate CA allowed below Root CA\n# - Active revocation on Intermediate CA and Leaf Certificates by the usage of build-in Certificate Revocation List (CRL)\nFLAGS=(--force\n  --template=\"${CARootTemplate}\"\n  --not-after=\"219168h\"\n  --password-file=\"${PwdFile}\"\n  --set country=\"${PKICountry}\"\n  --set organization=\"${PKIName}\"\n  --set organizationalUnit=\"${PKIOrganizationalUnit}\"\n  --set issuingCertificateURL=\"https://${FQDN}${LISTENER}/roots.pem\"\n  --set crlDistributionPoints=\"https://${FQDN}${LISTENER}/crl\")\n\n$STD step certificate create \"${PKIName} Root CA\" \\\n  \"$(step path)/certs/root_ca.crt\" \\\n  \"$(step path)/secrets/root_ca_key\" \\\n  \"${FLAGS[@]}\"\n\n# Generate Intermediate CA Certificate Bundle and Key\n# - Validity: 175368h (~20 Years)\n# - maxPathLen: 0 (Root -> Intermediate -> Leaf) => Intermediate CA is only allowed to issue Leaf Certificates\n# - Active revocation on Leaf Certificates by the usage of build-in Certificate Revocation List (CRL)\n# - Bundle: Certificate Chain (including Root CA Certificate)\nFLAGS=(--force\n  --template=\"${CAIntermediateTemplate}\"\n  --ca=\"$(step path)/certs/root_ca.crt\"\n  --ca-key=\"$(step path)/secrets/root_ca_key\"\n  --not-after=\"175368h\"\n  --ca-password-file=\"${PwdFile}\"\n  --password-file=\"${PwdFile}\"\n  --bundle\n  --set country=\"${PKICountry}\"\n  --set organization=\"${PKIName}\"\n  --set organizationalUnit=\"${PKIOrganizationalUnit}\"\n  --set issuingCertificateURL=\"https://${FQDN}${LISTENER}/roots.pem\"\n  --set crlDistributionPoints=\"https://${FQDN}${LISTENER}/crl\")\n\n$STD step certificate create \"${PKIName} Intermediate CA\" \\\n  \"$(step path)/certs/intermediate_ca.crt\" \\\n  \"$(step path)/secrets/intermediate_ca_key\" \\\n  \"${FLAGS[@]}\"\n\n# Install Root CA Certificate to System Trust Store\n$STD step certificate install --all \"$(step path)/certs/root_ca.crt\"\n$STD update-ca-certificates\n\nchown -R step:step \"$(step path)\"\nchmod -R 700 \"$(step path)\"\nmsg_ok \"Initialized step-ca\"\n\nmsg_info \"Start step-ca as a Daemon\"\n\n# https://smallstep.com/docs/step-ca/certificate-authority-server-production/#running-step-ca-as-a-daemon\ncat <<'EOF' >/etc/systemd/system/step-ca.service\n[Unit]\nDescription=step-ca service\nDocumentation=https://smallstep.com/docs/step-ca\nDocumentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production\nAfter=network-online.target\nWants=network-online.target\nStartLimitIntervalSec=30\nStartLimitBurst=3\nConditionFileNotEmpty=/etc/step-ca/config/ca.json\nConditionFileNotEmpty=/etc/step-ca/password.txt\n\n[Service]\nType=simple\nUser=step\nGroup=step\nEnvironment=STEPPATH=/etc/step-ca\nWorkingDirectory=/etc/step-ca\nExecStart=/usr/bin/step-ca config/ca.json --password-file password.txt\nExecReload=/bin/kill -USR1 $MAINPID\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=30\nStartLimitAction=reboot\n\n; Process capabilities & privileges\nAmbientCapabilities=CAP_NET_BIND_SERVICE\nCapabilityBoundingSet=CAP_NET_BIND_SERVICE\nSecureBits=keep-caps\nNoNewPrivileges=yes\n\n; Sandboxing\nSystemCallArchitectures=native\nSystemCallFilter=@system-service\nSystemCallFilter=~@resources @privileged\nRestrictNamespaces=yes\nLockPersonality=yes\nMemoryDenyWriteExecute=yes\nRestrictRealtime=yes\nRestrictSUIDSGID=yes\nPrivateMounts=yes\nProtectControlGroups=yes\nProtectKernelModules=yes\nProtectKernelTunables=yes\nProtectSystem=strict\nProtectHome=yes\nReadWritePaths=/etc/step-ca/db\n\n; Read only paths\nReadOnlyPaths=/etc/step-ca\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now step-ca\nmsg_ok \"Started step-ca as a Daemon\"\n\nfetch_and_deploy_gh_release \"step-badger\" \"lukasz-lobocki/step-badger\" \"prebuild\" \"latest\" \"/opt/step-badger\" \"step-badger_Linux_x86_64.tar.gz\"\nln -s /opt/step-badger/step-badger /usr/local/bin/step-badger\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/stirling-pdf-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.stirlingpdf.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  automake \\\n  autoconf \\\n  libtool \\\n  libleptonica-dev \\\n  pkg-config \\\n  zlib1g-dev \\\n  make \\\n  g++ \\\n  unpaper \\\n  fonts-urw-base35 \\\n  qpdf \\\n  poppler-utils \\\n  jbig2\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nJAVA_VERSION=\"25\" setup_java\n\nread -r -p \"${TAB3}Do you want to use Stirling-PDF with Login? (no/n = without Login) [Y/n] \" response\nresponse=${response,,} # Convert to lowercase\nlogin_mode=\"false\"\nif [[ \"$response\" == \"y\" || \"$response\" == \"yes\" || -z \"$response\" ]]; then\n  USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"stirling-pdf\" \"Stirling-Tools/Stirling-PDF\" \"singlefile\" \"latest\" \"/opt/Stirling-PDF\" \"Stirling-PDF-with-login.jar\"\n  mv /opt/Stirling-PDF/Stirling-PDF-with-login.jar /opt/Stirling-PDF/Stirling-PDF.jar\n  touch ~/.Stirling-PDF-login\n  login_mode=\"true\"\nelse\n  USE_ORIGINAL_FILENAME=true fetch_and_deploy_gh_release \"stirling-pdf\" \"Stirling-Tools/Stirling-PDF\" \"singlefile\" \"latest\" \"/opt/Stirling-PDF\" \"Stirling-PDF.jar\"\nfi\n\nmsg_info \"Installing LibreOffice Components\"\n$STD apt install -y \\\n  libreoffice-writer \\\n  libreoffice-calc \\\n  libreoffice-impress \\\n  libreoffice-core \\\n  libreoffice-common \\\n  libreoffice-base-core \\\n  libreoffice-script-provider-python \\\n  libreoffice-java-common \\\n  pngquant \\\n  weasyprint\nmsg_ok \"Installed LibreOffice Components\"\n\nmsg_info \"Installing Python Dependencies\"\nmkdir -p /tmp/stirling-pdf\n$STD uv venv --clear /opt/.venv\nexport PATH=\"/opt/.venv/bin:$PATH\"\nsource /opt/.venv/bin/activate\n$STD uv pip install --upgrade pip\n$STD uv pip install \\\n  opencv-python-headless \\\n  ocrmypdf \\\n  pillow \\\n  pdf2image\n$STD apt install -y python3-uno python3-pip\n$STD pip3 install --break-system-packages --timeout=120 unoserver\nln -sf /opt/.venv/bin/python3 /usr/local/bin/python3\nln -sf /opt/.venv/bin/pip /usr/local/bin/pip\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Installing Language Packs (Patience)\"\n$STD apt install -y 'tesseract-ocr-*'\nmsg_ok \"Installed Language Packs\"\n\nmsg_info \"Creating Environment Variables\"\ncat <<EOF >/opt/Stirling-PDF/.env\n# Java tuning\nJAVA_BASE_OPTS=\"-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70\"\nJAVA_CUSTOM_OPTS=\"\"\n\n# LibreOffice\nPATH=/opt/.venv/bin:/usr/lib/libreoffice/program:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nUNO_PATH=/usr/lib/libreoffice/program\nURE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc\nPYTHONPATH=/usr/lib/libreoffice/program:/opt/.venv/lib/python3.12/site-packages\nLD_LIBRARY_PATH=/usr/lib/libreoffice/program\n\nSTIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf\nTMPDIR=/tmp/stirling-pdf\nTEMP=/tmp/stirling-pdf\nTMP=/tmp/stirling-pdf\n\n# Paths\nPATH=/opt/.venv/bin:/usr/lib/libreoffice/program:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nEOF\n\nif [[ \"$login_mode\" == \"true\" ]]; then\n  cat <<EOF >>/opt/Stirling-PDF/.env\n# activate Login\nDISABLE_ADDITIONAL_FEATURES=false\nSECURITY_ENABLELOGIN=true\n\n# login credentials\nSECURITY_INITIALLOGIN_USERNAME=admin\nSECURITY_INITIALLOGIN_PASSWORD=stirling\nEOF\nfi\nmsg_ok \"Created Environment Variables\"\n\nmsg_info \"Refreshing Font Cache\"\n$STD fc-cache -fv\nmsg_ok \"Font Cache Updated\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/libreoffice-listener.service\n[Unit]\nDescription=LibreOffice Headless Listener Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nExecStart=/usr/lib/libreoffice/program/soffice --headless --invisible --nodefault --nofirststartwizard --nolockcheck --nologo --accept=\"socket,host=127.0.0.1,port=2002;urp;StarOffice.ComponentContext\"\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/stirlingpdf.service\n[Unit]\nDescription=Stirling-PDF service\nAfter=syslog.target network.target libreoffice-listener.service\nRequires=libreoffice-listener.service\n\n[Service]\nSuccessExitStatus=143\nType=simple\nUser=root\nGroup=root\nEnvironmentFile=/opt/Stirling-PDF/.env\nWorkingDirectory=/opt/Stirling-PDF\nExecStart=/usr/bin/java -jar Stirling-PDF.jar\nExecStop=/bin/kill -15 %n\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/unoserver.service\n[Unit]\nDescription=UnoServer RPC Interface\nAfter=libreoffice-listener.service\nRequires=libreoffice-listener.service\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/unoserver --port 2003 --interface 127.0.0.1\nRestart=always\nEnvironmentFile=/opt/Stirling-PDF/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now libreoffice-listener\nsystemctl enable -q --now stirlingpdf\nsystemctl enable -q --now unoserver\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/storybook-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/storybookjs/storybook\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\n\nmsg_info \"Preparing Storybook\"\nmkdir -p /opt/storybook\ncd /opt/storybook\nmsg_ok \"Important: Interactive configuration will start now.\"\n\nnpx -y storybook@latest init --yes --no-dev\nPROJECT_PATH=$(find /opt/storybook -maxdepth 2 -name \".storybook\" -type d 2>/dev/null | head -n1 | xargs dirname)\n\nif [[ -z \"$PROJECT_PATH\" ]]; then\n  PROJECT_PATH=\"/opt/storybook\"\nfi\n\ncd \"$PROJECT_PATH\"\necho \"$PROJECT_PATH\" >/opt/storybook/.projectpath\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/storybook.service\n[Unit]\nDescription=Storybook Dev Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=${PROJECT_PATH}\nExecStart=/usr/bin/npx storybook dev --host 0.0.0.0 --port 6006 --no-open\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now storybook\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/storyteller-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gitlab.com/storyteller-platform/storyteller\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  git \\\n  pkg-config \\\n  libsqlite3-dev \\\n  sqlite3 \\\n  python3-setuptools \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n\nfetch_and_deploy_gh_release \"readium\" \"readium/cli\" \"prebuild\" \"latest\" \"/opt/readium\" \"readium_linux_x86_64.tar.gz\"\nln -sf /opt/readium/readium /usr/local/bin/readium\nfetch_and_deploy_gl_release \"storyteller\" \"storyteller-platform/storyteller\" \"tarball\" \"latest\" \"/opt/storyteller\"\n\nmsg_info \"Setting up Storyteller\"\ncd /opt/storyteller\n$STD yarn install --network-timeout 600000\n$STD gcc -g -fPIC -rdynamic -shared web/sqlite/uuid.c -o web/sqlite/uuid.c.so\nSTORYTELLER_SECRET_KEY=$(openssl rand -base64 32)\ncat <<EOF >/opt/storyteller/.env\nSTORYTELLER_SECRET_KEY=${STORYTELLER_SECRET_KEY}\nSTORYTELLER_DATA_DIR=/opt/storyteller/data\nPORT=8001\nHOSTNAME=0.0.0.0\nREADIUM_PORT=9000\nNODE_ENV=production\nNEXT_TELEMETRY_DISABLED=1\nEOF\nmkdir -p /opt/storyteller/data\n{\n  echo \"Storyteller Credentials\"\n  echo \"=======================\"\n  echo \"Secret Key: ${STORYTELLER_SECRET_KEY}\"\n} >~/storyteller.creds\nmsg_ok \"Set up Storyteller\"\n\nmsg_info \"Building Storyteller\"\ncd /opt/storyteller\nexport CI=1\nexport NODE_ENV=production\nexport NEXT_TELEMETRY_DISABLED=1\nexport SQLITE_NATIVE_BINDING=/opt/storyteller/node_modules/better-sqlite3/build/Release/better_sqlite3.node\n$STD yarn workspaces foreach -Rpt --from @storyteller-platform/web --exclude @storyteller-platform/eslint run build\nmkdir -p /opt/storyteller/web/.next/standalone/web/.next/static\ncp -rT /opt/storyteller/web/.next/static /opt/storyteller/web/.next/standalone/web/.next/static\nif [[ -d /opt/storyteller/web/public ]]; then\n  mkdir -p /opt/storyteller/web/.next/standalone/web/public\n  cp -rT /opt/storyteller/web/public /opt/storyteller/web/.next/standalone/web/public\nfi\nmkdir -p /opt/storyteller/web/.next/standalone/web/migrations\ncp -rT /opt/storyteller/web/migrations /opt/storyteller/web/.next/standalone/web/migrations\nmkdir -p /opt/storyteller/web/.next/standalone/web/sqlite\ncp -rT /opt/storyteller/web/sqlite /opt/storyteller/web/.next/standalone/web/sqlite\nln -sf /opt/storyteller/.env /opt/storyteller/web/.next/standalone/web/.env\nmsg_ok \"Built Storyteller\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/storyteller.service\n[Unit]\nDescription=Storyteller\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/storyteller/web/.next/standalone/web\nEnvironmentFile=/opt/storyteller/.env\nExecStart=/usr/bin/node --enable-source-maps server.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now storyteller\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/strapi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pespinel\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://strapi.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  python3-setuptools \\\n  libvips42\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\n\nmsg_info \"Installing Strapi (Patience)\"\nmkdir -p /opt/strapi\ncd /opt/strapi\n$STD npx --yes create-strapi-app@latest . --quickstart --no-run --skip-cloud\nmsg_ok \"Installed Strapi\"\n\nmsg_info \"Building Strapi\"\ncd /opt/strapi\nexport NODE_OPTIONS=\"--max-old-space-size=3072\"\n$STD npm run build\nmsg_ok \"Built Strapi\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/strapi/.env\nHOST=0.0.0.0\nPORT=1337\nAPP_KEYS=$(openssl rand -base64 32)\nAPI_TOKEN_SALT=$(openssl rand -base64 32)\nADMIN_JWT_SECRET=$(openssl rand -base64 32)\nTRANSFER_TOKEN_SALT=$(openssl rand -base64 32)\nJWT_SECRET=$(openssl rand -base64 32)\nEOF\ncat <<EOF >/etc/systemd/system/strapi.service\n[Unit]\nDescription=Strapi CMS\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/strapi\nEnvironmentFile=/opt/strapi/.env\nExecStart=/usr/bin/npm run start\nRestart=on-failure\nEnvironment=NODE_ENV=production\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now strapi\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/streamlink-webui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/CrazyWolf13/streamlink-webui\n\n# Import Functions und Setup\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\"\nNODE_MODULE=\"npm@latest,yarn@latest\"\nsetup_nodejs\nsetup_uv\nfetch_and_deploy_gh_release \"streamlink-webui\" \"CrazyWolf13/streamlink-webui\" \"tarball\"\n\nmsg_info \"Setup ${APPLICATION}\"\nmkdir -p \"/opt/${APPLICATION}-download\"\n$STD uv venv --clear /opt/\"${APPLICATION}\"/backend/src/.venv\nsource /opt/\"${APPLICATION}\"/backend/src/.venv/bin/activate\n$STD uv pip install -r /opt/streamlink-webui/backend/src/requirements.txt --python=/opt/\"${APPLICATION}\"/backend/src/.venv\ncd /opt/\"${APPLICATION}\"/frontend/src\n$STD yarn install\n$STD yarn build\nchmod +x /opt/\"${APPLICATION}\"/start.sh\nmsg_ok \"Setup ${APPLICATION}\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/opt/\"${APPLICATION}\".env\nCLIENT_ID='your_client_id'\nCLIENT_SECRET='your_client_secret'\nDOWNLOAD_PATH='/opt/streamlink-webui-download'\n# BASE_URL='https://sub.domain.com' \\\n# REVERSE_PROXY=True \\\nEOF\n\ncat <<EOF >/etc/systemd/system/\"${APPLICATION}\".service\n[Unit]\nDescription=${APPLICATION} Service\nAfter=network.target\n\n[Service]\nEnvironmentFile=/opt/${APPLICATION}.env\nWorkingDirectory=/opt/${APPLICATION}/backend/src\nExecStart=/bin/bash -c 'source /opt/${APPLICATION}/backend/src/.venv/bin/activate && exec /opt/${APPLICATION}/start.sh'\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now \"${APPLICATION}\"\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/stylus-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/mmastrac/stylus\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"stylus\" \"mmastrac/stylus\" \"singlefile\" \"latest\" \"/usr/bin/\" \"*_linux_arm64\"\n\nmsg_info \"Configuring Stylus\"\n$STD stylus init /opt/stylus/\nmsg_ok \"Configured Stylus\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/stylus.service\n[Unit]\nDescription=Stylus Service\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=stylus run /opt/stylus/\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now stylus\nmsg_ok \"Created service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/sure-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://sure.am\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  redis-server \\\n  pkg-config \\\n  libpq-dev \\\n  libvips\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"Sure\" \"we-promise/sure\" \"tarball\" \"latest\" \"/opt/sure\"\n\nPG_VERSION=\"$(sed -n '/postgres:/s/[^[:digit:]]*//p' /opt/sure/compose.example.yml)\" setup_postgresql\nPG_DB_NAME=sure_production PG_DB_USER=sure_user setup_postgresql_db\nRUBY_VERSION=\"$(cat /opt/sure/.ruby-version)\" RUBY_INSTALL_RAILS=false setup_ruby\n\nmsg_info \"Building Sure\"\ncd /opt/sure\nexport RAILS_ENV=production\nexport BUNDLE_DEPLOYMENT=1\nexport BUNDLE_WITHOUT=development\n$STD ./bin/bundle install\n$STD ./bin/bundle exec bootsnap precompile --gemfile -j 0\n$STD ./bin/bundle exec bootsnap precompile -j 0 app/ lib/\nexport SECRET_KEY_BASE_DUMMY=1 && $STD ./bin/rails assets:precompile\nunset SECRET_KEY_BASE_DUMMY\nmsg_ok \"Built Sure\"\n\nmsg_info \"Configuring Sure\"\nKEY=\"$(openssl rand -hex 64)\"\nmkdir -p /etc/sure\nmv /opt/sure/.env.example /etc/sure/.env\nsed -i -e \"/^SECRET_KEY_BASE=/s/secret-value/${KEY}/\" \\\n  -e 's/_KEY_BASE=.*$/&\\n\\nRAILS_FORCE_SSL=false \\\n\\\n# Change to true when using a reverse proxy \\\nRAILS_ASSUME_SSL=false/' \\\n  -e \"/POSTGRES_PASSWORD=/s/postgres/${PG_DB_PASS}/\" \\\n  -e \"/POSTGRES_USER=/s/postgres/${PG_DB_USER}\\\\\nPOSTGRES_DB=${PG_DB_NAME}/\" \\\n  -e \"s|^APP_DOMAIN=|&${LOCAL_IP}|\" /etc/sure/.env\nmsg_ok \"Configured Sure\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/sure.service\n[Unit]\nDescription=Sure Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/sure\nEnvironment=RAILS_ENV=production\nEnvironment=BUNDLE_DEPLOYMENT=1\nEnvironment=BUNDLE_WITHOUT=development\nEnvironment=PATH=/root/.rbenv/shims:/root/.rbenv/bin:/usr/bin:\\$PATH\nEnvironmentFile=/etc/sure/.env\nExecStartPre=/opt/sure/bin/rails db:prepare\nExecStart=/opt/sure/bin/rails server\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/sure-worker.service\n[Unit]\nDescription=Sure Background Worker (Sidekiq)\nAfter=network.target redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/sure\nEnvironment=RAILS_ENV=production\nEnvironment=BUNDLE_DEPLOYMENT=1\nEnvironment=BUNDLE_WITHOUT=development\nEnvironment=PATH=/root/.rbenv/shims:/root/.rbenv/bin:/usr/bin:/usr/local/bin:/sbin:/bin\nEnvironmentFile=/etc/sure/.env\nExecStart=/opt/sure/bin/bundle exec sidekiq -e production\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n$STD systemctl enable -q --now sure sure-worker\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/swizzin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: EEJoshua\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://swizzin.ltd/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://swizzin.ltd/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://s5n.sh\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\nbash <(curl -fsSL https://s5n.sh)\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/syncthing-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://syncthing.net/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_deb822_repo \\\n  \"syncthing\" \\\n  \"https://syncthing.net/release-key.gpg\" \\\n  \"https://apt.syncthing.net/\" \\\n  \"syncthing\" \\\n  \"stable-v2\"\n\nmsg_info \"Setting up Syncthing\"\n$STD apt install -y syncthing\nsystemctl enable -q --now syncthing@root\nsleep 5\nsed -i \"{s/127.0.0.1:8384/0.0.0.0:8384/g}\" /root/.local/state/syncthing/config.xml\nsystemctl restart syncthing@root\nmsg_ok \"Setup Syncthing\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tandoor-install.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tandoor.dev/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  libpq-dev \\\n  libmagic-dev \\\n  libzbar0 \\\n  nginx \\\n  libsasl2-dev \\\n  libldap2-dev \\\n  libssl-dev \\\n  pkg-config \\\n  libxmlsec1-dev \\\n  libxml2-dev \\\n  libxmlsec1-openssl\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\nfetch_and_deploy_gh_release \"tandoor\" \"TandoorRecipes/recipes\" \"tarball\" \"latest\" \"/opt/tandoor\"\nPG_VERSION=\"17\" setup_postgresql\nPYTHON_VERSION=\"3.13\" setup_uv\nPG_DB_USER=\"tandoor\" PG_DB_NAME=\"db_recipes\" PG_DB_EXTENSIONS=\"unaccent,pg_trgm\" setup_postgresql_db\nSECRET_KEY=$(openssl rand -base64 45 | sed 's/\\//\\\\\\//g')\n\nmsg_info \"Setup Tandoor\"\nmkdir -p /opt/tandoor/{config,api,mediafiles,staticfiles}\ncd /opt/tandoor\n$STD uv venv --clear .venv --python=python3\n$STD uv pip install -r requirements.txt --python .venv/bin/python\ncd /opt/tandoor/vue3\n$STD yarn install\n$STD yarn build\ncat <<EOF >/opt/tandoor/.env\nSECRET_KEY=$SECRET_KEY\nALLOWED_HOSTS=$LOCAL_IP\nTZ=Europe/Berlin\n\nDB_ENGINE=django.db.backends.postgresql\nPOSTGRES_HOST=localhost\nPOSTGRES_DB=$PG_DB_NAME\nPOSTGRES_PORT=5432\nPOSTGRES_USER=$PG_DB_USER\nPOSTGRES_PASSWORD=$PG_DB_PASS\n\nSTATIC_URL=/staticfiles/\nMEDIA_URL=/media/\nEOF\n\nTANDOOR_VERSION=$(get_latest_github_release \"TandoorRecipes/recipes\")\ncat <<EOF >/opt/tandoor/cookbook/version_info.py\nTANDOOR_VERSION = \"$TANDOOR_VERSION\"\nTANDOOR_REF = \"bare-metal\"\nVERSION_INFO = []\nEOF\n\ncd /opt/tandoor\n$STD /opt/tandoor/.venv/bin/python manage.py migrate\n$STD /opt/tandoor/.venv/bin/python manage.py collectstatic --no-input\nmsg_ok \"Installed Tandoor\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/tandoor.service\n[Unit]\nDescription=gunicorn daemon for tandoor\nAfter=network.target\n\n[Service]\nType=simple\nRestart=always\nRestartSec=3\nWorkingDirectory=/opt/tandoor\nEnvironmentFile=/opt/tandoor/.env\nExecStart=/opt/tandoor/.venv/bin/gunicorn --error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output --bind unix:/opt/tandoor/tandoor.sock recipes.wsgi:application\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<'EOF' >/etc/nginx/conf.d/tandoor.conf\nserver {\n    listen 8002;\n    access_log /var/log/nginx/access.log;\n    error_log /var/log/nginx/error.log;\n    client_max_body_size 128M;\n    # serve media files\n    location /static/ {\n        alias /opt/tandoor/staticfiles/;\n    }\n\n    location /media/ {\n        alias /opt/tandoor/mediafiles/;\n    }\n\n    location / {\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;\n        proxy_pass http://unix:/opt/tandoor/tandoor.sock;\n    }\n}\nEOF\nsystemctl reload nginx\nsystemctl enable -q --now tandoor\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tasmoadmin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TasmoAdmin/TasmoAdmin\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\nfetch_and_deploy_gh_release \"tasmoadmin\" \"TasmoAdmin/TasmoAdmin\" \"prebuild\" \"latest\" \"/var/www/tasmoadmin\" \"tasmoadmin_v*.tar.gz\"\n\nmsg_info \"Configuring TasmoAdmin\"\nrm -rf /etc/php/8.4/apache2/conf.d/10-opcache.ini\nchown -R www-data:www-data /var/www/tasmoadmin\nchmod 775 /var/www/tasmoadmin/tmp /var/www/tasmoadmin/data\ncat <<EOF >/etc/apache2/sites-available/tasmoadmin.conf\n<VirtualHost *:9999>\n\tServerName tasmoadmin\n\tServerAdmin webmaster@localhost\n\tDocumentRoot /var/www/tasmoadmin\n\t<Directory /var/www/tasmoadmin>\n\tAllowOverride All\n\tOrder allow,deny\n\tallow from all\n\t</Directory>\n\tErrorLog /var/log/apache2/error.log\n\tLogLevel warn\n\tCustomLog /var/log/apache2/access.log combined\n\tServerSignature On\n</VirtualHost>\nEOF\nsed -i '6iListen 9999' /etc/apache2/ports.conf\n$STD a2ensite tasmoadmin\n$STD a2enmod rewrite\nsystemctl reload apache2\nsystemctl restart apache2\nmsg_ok \"Configured TasmoAdmin\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tasmocompiler-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/benzino77/tasmocompiler\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies. Patience\"\n$STD apt-get install -y \\\n  git \\\n  python3-venv\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn@latest\" setup_nodejs\n\nmsg_info \"Setup Platformio\"\ncurl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py\n$STD python3 get-platformio.py\nmsg_ok \"Setup Platformio\"\n\nmsg_info \"Setup TasmoCompiler\"\nmkdir /tmp/Tasmota\nRELEASE=$(curl -fsSL https://api.github.com/repos/benzino77/tasmocompiler/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\ncurl -fsSL \"https://github.com/benzino77/tasmocompiler/archive/refs/tags/v${RELEASE}.tar.gz\" -o \"/tmp/v${RELEASE}.tar.gz\"\ncd /tmp\ntar xzf /tmp/v${RELEASE}.tar.gz\nmv tasmocompiler-${RELEASE}/ /opt/tasmocompiler/\ncd /opt/tasmocompiler\n$STD yarn install\nexport NODE_OPTIONS=--openssl-legacy-provider\n$STD npm i\n$STD yarn build\nmkdir -p /usr/local/bin\nln -s ~/.platformio/penv/bin/platformio /usr/local/bin/platformio\nln -s ~/.platformio/penv/bin/pio /usr/local/bin/pio\nln -s ~/.platformio/penv/bin/piodebuggdb /usr/local/bin/piodebuggdb\nrm -f /tmp/v${RELEASE}.tar.gz\necho \"${RELEASE}\" >\"/opt/tasmocompiler_version.txt\"\nmsg_ok \"Setup TasmoCompiler\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/tasmocompiler.service\n[Unit]\nDescription=TasmoCompiler Service\nAfter=multi-user.target\n\n[Service]\nType=simple\nUser=root\nExecStart=/usr/bin/node /opt/tasmocompiler/server/app.js\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tasmocompiler\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tautulli-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tautulli.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.13\" setup_uv\nfetch_and_deploy_gh_release \"Tautulli\" \"Tautulli/Tautulli\" \"tarball\"\n\nmsg_info \"Installing Tautulli\"\ncd /opt/Tautulli\nTAUTULLI_VERSION=$(get_latest_github_release \"Tautulli/Tautulli\" \"false\")\necho \"${TAUTULLI_VERSION}\" >/opt/Tautulli/version.txt\necho \"master\" >/opt/Tautulli/branch.txt\n$STD uv venv --clear\n$STD source /opt/Tautulli/.venv/bin/activate\n$STD uv pip install -r requirements.txt\n$STD uv pip install pyopenssl\n$STD uv pip install \"setuptools<81\"\nmsg_ok \"Installed Tautulli\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/tautulli.service\n[Unit]\nDescription=Tautulli\nAfter=syslog.target network.target\n\n[Service]\nWorkingDirectory=/opt/Tautulli/\nRestart=on-failure\nRestartSec=5\nType=simple\nExecStart=/opt/Tautulli/.venv/bin/python3 /opt/Tautulli/Tautulli.py\nKillSignal=SIGINT\nTimeoutStopSec=20\nSyslogIdentifier=tautulli\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tautulli\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tdarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://home.tdarr.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y handbrake-cli\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Tdarr\"\nmkdir -p /opt/tdarr\ncd /opt/tdarr\nRELEASE=$(curl -fsSL https://f000.backblazeb2.com/file/tdarrs/versions.json | grep -oP '(?<=\"Tdarr_Updater\": \")[^\"]+' | grep linux_arm64 | head -n 1)\ncurl -fsSL \"$RELEASE\" -o Tdarr_Updater.zip\n$STD unzip Tdarr_Updater.zip\nchmod +x Tdarr_Updater\n$STD ./Tdarr_Updater\nrm -rf /opt/tdarr/Tdarr_Updater.zip\n[[ -f /opt/tdarr/Tdarr_Server/Tdarr_Server ]] || {\n  msg_error \"Tdarr_Updater failed — tdarr.io may be blocked by local DNS\"\n  exit 250\n}\nmsg_ok \"Installed Tdarr\"\n\nsetup_hwaccel\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/tdarr-server.service\n[Unit]\nDescription=Tdarr Server Daemon\nAfter=network.target\n# Enable if using ZFS, edit and enable if other FS mounting is required to access directory\n#Requires=zfs-mount.service\n\n[Service]\nUser=root\nGroup=root\nType=simple\nWorkingDirectory=/opt/tdarr/Tdarr_Server\nExecStartPre=/opt/tdarr/Tdarr_Updater\nExecStart=/opt/tdarr/Tdarr_Server/Tdarr_Server\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/tdarr-node.service\n[Unit]\nDescription=Tdarr Node Daemon\nAfter=network.target\nRequires=tdarr-server.service\n\n[Service]\nUser=root\nGroup=root\nType=simple\nWorkingDirectory=/opt/tdarr/Tdarr_Node\nExecStart=/opt/tdarr/Tdarr_Node/Tdarr_Node\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q tdarr-server tdarr-node\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/teable-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/teableio/teable\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"teable\" PG_DB_USER=\"teable\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"teable\" \"teableio/teable\" \"tarball\"\n\nmsg_info \"Setting up Teable\"\ncd /opt/teable\nTEABLE_VERSION=$(cat ~/.teable)\necho \"NEXT_PUBLIC_BUILD_VERSION=\\\"${TEABLE_VERSION}\\\"\" >>apps/nextjs-app/.env\nexport HUSKY=0\nexport NODE_OPTIONS=\"--max-old-space-size=8192\"\n$STD pnpm install --frozen-lockfile\n$STD pnpm -F @teable/db-main-prisma prisma-generate --schema ./prisma/postgres/schema.prisma\nmsg_ok \"Set up Teable\"\n\nmsg_info \"Building Teable\"\nNODE_ENV=production NEXT_BUILD_ENV_TYPECHECK=false \\\n  $STD pnpm -r --filter '!playground' run build\nmsg_ok \"Built Teable\"\n\nmsg_info \"Running Database Migrations\"\nPRISMA_DATABASE_URL=\"postgresql://teable:${PG_DB_PASS}@localhost:5432/teable?schema=public\" \\\n  $STD pnpm -F @teable/db-main-prisma prisma-migrate deploy --schema ./prisma/postgres/schema.prisma\nmsg_ok \"Ran Database Migrations\"\n\nmsg_info \"Configuring Teable\"\nmkdir -p /opt/teable/.assets /opt/teable/.temporary\nSECRET_KEY=$(openssl rand -base64 32)\ncat <<EOF >/opt/teable/.env\nPRISMA_DATABASE_URL=postgresql://teable:${PG_DB_PASS}@localhost:5432/teable?schema=public&statement_cache_size=1\nPUBLIC_ORIGIN=http://${LOCAL_IP}:3000\nSECRET_KEY=${SECRET_KEY}\nPORT=3000\nNODE_ENV=production\nNEXT_TELEMETRY_DISABLED=1\nBACKEND_CACHE_PROVIDER=sqlite\nBACKEND_CACHE_SQLITE_URI=sqlite:///opt/teable/.assets/.cache.db\nNEXTJS_DIR=apps/nextjs-app\nEOF\nln -sf /opt/teable /app\nrm -rf /opt/teable/static\nif [ -d \"/opt/teable/apps/nestjs-backend/static/static\" ]; then\n  ln -sf /opt/teable/apps/nestjs-backend/static/static /opt/teable/static\nelse\n  ln -sf /opt/teable/apps/nestjs-backend/static /opt/teable/static\nfi\nmsg_ok \"Configured Teable\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/teable.service\n[Unit]\nDescription=Teable\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/teable\nEnvironmentFile=/opt/teable/.env\nExecStart=/usr/bin/node apps/nestjs-backend/dist/index.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now teable\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/teamspeak-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021 (Slaviša Arežina)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://teamspeak.com/en/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nRELEASE=$(curl -fsSL https://teamspeak.com/en/downloads/#server | grep -oP 'teamspeak3-server_linux_arm64-\\K[0-9]+\\.[0-9]+\\.[0-9]+' | head -1)\n\nmsg_info \"Setting up Teamspeak Server\"\ncurl -fsSL \"https://files.teamspeak-services.com/releases/server/${RELEASE}/teamspeak3-server_linux_arm64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\ntar -xf ./ts3server.tar.bz2\nmv teamspeak3-server_linux_arm64/ /opt/teamspeak-server/\ntouch /opt/teamspeak-server/.ts3server_license_accepted\nrm -f ~/ts3server.tar.bz*\necho \"${RELEASE}\" >~/.teamspeak-server\nmsg_ok \"Setup Teamspeak Server\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/teamspeak-server.service\n[Unit]\nDescription=TeamSpeak3 Server\nWants=network-online.target\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/teamspeak-server\nUser=root\nType=forking\nExecStart=/opt/teamspeak-server/ts3server_startscript.sh start\nExecStop=/opt/teamspeak-server/ts3server_startscript.sh stop\nExecReload=/opt/teamspeak-server/ts3server_startscript.sh restart\nRestart=always\nRestartSec=15\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now teamspeak-server\nmsg_ok \"Created service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/technitiumdns-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://technitium.com/dns/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing ASP.NET Core Runtime\"\ncurl -SL -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/Sdk/9.0.306/dotnet-sdk-9.0.306-linux-arm64.tar.gz\ncurl -SL -o aspnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.10/aspnetcore-runtime-9.0.10-linux-arm64.tar.gz\n$STD mkdir -p /usr/share/dotnet\n$STD tar -zxf dotnet.tar.gz -C /usr/share/dotnet\n$STD tar -zxf aspnet.tar.gz -C /usr/share/dotnet\nln -s /usr/share/dotnet/dotnet /usr/bin/dotnet\nmsg_ok \"Installed ASP.NET Core Runtime\"\n\nRELEASE=$(curl -fsSL https://technitium.com/dns/ | grep -oP 'Version \\K[\\d.]+')\nfetch_and_deploy_from_url \"https://download.technitium.com/dns/DnsServerPortable.tar.gz\" /opt/technitium/dns\necho \"${RELEASE}\" >~/.technitium\n\nmsg_info \"Creating service\"\nmkdir -p /etc/dns /var/log/technitium/dns\nsed -i '/^User=/d;/^Group=/d' /opt/technitium/dns/systemd.service\ncp /opt/technitium/dns/systemd.service /etc/systemd/system/technitium.service\nsystemctl enable -q --now technitium\nmsg_ok \"Service created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/teddycloud-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dominik Siebel (dsiebel)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/toniebox-reverse-engineering/teddycloud\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libubsan1 \\\n  ffmpeg \\\n  curl \\\n  ca-certificates \\\n  wget \\\n  openssh-server\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"teddycloud\" \"toniebox-reverse-engineering/teddycloud\" \"prebuild\" \"latest\" \"/opt/teddycloud\" \"teddycloud.arm64.release*.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/teddycloud.service\n[Unit]\nDescription=TeddyCloud Server\nAfter=network.target\n\n[Service]\nUser=root\nType=simple\nExecStart=/opt/teddycloud/teddycloud\nWorkingDirectory=/opt/teddycloud\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q teddycloud\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/telegraf-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/influxdata/telegraf\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Setting up Telegraf repository\"\nsetup_deb822_repo \\\n  \"telegraf\" \\\n  \"https://repos.influxdata.com/influxdata-archive.key\" \\\n  \"https://repos.influxdata.com/debian\" \\\n  \"stable\"\nmsg_ok \"Setup Telegraf Repository\"\n\nmsg_info \"Setting up Telegraf\"\n$STD apt install -y telegraf\nmsg_ok \"Setup Telegraf\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/teleport-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://goteleport.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_deb822_repo \\\n  \"teleport\" \\\n  \"https://deb.releases.teleport.dev/teleport-pubkey.asc\" \\\n  \"https://apt.releases.teleport.dev/debian\" \\\n  \"trixie\" \\\n  \"stable/v18\"\n\nmsg_info \"Configuring Teleport\"\n$STD apt install -y teleport\n$STD teleport configure -o /etc/teleport.yaml\nsystemctl enable -q --now teleport\nsleep 10\ntctl users add teleport-admin --roles=editor,access --logins=root >~/teleportadmin.txt\nsed -i \"s|https://[^:]*:3080|https://${LOCAL_IP}:3080|g\" ~/teleportadmin.txt\nmsg_ok \"Configured Teleport\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/termix-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Termix-SSH/Termix\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  python3 \\\n  nginx \\\n  openssl \\\n  gettext-base \\\n  libcairo2-dev \\\n  libjpeg62-turbo-dev \\\n  libpng-dev \\\n  libtool-bin \\\n  uuid-dev \\\n  libvncserver-dev \\\n  freerdp3-dev \\\n  libssh2-1-dev \\\n  libtelnet-dev \\\n  libwebsockets-dev \\\n  libpulse-dev \\\n  libvorbis-dev \\\n  libwebp-dev \\\n  libssl-dev \\\n  libpango1.0-dev \\\n  libswscale-dev \\\n  libavcodec-dev \\\n  libavutil-dev \\\n  libavformat-dev\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Building Guacamole Server (guacd)\"\nfetch_and_deploy_gh_tag \"guacd\" \"apache/guacamole-server\" \"latest\" \"/opt/guacamole-server\"\ncd /opt/guacamole-server\nexport CPPFLAGS=\"-Wno-error=deprecated-declarations\"\n$STD autoreconf -fi\n$STD ./configure --with-init-dir=/etc/init.d --enable-allow-freerdp-snapshots\n$STD make\n$STD make install\n$STD ldconfig\ncd /opt\nrm -rf /opt/guacamole-server\nmsg_ok \"Built Guacamole Server (guacd)\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"termix\" \"Termix-SSH/Termix\" \"tarball\"\n\nmsg_info \"Building Frontend\"\ncd /opt/termix\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\nfind public/fonts -name \"*.ttf\" ! -name \"*Regular.ttf\" ! -name \"*Bold.ttf\" ! -name \"*Italic.ttf\" -delete 2>/dev/null || true\n$STD npm install --ignore-scripts --force\n$STD npm cache clean --force\n$STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Building Backend\"\n$STD npm rebuild better-sqlite3 --force\n$STD npm run build:backend\nmsg_ok \"Built Backend\"\n\nmsg_info \"Setting up Node Dependencies\"\ncd /opt/termix\n$STD npm ci --only=production --ignore-scripts --force\n$STD npm rebuild better-sqlite3 bcryptjs --force\n$STD npm cache clean --force\nmsg_ok \"Set up Node Dependencies\"\n\nmsg_info \"Setting up Directories\"\nmkdir -p /opt/termix/data \\\n  /opt/termix/uploads \\\n  /opt/termix/html \\\n  /opt/termix/nginx \\\n  /opt/termix/nginx/logs \\\n  /opt/termix/nginx/cache \\\n  /opt/termix/nginx/client_body\n\ncp -r /opt/termix/dist/* /opt/termix/html/ 2>/dev/null || true\ncp -r /opt/termix/src/locales /opt/termix/html/locales 2>/dev/null || true\ncp -r /opt/termix/public/fonts /opt/termix/html/fonts 2>/dev/null || true\nmsg_ok \"Set up Directories\"\n\nmsg_info \"Configuring Nginx\"\ncurl -fsSL \"https://raw.githubusercontent.com/Termix-SSH/Termix/main/docker/nginx.conf\" -o /etc/nginx/nginx.conf\nsed -i '/^master_process/d' /etc/nginx/nginx.conf\nsed -i '/^pid \\/app\\/nginx/d' /etc/nginx/nginx.conf\nsed -i 's|/app/html|/opt/termix/html|g' /etc/nginx/nginx.conf\nsed -i 's|/app/nginx|/opt/termix/nginx|g' /etc/nginx/nginx.conf\nsed -i 's|listen ${PORT};|listen 80;|g' /etc/nginx/nginx.conf\n\nmkdir -p /tmp/nginx\necho \"d /tmp/nginx 0755 nobody nobody -\" > /etc/tmpfiles.d/nginx-termix.conf\nmkdir -p /etc/systemd/system/nginx.service.d/\ncat > /etc/systemd/system/nginx.service.d/pidfile.conf << EOF\n[Service]\nPIDFile=/tmp/nginx/nginx.pid\nEOF\nsystemctl daemon-reload\nrm -f /etc/nginx/sites-enabled/default\nnginx -t\nsystemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating Service\"\nmkdir -p /etc/guacamole\ncat <<EOF >/etc/guacamole/guacd.conf\n[server]\nbind_host = 127.0.0.1\nbind_port = 4822\nEOF\n\ncat <<EOF >/opt/termix/.env\nNODE_ENV=production\nDATA_DIR=/opt/termix/data\nGUACD_HOST=127.0.0.1\nGUACD_PORT=4822\nEOF\n\ncat <<EOF >/etc/systemd/system/guacd.service\n[Unit]\nDescription=Guacamole Proxy Daemon (guacd)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/sbin/guacd -f -b 127.0.0.1 -l 4822\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/termix.service\n[Unit]\nDescription=Termix Backend\nAfter=network.target guacd.service\nWants=guacd.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/termix\nEnvironmentFile=/opt/termix/.env\nExecStart=/usr/bin/node /opt/termix/dist/backend/backend/starter.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now guacd termix\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/the-lounge-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://thelounge.chat/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"thelounge\" \"thelounge/thelounge-deb\" \"binary\"\nsystemctl enable -q --now thelounge\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/thingsboard-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/thingsboard/thingsboard\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libharfbuzz0b \\\n  fontconfig \\\n  fonts-dejavu-core\nmsg_ok \"Installed Dependencies\"\n\nJAVA_VERSION=\"17\" setup_java\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"thingsboard_db\" PG_DB_USER=\"thingsboard\" setup_postgresql_db\nfetch_and_deploy_gh_release \"thingsboard\" \"thingsboard/thingsboard\" \"binary\" \"latest\" \"/tmp\" \"thingsboard-*.deb\"\n\nmsg_info \"Configuring ThingsBoard\"\ncat <<EOF >/etc/thingsboard/conf/thingsboard.conf\n# DB Configuration\nexport DATABASE_TS_TYPE=sql\nexport SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/${PG_DB_NAME}\nexport SPRING_DATASOURCE_USERNAME=${PG_DB_USER}\nexport SPRING_DATASOURCE_PASSWORD=${PG_DB_PASS}\n# Specify partitioning size for timestamp key-value storage. Allowed values: DAYS, MONTHS, YEARS, INDEFINITE.\nexport SQL_POSTGRES_TS_KV_PARTITIONING=MONTHS\nEOF\nsystemctl daemon-reload\nmsg_ok \"Configured ThingsBoard\"\n\nmsg_info \"Running ThingsBoard Installation Script\"\n$STD /usr/share/thingsboard/bin/install/install.sh --loadDemo\nmsg_ok \"Ran Installation Script\"\n\nmsg_info \"Starting ThingsBoard Service\"\nsystemctl enable -q --now thingsboard\nmsg_ok \"Started ThingsBoard Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/threadfin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Threadfin/Threadfin\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ffmpeg \\\n  vlc\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"threadfin\" \"threadfin/threadfin\" \"singlefile\" \"latest\" \"/opt/threadfin\" \"Threadfin_linux_arm64\"\nmv /root/.threadfin /root/.threadfin_version\nmkdir -p /root/.threadfin\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/threadfin.service\n[Unit]\nDescription=Threadfin: M3U Proxy for Plex DVR and Emby/Jellyfin Live TV\nAfter=syslog.target network.target\n[Service]\nType=simple\nWorkingDirectory=/opt/threadfin\nExecStart=/opt/threadfin/threadfin\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now threadfin\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tianji-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/msgbyte/tianji\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  python3 \\\n  cmake \\\n  build-essential \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm@$(curl -s https://raw.githubusercontent.com/msgbyte/tianji/master/package.json | jq -r '.packageManager | split(\"@\")[1]')\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"tianji_db\" PG_DB_USER=\"tianji\" setup_postgresql_db\nPYTHON_VERSION=\"3.13\" setup_uv\nfetch_and_deploy_gh_release \"tianji\" \"msgbyte/tianji\" \"tarball\"\nTIANJI_SECRET=$(openssl rand -base64 256 | tr -dc 'A-Za-z' | head -c 64)\necho \"Tianji Secret: $TIANJI_SECRET\" >>~/tianji.creds\n\nmsg_info \"Setting up Tianji\"\ncd /opt/tianji\n$STD pnpm install --filter @tianji/client... --config.dedupe-peer-dependents=false --frozen-lockfile\n$STD pnpm build:static\n$STD pnpm install --filter @tianji/server... --config.dedupe-peer-dependents=false\nmkdir -p ./src/server/public\ncp -r ./geo ./src/server/public\n$STD pnpm build:server\ncat <<EOF >/opt/tianji/src/server/.env\nDATABASE_URL=\"postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME?schema=public\"\nOPENAI_API_KEY=\"\"\nJWT_SECRET=\"$TIANJI_SECRET\"\nEOF\ncd /opt/tianji/src/server\n$STD pnpm db:migrate:apply\nrm -rf /opt/tianji/src/client\nrm -rf /opt/tianji/website\nrm -rf /opt/tianji/reporter\nmsg_ok \"Setup Tianji\"\n\nmsg_info \"Setup AppRise\"\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\n$STD uv pip install apprise cryptography --system\nmsg_ok \"Setup AppRise\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/tianji.service\n[Unit]\nDescription=Tianji Server\nAfter=network.target\n\n[Service]\nExecStart=/usr/bin/node /opt/tianji/src/server/dist/src/server/main.js\nWorkingDirectory=/opt/tianji/src/server\nRestart=always\nRestartSec=10\nEnvironment=NODE_ENV=production\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tianji\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tinyauth-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/steveiliop56/tinyauth\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  openssl \\\n  apache2-utils\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"tinyauth\" \"steveiliop56/tinyauth\" \"singlefile\" \"latest\" \"/opt/tinyauth\" \"tinyauth-amd64\"\n\nmsg_info \"Setting up Tinyauth\"\nPASS=$(openssl rand -base64 8 | tr -dc 'a-zA-Z0-9' | head -c 8)\nUSER=$(htpasswd -Bbn \"tinyauth\" \"${PASS}\")\ncat <<EOF >/opt/tinyauth/credentials.txt\nTinyauth Credentials\nUsername: tinyauth\nPassword: ${PASS}\nEOF\nmsg_ok \"Set up Tinyauth\"\n\nread -r -p \"${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): \" app_url\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/tinyauth/.env\nTINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db\nTINYAUTH_AUTH_USERS='${USER}'\nTINYAUTH_APPURL=${app_url}\nEOF\ncat <<EOF >/etc/systemd/system/tinyauth.service\n[Unit]\nDescription=Tinyauth Service\nAfter=network.target\n\n[Service]\nType=simple\nEnvironmentFile=/opt/tinyauth/.env\nExecStart=/opt/tinyauth/tinyauth\nWorkingDirectory=/opt/tinyauth\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tinyauth\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/traccar-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.traccar.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"traccar\" \"traccar/traccar\" \"prebuild\" \"latest\" \"/opt/traccar\" \"traccar-linux-arm*.zip\"\n\nmsg_info \"Configuring Traccar\"\ncd /opt/traccar\n$STD ./traccar.run\n[ -f README.txt ] || [ -f traccar.run ] && rm -f README.txt traccar.run\nmsg_ok \"Configured Traccar\"\n\nmsg_info \"Starting service\"\nsystemctl enable -q --now traccar\nmsg_ok \"Service started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tracearr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: durzo\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/connorgallopo/Tracearr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y redis-server\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nPG_VERSION=\"18\" setup_postgresql\n\nmsg_info \"Installing pnpm\"\nPNPM_VERSION=\"$(curl -fsSL \"https://raw.githubusercontent.com/connorgallopo/Tracearr/refs/heads/main/package.json\" | jq -r '.packageManager | split(\"@\")[1]' | cut -d'+' -f1)\"\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\n$STD corepack enable pnpm\n$STD corepack prepare pnpm@${PNPM_VERSION} --activate\nmsg_ok \"Installed pnpm\"\n\nmsg_info \"Installing TimescaleDB\"\nsetup_deb822_repo \\\n  \"timescaledb\" \\\n  \"https://packagecloud.io/timescale/timescaledb/gpgkey\" \\\n  \"https://packagecloud.io/timescale/timescaledb/debian\" \\\n  \"$(get_os_info codename)\"\n  \n$STD apt install -y \\\n    timescaledb-2-postgresql-18 \\\n    timescaledb-tools \\\n    timescaledb-toolkit-postgresql-18\ntotal_ram_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')\nram_for_tsdb=$((total_ram_kb / 1024 / 2))\n$STD timescaledb-tune -yes -memory \"$ram_for_tsdb\"MB\n$STD systemctl restart postgresql\nmsg_ok \"Installed TimescaleDB\"\n\nPG_DB_NAME=\"tracearr_db\" PG_DB_USER=\"tracearr\" PG_DB_EXTENSIONS=\"timescaledb,timescaledb_toolkit\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\n\nmsg_info \"Installing tailscale\"\nsetup_deb822_repo \\\n  \"tailscale\" \\\n  \"https://pkgs.tailscale.com/stable/$(get_os_info id)/$(get_os_info codename).noarmor.gpg\" \\\n  \"https://pkgs.tailscale.com/stable/$(get_os_info id)/\" \\\n  \"$(get_os_info codename)\"\n$STD apt install -y tailscale\n# Tracearr runs tailscaled in user mode, disable the service.\n$STD systemctl disable --now tailscaled\n$STD systemctl stop tailscaled\nmsg_ok \"Installed tailscale\"\n\nfetch_and_deploy_gh_release \"tracearr\" \"connorgallopo/Tracearr\" \"tarball\" \"latest\" \"/opt/tracearr.build\"\n\nmsg_info \"Building Tracearr\"\nexport TZ=$(cat /etc/timezone)\nexport NODE_OPTIONS=\"--max-old-space-size=4096\"\ncd /opt/tracearr.build\n$STD pnpm install --frozen-lockfile --force\n$STD pnpm turbo telemetry disable\n$STD pnpm turbo run build --no-daemon --filter=@tracearr/shared --filter=@tracearr/server --filter=@tracearr/web\nmkdir -p /opt/tracearr/{packages/shared,apps/server,apps/web,apps/server/src/db}\ncp -rf package.json /opt/tracearr/\ncp -rf pnpm-workspace.yaml /opt/tracearr/\ncp -rf pnpm-lock.yaml /opt/tracearr/\ncp -rf apps/server/package.json /opt/tracearr/apps/server/\ncp -rf apps/server/dist /opt/tracearr/apps/server/dist\ncp -rf apps/server/scripts /opt/tracearr/apps/server/scripts\ncp -rf apps/web/dist /opt/tracearr/apps/web/dist\ncp -rf packages/shared/package.json /opt/tracearr/packages/shared/\ncp -rf packages/shared/dist /opt/tracearr/packages/shared/dist\ncp -rf apps/server/src/db/migrations /opt/tracearr/apps/server/src/db/migrations\ncp -rf data /opt/tracearr/data\nmkdir -p /opt/tracearr/data/image-cache\nrm -rf /opt/tracearr.build\ncd /opt/tracearr\n$STD pnpm install --prod --frozen-lockfile --ignore-scripts\nmsg_ok \"Built Tracearr\"\n\nmsg_info \"Configuring Tracearr\"\n$STD useradd -r -s /bin/false -U tracearr\n$STD chown -R tracearr:tracearr /opt/tracearr\ninstall -d -m 750 -o tracearr -g tracearr /data/tracearr\ninstall -d -m 750 -o tracearr -g tracearr /data/backup\nexport JWT_SECRET=$(openssl rand -hex 32)\nexport COOKIE_SECRET=$(openssl rand -hex 32)\ncat <<EOF >/data/tracearr/.env\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}\nREDIS_URL=redis://127.0.0.1:6379\nPORT=3000\nHOST=0.0.0.0\nNODE_ENV=production\nTZ=${TZ}\nLOG_LEVEL=info\nJWT_SECRET=$JWT_SECRET\nCOOKIE_SECRET=$COOKIE_SECRET\nAPP_VERSION=$(cat /root/.tracearr)\n#CORS_ORIGIN=http://localhost:5173\nEOF\nchmod 600 /data/tracearr/.env\nchown -R tracearr:tracearr /data/tracearr\nmsg_ok \"Configured Tracearr\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/data/tracearr/prestart.sh\n#!/usr/bin/env bash\n# =============================================================================\n# Tune PostgreSQL for available resources (runs every startup)\n# =============================================================================\n# timescaledb-tune automatically optimizes PostgreSQL settings based on\n# available RAM and CPU. Safe to run repeatedly - recalculates if resources change.\nif command -v timescaledb-tune &> /dev/null; then\n    total_ram_kb=\\$(grep MemTotal /proc/meminfo | awk '{print \\$2}')\n    ram_for_tsdb=\\$((total_ram_kb / 1024 / 2))\n    timescaledb-tune -yes -memory \"\\$ram_for_tsdb\"MB --quiet 2>/dev/null \\\n        || echo \"Warning: timescaledb-tune failed (non-fatal)\"\nfi\n# =============================================================================\n# Ensure required PostgreSQL settings for Tracearr\n# =============================================================================\npg_config_file=\"/etc/postgresql/18/main/postgresql.conf\"\nif [ -f \\$pg_config_file ]; then\n    # Ensure max_tuples_decompressed_per_dml_transaction is set\n    if grep -q \"^timescaledb\\.max_tuples_decompressed_per_dml_transaction\" \\$pg_config_file; then\n        # Setting exists (uncommented) - update if not 0\n        current_value=\\$(grep \"^timescaledb\\.max_tuples_decompressed_per_dml_transaction\" \\$pg_config_file | grep -oE '[0-9]+' | head -1)\n        if [ -n \"\\$current_value\" ] && [ \"\\$current_value\" -ne 0 ]; then\n            sed -i \"s/^timescaledb\\.max_tuples_decompressed_per_dml_transaction.*/timescaledb.max_tuples_decompressed_per_dml_transaction = 0/\" \\$pg_config_file\n        fi\n    elif ! grep -q \"^timescaledb\\.max_tuples_decompressed_per_dml_transaction\" \\$pg_config_file; then\n        echo \"\" >> \\$pg_config_file\n        echo \"# Allow unlimited tuple decompression for migrations on compressed hypertables\" >> \\$pg_config_file\n        echo \"timescaledb.max_tuples_decompressed_per_dml_transaction = 0\" >> \\$pg_config_file\n    fi\n    # Ensure max_locks_per_transaction is set (for existing databases)\n    if grep -q \"^max_locks_per_transaction\" \\$pg_config_file; then\n        # Setting exists (uncommented) - update if below 4096\n        current_value=\\$(grep \"^max_locks_per_transaction\" \\$pg_config_file | grep -oE '[0-9]+' | head -1)\n        if [ -n \"\\$current_value\" ] && [ \"\\$current_value\" -lt 4096 ]; then\n            sed -i \"s/^max_locks_per_transaction.*/max_locks_per_transaction = 4096/\" \\$pg_config_file\n        fi\n    elif ! grep -q \"^max_locks_per_transaction\" \\$pg_config_file; then\n        echo \"\" >> \\$pg_config_file\n        echo \"# Increase lock table size for TimescaleDB hypertables with many chunks\" >> \\$pg_config_file\n        echo \"max_locks_per_transaction = 4096\" >> \\$pg_config_file\n    fi\nfi\nsystemctl restart postgresql\nsudo -u postgres psql -c \"ALTER USER tracearr WITH SUPERUSER;\"\nEOF\nchmod +x /data/tracearr/prestart.sh\ncat <<EOF >/lib/systemd/system/tracearr.service\n[Unit]\nDescription=Tracearr Web Server\nAfter=network.target postgresql.service redis-server.service\n\n[Service]\nType=simple\nKillMode=control-group\nEnvironmentFile=/data/tracearr/.env\nWorkingDirectory=/opt/tracearr\nExecStartPre=+/data/tracearr/prestart.sh\nExecStart=node /opt/tracearr/apps/server/dist/index.js\nRestart=on-failure\nRestartSec=10\nUser=tracearr\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now postgresql redis-server tracearr\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tracktor-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tracktor.bytedge.in\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"tracktor\" \"javedh-dev/tracktor\" \"tarball\" \"latest\" \"/opt/tracktor\"\n\nmsg_info \"Configuring Tracktor\"\ncd /opt/tracktor\n$STD npm install\n$STD npm run build\nmkdir -p /opt/tracktor-data/{uploads,logs}\ncat <<EOF >/opt/tracktor.env\nNODE_ENV=production\n# Set this to the path of the database file. Default - ./tracktor.db\nDB_PATH=/opt/tracktor-data/tracktor.db\n# Set this to the path of the uploads directory. Default - ./uploads\nUPLOADS_DIR=\"/opt/tracktor-data/uploads\"\n# Set this to the path of the logs directory. Default - ./logs\nLOG_DIR=\"/opt/tracktor-data/logs\"\n# Hostname to bind the server to. Default - 0.0.0.0\n#HOST=\"0.0.0.0\"\n# Port to bind the server to. Default - 3000\n#PORT=3000\n# Set this to remove upload size limitations. Default - 512 Kb\nBODY_SIZE_LIMIT=Infinity\n# Enable request logging. Default - true\n#LOG_REQUESTS=true\n# Set the logging level. Options - error, warn, info, verbose, debug, silly. Default - info\n#LOG_LEVEL=\"info\"\n# Enable demo mode. Default - false\n#TRACKTOR_DEMO_MODE=false\n# Force reseeding of data on every startup. Default - false\n#FORCE_DATA_SEED=false\nEOF\nmsg_ok \"Configured Tracktor\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/tracktor.service\n[Unit]\nDescription=Tracktor Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/tracktor\nEnvironmentFile=/opt/tracktor.env\nExecStart=/usr/bin/npm start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tracktor\nmsg_ok \"Created service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/traefik-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://traefik.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"traefik\" \"traefik/traefik\" \"prebuild\" \"latest\" \"/usr/bin\" \"traefik_v*_linux_arm64.tar.gz\"\nmkdir -p /etc/traefik/{conf.d,ssl}\n\nmsg_info \"Creating Traefik configuration\"\ncat <<EOF >/etc/traefik/traefik.yaml\nproviders:\n  file:\n    directory: /etc/traefik/conf.d/\n\nentryPoints:\n  web:\n    address: ':80'\n    http:\n      redirections:\n        entryPoint:\n          to: websecure\n          scheme: https\n  websecure:\n    address: ':443'\n    http:\n      tls:\n        certResolver: letsencrypt\n  traefik:\n    address: ':8080'\n\ncertificatesResolvers:\n  letsencrypt:\n    acme:\n      email: \"foo@bar.com\"\n      storage: /etc/traefik/ssl/acme.json\n      tlsChallenge: {}\n\napi:\n  dashboard: true\n  insecure: true\n\nlog:\n  filePath: /var/log/traefik/traefik.log\n  format: json\n  level: INFO\n\naccessLog:\n  filePath: /var/log/traefik/traefik-access.log\n  format: json\n  filters:\n    statusCodes:\n      - \"200\"\n      - \"400-599\"\n    retryAttempts: true\n    minDuration: \"10ms\"\n  bufferingSize: 0\n  fields:\n    headers:\n      defaultMode: drop\n      names:\n        User-Agent: keep\nEOF\nmsg_ok \"Created Traefik configuration\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/etc/systemd/system/traefik.service\n[Unit]\nDescription=Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience\n\n[Service]\nType=notify\nExecStart=/usr/bin/traefik --configFile=/etc/traefik/traefik.yaml\nRestart=on-failure\nExecReload=/bin/kill -USR1 \\$MAINPID\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now traefik\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/transmission-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://transmissionbt.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Transmission\"\n$STD apt install -y transmission-daemon\nsystemctl stop transmission-daemon\nsed -i '{s/\"rpc-whitelist-enabled\": true/\"rpc-whitelist-enabled\": false/g; s/\"rpc-host-whitelist-enabled\": true,/\"rpc-host-whitelist-enabled\": false,/g}' /etc/transmission-daemon/settings.json\nsystemctl start transmission-daemon\nmsg_ok \"Installed Transmission\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/transmute-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/transmute-app/transmute\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nUV_PYTHON=\"3.13\" setup_uv\nNODE_VERSION=\"25\" setup_nodejs\nsetup_ffmpeg\nsetup_gs\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  inkscape \\\n  tesseract-ocr \\\n  libreoffice-impress \\\n  libreoffice-common \\\n  libmagic1 \\\n  xvfb \\\n  libsm6 \\\n  libxext6 \\\n  libpango-1.0-0 \\\n  libopengl0 \\\n  libpangocairo-1.0-0 \\\n  libgdk-pixbuf-2.0-0 \\\n  libffi-dev \\\n  libcairo2 \\\n  librsvg2-bin \\\n  unrar-free \\\n  python3-numpy \\\n  python3-lxml \\\n  python3-tinycss2 \\\n  python3-cssselect\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"pandoc\" \"jgm/pandoc\" \"binary\" \"latest\" \"\" \"pandoc-*-amd64.deb\"\nfetch_and_deploy_gh_release \"calibre\" \"kovidgoyal/calibre\" \"prebuild\" \"latest\" \"/opt/calibre\" \"calibre-*-x86_64.txz\"\nln -sf /opt/calibre/ebook-convert /usr/bin/ebook-convert\nln -sf /usr/local/bin/ffmpeg /usr/bin/ffmpeg\nfetch_and_deploy_gh_release \"drawio\" \"jgraph/drawio-desktop\" \"binary\" \"latest\" \"\" \"drawio-amd64-*.deb\"\nfetch_and_deploy_gh_release \"transmute\" \"transmute-app/transmute\" \"tarball\"\n\nmsg_info \"Setting up Python Backend\"\ncd /opt/transmute\n$STD uv venv --clear /opt/transmute/.venv\n$STD uv pip install --python /opt/transmute/.venv/bin/python -r requirements.txt\nln -sf /opt/transmute/.venv/bin/weasyprint /usr/bin/weasyprint\nmsg_ok \"Set up Python Backend\"\n\nmsg_info \"Configuring Transmute\"\nSECRET_KEY=$(openssl rand -hex 64)\ncat <<EOF >/opt/transmute/backend/.env\nAUTH_SECRET_KEY=${SECRET_KEY}\nHOST=0.0.0.0\nPORT=3313\nDATA_DIR=/opt/transmute/data\nWEB_DIR=/opt/transmute/frontend/dist\nQT_QPA_PLATFORM=offscreen\nEOF\nmkdir -p /opt/transmute/data\nmsg_ok \"Configured Transmute\"\n\nmsg_info \"Building Frontend\"\ncd /opt/transmute/frontend\n$STD npm ci\n$STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/transmute.service\n[Unit]\nDescription=Transmute File Converter\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/transmute\nEnvironmentFile=/opt/transmute/backend/.env\nExecStart=/usr/bin/xvfb-run -a -s \"-screen 0 1024x768x24 -nolisten tcp\" /opt/transmute/.venv/bin/python backend/main.py\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now transmute\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/trek-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/mauriceboe/TREK\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"trek\" \"mauriceboe/TREK\" \"tarball\"\n\nmsg_info \"Building Client\"\ncd /opt/trek/client\n$STD npm ci\n$STD npm run build\nmsg_ok \"Built Client\"\n\nmsg_info \"Setting up Server\"\ncd /opt/trek/server\n$STD npm ci\nmkdir -p /opt/trek/server/public\ncp -r /opt/trek/client/dist/* /opt/trek/server/public/\ncp -r /opt/trek/client/public/fonts /opt/trek/server/public/fonts 2>/dev/null || true\nmkdir -p /opt/trek/{data/logs,uploads/{files,covers,avatars,photos}}\nrm -rf /opt/trek/server/data /opt/trek/server/uploads\nln -s /opt/trek/data /opt/trek/server/data\nln -s /opt/trek/uploads /opt/trek/server/uploads\nENCRYPTION_KEY=$(openssl rand -hex 32)\nADMIN_EMAIL=\"admin@trek.local\"\nADMIN_PASSWORD=$(openssl rand -base64 18 | tr -dc 'A-Za-z0-9' | head -c 16)\ncat <<EOF >/opt/trek/server/.env\nNODE_ENV=production\nPORT=3000\nENCRYPTION_KEY=${ENCRYPTION_KEY}\nADMIN_EMAIL=${ADMIN_EMAIL}\nADMIN_PASSWORD=${ADMIN_PASSWORD}\nCOOKIE_SECURE=false\nFORCE_HTTPS=false\nLOG_LEVEL=info\nTZ=UTC\nEOF\nchmod 600 /opt/trek/server/.env\nmsg_ok \"Set up Server\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/trek.service\n[Unit]\nDescription=TREK Travel Planner\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/trek/server\nEnvironmentFile=/opt/trek/server/.env\nExecStart=/usr/bin/node --import tsx src/index.ts\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now trek\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/trilium-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/TriliumNext/Trilium\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"Trilium\" \"TriliumNext/Trilium\" \"prebuild\" \"latest\" \"/opt/trilium\" \"TriliumNotes-Server-*linux-arm64.tar.xz\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/trilium.service\n[Unit]\nDescription=Trilium Daemon\nAfter=syslog.target network.target\n\n[Service]\nUser=root\nType=simple\nExecStart=/opt/trilium/trilium.sh\nWorkingDirectory=/opt/trilium/\nTimeoutStopSec=20\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q trilium\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/trip-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/itskovacs/TRIP\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nPYTHON_VERSION=\"3.12\" setup_uv\nfetch_and_deploy_gh_release \"trip\" \"itskovacs/TRIP\" \"tarball\"\n\nmsg_info \"Building Frontend\"\ncd /opt/trip/src\n$STD npm install\n$STD npm run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Setting up Backend\"\ncd /opt/trip/backend\n$STD uv venv --clear /opt/trip/.venv\n$STD uv pip install --python /opt/trip/.venv/bin/python -r trip/requirements.txt\nmsg_ok \"Set up Backend\"\n\nmsg_info \"Configuring Application\"\nmkdir -p /opt/trip/frontend\ncp -r /opt/trip/src/dist/trip/browser/* /opt/trip/frontend/\nmkdir -p /opt/trip_storage/{attachments,backups,assets}\n\ncat <<EOF >/opt/trip.env\n# TRIP Configuration\n# https://itskovacs.github.io/trip/docs/getting-started/configuration/\nATTACHMENTS_FOLDER=/opt/trip_storage/attachments\nBACKUPS_FOLDER=/opt/trip_storage/backups\nASSETS_FOLDER=/opt/trip_storage/assets\nFRONTEND_FOLDER=/opt/trip/frontend\nSQLITE_FILE=/opt/trip_storage/trip.sqlite\nEOF\nmsg_ok \"Configured Application\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/trip.service\n[Unit]\nDescription=TRIP - Minimalist POI Map Tracker and Trip Planner\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/trip/backend\nEnvironmentFile=/opt/trip.env\nExecStart=/opt/trip/.venv/bin/fastapi run /opt/trip/backend/trip/main.py --host 0.0.0.0 --port 8000\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now trip\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tubearchivist-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/tubearchivist/tubearchivist\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  git \\\n  nginx \\\n  redis-server \\\n  atomicparsley \\\n  python3-dev \\\n  libldap2-dev \\\n  libsasl2-dev \\\n  libssl-dev \\\n  sqlite3 \\\n  ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nUV_PYTHON=\"3.13\" setup_uv\nNODE_VERSION=\"24\" setup_nodejs\n\nfetch_and_deploy_gh_release \"deno\" \"denoland/deno\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"deno-x86_64-unknown-linux-gnu.zip\"\n\nmsg_info \"Installing ElasticSearch\"\nsetup_deb822_repo \\\n  \"elastic-8.x\" \\\n  \"https://artifacts.elastic.co/GPG-KEY-elasticsearch\" \\\n  \"https://artifacts.elastic.co/packages/8.x/apt\" \\\n  \"stable\" \\\n  \"main\"\nES_JAVA_OPTS=\"-Xms1g -Xmx1g\" $STD apt install -y elasticsearch\nmsg_ok \"Installed ElasticSearch\"\n\nmsg_info \"Configuring ElasticSearch\"\ncat <<EOF >/etc/elasticsearch/elasticsearch.yml\ncluster.name: tubearchivist\npath.data: /var/lib/elasticsearch\npath.logs: /var/log/elasticsearch\npath.repo: [\"/var/lib/elasticsearch/snapshot\"]\nnetwork.host: 127.0.0.1\nxpack.security.enabled: false\nxpack.security.transport.ssl.enabled: false\nxpack.security.http.ssl.enabled: false\nEOF\nmkdir -p /var/lib/elasticsearch/snapshot\nchown -R elasticsearch:elasticsearch /var/lib/elasticsearch/snapshot\ncat <<EOF >/etc/elasticsearch/jvm.options.d/heap.options\n-Xms1g\n-Xmx1g\nEOF\nsysctl -w vm.max_map_count=262144 2>/dev/null || true\ncat <<EOF >/etc/sysctl.d/99-elasticsearch.conf\nvm.max_map_count=262144\nEOF\nsystemctl enable -q --now elasticsearch\nmsg_ok \"Configured ElasticSearch\"\n\nfetch_and_deploy_gh_release \"tubearchivist\" \"tubearchivist/tubearchivist\" \"tarball\"\n\nmsg_info \"Building Frontend\"\ncd /opt/tubearchivist/frontend\n$STD npm install\n$STD npm run build:deploy\nmkdir -p /opt/tubearchivist/backend/static\ncp -r /opt/tubearchivist/frontend/dist/* /opt/tubearchivist/backend/static/\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Setting up Tube Archivist\"\ncp /opt/tubearchivist/docker_assets/backend_start.py /opt/tubearchivist/backend/\n$STD uv venv /opt/tubearchivist/.venv\n$STD uv pip install --python /opt/tubearchivist/.venv/bin/python -r /opt/tubearchivist/backend/requirements.txt\nif [[ -f /opt/tubearchivist/backend/requirements.plugins.txt ]]; then\n  mkdir -p /opt/yt_plugins/bgutil\n  $STD uv pip install --python /opt/tubearchivist/.venv/bin/python --target /opt/yt_plugins/bgutil -r /opt/tubearchivist/backend/requirements.plugins.txt\nfi\nTA_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nES_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmkdir -p /opt/tubearchivist/{cache,media}\nln -sf /opt/tubearchivist/cache /cache\nln -sf /opt/tubearchivist/media /youtube\ncat <<EOF >/opt/tubearchivist/.env\nTA_HOST=http://${LOCAL_IP}:8000\nTA_USERNAME=admin\nTA_PASSWORD=${TA_PASSWORD}\nTA_BACKEND_PORT=8080\nTA_APP_DIR=/opt/tubearchivist/backend\nTA_CACHE_DIR=/cache\nTA_MEDIA_DIR=/youtube\nES_SNAPSHOT_DIR=/var/lib/elasticsearch/snapshot\nELASTIC_PASSWORD=${ES_PASSWORD}\nREDIS_CON=redis://localhost:6379\nES_URL=http://localhost:9200\nTZ=UTC\nPYTHONUNBUFFERED=1\nYTDLP_PLUGIN_DIRS=/opt/yt_plugins\nEOF\n{\n  echo \"Tube Archivist Credentials\"\n  echo \"==========================\"\n  echo \"Username: admin\"\n  echo \"Password: ${TA_PASSWORD}\"\n  echo \"Elasticsearch Password: ${ES_PASSWORD}\"\n} >~/tubearchivist.creds\nsystemctl enable -q --now redis-server\nmsg_ok \"Set up Tube Archivist\"\n\nmsg_info \"Configuring Nginx\"\nsed -i 's/^user www-data;$/user root;/' /etc/nginx/nginx.conf\ncat <<'EOF' >/etc/nginx/sites-available/default\nserver {\n    listen 8000;\n\n    location = /_auth {\n        internal;\n        proxy_pass http://localhost:8080/api/ping/;\n        proxy_pass_request_body off;\n        proxy_set_header Content-Length \"\";\n        proxy_set_header Host $http_host;\n        proxy_set_header Cookie $http_cookie;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    }\n\n    location /cache/videos/ {\n        auth_request /_auth;\n        alias /cache/videos/;\n    }\n\n    location /cache/channels/ {\n        auth_request /_auth;\n        alias /cache/channels/;\n    }\n\n    location /cache/playlists/ {\n        auth_request /_auth;\n        alias /cache/playlists/;\n    }\n\n    location /media/ {\n        auth_request /_auth;\n        alias /youtube/;\n        types {\n            text/vtt vtt;\n        }\n    }\n\n    location /youtube/ {\n        auth_request /_auth;\n        alias /youtube/;\n        types {\n            video/mp4 mp4;\n        }\n    }\n\n    location /api {\n        include proxy_params;\n        proxy_pass http://localhost:8080;\n    }\n\n    location /admin {\n        include proxy_params;\n        proxy_pass http://localhost:8080;\n    }\n\n    location /static/ {\n        alias /opt/tubearchivist/backend/staticfiles/;\n    }\n\n    root /opt/tubearchivist/backend/static;\n    index index.html;\n\n    location ~* ^/(?!static/|cache/).*\\.(?:css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {\n        try_files $uri $uri/ /index.html =404;\n    }\n\n    location = /index.html {\n        add_header Cache-Control \"no-store, no-cache, must-revalidate\";\n        add_header Pragma \"no-cache\";\n        expires 0;\n    }\n\n    location / {\n        add_header Cache-Control \"no-store, no-cache, must-revalidate\";\n        add_header Pragma \"no-cache\";\n        expires 0;\n        try_files $uri $uri/ /index.html =404;\n    }\n}\nEOF\nsystemctl enable -q nginx\nsystemctl restart nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating Services\"\ncat <<'RUNEOF' >/opt/tubearchivist/backend/run.sh\n#!/bin/bash\nset -e\ncd /opt/tubearchivist/backend\nset -a\nsource /opt/tubearchivist/.env\nset +a\nPYTHON=/opt/tubearchivist/.venv/bin/python\n\necho \"Waiting for ElasticSearch...\"\nfor i in $(seq 1 30); do\n  if curl -sf http://localhost:9200/_cluster/health >/dev/null 2>&1; then\n    break\n  fi\n  sleep 2\ndone\n\n$PYTHON manage.py migrate\n$PYTHON manage.py collectstatic --noinput -c\n$PYTHON manage.py ta_envcheck\n$PYTHON manage.py ta_connection\n$PYTHON manage.py ta_startup\n\nexec $PYTHON backend_start.py\nRUNEOF\nchmod +x /opt/tubearchivist/backend/run.sh\nln -sf /opt/tubearchivist/.env /opt/tubearchivist/backend/.env\ncat <<EOF >/etc/systemd/system/tubearchivist.service\n[Unit]\nDescription=Tube Archivist Backend\nAfter=network.target elasticsearch.service redis-server.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/tubearchivist/backend\nEnvironmentFile=/opt/tubearchivist/.env\nEnvironment=PATH=/opt/tubearchivist/.venv/bin:/usr/local/bin:/usr/bin:/bin\nExecStart=/opt/tubearchivist/backend/run.sh\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/tubearchivist-celery.service\n[Unit]\nDescription=Tube Archivist Celery Worker\nAfter=tubearchivist.service redis-server.service elasticsearch.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/tubearchivist/backend\nEnvironmentFile=/opt/tubearchivist/.env\nEnvironment=PATH=/opt/tubearchivist/.venv/bin:/usr/local/bin:/usr/bin:/bin\nExecStart=/opt/tubearchivist/.venv/bin/celery -A task worker --loglevel=error --concurrency=4 --max-tasks-per-child=5 --max-memory-per-child=150000\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/tubearchivist-beat.service\n[Unit]\nDescription=Tube Archivist Celery Beat\nAfter=tubearchivist.service redis-server.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/tubearchivist/backend\nEnvironmentFile=/opt/tubearchivist/.env\nEnvironment=PATH=/opt/tubearchivist/.venv/bin:/usr/local/bin:/usr/bin:/bin\nExecStartPre=/bin/bash -c 'for i in \\$(seq 1 60); do sqlite3 /cache/db.sqlite3 \"SELECT 1 FROM django_celery_beat_crontabschedule LIMIT 1\" 2>/dev/null && exit 0; sleep 2; done; exit 1'\nExecStart=/opt/tubearchivist/.venv/bin/celery -A task beat --loglevel=error --scheduler django_celery_beat.schedulers:DatabaseScheduler\nRestart=always\nRestartSec=5\nRuntimeMaxSec=3600\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tubearchivist tubearchivist-celery tubearchivist-beat\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tududi-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tududi.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  sqlite3 \\\n  yq\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"tududi\" \"chrisvel/tududi\" \"tarball\" \"latest\" \"/opt/tududi\"\n\nmsg_info \"Configuring Tududi\"\ncd /opt/tududi\n$STD npm install\nexport NODE_ENV=production\n$STD npm run frontend:build\nmv ./dist ./backend\nmsg_ok \"Configured Tududi\"\n\nmsg_info \"Creating env and database\"\nDB_LOCATION=\"/opt/tududi-db\"\nUPLOAD_DIR=\"/opt/tududi-uploads\"\nmkdir -p {\"$DB_LOCATION\",\"$UPLOAD_DIR\"}\nSECRET=\"$(openssl rand -hex 64)\"\nsed -e '/^NODE_ENV=/s/=.*$/=production/' \\\n  -e 's/^TUDUDI_USER/# TUDUDI_USER/g' \\\n  -e \"/_SECRET=/s/=.*$/=${SECRET}/\" \\\n  -e '/^# DB_FILE=/s/^# //' \\\n  -e \"s|^DB_FILE=.*|DB_FILE=${DB_LOCATION}/production.sqlite3|\" \\\n  -e \"/^# TUDUDI_ALLOWED/s/^# //; \\\n    \\|_ORIGINS=|s|=.*$|=<your tududi IP or FDQN>|\" \\\n  -e \"/^# TUDUDI_UPLOAD/s/^# //; \\\n    \\|UPLOAD_PATH=|s|=.*$|=${UPLOAD_DIR}|\" \\\n  /opt/tududi/backend/.env.example >/opt/tududi/backend/.env\nexport DB_FILE=\"${DB_LOCATION}/production.sqlite3\"\n$STD npm run db:init\nmsg_ok \"Created env and database\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/tududi.service\n[Unit]\nDescription=Tududi Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/tududi/backend\nEnvironmentFile=/opt/tududi/backend/.env\nExecStart=/usr/bin/bash /opt/tududi/backend/cmd/start.sh\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tududi\nmsg_ok \"Created service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/tunarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: chrisbenincasa\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://tunarr.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_hwaccel\n\nfetch_and_deploy_gh_release \"tunarr\" \"chrisbenincasa/tunarr\" \"prebuild\" \"latest\" \"/opt/tunarr\" \"*linux-arm64.tar.gz\"\ncd /opt/tunarr\nmv tunarr* tunarr\nfetch_and_deploy_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\" \"prebuild\" \"latest\" \"/opt/ErsatzTV-ffmpeg\" \"*-linuarm64-gpl-7.1.tar.xz\"\n\nmsg_info \"Set ErsatzTV-ffmpeg links\"\nchmod +x /opt/ErsatzTV-ffmpeg/bin/*\nln -sf /opt/ErsatzTV-ffmpeg/bin/ffmpeg /usr/bin/ffmpeg\nln -sf /opt/ErsatzTV-ffmpeg/bin/ffplay /usr/bin/ffplay\nln -sf /opt/ErsatzTV-ffmpeg/bin/ffprobe /usr/bin/ffprobe\nmsg_ok \"ffmpeg links set\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/tunarr.service\n[Unit]\nDescription=Tunarr Service\nAfter=multi-user.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/tunarr\nExecStart=/opt/tunarr/tunarr\nRestart=always\nRestartSec=30\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now tunarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/twingate-connector-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ), twingate-andrewb\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.twingate.com/docs/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\ninstall -d -m 0700 /etc/twingate\naccess_token=\"\"\nrefresh_token=\"\"\nnetwork=\"\"\nwhile [[ -z \"$access_token\" ]]; do\n  read -rp \"${TAB3}Please enter your access token: \" access_token\ndone\nwhile [[ -z \"$refresh_token\" ]]; do\n  read -rp \"${TAB3}Please enter your refresh token: \" refresh_token\ndone\nwhile [[ -z \"$network\" ]]; do\n  read -rp \"${TAB3}Please enter your network name: \" network\ndone\n\nmsg_info \"Setup Twingate Repository\"\ncurl -fsSL \"https://packages.twingate.com/apt/gpg.key\" | gpg --dearmor -o /usr/share/keyrings/twingate-connector-keyring.gpg\necho \"deb [signed-by=/usr/share/keyrings/twingate-connector-keyring.gpg] https://packages.twingate.com/apt/ /\" >/etc/apt/sources.list.d/twingate.list\n$STD apt-get update\nmsg_ok \"Setup Twingate Repository\"\n\nmsg_info \"Setup Twingate Connector\"\n$STD apt-get install -y twingate-connector\nmsg_ok \"Setup Twingate Connector\"\n\nmsg_info \"Configure Twingate-Connector\"\n{\n  echo \"TWINGATE_NETWORK=${network}\"\n  echo \"TWINGATE_ACCESS_TOKEN=${access_token}\"\n  echo \"TWINGATE_REFRESH_TOKEN=${refresh_token}\"\n  echo \"TWINGATE_LABEL_HOSTNAME=$(hostname)\"\n  echo \"TWINGATE_LABEL_DEPLOYED_BY=proxmox\"\n} >/etc/twingate/connector.conf\nchmod 600 /etc/twingate/connector.conf\nmsg_ok \"Configured Twingate-Connector\"\n\nmsg_info \"Starting Service\"\nsystemctl enable -q --now twingate-connector\nmsg_ok \"Service started\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/ubuntu-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ubuntu.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/uhf-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: zackwithak13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.uhfapp.com/server\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\nsetup_hwaccel\n\nmsg_info \"Installing Dependencies\"\nsetup_ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting Up UHF Server Environment\"\nmkdir -p /etc/uhf-server\nmkdir -p /var/lib/uhf-server/data\nmkdir -p /var/lib/uhf-server/recordings\ncat <<EOF >/etc/uhf-server/.env\nAPI_HOST=0.0.0.0\nAPI_PORT=7568\nRECORDINGS_DIR=/var/lib/uhf-server/recordings\nDB_PATH=/var/lib/uhf-server/data/db.json\nLOG_LEVEL=INFO\nEOF\nmsg_ok \"Set Up UHF Server Environment\"\n\nfetch_and_deploy_gh_release \"comskip\" \"swapplications/comskip\" \"prebuild\" \"latest\" \"/opt/comskip\" \"comskip-arm64-*.zip\"\nfetch_and_deploy_gh_release \"uhf-server\" \"swapplications/uhf-server-dist\" \"prebuild\" \"latest\" \"/opt/uhf-server\" \"UHF.Server-linux-arm64-*.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/uhf-server.service\n[Unit]\nDescription=UHF Server service\nAfter=syslog.target network-online.target\n[Service]\nType=simple\nWorkingDirectory=/opt/uhf-server\nEnvironmentFile=/etc/uhf-server/.env\nExecStart=/opt/uhf-server/uhf-server\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now uhf-server\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/umami-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://umami.is/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm@latest\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"umamidb\" PG_DB_USER=\"umami\" setup_postgresql_db\nfetch_and_deploy_gh_release \"umami\" \"umami-software/umami\" \"tarball\"\n\nmsg_info \"Configuring Umami\"\ncd /opt/umami\n$STD pnpm install\necho -e \"DATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\" >>/opt/umami/.env\n$STD pnpm run build\nmsg_ok \"Configured Umami\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/umami.service\n[Unit]\nDescription=umami\n\n[Service]\nType=simple\nRestart=always\nUser=root\nWorkingDirectory=/opt/umami\nExecStart=/usr/bin/pnpm run start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now umami\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/umlautadaptarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: elvito\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/PCJones/UmlautAdaptarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\nsetup_deb822_repo \\\n  \"microsoft\" \\\n  \"https://packages.microsoft.com/keys/microsoft.asc\" \\\n  \"https://packages.microsoft.com/debian/12/prod/\" \\\n  \"bookworm\" \\\n  \"main\"\n$STD apt install -y \\\n  dotnet-sdk-8.0 \\\n  aspnetcore-runtime-8.0\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"UmlautAdaptarr\" \"PCJones/Umlautadaptarr\" \"prebuild\" \"latest\" \"/opt/UmlautAdaptarr\" \"linux-x64.zip\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/umlautadaptarr.service\n[Unit]\nDescription=UmlautAdaptarr Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/UmlautAdaptarr\nExecStart=/usr/bin/dotnet /opt/UmlautAdaptarr/UmlautAdaptarr.dll --urls=http://0.0.0.0:5005\nRestart=always\nUser=root\nGroup=root\nEnvironment=ASPNETCORE_ENVIRONMENT=Production\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now umlautadaptarr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/unbound-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: wimb0\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/NLnetLabs/unbound\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y logrotate\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Unbound\"\nmkdir -p /etc/unbound/unbound.conf.d\ncat <<EOF >/etc/unbound/unbound.conf.d/unbound.conf\nserver:\n  interface: 0.0.0.0\n  port: 5335\n  do-ip6: no\n  hide-identity: yes\n  hide-version: yes\n  harden-referral-path: yes\n  cache-min-ttl: 300\n  cache-max-ttl: 14400\n  serve-expired: yes\n  serve-expired-ttl: 3600\n  prefetch: yes\n  prefetch-key: yes\n  target-fetch-policy: \"3 2 1 1 1\"\n  unwanted-reply-threshold: 10000000\n  rrset-cache-size: 256m\n  msg-cache-size: 128m\n  so-rcvbuf: 1m\n  private-address: 192.168.0.0/16\n  private-address: 169.254.0.0/16\n  private-address: 172.16.0.0/12\n  private-address: 10.0.0.0/8\n  private-address: fd00::/8\n  private-address: fe80::/10\n  access-control: 192.168.0.0/16 allow\n  access-control: 172.16.0.0/12 allow\n  access-control: 10.0.0.0/8 allow\n  access-control: 127.0.0.1/32 allow\n  chroot: \"\"\n  logfile: /var/log/unbound.log\nEOF\n\n$STD apt install -y \\\n  unbound \\\n  unbound-host\n\ntouch /var/log/unbound.log\nchown unbound:unbound /var/log/unbound.log\nsleep 5\nsystemctl restart unbound\nmsg_ok \"Installed Unbound\"\n\nmsg_info \"Configuring Logrotate\"\ncat <<EOF >/etc/logrotate.d/unbound\n/var/log/unbound.log {\n  daily\n  rotate 7\n  missingok\n  notifempty\n  compress\n  delaycompress\n  sharedscripts\n  create 644\n  postrotate\n    /usr/sbin/unbound-control log_reopen\n  endscript\n}\nEOF\nsystemctl restart logrotate\nmsg_ok \"Configured Logrotate\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/unifi-os-server-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://ui.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nif [[ \"${CTTYPE:-1}\" != \"0\" ]]; then\n  msg_error \"UniFi OS Server requires a privileged LXC container.\"\n  msg_error \"Recreate the container with unprivileged=0.\"\n  exit 10\nfi\n\nif [[ ! -e /dev/net/tun ]]; then\n  msg_error \"Missing /dev/net/tun in container.\"\n  msg_error \"Enable TUN/TAP (var_tun=yes) or add /dev/net/tun passthrough.\"\n  exit 236\nfi\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  podman \\\n  uidmap \\\n  slirp4netns\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Installing sysctl wrapper (ignore non-critical errors)\"\ncat <<'EOF' >/usr/local/sbin/sysctl\n#!/bin/sh\n/usr/sbin/sysctl \"$@\" || true\nexit 0\nEOF\nchmod +x /usr/local/sbin/sysctl\nexport PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\nmsg_ok \"Sysctl wrapper installed\"\n\nmsg_info \"Fetching latest UniFi OS Server\"\nAPI_URL=\"https://fw-update.ui.com/api/firmware-latest\"\nTEMP_JSON=\"$(mktemp)\"\nif ! curl -fsSL \"$API_URL\" -o \"$TEMP_JSON\"; then\n  rm -f \"$TEMP_JSON\"\n  msg_error \"Failed to fetch data from Ubiquiti API\"\n  exit 250\nfi\nLATEST=$(jq -r '\n  ._embedded.firmware\n  | map(select(.product == \"unifi-os-server\"))\n  | map(select(.platform == \"linux-arm64\"))\n  | sort_by(.version_major, .version_minor, .version_patch)\n  | last\n' \"$TEMP_JSON\")\nUOS_VERSION=$(echo \"$LATEST\" | jq -r '.version' | sed 's/^v//')\nUOS_URL=$(echo \"$LATEST\" | jq -r '._links.data.href')\nrm -f \"$TEMP_JSON\"\nif [[ -z \"$UOS_URL\" || -z \"$UOS_VERSION\" || \"$UOS_URL\" == \"null\" ]]; then\n  msg_error \"Failed to parse UniFi OS Server version or download URL\"\n  exit 250\nfi\nmsg_ok \"Found UniFi OS Server ${UOS_VERSION}\"\n\nmsg_info \"Downloading UniFi OS Server installer\"\nmkdir -p /usr/local/sbin\ncurl -fsSL \"$UOS_URL\" -o /usr/local/sbin/unifi-os-server.bin\nchmod +x /usr/local/sbin/unifi-os-server.bin\nmsg_ok \"Downloaded UniFi OS Server installer\"\n\nmsg_info \"Installing UniFi OS Server (this takes a few minutes)\"\n$STD /usr/local/sbin/unifi-os-server.bin <<<\"y\"\nmsg_ok \"UniFi OS Server installed\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/unmanic-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.unmanic.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y \\\n  ffmpeg \\\n  python3-pip\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Installed Dependencies\"\n\nsetup_hwaccel\n\nmsg_info \"Installing Unmanic\"\n$STD pip3 install unmanic\nmsg_ok \"Installed Unmanic\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/unmanic.service\n[Unit]\nDescription=Unmanic - Library Optimiser\nAfter=network-online.target\nStartLimitInterval=200\nStartLimitBurst=3\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/unmanic\nRestart=always\nRestartSec=30\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable --now -q unmanic.service\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/upgopher-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Eduardo González (wanetty)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wanetty/upgopher\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"upgopher\" \"wanetty/upgopher\" \"prebuild\" \"latest\" \"/opt/upgopher\" \"upgopher_*_linux_arm64.tar.gz\"\n\nmsg_info \"Installing Upgopher\"\nchmod +x /opt/upgopher/upgopher\nmkdir -p /opt/upgopher/uploads\nmsg_ok \"Installed Upgopher\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/upgopher.service\n[Unit]\nDescription=Upgopher File Server\nDocumentation=https://github.com/wanetty/upgopher\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/upgopher\nExecStart=/opt/upgopher/upgopher -port 9090 -dir /opt/upgopher/uploads\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now upgopher\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/upsnap-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/seriousm4x/UpSnap\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nmap \\\n  samba \\\n  samba-common-bin \\\n  openssh-client \\\n  openssh-server \\\n  sshpass\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"upsnap\" \"seriousm4x/UpSnap\" \"prebuild\" \"latest\" \"/opt/upsnap\" \"UpSnap_*_linux_arm64.zip\"\nsetcap 'cap_net_raw=+ep' /opt/upsnap/upsnap\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/upsnap.service\n[Unit]\nDescription=UpSnap Service\nDocumentation=https://github.com/seriousm4x/UpSnap/wiki\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nRestart=on-failure\nWorkingDirectory=/opt/upsnap\nExecStart=/opt/upsnap/upsnap serve --http=0.0.0.0:8090\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now upsnap\nmsg_ok \"Service Created\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/uptimekuma-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://uptime.kuma.pet/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y chromium\nmsg_ok \"Installed dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"uptime-kuma\" \"louislam/uptime-kuma\" \"tarball\"\n\nmsg_info \"Installing Uptime Kuma\"\ncd /opt/uptime-kuma\n$STD npm ci --omit dev\n$STD npm run download-dist\nmsg_ok \"Installed Uptime Kuma\"\n\nmsg_info \"Creating Service\"\nln -s /usr/bin/chromium /opt/uptime-kuma/chromium\ncat <<EOF >/etc/systemd/system/uptime-kuma.service\n[Unit]\nDescription=uptime-kuma\n\n[Service]\nType=simple\nRestart=always\nUser=root\nWorkingDirectory=/opt/uptime-kuma\nExecStart=/usr/bin/npm start\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now uptime-kuma\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/urbackupserver-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.urbackup.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y debconf-utils\nmsg_ok \"Installed Dependencies\"\n\nsetup_deb822_repo \\\n  \"urbackup\" \\\n  \"https://download.opensuse.org/repositories/home:uroni/Debian_13/Release.key\" \\\n  \"http://download.opensuse.org/repositories/home:/uroni/Debian_13/\" \\\n  \"./\" \\\n  \"\"\n\nmsg_info \"Setting up UrBackup Server\"\nmkdir -p /opt/urbackup/backups\necho \"urbackup-server urbackup/backuppath string /opt/urbackup/backups\" | debconf-set-selections\n$STD apt install -y urbackup-server\nmsg_ok \"Setup UrBackup Server\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/valkey-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pshankinclarke (lazarillo)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://valkey.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Valkey\"\n$STD apt update\n$STD apt install -y valkey openssl\nsed -i 's/^bind .*/bind 0.0.0.0/' /etc/valkey/valkey.conf\n\nPASS=\"$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c32)\"\necho \"requirepass $PASS\" >> /etc/valkey/valkey.conf\necho \"$PASS\" >~/valkey.creds\nchmod 600 ~/valkey.creds\n\nMEMTOTAL_MB=$(free -m | grep ^Mem: | awk '{print $2}')\n# reserve 25% of a node type's maxmemory value for system use\nMAXMEMORY_MB=$((MEMTOTAL_MB * 75 / 100))\n\necho \"\" >> /etc/valkey/valkey.conf\necho \"# Memory-optimized settings for small-scale deployments\" >> /etc/valkey/valkey.conf\necho \"maxmemory ${MAXMEMORY_MB}mb\" >> /etc/valkey/valkey.conf\necho \"maxmemory-policy allkeys-lru\" >> /etc/valkey/valkey.conf\necho \"maxmemory-samples 10\" >> /etc/valkey/valkey.conf\nmsg_ok \"Installed Valkey\"\n\necho\nread -r -p \"${TAB3}Enable TLS for Valkey (Sentinel mode does not supported)? [y/N]: \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n    read -r -p \"${TAB3}Use TLS-only mode (disable TCP port 6379)? [y/N]: \" tls_only\n    msg_info \"Configuring TLS for Valkey...\"\n\n    create_self_signed_cert \"Valkey\"\n    TLS_DIR=\"/etc/ssl/valkey\"\n    TLS_CERT=\"$TLS_DIR/valkey.crt\"\n    TLS_KEY=\"$TLS_DIR/valkey.key\"\n    chown valkey:valkey \"$TLS_CERT\" \"$TLS_KEY\"\n\n    if [[ ${tls_only,,} =~ ^(y|yes)$ ]]; then\n    {\n      echo \"\"\n      echo \"# TLS configuration generated by Proxmox VE Valkey helper-script\"\n      echo \"port 0\"\n      echo \"tls-port 6379\"\n      echo \"tls-cert-file $TLS_DIR/valkey.crt\"\n      echo \"tls-key-file $TLS_DIR/valkey.key\"\n      echo \"tls-auth-clients no\"\n    } >> /etc/valkey/valkey.conf\n    msg_ok \"Enabled TLS-only mode on port 6379\"\n    else\n    {\n      echo \"\"\n      echo \"# TLS configuration generated by Proxmox VE Valkey helper-script\"\n      echo \"tls-port 6380\"\n      echo \"tls-cert-file $TLS_DIR/valkey.crt\"\n      echo \"tls-key-file $TLS_DIR/valkey.key\"\n      echo \"tls-auth-clients no\"\n    } >> /etc/valkey/valkey.conf\n    msg_ok \"Enabled TLS on port 6380 and TCP on 6379\"\n    fi\nfi\n\nsystemctl enable -q --now valkey-server\nsystemctl restart valkey-server\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/vaultwarden-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/dani-garcia/vaultwarden\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  pkgconf \\\n  libssl-dev \\\n  libmariadb-dev-compat \\\n  libpq-dev \\\n  argon2 \\\n  ssl-cert\nmsg_ok \"Installed Dependencies\"\n\nsetup_rust\nfetch_and_deploy_gh_release \"vaultwarden\" \"dani-garcia/vaultwarden\" \"tarball\" \"latest\" \"/tmp/vaultwarden-src\"\n\nmsg_info \"Building Vaultwarden (Patience)\"\ncd /tmp/vaultwarden-src\nVW_VERSION=$(get_latest_github_release \"dani-garcia/vaultwarden\")\nexport VW_VERSION\n$STD cargo build --features \"sqlite,mysql,postgresql\" --release\nmsg_ok \"Built Vaultwarden\"\n\nmsg_info \"Setting up Vaultwarden\"\n$STD addgroup --system vaultwarden\n$STD adduser --system --home /opt/vaultwarden --shell /usr/sbin/nologin --no-create-home --gecos 'vaultwarden' --ingroup vaultwarden --disabled-login --disabled-password vaultwarden\nmkdir -p /opt/vaultwarden/{bin,data,web-vault}\ncp target/release/vaultwarden /opt/vaultwarden/bin/\ncd ~ && rm -rf /tmp/vaultwarden-src\nmsg_ok \"Set up Vaultwarden\"\n\nfetch_and_deploy_gh_release \"vaultwarden_webvault\" \"dani-garcia/bw_web_builds\" \"prebuild\" \"latest\" \"/opt/vaultwarden/web-vault\" \"bw_web_*.tar.gz\"\n\nmsg_info \"Configuring Vaultwarden\"\ncat <<EOF >/opt/vaultwarden/.env\nADMIN_TOKEN=''\nROCKET_ADDRESS=0.0.0.0\nROCKET_TLS='{certs=\"/opt/vaultwarden/ssl-cert-snakeoil.pem\",key=\"/opt/vaultwarden/ssl-cert-snakeoil.key\"}'\nDATA_FOLDER=/opt/vaultwarden/data\nDATABASE_MAX_CONNS=10\nWEB_VAULT_FOLDER=/opt/vaultwarden/web-vault\nWEB_VAULT_ENABLED=true\nEOF\nmv /etc/ssl/certs/ssl-cert-snakeoil.pem /opt/vaultwarden/\nmv /etc/ssl/private/ssl-cert-snakeoil.key /opt/vaultwarden/\n\nchown -R vaultwarden:vaultwarden /opt/vaultwarden/\nchown root:root /opt/vaultwarden/bin/vaultwarden\nchmod +x /opt/vaultwarden/bin/vaultwarden\nchown -R root:root /opt/vaultwarden/web-vault/\nchmod +r /opt/vaultwarden/.env\nmsg_ok \"Configured Vaultwarden\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/vaultwarden.service\n[Unit]\nDescription=Bitwarden Server (Powered by Vaultwarden)\nDocumentation=https://github.com/dani-garcia/vaultwarden\nAfter=network.target\n\n[Service]\nUser=vaultwarden\nGroup=vaultwarden\nEnvironmentFile=-/opt/vaultwarden/.env\nExecStart=/opt/vaultwarden/bin/vaultwarden\nLimitNOFILE=65535\nLimitNPROC=4096\nPrivateTmp=true\nPrivateDevices=true\nProtectHome=true\nProtectSystem=strict\nDevicePolicy=closed\nProtectControlGroups=yes\nProtectKernelModules=yes\nProtectKernelTunables=yes\nRestrictNamespaces=yes\nRestrictRealtime=yes\nMemoryDenyWriteExecute=yes\nLockPersonality=yes\nWorkingDirectory=/opt/vaultwarden\nReadWriteDirectories=/opt/vaultwarden/data\nAmbientCapabilities=CAP_NET_BIND_SERVICE\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now vaultwarden\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/verdaccio-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: BrynnJKnight\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://verdaccio.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" NODE_MODULE=\"verdaccio\" setup_nodejs\n\nmsg_info \"Configuring Verdaccio\"\nmkdir -p /opt/verdaccio/config\nmkdir -p /opt/verdaccio/storage\ncat <<EOF >/opt/verdaccio/config/config.yaml\n# Verdaccio configuration\nstorage: /opt/verdaccio/storage\nauth:\n  htpasswd:\n    file: /opt/verdaccio/storage/htpasswd\n    max_users: 1000\nuplinks:\n  npmjs:\n    url: https://registry.npmjs.org/\npackages:\n  '@*/*':\n    access: \\$all\n    publish: \\$authenticated\n    proxy: npmjs\n  '**':\n    access: \\$all\n    publish: \\$authenticated\n    proxy: npmjs\nmiddlewares:\n  audit:\n    enabled: true\nlogs:\n  - {type: stdout, format: pretty, level: http}\nlisten:\n  - 0.0.0.0:4873\nweb:\n  enable: true\n  title: Verdaccio\n  gravatar: true\n  sort_packages: asc\n  login: true\nEOF\nchown -R root:root /opt/verdaccio\nchmod -R 755 /opt/verdaccio\nmsg_ok \"Configured Verdaccio\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/verdaccio.service\n[Unit]\nDescription=Verdaccio lightweight private npm proxy registry\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/verdaccio --config /opt/verdaccio/config/config.yaml\nRestart=on-failure\nStandardOutput=journal\nStandardError=journal\nSyslogIdentifier=verdaccio\nKillMode=control-group\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now verdaccio\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/versitygw-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/versity/versitygw\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"versitygw\" \"versity/versitygw\" \"binary\"\n\nWEBUI_CONF=\"\"\nread -rp \"Would you like to enable the VersityGW WebGUI (Beta)? (y/N): \" webui_prompt\nif [[ \"${webui_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  WEBUI_CONF=\"\\nVGW_WEBUI_PORT=:7071\\nVGW_WEBUI_NO_TLS=true\"\n  msg_ok \"WebGUI will be enabled on port 7071\"\nfi\n\nmsg_info \"Configuring VersityGW\"\nmkdir -p /opt/versitygw-data\nACCESS_KEY=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-20)\nSECRET_KEY=$(openssl rand -base64 36 | tr -dc 'a-zA-Z0-9' | cut -c1-40)\n\ncat <<EOF >/etc/versitygw.d/gateway.conf\nVGW_BACKEND=posix\nVGW_BACKEND_ARG=/opt/versitygw-data\nVGW_PORT=:7070\nROOT_ACCESS_KEY_ID=${ACCESS_KEY}\nROOT_SECRET_ACCESS_KEY=${SECRET_KEY}\nEOF\n\nif [[ -n \"$WEBUI_CONF\" ]]; then\n  echo -e \"$WEBUI_CONF\" >>/etc/versitygw.d/gateway.conf\nfi\nmsg_ok \"Configured VersityGW\"\n\nmsg_info \"Enabling Service\"\nsystemctl enable -q --now versitygw@gateway\nmsg_ok \"Enabled Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/victoriametrics-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/VictoriaMetrics/VictoriaMetrics\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Getting latest version of VictoriaMetrics\"\nvictoriametrics_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaMetrics/releases/latest\" |\n  jq -r '.assets[].name' |\n  grep -E '^victoria-metrics-linux-arm64-v[0-9.]+\\.tar\\.gz$')\nvmutils_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaMetrics/releases/latest\" |\n  jq -r '.assets[].name' |\n  grep -E '^vmutils-linux-arm64-v[0-9.]+\\.tar\\.gz$')\nmsg_ok \"Got latest version of VictoriaMetrics\"\n\nvictoriametrics_release=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaMetrics/releases\" |\n  jq -r '.[] | select(.assets[].name | match(\"^victoria-metrics-linux-amd64-v[0-9.]+.tar.gz$\")) | .tag_name' |\n  head -n 1)\nvictoriametrics_filename=\"victoria-metrics-linux-amd64-${victoriametrics_release}.tar.gz\"\nvmutils_filename=\"vmutils-linux-amd64-${victoriametrics_release}.tar.gz\"\nmsg_ok \"Got version $victoriametrics_release of VictoriaMetrics\"\n\nfetch_and_deploy_gh_release \"victoriametrics\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"$victoriametrics_release\" \"/opt/victoriametrics\" \"$victoriametrics_filename\"\nfetch_and_deploy_gh_release \"vmutils\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"$victoriametrics_release\" \"/opt/victoriametrics\" \"$vmutils_filename\"\n\nread -r -p \"${TAB3}Would you like to add VictoriaLogs? <y/N> \" prompt\n\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  vmlogs_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaLogs/releases/latest\" |\n    jq -r '.assets[].name' |\n    grep -E '^victoria-logs-linux-arm64-v[0-9.]+\\.tar\\.gz$')\n  vlutils_filename=$(curl -fsSL \"https://api.github.com/repos/VictoriaMetrics/VictoriaLogs/releases/latest\" |\n    jq -r '.assets[].name' |\n    grep -E '^vlutils-linux-arm64-v[0-9.]+\\.tar\\.gz$')\n  fetch_and_deploy_gh_release \"victorialogs\" \"VictoriaMetrics/VictoriaLogs\" \"prebuild\" \"latest\" \"/opt/victoriametrics\" \"$vmlogs_filename\"\n  fetch_and_deploy_gh_release \"vlutils\" \"VictoriaMetrics/VictoriaLogs\" \"prebuild\" \"latest\" \"/opt/victoriametrics\" \"$vlutils_filename\"\nfi\n\nmsg_info \"Setup VictoriaMetrics\"\nmkdir -p /opt/victoriametrics/data\nchmod +x /opt/victoriametrics/*\nmsg_ok \"Setup VictoriaMetrics\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/victoriametrics.service\n[Unit]\nDescription=VictoriaMetrics Service\n\n[Service]\nType=simple\nRestart=always\nUser=root\nWorkingDirectory=/opt/victoriametrics\nExecStart=/opt/victoriametrics/victoria-metrics-prod --storageDataPath=\"/opt/victoriametrics/data\"\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now victoriametrics\n\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  cat <<EOF >/etc/systemd/system/victoriametrics-logs.service\n[Unit]\nDescription=VictoriaMetrics Service\n\n[Service]\nType=simple\nRestart=always\nUser=root\nWorkingDirectory=/opt/victoriametrics\nExecStart=/opt/victoriametrics/victoria-logs-prod\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now victoriametrics-logs\nfi\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/vikunja-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://vikunja.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"vikunja\" \"go-vikunja/vikunja\" \"binary\"\n\nmsg_info \"Setting up Vikunja\"\nsed -i 's|^# \\(service:\\)|\\1|' /etc/vikunja/config.yml\nsed -i \"s|^  # \\(publicurl: \\).*|  \\1\\\"http://$LOCAL_IP\\\"|\" /etc/vikunja/config.yml\nsed -i \"0,/^  # \\(timezone: \\).*/s||  \\1${tz}|\" /etc/vikunja/config.yml\nsystemctl enable -q --now vikunja\nmsg_ok \"Set up Vikunja\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wallabag-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wallabag.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  redis-server \\\n  imagemagick\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"wallabag\" MARIADB_DB_USER=\"wallabag\" setup_mariadb_db\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULE=\"tidy\" setup_php\nsetup_composer\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"wallabag\" \"wallabag/wallabag\" \"prebuild\" \"latest\" \"/opt/wallabag\" \"wallabag-*.tar.gz\"\n\nmsg_info \"Configuring Wallabag\"\ncd /opt/wallabag\nSECRET_KEY=\"$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)\"\ncat <<EOF >/opt/wallabag/app/config/parameters.yml\nparameters:\n    database_driver: pdo_mysql\n    database_host: 127.0.0.1\n    database_port: 3306\n    database_name: ${MARIADB_DB_NAME}\n    database_user: ${MARIADB_DB_USER}\n    database_password: ${MARIADB_DB_PASS}\n    database_path: null\n    database_table_prefix: wallabag_\n    database_socket: null\n    database_charset: utf8mb4\n\n    domain_name: http://${LOCAL_IP}:8000\n    server_name: Wallabag\n\n    mailer_dsn: null\n\n    locale: en\n\n    secret: ${SECRET_KEY}\n\n    twofactor_auth: false\n    twofactor_sender: no-reply@wallabag.org\n\n    fosuser_registration: true\n    fosuser_confirmation: false\n\n    fos_oauth_server_access_token_lifetime: 3600\n    fos_oauth_server_refresh_token_lifetime: 1209600\n\n    from_email: no-reply@wallabag.org\n\n    rss_limit: 50\n\n    rabbitmq_host: localhost\n    rabbitmq_port: 5672\n    rabbitmq_user: guest\n    rabbitmq_password: guest\n    rabbitmq_prefetch_count: 10\n\n    redis_scheme: tcp\n    redis_host: localhost\n    redis_port: 6379\n    redis_path: null\n    redis_password: null\n\n    sentry_dsn: null\nEOF\nchown -R www-data:www-data /opt/wallabag\nmsg_ok \"Configured Wallabag\"\n\nmsg_info \"Installing Wallabag (Patience)\"\nexport COMPOSER_ALLOW_SUPERUSER=1\nexport SYMFONY_ENV=prod\ncd /opt/wallabag\n$STD php bin/console wallabag:install --env=prod --no-interaction\n$STD php bin/console cache:clear --env=prod\nchown -R www-data:www-data /opt/wallabag\nchmod -R 755 /opt/wallabag/{var,web/assets}\nmsg_ok \"Installed Wallabag\"\n\nmsg_info \"Configuring Nginx\"\ncat <<'EOF' >/etc/nginx/sites-available/wallabag\nserver {\n    listen 8000;\n    server_name _;\n    root /opt/wallabag/web;\n\n    add_header X-Frame-Options \"SAMEORIGIN\";\n    add_header X-Content-Type-Options \"nosniff\";\n\n    index app.php;\n    charset utf-8;\n\n    location / {\n        try_files $uri /app.php$is_args$args;\n    }\n\n    location ~ ^/app\\.php(/|$) {\n        fastcgi_pass unix:/run/php/php8.3-fpm.sock;\n        fastcgi_split_path_info ^(.+\\.php)(/.*)$;\n        include fastcgi_params;\n        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n        fastcgi_param DOCUMENT_ROOT $realpath_root;\n        internal;\n    }\n\n    location ~ \\.php$ {\n        return 404;\n    }\n\n    location ~ /\\.(?!well-known).* {\n        deny all;\n    }\n\n    error_log /var/log/nginx/wallabag_error.log;\n    access_log /var/log/nginx/wallabag_access.log;\n}\nEOF\n\nln -sf /etc/nginx/sites-available/wallabag /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Enabling Services\"\nsystemctl enable -q --now redis-server\nsystemctl enable -q --now php8.3-fpm\nsystemctl enable -q --now nginx\nmsg_ok \"Enabled Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wallos-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/ellite/wallos\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" setup_php\nfetch_and_deploy_gh_release \"wallos\" \"ellite/Wallos\" \"tarball\"\n\nmsg_info \"Installing Wallos (Patience)\"\ncd /opt/wallos\nmv /opt/wallos/db/wallos.empty.db /opt/wallos/db/wallos.db\nchown -R www-data:www-data /opt/wallos\nchmod -R 755 /opt/wallos\ncat <<EOF >/etc/apache2/sites-available/wallos.conf\n<VirtualHost *:80>\n    ServerAdmin webmaster@localhost\n    DocumentRoot /opt/wallos\n\n    <Directory /opt/wallos>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog \\${APACHE_LOG_DIR}/wallos_error.log\n    CustomLog \\${APACHE_LOG_DIR}/wallos_access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite wallos.conf\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\n$STD curl http://localhost/endpoints/db/migrate.php\nmsg_ok \"Installed Wallos\"\n\nmsg_info \"Setting up Crontabs\"\n$STD apt-get -y install cron\nmkdir -p /var/log/cron\ncat <<EOF >/opt/wallos.cron\n0 1 * * * php /opt/wallos/endpoints/cronjobs/updatenextpayment.php >> /var/log/cron/updatenextpayment.log 2>&1\n0 2 * * * php /opt/wallos/endpoints/cronjobs/updateexchange.php >> /var/log/cron/updateexchange.log 2>&1\n0 8 * * * php /opt/wallos/endpoints/cronjobs/sendcancellationnotifications.php >> /var/log/cron/sendcancellationnotifications.log 2>&1\n0 9 * * * php /opt/wallos/endpoints/cronjobs/sendnotifications.php >> /var/log/cron/sendnotifications.log 2>&1\n*/2 * * * * php /opt/wallos/endpoints/cronjobs/sendverificationemails.php >> /var/log/cron/sendverificationemail.log 2>&1\n*/2 * * * * php /opt/wallos/endpoints/cronjobs/sendresetpasswordemails.php >> /var/log/cron/sendresetpasswordemails.log 2>&1\n0 */6 * * * php /opt/wallos/endpoints/cronjobs/checkforupdates.php >> /var/log/cron/checkforupdates.log 2>&1\n30 1 * * 1 php /opt/wallos/endpoints/cronjobs/storetotalyearlycost.php >> /var/log/cron/storetotalyearlycost.log 2>&1\nEOF\ncrontab /opt/wallos.cron\nmsg_ok \"Crontabs setup\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wanderer-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rrole\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wanderer.to\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nsetup_go\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"meilisearch\" \"meilisearch/meilisearch\" \"singlefile\" \"latest\" \"/usr/bin\" \"meilisearch-linux-aarch64\"\nmkdir -p /opt/wanderer/{source,data/pb_data,data/meili_data}\nfetch_and_deploy_gh_release \"wanderer\" \"open-wanderer/wanderer\" \"tarball\" \"latest\" \"/opt/wanderer/source\"\n\nmsg_info \"Installing wanderer (patience)\"\ncd /opt/wanderer/source/db\n$STD go mod tidy\n$STD go build\ncd /opt/wanderer/source/web\n$STD npm ci -s vitest\n$STD npm ci --omit=dev\n$STD npm run build\nmsg_ok \"Installed wanderer\"\n\nmsg_info \"Creating Service\"\nMEILI_KEY=$(openssl rand -hex 32)\nPOCKETBASE_KEY=$(openssl rand -hex 16)\n\ncat <<EOF >/opt/wanderer/.env\nORIGIN=http://${LOCAL_IP}:3000\nMEILI_HTTP_ADDR=${LOCAL_IP}:7700\nMEILI_URL=http://${LOCAL_IP}:7700\nMEILI_MASTER_KEY=${MEILI_KEY}\nPB_URL=${LOCAL_IP}:8090\nPUBLIC_POCKETBASE_URL=http://${LOCAL_IP}:8090\nPUBLIC_VALHALLA_URL=https://valhalla1.openstreetmap.de\nPOCKETBASE_ENCRYPTION_KEY=${POCKETBASE_KEY}\nPB_DB_LOCATION=/opt/wanderer/data/pb_data\nMEILI_DB_PATH=/opt/wanderer/data/meili_data\nEOF\n\ncat <<EOF >/opt/wanderer/start.sh\n#!/usr/bin/env bash\n\ntrap \"kill 0\" EXIT\n\ncd /opt/wanderer/source/search && meilisearch --experimental-dumpless-upgrade --master-key \\$MEILI_MASTER_KEY &\nsleep 1\ncd /opt/wanderer/source/db && ./pocketbase serve --http=\\$PB_URL --dir=\\$PB_DB_LOCATION &\ncd /opt/wanderer/source/web && node build &\n\nwait -n\nEOF\nchmod +x /opt/wanderer/start.sh\n\ncat <<'EOF' >/usr/local/bin/wanderer-pb\n#!/usr/bin/env bash\nset -a\nsource /opt/wanderer/.env\nset +a\ncd /opt/wanderer/source/db\nexec ./pocketbase \"$@\" --dir=\"$PB_DB_LOCATION\"\nEOF\nchmod +x /usr/local/bin/wanderer-pb\n\ncat <<EOF >/etc/systemd/system/wanderer-web.service\n[Unit]\nDescription=wanderer\nAfter=network.target\nStartLimitIntervalSec=10\nStartLimitBurst=5\n\n[Service]\nType=simple\nEnvironmentFile=/opt/wanderer/.env\nExecStart=/usr/bin/bash /opt/wanderer/start.sh\nRestart=always\nRestartSec=1\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsleep 1\nsystemctl enable -q --now wanderer-web\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/warracker-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/sassanix/Warracker/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n    build-essential \\\n    libpq-dev \\\n    nginx\nmsg_ok \"Installed Dependencies\"\n\nPYTHON_VERSION=\"3.12\" setup_uv\nPG_VERSION=\"17\" setup_postgresql\n\nmsg_info \"Setup PostgreSQL\"\nDB_NAME=\"warranty_db\"\nDB_USER=\"warranty_user\"\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\nDB_ADMIN_USER=\"warracker_admin\"\nDB_ADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\n$STD sudo -u postgres psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE USER $DB_ADMIN_USER WITH PASSWORD '$DB_ADMIN_PASS' SUPERUSER;\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME OWNER $DB_ADMIN_USER;\"\n$STD sudo -u postgres psql -d \"$DB_NAME\" -c \"GRANT USAGE ON SCHEMA public TO $DB_USER;\"\n$STD sudo -u postgres psql -d \"$DB_NAME\" -c \"GRANT CREATE ON SCHEMA public TO $DB_USER;\"\n$STD sudo -u postgres psql -d \"$DB_NAME\" -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO $DB_USER;\"\n{\n    echo \"Application Credentials\"\n    echo \"DB_NAME: $DB_NAME\"\n    echo \"DB_USER: $DB_USER\"\n    echo \"DB_PASS: $DB_PASS\"\n    echo \"DB_ADMIN_USER: $DB_ADMIN_USER\"\n    echo \"DB_ADMIN_PASS: $DB_ADMIN_PASS\"\n} >>~/warracker.creds\nmsg_ok \"Setup PostgreSQL\"\n\nfetch_and_deploy_gh_release \"warracker\" \"sassanix/Warracker\" \"tarball\" \"latest\" \"/opt/warracker\"\n\nmsg_info \"Installing Warracker\"\ncd /opt/warracker/backend\n$STD uv venv --clear .venv\n$STD source .venv/bin/activate\n$STD uv pip install -r requirements.txt\nmv /opt/warracker/env.example /opt/.env\nsed -i \\\n    -e \"s/your_secure_database_password/$DB_PASS/\" \\\n    -e \"s/your_secure_admin_password/$DB_ADMIN_PASS/\" \\\n    -e \"s|^# DB_PORT=5432$|DB_HOST=127.0.0.1|\" \\\n    -e \"s|your_very_secure_flask_secret_key_change_this_in_production|$(openssl rand -base64 32 | tr -d '\\n')|\" \\\n    /opt/.env\nmkdir -p /data/uploads\nmsg_ok \"Installed Warracker\"\n\nmsg_info \"Configuring Nginx\"\nmv /opt/warracker/nginx.conf /etc/nginx/sites-available/warracker.conf\nsed -i \\\n    -e \"s|alias /var/www/html/locales/;|alias /opt/warracker/locales/;|\" \\\n    -e \"s|/var/www/html|/opt/warracker/frontend|g\" \\\n    -e \"s/client_max_body_size __NGINX_MAX_BODY_SIZE_CONFIG_VALUE__/client_max_body_size 32M/\" \\\n    /etc/nginx/sites-available/warracker.conf\nln -s /etc/nginx/sites-available/warracker.conf /etc/nginx/sites-enabled/warracker.conf\nrm /etc/nginx/sites-enabled/default\nsystemctl restart nginx\n\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating systemd services\"\ncat <<EOF >/etc/systemd/system/warrackermigration.service\n[Unit]\nDescription=Warracker Migration Service\nAfter=network.target\n\n[Service]\nType=oneshot\nWorkingDirectory=/opt/warracker/backend/migrations\nEnvironmentFile=/opt/.env\nExecStart=/opt/warracker/backend/.venv/bin/python apply_migrations.py\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/warracker.service\n[Unit]\nDescription=Warracker Service\nAfter=network.target warrackermigration.service\nRequires=warrackermigration.service\n\n[Service]\nWorkingDirectory=/opt/warracker\nEnvironmentFile=/opt/.env\nExecStart=/opt/warracker/backend/.venv/bin/gunicorn --config /opt/warracker/backend/gunicorn_config.py backend:create_app() --bind 127.0.0.1:5000\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now warracker\nmsg_ok \"Started Warracker Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wastebin-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/matze/wastebin\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y zstd\nmsg_ok \"Installed dependencies\"\n\nmsg_info \"Installing Wastebin\"\ntemp_file=$(mktemp)\nRELEASE=$(curl -fsSL https://api.github.com/repos/matze/wastebin/releases/latest | grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3) }')\ncurl -fsSL \"https://github.com/matze/wastebin/releases/download/${RELEASE}/wastebin_${RELEASE}_aarch64-unknown-linux-musl.tar.zst\" -o \"$temp_file\"\ntar -xf \"$temp_file\"\nmkdir -p /opt/wastebin\nmv wastebin* /opt/wastebin/\nchmod +x /opt/wastebin/wastebin\nchmod +x /opt/wastebin/wastebin-ctl\n\nmkdir -p /opt/wastebin-data\ncat <<EOF >/opt/wastebin-data/.env\nWASTEBIN_DATABASE_PATH=/opt/wastebin-data/wastebin.db\nWASTEBIN_CACHE_SIZE=1024\nWASTEBIN_HTTP_TIMEOUT=30\nWASTEBIN_SIGNING_KEY=$(openssl rand -hex 32)\nWASTEBIN_PASTE_EXPIRATIONS=0,600,3600=d,86400,604800,2419200,29030400\nEOF\nrm -f \"$temp_file\"\necho \"${RELEASE}\" >\"/opt/${APPLICATION}_version.txt\"\nmsg_ok \"Installed Wastebin\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wastebin.service\n[Unit]\nDescription=Start Wastebin Service\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/wastebin\nExecStart=/root/.cargo/bin/cargo run --release --quiet\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now wastebin\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/watcharr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/sbondCo/Watcharr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y gcc\nmsg_ok \"Installed Dependencies\"\n\nsetup_go\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"watcharr\" \"sbondCo/Watcharr\" \"tarball\"\n\nmsg_info \"Setup Watcharr\"\ncd /opt/watcharr\n$STD npm i\n$STD npm run build\nmv ./build ./server/ui\ncd server\nexport CGO_ENABLED=1 GOOS=linux\n$STD go mod download\n$STD go build -o ./watcharr\nmsg_ok \"Setup Watcharr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/watcharr.service\n[Unit]\nDescription=Watcharr Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/watcharr/server\nExecStart=/opt/watcharr/server/watcharr\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now watcharr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/watchyourlan-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/aceberg/WatchYourLAN\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  arp-scan \\\n  ieee-data \\\n  libwww-perl\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"watchyourlan\" \"aceberg/WatchYourLAN\" \"binary\"\n\nmsg_info \"Configuring WatchYourLAN\"\nmkdir /data\ncat <<EOF >/data/config.yaml\narp_timeout: \"500\"\nauth: false\nauth_expire: 7d\nauth_password: \"\"\nauth_user: \"\"\ncolor: dark\ndbpath: /data/db.sqlite\nguiip: 0.0.0.0\nguiport: \"8840\"\nhistory_days: \"30\"\niface: eth0\nignoreip: \"no\"\nloglevel: verbose\nshoutrrr_url: \"\"\ntheme: solar\ntimeout: 60\nEOF\nmsg_ok \"Configured WatchYourLAN\"\n\nmsg_info \"Creating Service\"\nsed -i 's|/etc/watchyourlan/config.yaml|/data/config.yaml|' /lib/systemd/system/watchyourlan.service\nsystemctl enable -q --now watchyourlan\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wavelog-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Don Locke (DonLocke)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wavelog/wavelog\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_APACHE=\"YES\" PHP_MAX_EXECUTION_TIME=\"600\" setup_php\nsetup_mariadb\nMARIADB_DB_NAME=\"wavelog\" MARIADB_DB_USER=\"waveloguser\" setup_mariadb_db\nfetch_and_deploy_gh_release \"wavelog\" \"wavelog/wavelog\" \"tarball\"\n\nmsg_info \"Configuring Wavelog\"\nchown -R www-data:www-data /opt/wavelog/\nfind /opt/wavelog/ -type d -exec chmod 755 {} \\;\nfind /opt/wavelog/ -type f -exec chmod 664 {} \\;\nmsg_ok \"Configured Wavelog\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/apache2/sites-available/wavelog.conf\n<VirtualHost *:80>\n    ServerAdmin webmaster@localhost\n    DocumentRoot /opt/wavelog\n\n    <Directory /opt/wavelog>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n\n    ErrorLog /var/log/apache2/error.log\n    CustomLog /var/log/apache2/access.log combined\n</VirtualHost>\nEOF\n$STD a2ensite wavelog.conf\n$STD a2dissite 000-default.conf\n$STD systemctl reload apache2\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wazuh-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wazuh.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nRELEASE=$(curl -fsSL https://api.github.com/repos/wazuh/wazuh/releases/latest | grep '\"tag_name\"' | awk -F '\"' '{print substr($4, 2, length($2)-4)}')\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://wazuh.com/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://packages.wazuh.com/$RELEASE/wazuh-install.sh \"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nmsg_info \"Setup Wazuh\"\ncurl -fsSL https://packages.wazuh.com/$RELEASE/wazuh-install.sh -o wazuh-install.sh\nchmod +x wazuh-install.sh\nif [ \"$STD\" = \"silent\" ]; then\n  bash wazuh-install.sh -a >>~/wazuh-install.output\nelse\n  bash wazuh-install.sh -a | tee -a ~/wazuh-install.output\nfi\ncat ~/wazuh-install.output | grep -E \"User|Password\" | awk '{$1=$1};1' | sed '1i wazuh-credentials' >~/wazuh.creds\nrm -f wazuh-*.sh\nrm -f ~/wazuh-install.output\nmsg_ok \"Setup Wazuh\"\n\n# Fix LXC container false positives in rootcheck\n# When running Wazuh in an LXC container, /dev/.lxc/* paths trigger false alerts\nif [ -d /dev/.lxc ]; then\n  msg_info \"Adding LXC rootcheck exclusion\"\n  sed -i '/<\\/rootcheck>/i \\    <ignore>/dev/.lxc</ignore>' /var/ossec/etc/ossec.conf\n  msg_ok \"Added LXC rootcheck exclusion\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wealthfolio-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wealthfolio.app/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  pkg-config \\\n  libssl-dev \\\n  build-essential \\\n  libsqlite3-dev \\\n  argon2\nmsg_ok \"Installed Dependencies\"\n\nsetup_rust\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm\" setup_nodejs\nfetch_and_deploy_gh_release \"wealthfolio\" \"afadil/wealthfolio\" \"tarball\"\n\nmsg_info \"Building Frontend (patience)\"\ncd /opt/wealthfolio\nexport BUILD_TARGET=web\n$STD pnpm install --frozen-lockfile\n$STD pnpm --filter frontend... build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Building Backend (patience)\"\nsource ~/.cargo/env\n$STD cargo build --release --manifest-path apps/server/Cargo.toml\ncp /opt/wealthfolio/target/release/wealthfolio-server /usr/local/bin/wealthfolio-server\nchmod +x /usr/local/bin/wealthfolio-server\nmsg_ok \"Built Backend\"\n\nmsg_info \"Configuring Wealthfolio\"\nmkdir -p /opt/wealthfolio_data\nSECRET_KEY=$(openssl rand -base64 32)\nWF_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-16)\nWF_PASSWORD_HASH=$(echo -n \"$WF_PASSWORD\" | argon2 \"$(openssl rand -base64 16)\" -id -e)\ncat <<EOF >/opt/wealthfolio/.env\nWF_LISTEN_ADDR=0.0.0.0:8080\nWF_DB_PATH=/opt/wealthfolio_data/wealthfolio.db\nWF_SECRET_KEY=${SECRET_KEY}\nWF_AUTH_PASSWORD_HASH=${WF_PASSWORD_HASH}\nWF_STATIC_DIR=/opt/wealthfolio/dist\nWF_REQUEST_TIMEOUT_MS=30000\nWF_CORS_ALLOW_ORIGINS=http://${LOCAL_IP}:8080\nEOF\necho \"WF_PASSWORD=${WF_PASSWORD}\" >~/wealthfolio.creds\nmsg_ok \"Configured Wealthfolio\"\n\nmsg_info \"Cleaning Up\"\nrm -rf /opt/wealthfolio/target\nrm -rf /root/.cargo/registry\nrm -rf /opt/wealthfolio/node_modules\nmsg_ok \"Cleaned Up\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wealthfolio.service\n[Unit]\nDescription=Wealthfolio Investment Tracker\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/wealthfolio\nEnvironmentFile=/opt/wealthfolio/.env\nExecStart=/usr/local/bin/wealthfolio-server\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now wealthfolio\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/web-check-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/lissy93/web-check\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\nexport DEBIAN_FRONTEND=noninteractive\n$STD apt -y install --no-install-recommends \\\n  git \\\n  traceroute \\\n  build-essential \\\n  xvfb \\\n  dbus \\\n  xorg \\\n  gtk2-engines-pixbuf \\\n  dbus-x11 \\\n  xfonts-base \\\n  xfonts-100dpi \\\n  xfonts-75dpi \\\n  xfonts-scalable \\\n  imagemagick \\\n  x11-apps\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n\nmsg_info \"Setup Python3\"\n$STD apt install -y python3\nrm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\nmsg_ok \"Setup Python3\"\n\nmsg_info \"Installing Chromium\"\ncurl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome-keyring.gpg\ncat <<EOF | sudo tee /etc/apt/sources.list.d/google-chrome.sources >/dev/null\nTypes: deb\nURIs: http://dl.google.com/linux/chrome/deb/\nSuites: stable\nComponents: main\nArchitectures: arm64\nSigned-By: /usr/share/keyrings/google-chrome-keyring.gpg\nEOF\n$STD apt update\n$STD apt -y install \\\n  chromium \\\n  libxss1 \\\n  lsb-release\nmsg_ok \"Installed Chromium\"\n\nmsg_info \"Setting up Chromium\"\n/usr/bin/chromium --no-sandbox --version >/etc/chromium-version\nchmod 755 /usr/bin/chromium\nmsg_ok \"Setup Chromium\"\n\nfetch_and_deploy_gh_release \"web-check\" \"Lissy93/web-check\" \"tarball\"\n\nmsg_info \"Installing Web-Check (Patience)\"\ncd /opt/web-check\ncat <<'EOF' >/opt/web-check/.env\nCHROME_PATH=/usr/bin/chromium\nPUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium\nPUPPETEER_SKIP_DOWNLOAD='true'\nHEADLESS=true\nGOOGLE_CLOUD_API_KEY=''\nREACT_APP_SHODAN_API_KEY=''\nREACT_APP_WHO_API_KEY=''\nSECURITY_TRAILS_API_KEY=''\nCLOUDMERSIVE_API_KEY=''\nTRANCO_USERNAME=''\nTRANCO_API_KEY=''\nURL_SCAN_API_KEY=''\nBUILT_WITH_API_KEY=''\nTORRENT_IP_API_KEY=''\nPORT='3000'\nDISABLE_GUI='false'\nAPI_TIMEOUT_LIMIT='10000'\nAPI_CORS_ORIGIN='*'\nAPI_ENABLE_RATE_LIMIT='false'\nREACT_APP_API_ENDPOINT='/api'\nENABLE_ANALYTICS='false'\nEOF\n$STD yarn install --frozen-lockfile --network-timeout 100000\nmsg_ok \"Installed Web-Check\"\n\nmsg_info \"Building Web-Check\"\n$STD yarn build --production\nmsg_ok \"Built Web-Check\"\n\nmsg_info \"Creating Service\"\ncat <<'EOF' >/opt/run_web-check.sh\n#!/bin/bash\nSCREEN_RESOLUTION=\"1280x1024x24\"\nif ! systemctl is-active --quiet dbus; then\n  echo \"Warning: dbus service is not running. Some features may not work properly.\"\nfi\n[[ -z \"${DISPLAY}\" ]] && export DISPLAY=\":99\"\nXvfb \"${DISPLAY}\" -screen 0 \"${SCREEN_RESOLUTION}\" &\nXVFB_PID=$!\nsleep 2\ncd /opt/web-check\nexec yarn start\nEOF\nchmod +x /opt/run_web-check.sh\ncat <<'EOF' >/etc/systemd/system/web-check.service\n[Unit]\nDescription=Web Check Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nGroup=root\nWorkingDirectory=/opt/web-check\nEnvironmentFile=/opt/web-check/.env\nExecStartPre=/bin/bash -c \"service dbus start || true\"\nExecStartPre=/bin/bash -c \"if ! pgrep -f 'Xvfb.*:99' > /dev/null; then Xvfb :99 -screen 0 1280x1024x24 & fi\"\nExecStart=/opt/run_web-check.sh\nRestart=on-failure\nEnvironment=DISPLAY=:99\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now web-check\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wger-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wger-project/wger\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  nginx \\\n  redis-server \\\n  libpq-dev\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"sass\" setup_nodejs\nsetup_uv\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"wger\" PG_DB_USER=\"wger\" setup_postgresql_db\nfetch_and_deploy_gh_release \"wger\" \"wger-project/wger\" \"tarball\"\n\nmsg_info \"Setting up wger\"\nmkdir -p /opt/wger/{static,media}\nchmod o+w /opt/wger/media\ncd /opt/wger\n$STD corepack enable\n$STD npm install\n$STD npm run build:css:sass\n$STD uv venv\n$STD uv pip install . --group docker\nSECRET_KEY=$(openssl rand -base64 40)\ncat <<EOF >/opt/wger/.env\nDJANGO_SETTINGS_MODULE=settings.main\nPYTHONPATH=/opt/wger\n\nDJANGO_DB_ENGINE=django.db.backends.postgresql\nDJANGO_DB_DATABASE=${PG_DB_NAME}\nDJANGO_DB_USER=${PG_DB_USER}\nDJANGO_DB_PASSWORD=${PG_DB_PASS}\nDJANGO_DB_HOST=localhost\nDJANGO_DB_PORT=5432\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\n\nDJANGO_MEDIA_ROOT=/opt/wger/media\nDJANGO_STATIC_ROOT=/opt/wger/static\nDJANGO_STATIC_URL=/static/\n\nALLOWED_HOSTS=${LOCAL_IP},localhost,127.0.0.1\nCSRF_TRUSTED_ORIGINS=http://${LOCAL_IP}:3000\n\nUSE_X_FORWARDED_HOST=True\nSECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,http\n\nDJANGO_CACHE_BACKEND=django_redis.cache.RedisCache\nDJANGO_CACHE_LOCATION=redis://127.0.0.1:6379/1\nDJANGO_CACHE_TIMEOUT=300\nDJANGO_CACHE_CLIENT_CLASS=django_redis.client.DefaultClient\nAXES_CACHE_ALIAS=default\n\nUSE_CELERY=True\nCELERY_BROKER=redis://127.0.0.1:6379/2\nCELERY_BACKEND=redis://127.0.0.1:6379/2\n\nSITE_URL=http://${LOCAL_IP}:3000\nSECRET_KEY=${SECRET_KEY}\nEOF\nset -a && source /opt/wger/.env && set +a\n$STD uv run wger bootstrap\n$STD uv run python manage.py collectstatic --no-input\ncat <<EOF | uv run python manage.py shell\nfrom django.contrib.auth import get_user_model\nUser = get_user_model()\n\nuser, created = User.objects.get_or_create(\n    username=\"admin\",\n    defaults={\"email\": \"admin@localhost\"},\n)\n\nif created:\n    user.set_password(\"${PG_DB_PASS}\")\n    user.is_superuser = True\n    user.is_staff = True\n    user.save()\nEOF\nmsg_ok \"Set up wger\"\nmsg_info \"Creating Config and Services\"\ncat <<EOF >/etc/systemd/system/wger.service\n[Unit]\nDescription=wger Gunicorn\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/wger\nEnvironmentFile=/opt/wger/.env\nExecStart=/opt/wger/.venv/bin/gunicorn \\\n  --bind 127.0.0.1:8000 \\\n  --workers 3 \\\n  --threads 2 \\\n  --timeout 120 \\\n  wger.wsgi:application\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<EOF >/etc/systemd/system/celery.service\n[Unit]\nDescription=wger Celery Worker\nAfter=network.target redis-server.service\nRequires=redis-server.service\n\n[Service]\nWorkingDirectory=/opt/wger\nEnvironmentFile=/opt/wger/.env\nExecStart=/opt/wger/.venv/bin/celery -A wger worker -l info\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nmkdir -p /var/lib/wger/celery\nchmod 700 /var/lib/wger/celery\ncat <<EOF >/etc/systemd/system/celery-beat.service\n[Unit]\nDescription=wger Celery Beat\nAfter=network.target redis-server.service\nRequires=redis-server.service\n\n[Service]\nWorkingDirectory=/opt/wger\nEnvironmentFile=/opt/wger/.env\nExecStart=/opt/wger/.venv/bin/celery -A wger beat -l info \\\n  --schedule /var/lib/wger/celery/celerybeat-schedule\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\ncat <<'EOF' >/etc/nginx/sites-available/wger\nserver {\n    listen 3000;\n    server_name _;\n\n    client_max_body_size 20M;\n\n    location /static/ {\n        alias /opt/wger/static/;\n        expires 30d;\n    }\n\n    location /media/ {\n        alias /opt/wger/media/;\n    }\n\n    location / {\n        proxy_pass http://127.0.0.1:8000;\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_redirect off;\n    }\n}\nEOF\n$STD rm -f /etc/nginx/sites-enabled/default\n$STD ln -sf /etc/nginx/sites-available/wger /etc/nginx/sites-enabled/wger\nsystemctl enable -q --now redis-server nginx wger celery celery-beat\nsystemctl restart nginx\nmsg_ok \"Created Config and Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/whisparr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/Whisparr/Whisparr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3 libicu76\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_from_url \"https://whisparr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm64\" /opt/Whisparr\n\nmsg_info \"Configuring Whisparr\"\nmkdir -p /var/lib/whisparr/\nchmod 775 /var/lib/whisparr/\nchmod 775 /opt/Whisparr\nmsg_ok \"Configured Whisparr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/whisparr.service\n[Unit]\nDescription=whisparr Daemon\nAfter=syslog.target network.target\n\n[Service]\nUMask=0002\nType=simple\nExecStart=/opt/Whisparr/Whisparr -nobrowser -data=/var/lib/whisparr/\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now whisparr\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/whodb-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://whodb.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"whodb\" \"clidey/whodb\" \"singlefile\" \"latest\" \"/opt/whodb\" \"whodb-*-linux-amd64\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/whodb.service\n[Unit]\nDescription=WhoDB Database Management\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/whodb\nExecStart=/opt/whodb/whodb\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now whodb\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wikijs-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://js.wiki/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn,node-gyp\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"wiki\" PG_DB_USER=\"wikijs_user\" PG_DB_EXTENSIONS=\"pg_trgm\" setup_postgresql_db\nfetch_and_deploy_gh_release \"wikijs\" \"requarks/wiki\" \"prebuild\" \"latest\" \"/opt/wikijs\" \"wiki-js.tar.gz\"\n\nmsg_info \"Configuring Wiki.js\"\nmv /opt/wikijs/config.sample.yml /opt/wikijs/config.yml\nsed -i -E 's|^( *user: ).*|\\1'\"$PG_DB_USER\"'|' /opt/wikijs/config.yml\nsed -i -E 's|^( *pass: ).*|\\1'\"$PG_DB_PASS\"'|' /opt/wikijs/config.yml\nmsg_ok \"Configured Wiki.js\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wikijs.service\n[Unit]\nDescription=Wiki.js\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/node server\nRestart=always\nUser=root\nEnvironment=NODE_ENV=production\nWorkingDirectory=/opt/wikijs\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now wikijs\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wireguard-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.wireguard.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y git\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing WireGuard\"\n$STD apt install -y wireguard wireguard-tools net-tools iptables\nDEBIAN_FRONTEND=noninteractive apt -o Dpkg::Options::=\"--force-confnew\" install -y iptables-persistent &>/dev/null\n$STD netfilter-persistent reload\nmsg_ok \"Installed WireGuard\"\n\nread -r -p \"${TAB3}Would you like to add WGDashboard? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  git clone -q https://github.com/WGDashboard/WGDashboard.git /etc/wgdashboard\n\n  msg_info \"Installing WGDashboard\"\n  cd /etc/wgdashboard/src\n  chmod u+x wgd.sh\n  $STD ./wgd.sh install\n  . /etc/os-release\n  if [ \"$VERSION_CODENAME\" = \"trixie\" ]; then\n    echo \"net.ipv4.ip_forward=1\" >>/etc/sysctl.d/sysctl.conf\n    $STD sysctl -p /etc/sysctl.d/sysctl.conf\n  else\n    echo \"net.ipv4.ip_forward=1\" >>/etc/sysctl.conf\n    $STD sysctl -p /etc/sysctl.conf\n  fi\n  msg_ok \"Installed WGDashboard\"\n\n  msg_info \"Create Example Config for WGDashboard\"\n  private_key=$(wg genkey)\n  cat <<EOF >/etc/wireguard/wg0.conf\n[Interface]\nPrivateKey = ${private_key}\nAddress = 10.0.0.1/24\nSaveConfig = true\nPostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;\nPostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;\nListenPort = 51820\nEOF\n  msg_ok \"Created Example Config for WGDashboard\"\n\n  msg_info \"Creating Service\"\n  cat <<EOF >/etc/systemd/system/wg-dashboard.service\n[Unit]\nAfter=syslog.target network-online.target\nWants=wg-quick.target\nConditionPathIsDirectory=/etc/wireguard\n\n[Service]\nType=forking\nPIDFile=/etc/wgdashboard/src/gunicorn.pid\nWorkingDirectory=/etc/wgdashboard/src\nExecStart=/etc/wgdashboard/src/wgd.sh start\nExecStop=/etc/wgdashboard/src/wgd.sh stop\nExecReload=/etc/wgdashboard/src/wgd.sh restart\nTimeoutSec=120\nPrivateTmp=yes\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now wg-dashboard\n  msg_ok \"Created Service\"\nfi\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wishlist-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dunky13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/cmintey/wishlist\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  openssl \\\n  caddy\nmsg_ok \"Installed dependencies\"\n\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm@10\" setup_nodejs\nfetch_and_deploy_gh_release \"wishlist\" \"cmintey/wishlist\" \"tarball\"\nLATEST_APP_VERSION=$(get_latest_github_release \"cmintey/wishlist\")\n\nmsg_info \"Installing Wishlist\"\ncd /opt/wishlist\ncp .env.example .env\nsed -i \"s|^ORIGIN=.*|ORIGIN=http://${LOCAL_IP}:3280|\" /opt/wishlist/.env\necho \"\" >>/opt/wishlist/.env\necho \"NODE_ENV=production\" >>/opt/wishlist/.env\n$STD pnpm install --frozen-lockfile\n$STD pnpm svelte-kit sync\n$STD pnpm prisma generate\nsed -i 's|/usr/src/app/|/opt/wishlist/|g' $(grep -rl '/usr/src/app/' /opt/wishlist)\nexport VERSION=\"v${LATEST_APP_VERSION}\"\nexport SHA=\"v${LATEST_APP_VERSION}\"\n$STD pnpm run build\n$STD pnpm prune --prod\nchmod +x /opt/wishlist/entrypoint.sh\nmkdir -p /opt/wishlist/uploads\nmkdir -p /opt/wishlist/data\nmsg_ok \"Installed Wishlist\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wishlist.service\n[Unit]\nDescription=Wishlist Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/wishlist\nEnvironmentFile=/opt/wishlist/.env\nExecStart=/usr/bin/env sh -c './entrypoint.sh'\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now wishlist\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wizarr-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/wizarrrr/wizarr\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y sqlite3\nmsg_ok \"Installed Dependencies\"\n\nsetup_uv\nNODE_VERSION=\"22\" setup_nodejs\n\nfetch_and_deploy_gh_release \"wizarr\" \"wizarrrr/wizarr\" \"tarball\"\n\nmsg_info \"Configure Wizarr\"\ncd /opt/wizarr\n$STD /usr/local/bin/uv sync --frozen\n$STD /usr/local/bin/uv run --frozen pybabel compile -d app/translations\n$STD npm --prefix app/static install\n$STD npm --prefix app/static run build:css\nmkdir -p ./.cache\ncat <<EOF >/opt/wizarr/.env\nFLASK_ENV=production\nGUNICORN_WORKERS=4\nAPP_URL=http://${LOCAL_IP}\nDISABLE_BUILTIN_AUTH=false\nLOG_LEVEL=INFO\nAPP_VERSION=v$(get_latest_github_release \"wizarrrr/wizarr\")\nEOF\n\ncat <<EOF >/opt/wizarr/start.sh\n#!/usr/bin/env bash\n\nuv run --frozen gunicorn \\\n    --config gunicorn.conf.py \\\n    --preload \\\n    --bind 0.0.0.0:5690 \\\n    --umask 007 \\\n    run:app\nEOF\nchmod u+x /opt/wizarr/start.sh\nmsg_ok \"Configure Wizarr\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/wizarr.service\n[Unit]\nDescription=Wizarr Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/wizarr\nEnvironmentFile=/opt/wizarr/.env\nExecStart=/opt/wizarr/start.sh\nRestart=on-abnormal\n\n[Install]\nWantedBy=multi-user.target\nEOF\nmsg_ok \"Created Service\"\n\nmsg_info \"Running DB upgrade\"\nexport FLASK_SKIP_SCHEDULER=true\n$STD /usr/local/bin/uv run --frozen flask db upgrade\nmsg_ok \"DB upgrade complete\"\n\nsystemctl enable -q --now wizarr\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/wordpress-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://wordpress.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_APACHE=\"YES\" PHP_MODULE=\"snmp,imap\" setup_php\nsetup_mariadb\nMARIADB_DB_NAME=\"wordpress_db\" MARIADB_DB_USER=\"wordpress\" setup_mariadb_db\nfetch_and_deploy_from_url \"https://wordpress.org/latest.zip\" /var/www/html/wordpress\n\nmsg_info \"Installing Wordpress (Patience)\"\nchown -R www-data:www-data /var/www/html/wordpress\ncd /var/www/html/wordpress\nfind . -type d -exec chmod 755 {} \\;\nfind . -type f -exec chmod 644 {} \\;\nmv wp-config-sample.php wp-config.php\nsed -i -e \"s|^define( 'DB_NAME', '.*' );|define( 'DB_NAME', '$MARIADB_DB_NAME' );|\" \\\n  -e \"s|^define( 'DB_USER', '.*' );|define( 'DB_USER', '$MARIADB_DB_USER' );|\" \\\n  -e \"s|^define( 'DB_PASSWORD', '.*' );|define( 'DB_PASSWORD', '$MARIADB_DB_PASS' );|\" \\\n  /var/www/html/wordpress/wp-config.php\nmsg_ok \"Installed Wordpress\"\n\nmsg_info \"Setup Services\"\ncat <<EOF >/etc/apache2/sites-available/wordpress.conf\n<VirtualHost *:80>\n    ServerName yourdomain.com\n    DocumentRoot /var/www/html/wordpress\n\n    <Directory /var/www/html/wordpress>\n        AllowOverride All\n    </Directory>\n\n    ErrorLog \\${APACHE_LOG_DIR}/error.log\n    CustomLog \\${APACHE_LOG_DIR}/access.log combined\n\n</VirtualHost>\nEOF\n$STD a2ensite wordpress.conf\n$STD a2dissite 000-default.conf\nsystemctl reload apache2\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/writefreely-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: StellaeAlis\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/writefreely/writefreely\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y crudini\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"writefreely\" MARIADB_DB_USER=\"writefreely\" setup_mariadb_db\nfetch_and_deploy_gh_release \"writefreely\" \"writefreely/writefreely\" \"prebuild\" \"latest\" \"/opt/writefreely\" \"writefreely_*_linux_arm64.tar.gz\"\n\nmsg_info \"Setting up WriteFreely\"\ncd /opt/writefreely\n$STD ./writefreely config generate\n$STD ./writefreely keys generate\nmsg_ok \"Setup WriteFreely\"\n\nmsg_info \"Configuring WriteFreely\"\n$STD crudini --set config.ini server port 80\n$STD crudini --set config.ini server bind $LOCAL_IP\n$STD crudini --set config.ini database username $MARIADB_DB_USER\n$STD crudini --set config.ini database password $MARIADB_DB_PASS\n$STD crudini --set config.ini database database $MARIADB_DB_NAME\n$STD crudini --set config.ini app host http://$LOCAL_IP:80\n$STD ./writefreely db init\nln -s /opt/writefreely/writefreely /usr/local/bin/writefreely\nmsg_ok \"Configured WriteFreely\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/writefreely.service\n[Unit]\nDescription=WriteFreely Service\nAfter=syslog.target network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/writefreely\nExecStart=/opt/writefreely/writefreely\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now writefreely\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/yamtrack-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/FuzzyGrim/Yamtrack\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  nginx \\\n  redis-server\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"yamtrack\" PG_DB_USER=\"yamtrack\" setup_postgresql_db\nPYTHON_VERSION=\"3.12\" setup_uv\n\nfetch_and_deploy_gh_release \"yamtrack\" \"FuzzyGrim/Yamtrack\" \"tarball\"\n\nmsg_info \"Installing Python Dependencies\"\ncd /opt/yamtrack\n$STD uv venv .venv\n$STD uv pip install --no-cache-dir -r requirements.txt\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Configuring Yamtrack\"\nSECRET=$(openssl rand -hex 32)\ncat <<EOF >/opt/yamtrack/src/.env\nSECRET=${SECRET}\nDB_HOST=localhost\nDB_NAME=${PG_DB_NAME}\nDB_USER=${PG_DB_USER}\nDB_PASSWORD=${PG_DB_PASS}\nDB_PORT=5432\nREDIS_URL=redis://localhost:6379\nURLS=http://${LOCAL_IP}:8000\nEOF\n\ncd /opt/yamtrack/src\n$STD /opt/yamtrack/.venv/bin/python manage.py migrate\n$STD /opt/yamtrack/.venv/bin/python manage.py collectstatic --noinput\nmsg_ok \"Configured Yamtrack\"\n\nmsg_info \"Configuring Nginx\"\nrm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default\ncp /opt/yamtrack/nginx.conf /etc/nginx/nginx.conf\nsed -i 's|user abc;|user www-data;|' /etc/nginx/nginx.conf\nsed -i 's|pid /tmp/nginx.pid;|pid /run/nginx.pid;|' /etc/nginx/nginx.conf\nsed -i 's|/yamtrack/staticfiles/|/opt/yamtrack/src/staticfiles/|' /etc/nginx/nginx.conf\nsed -i 's|error_log /dev/stderr|error_log /var/log/nginx/error.log|' /etc/nginx/nginx.conf\nsed -i 's|access_log /dev/stdout|access_log /var/log/nginx/access.log|' /etc/nginx/nginx.conf\n$STD nginx -t\nsystemctl enable -q nginx\n$STD systemctl restart nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/yamtrack.service\n[Unit]\nDescription=Yamtrack Gunicorn\nAfter=network.target postgresql.service redis-server.service\nRequires=postgresql.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/yamtrack/src\nExecStart=/opt/yamtrack/.venv/bin/gunicorn config.wsgi:application -b 127.0.0.1:8001 -w 2 --timeout 120\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/yamtrack-celery.service\n[Unit]\nDescription=Yamtrack Celery Worker\nAfter=network.target postgresql.service redis-server.service yamtrack.service\nRequires=postgresql.service redis-server.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/yamtrack/src\nExecStart=/opt/yamtrack/.venv/bin/celery -A config worker --beat --scheduler django --loglevel INFO\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable -q --now redis-server yamtrack yamtrack-celery\nmsg_ok \"Created Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/yourls-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://yourls.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y nginx\nmsg_ok \"Installed Dependencies\"\n\nsetup_mariadb\nMARIADB_DB_NAME=\"yourls\" MARIADB_DB_USER=\"yourls\" setup_mariadb_db\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULE=\"mysql,mbstring,gd,xml,curl\" setup_php\n\nfetch_and_deploy_gh_release \"yourls\" \"YOURLS/YOURLS\" \"tarball\"\n\nmsg_info \"Configuring YOURLS\"\nCOOKIEKEY=$(openssl rand -hex 24)\nYOURLS_PASS=$(openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | cut -c1-16)\ncat <<EOF >/opt/yourls/user/config.php\n<?php\ndefine( 'YOURLS_DB_USER', '${MARIADB_DB_USER}' );\ndefine( 'YOURLS_DB_PASS', '${MARIADB_DB_PASS}' );\ndefine( 'YOURLS_DB_NAME', '${MARIADB_DB_NAME}' );\ndefine( 'YOURLS_DB_HOST', 'localhost' );\ndefine( 'YOURLS_DB_PREFIX', 'yourls_' );\ndefine( 'YOURLS_SITE', 'http://${LOCAL_IP}' );\ndefine( 'YOURLS_LANG', '' );\ndefine( 'YOURLS_UNIQUE_URLS', true );\ndefine( 'YOURLS_PRIVATE', true );\ndefine( 'YOURLS_COOKIEKEY', '${COOKIEKEY}' );\n\\$yourls_user_passwords = [\n    'admin' => '${YOURLS_PASS}',\n];\ndefine( 'YOURLS_URL_CONVERT', 36 );\ndefine( 'YOURLS_DEBUG', false );\nEOF\nchown -R www-data:www-data /opt/yourls\nmsg_ok \"Configured YOURLS\"\n\nmsg_info \"Configuring Nginx\"\ncat <<EOF >/etc/nginx/sites-available/yourls\nserver {\n    listen 80 default_server;\n    server_name _;\n    root /opt/yourls;\n    index index.php;\n\n    location / {\n        try_files \\$uri \\$uri/ /yourls-loader.php\\$is_args\\$args;\n    }\n\n    location ~ \\.php\\$ {\n        try_files \\$uri =404;\n        fastcgi_split_path_info ^(.+\\.php)(/.+)\\$;\n        fastcgi_pass unix:/run/php/php8.3-fpm.sock;\n        fastcgi_index index.php;\n        include fastcgi_params;\n        fastcgi_param SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name;\n        fastcgi_param PATH_INFO \\$fastcgi_path_info;\n    }\n\n    location ~* \\.(jpg|jpeg|gif|css|png|js|ico|woff|woff2)\\$ {\n        access_log off;\n        expires max;\n    }\n\n    location ~ /\\.ht {\n        deny all;\n    }\n}\nEOF\nln -sf /etc/nginx/sites-available/yourls /etc/nginx/sites-enabled/yourls\nrm -f /etc/nginx/sites-enabled/default\n$STD nginx -t\nsystemctl enable -q --now nginx\nsystemctl reload nginx\nmsg_ok \"Configured Nginx\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/yt-dlp-webui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/marcopiovanello/yt-dlp-web-ui\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"yt-dlp-webui\" \"marcopiovanello/yt-dlp-web-ui\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"yt-dlp-webui_linux-arm64\"\nfetch_and_deploy_gh_release \"yt-dlp\" \"yt-dlp/yt-dlp\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"yt-dlp\"\n\nmsg_info \"Setting up YT-DLP-WEBUI\"\nmkdir -p /opt/yt-dlp-webui\nmkdir /downloads\nRPC_PASSWORD=$(openssl rand -base64 16)\n{\n  echo \"yt-dlp-webui-Credentials\"\n  echo \"Username: admin\"\n  echo \"Password: ${RPC_PASSWORD}\"\n} >>~/yt-dlp-webui.creds\n\ncat <<EOF >/opt/yt-dlp-webui/config.conf\n# Host where server will listen at (default: \"0.0.0.0\")\n#host: 0.0.0.0\n\n# Port where server will listen at (default: 3033)\nport: 3033\n\n# Directory where downloaded files will be stored (default: \".\")\ndownloadPath: /downloads\n\n# [optional] Enable RPC authentication (requires username and password)\nrequire_auth: true\nusername: admin\npassword: ${RPC_PASSWORD}\n\n# [optional] The download queue size (default: logical cpu cores)\nqueue_size: 4 # min. 2\n\n# [optional] Full path to the yt-dlp (default: \"yt-dlp\")\ndownloaderPath: /usr/local/bin/yt-dlp\n\n# [optional] Enable file based logging with rotation (default: false)\n#enable_file_logging: false\n\n# [optional] Directory where the log file will be stored (default: \".\")\n#log_path: .\n\n# [optional] Directory where the session database file will be stored (default: \".\")\n#session_file_path: .\n\n# [optional] Path where the sqlite database will be created/opened (default: \"./local.db\")\n#local_database_path\n\n# [optional] Path where a custom frontend will be loaded (instead of the embedded one)\n#frontend_path: ./web/solid-frontend\nEOF\nmsg_ok \"Set up YT-DLP-WEBUI\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/yt-dlp-webui.service\n[Unit]\nDescription=yt-dlp-webui service file\nAfter=network.target\n\n[Service]\nExecStart=/usr/local/bin/yt-dlp-webui --conf /opt/yt-dlp-webui/config.conf\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now yt-dlp-webui\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/yubal-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Crazywolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/guillevc/yubal\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  build-essential \\\n  libssl-dev \\\n  libffi-dev \\\n  python3-dev \\\n  ffmpeg \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Bun\"\nexport BUN_INSTALL=/opt/bun\ncurl -fsSL https://bun.sh/install | $STD bash\nln -sf /opt/bun/bin/bun /usr/local/bin/bun\nln -sf /opt/bun/bin/bunx /usr/local/bin/bunx\nmsg_ok \"Installed Bun\"\n\nUV_VERSION=\"0.7.19\" PYTHON_VERSION=\"3.12\" setup_uv\n\nmsg_info \"Installing Deno\"\n$STD sh -c \"curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh -s -- -y\"\nmsg_ok \"Installed Deno\"\n\nmsg_info \"Creating directories\"\nmkdir -p /opt/yubal \\\n  /opt/yubal_data \\\n  /opt/yubal_config\nmsg_ok \"Created directories\"\n\nfetch_and_deploy_gh_release \"yubal\" \"guillevc/yubal\" \"tarball\" \"latest\" \"/opt/yubal\"\n\nmsg_info \"Building Frontend\"\ncd /opt/yubal/web\n$STD bun install --frozen-lockfile\nVERSION=$(get_latest_github_release \"guillevc/yubal\")\nVITE_VERSION=$VERSION VITE_COMMIT_SHA=$VERSION VITE_IS_RELEASE=true $STD bun run build\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Installing Python Dependencies\"\ncd /opt/yubal\nexport UV_CONCURRENT_DOWNLOADS=1\n$STD uv sync --package yubal-api --no-dev --frozen\nmsg_ok \"Installed Python Dependencies\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/opt/yubal.env\nYUBAL_HOST=0.0.0.0\nYUBAL_PORT=8000\nYUBAL_DATA=/opt/yubal_data\nYUBAL_CONFIG=/opt/yubal_config\nYUBAL_ROOT=/opt/yubal\nPYTHONUNBUFFERED=1\nEOF\ncat <<EOF >/etc/systemd/system/yubal.service\n[Unit]\nDescription=Yubal\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/yubal\nEnvironmentFile=/opt/yubal.env\nEnvironment=\"PATH=/opt/yubal/.venv/bin:/usr/local/bin:/usr/bin:/bin\"\nExecStart=/opt/yubal/.venv/bin/python -m yubal_api\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now yubal\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/yunohost-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://yunohost.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  apt-transport-https \\\n  lsb-release \\\n  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://yunohost.org/).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://install.yunohost.org\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! \"$CONFIRM\" =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nmsg_info \"Installing YunoHost (Patience)\"\ntouch /etc/.pve-ignore.resolv.conf\ncurl -fsSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg\n$STD bash <(curl -fsSL https://install.yunohost.org) -a\nmsg_ok \"Installed YunoHost\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zabbix-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zabbix.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"zabbixdb\" PG_DB_USER=\"zabbix\" setup_postgresql_db\n\nread -rp \"Choose Zabbix version [1] 7.0 LTS  [2] 7.4 (Latest Stable)  [3] Latest available (default: 2): \" ZABBIX_CHOICE\nZABBIX_CHOICE=${ZABBIX_CHOICE:-2}\ncase \"$ZABBIX_CHOICE\" in\n1) ZABBIX_VERSION=\"7.0\" ;;\n2) ZABBIX_VERSION=\"7.4\" ;;\n3) ZABBIX_VERSION=$(curl -fsSL https://repo.zabbix.com/zabbix/ |\n  grep -oP '(?<=href=\")[0-9]+\\.[0-9]+(?=/\")' | sort -V | tail -n1) ;;\n*)\n  ZABBIX_VERSION=\"7.4\"\n  echo \"Invalid choice. Defaulting to 7.4.\"\n  ;;\nesac\n\nmsg_info \"Installing Zabbix $ZABBIX_VERSION\"\ncd /tmp\n\nif [[ \"$ZABBIX_VERSION\" == \"7.0\" ]]; then\n  ZABBIX_DEB_URL=\"https://repo.zabbix.com/zabbix/${ZABBIX_VERSION}/debian/pool/main/z/zabbix-release/zabbix-release_latest_${ZABBIX_VERSION}+debian13_all.deb\"\n  ZABBIX_DEB_FILE=\"zabbix-release_latest_${ZABBIX_VERSION}+debian13_all.deb\"\nelse\n  ZABBIX_DEB_URL=\"https://repo.zabbix.com/zabbix/${ZABBIX_VERSION}/release/debian/pool/main/z/zabbix-release/zabbix-release_latest+debian13_all.deb\"\n  ZABBIX_DEB_FILE=\"zabbix-release_latest+debian13_all.deb\"\nfi\n\ncurl -fsSL \"$ZABBIX_DEB_URL\" -o /tmp/\"$ZABBIX_DEB_FILE\"\n$STD dpkg -i /tmp/\"$ZABBIX_DEB_FILE\"\n$STD apt update\n$STD apt install -y zabbix-server-pgsql zabbix-frontend-php php8.4-pgsql zabbix-apache-conf zabbix-sql-scripts\n\nif [[ \"$ZABBIX_VERSION\" == \"7.0\" ]]; then\n  ZABBIX_SQL=\"/usr/share/zabbix-sql-scripts/postgresql/server.sql.gz\"\nelse\n  ZABBIX_SQL=\"/usr/share/zabbix/sql-scripts/postgresql/server.sql.gz\"\nfi\n\nzcat \"$ZABBIX_SQL\" | sudo -u \"$PG_DB_USER\" psql \"$PG_DB_NAME\" &>/dev/null\nsed -i \"s/^DBName=.*/DBName=$PG_DB_NAME/\" /etc/zabbix/zabbix_server.conf\nsed -i \"s/^DBUser=.*/DBUser=$PG_DB_USER/\" /etc/zabbix/zabbix_server.conf\nsed -i \"s/^# DBPassword=.*/DBPassword=$PG_DB_PASS/\" /etc/zabbix/zabbix_server.conf\nmsg_ok \"Installed Zabbix $ZABBIX_VERSION\"\n\nwhile true; do\n  read -rp \"Which agent do you want to install? [1=agent (classic), 2=agent2 (modern), default=1]: \" AGENT_CHOICE\n  case \"$AGENT_CHOICE\" in\n  2)\n    AGENT_PKG=\"zabbix-agent2\"\n    break\n    ;;\n  \"\" | 1)\n    AGENT_PKG=\"zabbix-agent\"\n    break\n    ;;\n  *)\n    echo \"Invalid choice. Please enter 1 or 2.\"\n    ;;\n  esac\ndone\nmsg_ok \"Selected $AGENT_PKG\"\n\nif [ \"$AGENT_PKG\" = \"zabbix-agent2\" ]; then\n  echo \"Choose plugins for Zabbix Agent2:\"\n  echo \"1) PostgreSQL only (default, recommended)\"\n  echo \"2) All plugins (may cause issues)\"\n  read -rp \"Choose option [1-2]: \" PLUGIN_CHOICE\n\n  case \"$PLUGIN_CHOICE\" in\n  2)\n    $STD apt install -y zabbix-agent2 zabbix-agent2-plugin-*\n    ;;\n  *)\n    $STD apt install -y zabbix-agent2 zabbix-agent2-plugin-postgresql\n    ;;\n  esac\n\n  if [ -f /etc/zabbix/zabbix_agent2.d/plugins.d/nvidia.conf ]; then\n    sed -i 's|^Plugins.NVIDIA.System.Path=.*|# Plugins.NVIDIA.System.Path=/usr/libexec/zabbix/zabbix-agent2-plugin-nvidia-gpu|' \\\n      /etc/zabbix/zabbix_agent2.d/plugins.d/nvidia.conf\n  fi\nelse\n  $STD apt install -y zabbix-agent\nfi\n\nmsg_info \"Configuring Fping\"\nif command -v fping >/dev/null 2>&1; then\n  FPING_PATH=$(command -v fping)\n  sed -i \"s|^#\\?FpingLocation=.*|FpingLocation=$FPING_PATH|\" /etc/zabbix/zabbix_server.conf\nfi\n\nif command -v fping6 >/dev/null 2>&1; then\n  FPING6_PATH=$(command -v fping6)\n  sed -i \"s|^#\\?Fping6Location=.*|Fping6Location=$FPING6_PATH|\" /etc/zabbix/zabbix_server.conf\nfi\nmsg_ok \"Configured Fping\"\n\nmsg_info \"Starting Services\"\nif [ \"$AGENT_PKG\" = \"zabbix-agent2\" ]; then\n  AGENT_SERVICE=\"zabbix-agent2\"\nelse\n  AGENT_SERVICE=\"zabbix-agent\"\nfi\n\nsystemctl restart zabbix-server apache2\nsystemctl enable -q --now zabbix-server $AGENT_SERVICE apache2\nrm -rf /tmp/zabbix-release_*.deb\nmsg_ok \"Started Services\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zammad-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zammad.com\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  nginx \\\n  apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Setting up Elasticsearch\"\nsetup_deb822_repo \\\n  \"elasticsearch\" \\\n  \"https://artifacts.elastic.co/GPG-KEY-elasticsearch\" \\\n  \"https://artifacts.elastic.co/packages/7.x/apt\" \\\n  \"stable\" \\\n  \"main\"\n$STD apt install -y elasticsearch\nsed -i 's/^#\\{0,2\\} *-Xms[0-9]*g.*/-Xms2g/' /etc/elasticsearch/jvm.options\nsed -i 's/^#\\{0,2\\} *-Xmx[0-9]*g.*/-Xmx2g/' /etc/elasticsearch/jvm.options\ncat <<EOF >>/etc/elasticsearch/elasticsearch.yml\ndiscovery.type: single-node\nxpack.security.enabled: false\nbootstrap.memory_lock: false\nEOF\n$STD /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment -b\nsystemctl daemon-reload\nsystemctl enable -q elasticsearch\nsystemctl restart -q elasticsearch\nfor i in $(seq 1 30); do\n  if curl -s http://localhost:9200 >/dev/null 2>&1; then\n    break\n  fi\n  sleep 2\ndone\nmsg_ok \"Setup Elasticsearch\"\n\nmsg_info \"Installing Zammad\"\nsetup_deb822_repo \\\n  \"zammad\" \\\n  \"https://dl.packager.io/srv/zammad/zammad/key\" \\\n  \"https://dl.packager.io/srv/deb/zammad/zammad/stable/debian\" \\\n  \"$(get_os_info version_id)\" \\\n  \"main\"\n$STD apt install -y zammad\n$STD zammad run rails r \"Setting.set('es_url', 'http://localhost:9200')\"\n$STD zammad run rake zammad:searchindex:rebuild\nmsg_ok \"Installed Zammad\"\n\nmsg_info \"Setup Services\"\ncp /opt/zammad/contrib/nginx/zammad.conf /etc/nginx/sites-available/zammad.conf\nsed -i \"s/server_name localhost;/server_name $LOCAL_IP;/g\" /etc/nginx/sites-available/zammad.conf\nln -sf /etc/nginx/sites-available/zammad.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD systemctl reload nginx\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zerobyte-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: community-scripts\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/nicotsx/zerobyte\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\necho \"davfs2 davfs2/suid_file boolean false\" | debconf-set-selections\n$STD apt-get install -y \\\n  bzip2 \\\n  fuse3 \\\n  git \\\n  sshfs \\\n  davfs2 \\\n  openssh-client\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"restic\" \"restic/restic\" \"singlefile\" \"latest\" \"/usr/local/bin\" \"restic_*_linux_amd64.bz2\"\nmv /usr/local/bin/restic /usr/local/bin/restic.bz2\nbzip2 -d /usr/local/bin/restic.bz2\nchmod +x /usr/local/bin/restic\n\nfetch_and_deploy_gh_release \"rclone\" \"rclone/rclone\" \"prebuild\" \"latest\" \"/opt/rclone\" \"rclone-*-linux-amd64.zip\"\nln -sf /opt/rclone/rclone /usr/local/bin/rclone\n\nfetch_and_deploy_gh_release \"shoutrrr\" \"nicholas-fedor/shoutrrr\" \"prebuild\" \"latest\" \"/opt/shoutrrr\" \"shoutrrr_linux_amd64_*.tar.gz\"\nln -sf /opt/shoutrrr/shoutrrr /usr/local/bin/shoutrrr\n\nmsg_info \"Installing Bun\"\nexport BUN_INSTALL=\"/root/.bun\"\ncurl -fsSL https://bun.sh/install | $STD bash\nln -sf /root/.bun/bin/bun /usr/local/bin/bun\nln -sf /root/.bun/bin/bunx /usr/local/bin/bunx\nmsg_ok \"Installed Bun\"\n\nNODE_VERSION=\"24\" setup_nodejs\nfetch_and_deploy_gh_release \"zerobyte\" \"nicotsx/zerobyte\" \"tarball\"\n\nmsg_info \"Building Zerobyte (Patience)\"\ncd /opt/zerobyte\nexport VITE_RESTIC_VERSION=$(cat ~/.restic)\nexport VITE_RCLONE_VERSION=$(cat ~/.rclone)\nexport VITE_SHOUTRRR_VERSION=$(cat ~/.shoutrrr)\nexport NODE_OPTIONS=\"--max-old-space-size=3072\"\n$STD bun install\n$STD node ./node_modules/vite/bin/vite.js build\nmsg_ok \"Built Zerobyte\"\n\nmsg_info \"Configuring Zerobyte\"\nmkdir -p /var/lib/zerobyte/{data,restic/cache,repositories,volumes}\nAPP_SECRET=$(openssl rand -hex 32)\ncat <<EOF >/opt/zerobyte/.env\nBASE_URL=http://${LOCAL_IP}:4096\nAPP_SECRET=${APP_SECRET}\nPORT=4096\nZEROBYTE_DATABASE_URL=/var/lib/zerobyte/data/zerobyte.db\nRESTIC_CACHE_DIR=/var/lib/zerobyte/restic/cache\nZEROBYTE_REPOSITORIES_DIR=/var/lib/zerobyte/repositories\nZEROBYTE_VOLUMES_DIR=/var/lib/zerobyte/volumes\nMIGRATIONS_PATH=/opt/zerobyte/app/drizzle\nNODE_ENV=production\nEOF\nmsg_ok \"Configured Zerobyte\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/zerobyte.service\n[Unit]\nDescription=Zerobyte Backup Automation\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/zerobyte\nEnvironmentFile=/opt/zerobyte/.env\nExecStart=/usr/local/bin/bun .output/server/index.mjs\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zerobyte\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zerotier-one-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zerotier.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_warn \"WARNING: This script will run an external installer from a third-party source (https://install.zerotier.com).\"\nmsg_warn \"The following code is NOT maintained or audited by our repository.\"\nmsg_warn \"If you have any doubts or concerns, please review the installer code before proceeding:\"\nmsg_custom \"${TAB3}${GATEWAY}${BGN}${CL}\" \"\\e[1;34m\" \"→  https://install.zerotier.com\"\necho\nread -r -p \"${TAB3}Do you want to continue? [y/N]: \" CONFIRM\nif [[ ! $CONFIRM =~ ^([yY][eE][sS]|[yY])$ ]]; then\n  msg_error \"Aborted by user. No changes have been made.\"\n  exit 10\nfi\n\nmsg_info \"Setting up Zerotier-One\"\ncurl -fsSL https://raw.githubusercontent.com/zerotier/ZeroTierOne/main/doc/contact%40zerotier.com.gpg | gpg --import >/dev/null 2>&1\ncurl -fsSL https://install.zerotier.com -o /tmp/zerotier-install.sh\nif gpg --verify /tmp/zerotier-install.sh >/dev/null 2>&1; then\n  $STD bash /tmp/zerotier-install.sh\nelse\n  msg_warn \"Could not verify signature of Zerotier-One install script. Exiting...\"\n  exit 250\nfi\nmsg_ok \"Setup Zerotier-One\"\n\nmsg_info \"Setting up UI\"\ncurl -O https://s3-us-west-1.amazonaws.com/key-networks/deb/ztncui/1/x86_64/ztncui_0.8.14_arm64.deb\ndpkg -i ztncui_0.8.14_arm64.deb\nsh -c \"echo ZT_TOKEN=$(cat /var/lib/zerotier-one/authtoken.secret) > /opt/key-networks/ztncui/.env\"\necho HTTPS_PORT=3443 >>/opt/key-networks/ztncui/.env\necho NODE_ENV=production >>/opt/key-networks/ztncui/.env\nchmod 400 /opt/key-networks/ztncui/.env\nchown ztncui:ztncui /opt/key-networks/ztncui/.env\nsystemctl restart ztncui\nmsg_ok \"Setup UI.\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zigbee2mqtt-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.zigbee2mqtt.io/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  git \\\n  build-essential\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" NODE_MODULE=\"pnpm@$(curl -fsSL https://raw.githubusercontent.com/Koenkk/zigbee2mqtt/master/package.json | jq -r '.packageManager | split(\"@\")[1]')\" setup_nodejs\nfetch_and_deploy_gh_release \"Zigbee2MQTT\" \"Koenkk/zigbee2mqtt\" \"tarball\" \"latest\" \"/opt/zigbee2mqtt\"\n\nmsg_info \"Setting up Zigbee2MQTT\"\nmv /opt/zigbee2mqtt/data/configuration.example.yaml /opt/zigbee2mqtt/data/configuration.yaml\ncd /opt/zigbee2mqtt\necho \"packageImportMethod: hardlink\" >>./pnpm-workspace.yaml\n$STD pnpm install --no-frozen-lockfile\n$STD pnpm build\nmsg_ok \"Setup Zigbee2MQTT\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/zigbee2mqtt.service\n[Unit]\nDescription=zigbee2mqtt\nAfter=network.target\n\n[Service]\nEnvironment=NODE_ENV=production\nExecStart=/usr/bin/pnpm start\nWorkingDirectory=/opt/zigbee2mqtt\nStandardOutput=inherit\nStandardError=inherit\nRestart=always\nUser=root\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zigbee2mqtt\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zipline-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/diced/zipline\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"ziplinedb\" PG_DB_USER=\"zipline\" setup_postgresql_db\nfetch_and_deploy_gh_release \"zipline\" \"diced/zipline\" \"tarball\"\nSECRET_KEY=\"$(openssl rand -base64 42 | tr -dc 'a-zA-Z0-9')\"\necho \"Zipline Secret Key: ${SECRET_KEY}\" >>~/zipline.creds\n\nmsg_info \"Installing Zipline (Patience)\"\ncd /opt/zipline\ncat <<EOF >/opt/zipline/.env\nDATABASE_URL=postgres://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\nCORE_SECRET=$SECRET_KEY\nCORE_HOSTNAME=0.0.0.0\nCORE_PORT=3000\nCORE_RETURN_HTTPS=false\nDATASOURCE_TYPE=local\nDATASOURCE_LOCAL_DIRECTORY=/opt/zipline-uploads\nEOF\nmkdir -p /opt/zipline-uploads\n$STD pnpm install\n$STD pnpm build\nmsg_ok \"Installed Zipline\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/zipline.service\n[Unit]\nDescription=Zipline Service\nAfter=network.target\n\n[Service]\nWorkingDirectory=/opt/zipline\nExecStart=/usr/bin/pnpm start\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zipline\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zitadel-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: dave-yap\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zitadel.com/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies (Patience)\"\n$STD apt install -y ca-certificates lsof\nmsg_ok \"Installed Dependecies\"\n\nPG_VERSION=\"17\" setup_postgresql\n\nmsg_info \"Installing Postgresql\"\nDB_NAME=\"zitadel\"\nDB_USER=\"zitadel\"\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\nDB_ADMIN_USER=\"root\"\nDB_ADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\nsystemctl start postgresql\n$STD sudo -u postgres psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';\"\n$STD sudo -u postgres psql -c \"CREATE USER $DB_ADMIN_USER WITH PASSWORD '$DB_ADMIN_PASS' SUPERUSER;\"\n$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME OWNER $DB_ADMIN_USER;\"\n{\n  echo \"Application Credentials\"\n  echo \"DB_NAME: $DB_NAME\"\n  echo \"DB_USER: $DB_USER\"\n  echo \"DB_PASS: $DB_PASS\"\n  echo \"DB_ADMIN_USER: $DB_ADMIN_USER\"\n  echo \"DB_ADMIN_PASS: $DB_ADMIN_PASS\"\n} >>~/zitadel.creds\nmsg_ok \"Installed PostgreSQL\"\n\nfetch_and_deploy_gh_release \"zitadel\" \"zitadel/zitadel\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"zitadel-linux-arm64.tar.gz\"\n\nmsg_info \"Setting up Zitadel Environments\"\nmkdir -p /opt/zitadel\necho \"/opt/zitadel/config.yaml\" >\"/opt/zitadel/.config\"\nhead -c 32 < <(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9') >\"/opt/zitadel/.masterkey\"\n{\n  echo \"Config location: $(cat \"/opt/zitadel/.config\")\"\n  echo \"Masterkey: $(cat \"/opt/zitadel/.masterkey\")\"\n} >>~/zitadel.creds\ncat <<EOF >/opt/zitadel/config.yaml\nPort: 8080\nExternalPort: 8080\nExternalDomain: localhost\nExternalSecure: false\nTLS:\n  Enabled: false\n  KeyPath: \"\"\n  Key: \"\"\n  CertPath: \"\"\n  Cert: \"\"\n\nDatabase:\n  postgres:\n    Host: localhost\n    Port: 5432\n    Database: ${DB_NAME}\n    User:\n      Username: ${DB_USER}\n      Password: ${DB_PASS}\n      SSL:\n        Mode: disable\n        RootCert: \"\"\n        Cert: \"\"\n        Key: \"\"\n    Admin:\n      Username: ${DB_ADMIN_USER}\n      Password: ${DB_ADMIN_PASS}\n      SSL:\n        Mode: disable\n        RootCert: \"\"\n        Cert: \"\"\n        Key: \"\"\nDefaultInstance:\n  Features:\n    LoginV2:\n      Required: false\nEOF\nmsg_ok \"Installed Zitadel Enviroments\"\n\nmsg_info \"Creating Services\"\ncat <<EOF >/etc/systemd/system/zitadel.service\n[Unit]\nDescription=ZITADEL Identiy Server\nAfter=network.target postgresql.service\nWants=postgresql.service\n\n[Service]\nType=simple\nUser=zitadel\nGroup=zitadel\nExecStart=/usr/local/bin/zitadel start --masterkeyFile \"/opt/zitadel/.masterkey\" --config \"/opt/zitadel/config.yaml\"\nRestart=always\nRestartSec=5\nTimeoutStartSec=0\n\n# Security Hardening options\nProtectSystem=full\nProtectHome=true\nPrivateTmp=true\nNoNewPrivileges=true\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zitadel\nmsg_ok \"Created Services\"\n\nmsg_info \"Zitadel initial setup\"\nzitadel start-from-init --masterkeyFile /opt/zitadel/.masterkey --config /opt/zitadel/config.yaml &>/dev/null &\nsleep 60\nkill $(lsof -i | awk '/zitadel/ {print $2}' | head -n1)\nuseradd zitadel\nmsg_ok \"Zitadel initialized\"\n\nmsg_info \"Set ExternalDomain to current IP and restart Zitadel\"\nIP=$(ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\nsed -i \"0,/localhost/s/localhost/${IP}/\" /opt/zitadel/config.yaml\nsystemctl stop -q zitadel\n$STD zitadel setup --masterkeyFile /opt/zitadel/.masterkey --config /opt/zitadel/config.yaml\nsystemctl restart -q zitadel\nmsg_ok \"Zitadel restarted with ExternalDomain set to current IP\"\n\nmsg_info \"Create zitadel-rerun.sh\"\ncat <<EOF >~/zitadel-rerun.sh\nsystemctl stop zitadel\ntimeout --kill-after=5s 15s zitadel setup --masterkeyFile /opt/zitadel/.masterkey --config /opt/zitadel/config.yaml\nsystemctl restart zitadel\nEOF\nmsg_ok \"Bash script for rerunning Zitadel after changing Zitadel config.yaml\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zoraxy-install.sh",
    "content": "f#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zoraxy.aroz.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"zoraxy\" \"tobychui/zoraxy\" \"singlefile\" \"latest\" \"/opt/zoraxy\" \"zoraxy_linux_arm64\"\nln -s /opt/zoraxy/zoraxy /usr/local/bin/zoraxy\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/zoraxy.service\n[Unit]\nDescription=General purpose request proxy and forwarding tool\nAfter=syslog.target network-online.target\n\n[Service]\nExecStart=/opt/zoraxy/./zoraxy\nWorkingDirectory=/opt/zoraxy/\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zoraxy\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zot-registry-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zotregistry.dev/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y apache2-utils\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"zot\" \"project-zot/zot\" \"singlefile\" \"latest\" \"/usr/bin\" \"zot-linux-arm64\"\n\nmsg_info \"Configuring Zot Registry\"\nmkdir -p /etc/zot\ncurl -fsSL https://raw.githubusercontent.com/project-zot/zot/refs/heads/main/examples/config-ui.json -o /etc/zot/config.json\nZOTPASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n$STD htpasswd -b -B -c /etc/zot/htpasswd admin \"$ZOTPASSWORD\"\n{\n  echo \"Zot-Credentials\"\n  echo \"Zot User: admin\"\n  echo \"Zot Password: $ZOTPASSWORD\"\n} >>~/zot.creds\nmsg_ok \"Configured Zot Registry\"\n\nmsg_info \"Setup Service\"\ncat <<EOF >/etc/systemd/system/zot.service\n[Unit]\nDescription=OCI Distribution Registry\nDocumentation=https://zotregistry.dev/\nAfter=network.target auditd.service local-fs.target\n\n[Service]\nType=simple\nExecStart=/usr/bin/zot serve /etc/zot/config.json\nRestart=on-failure\nUser=root\nLimitNOFILE=500000\nMemoryHigh=2G\nMemoryMax=4G\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zot\nmsg_ok \"Setup Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "install/zwave-js-ui-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://zwave-js.github.io/zwave-js-ui/#/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nfetch_and_deploy_gh_release \"zwave-js-ui\" \"zwave-js/zwave-js-ui\" \"prebuild\" \"latest\" \"/opt/zwave-js-ui\" \"zwave-js-ui*-linux.zip\"\n\nmsg_info \"Configuring Z-Wave JS UI\"\nmkdir -p /opt/zwave_store\ncat <<EOF >/opt/.env\nZWAVEJS_EXTERNAL_CONFIG=/opt/zwave_store/.config-db\nSTORE_DIR=/opt/zwave_store\nEOF\nmsg_ok \"Configured Z-Wave JS UI\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/zwave-js-ui.service\n[Unit]\nDescription=zwave-js-ui\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/zwave-js-ui\nExecStart=/opt/zwave-js-ui/zwave-js-ui\nEnvironmentFile=/opt/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now zwave-js-ui\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "misc/add-beszel-agent-lxc.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\nclear\ncat <<\"EOF\"\n    ____                       __      ___                    __ \n   / __ )___  _________  ___  / /     /   | ____ ____  ____  / /_\n  / __  / _ \\/ ___/_  / / _ \\/ /_____/ /| |/ __ `/ _ \\/ __ \\/ __/\n / /_/ /  __(__  ) / /_/  __/ /_____/ ___ / /_/ /  __/ / / / /_  \n/_____/\\___/____/ /___/\\___/_/     /_/  |_\\__, /\\___/_/ /_/\\__/  \n                                         /____/                  \nEOF\n}\nheader_info\nset -e\nwhile true; do\n  read -p \"This will add Beszel Agent to an existing LXC Container ONLY. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nheader_info\necho \"Loading...\"\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\n\nNODE=$(hostname)\nMSG_MAX_LENGTH=0\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\n\nwhile [ -z \"${CTID:+x}\" ]; do\n  CTID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --radiolist \\\n    \"\\nSelect a container to add Beszel Agent to:\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3) || exit\ndone\n\nCTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf\nheader_info\nmsg \"Installing Beszel Agent...\"\npct exec \"$CTID\" -- bash -c 'curl -sL https://raw.githubusercontent.com/henrygd/beszel/main/supplemental/scripts/install-agent.sh -o  install-beszel-agent.sh && chmod +x install-beszel-agent.sh && ./install-beszel-agent.sh && rm -f ./install-beszel-agent.sh &>/dev/null' || exit\nmsg \"\\e[1;32m ✔ Installed Beszel Agent\\e[0m\"\n"
  },
  {
    "path": "misc/alpine-install.func",
    "content": "# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nif ! command -v curl >/dev/null 2>&1; then\n  apk update && apk add curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nload_functions\ncatch_errors\n\n# Persist diagnostics setting inside container (exported from build.func)\n# so addon scripts running later can find the user's choice\nif [[ ! -f /usr/local/community-scripts/diagnostics ]]; then\n  mkdir -p /usr/local/community-scripts\n  echo \"DIAGNOSTICS=${DIAGNOSTICS:-no}\" >/usr/local/community-scripts/diagnostics\nfi\n\n# Get LXC IP address (must be called INSIDE container, after network is up)\nget_lxc_ip\n\n# ------------------------------------------------------------------------------\n# post_progress_to_api()\n#\n# - Lightweight progress ping from inside the container\n# - Updates the existing telemetry record status\n# - Arguments:\n#   * $1: status (optional, default: \"configuring\")\n# - Signals that the installation is actively progressing (not stuck)\n# - Fire-and-forget: never blocks or fails the script\n# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set\n# ------------------------------------------------------------------------------\npost_progress_to_api() {\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  local progress_status=\"${1:-configuring}\"\n\n  curl -fsS -m 5 -X POST \"https://telemetry.community-scripts.org/telemetry\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"{\\\"random_id\\\":\\\"${RANDOM_UUID}\\\",\\\"execution_id\\\":\\\"${EXECUTION_ID:-${RANDOM_UUID}}\\\",\\\"type\\\":\\\"lxc\\\",\\\"nsapp\\\":\\\"${app:-unknown}\\\",\\\"status\\\":\\\"${progress_status}\\\"}\" &>/dev/null || true\n}\n\n# This function enables IPv6 if it's not disabled and sets verbose mode\nverb_ip6() {\n  set_std_mode # Set STD mode based on VERBOSE\n\n  if [ \"${IPV6_METHOD:-}\" = \"disable\" ]; then\n    msg_info \"Disabling IPv6 (this may affect some services)\"\n    $STD sysctl -w net.ipv6.conf.all.disable_ipv6=1\n    $STD sysctl -w net.ipv6.conf.default.disable_ipv6=1\n    $STD sysctl -w net.ipv6.conf.lo.disable_ipv6=1\n    mkdir -p /etc/sysctl.d\n    $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <<EOF\nnet.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1\nnet.ipv6.conf.lo.disable_ipv6 = 1\nEOF\n    $STD rc-update add sysctl default\n    msg_ok \"Disabled IPv6\"\n  fi\n}\n\n# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection\nsetting_up_container() {\n  msg_info \"Setting up Container OS\"\n  while [ $i -gt 0 ]; do\n    if [ \"$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)\" != \"\" ]; then\n      break\n    fi\n    echo 1>&2 -en \"${CROSS}${RD} No Network! \"\n    sleep $RETRY_EVERY\n    i=$((i - 1))\n  done\n\n  if [ \"$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)\" = \"\" ]; then\n    echo 1>&2 -e \"\\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}\"\n    echo -e \"${NETWORK}Check Network Settings\"\n    exit 121\n  fi\n  msg_ok \"Set up Container OS\"\n  msg_ok \"Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}\"\n  post_progress_to_api\n}\n\n# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected\nnetwork_check() {\n  set +e\n  trap - ERR\n  ipv4_connected=false\n\n  # Check IPv4 connectivity to Cloudflare, Google & Quad9 DNS servers\n  if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then\n    msg_ok \"IPv4 Internet Connected\"\n    ipv4_connected=true\n  else\n    msg_error \"IPv4 Internet Not Connected\"\n  fi\n\n  if [[ $ipv4_connected == false ]]; then\n    read -r -p \"No Internet detected, would you like to continue anyway? <y/N> \" prompt\n    if [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n      echo -e \"${INFO}${RD}Expect Issues Without Internet${CL}\"\n    else\n      echo -e \"${NETWORK}Check Network Settings\"\n      exit 122\n    fi\n  fi\n\n  # DNS resolution checks for GitHub-related domains\n  GIT_HOSTS=(\"github.com\" \"raw.githubusercontent.com\" \"api.github.com\" \"git.community-scripts.org\")\n  GIT_STATUS=\"Git DNS:\"\n  DNS_FAILED=false\n\n  for HOST in \"${GIT_HOSTS[@]}\"; do\n    RESOLVEDIP=$(getent hosts \"$HOST\" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1)\n    if [[ -z \"$RESOLVEDIP\" ]]; then\n      GIT_STATUS+=\"$HOST:($DNSFAIL)\"\n      DNS_FAILED=true\n    else\n      GIT_STATUS+=\" $HOST:($DNSOK)\"\n    fi\n  done\n\n  if [[ \"$DNS_FAILED\" == true ]]; then\n    fatal \"$GIT_STATUS\"\n  else\n    msg_ok \"$GIT_STATUS\"\n  fi\n\n  set -e\n  trap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\n}\n\n# This function updates the Container OS by running apk upgrade with mirror fallback\nupdate_os() {\n  msg_info \"Updating Container OS\"\n  if ! $STD apk -U upgrade; then\n    msg_warn \"apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors...\"\n    local alpine_mirrors=\"mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org\"\n    local apk_ok=false\n    for m in $(printf '%s\\n' $alpine_mirrors | shuf); do\n      if timeout 2 bash -c \"echo >/dev/tcp/$m/80\" 2>/dev/null; then\n        msg_custom \"${INFO}\" \"${YW}\" \"Attempting mirror: ${m}\"\n        cat <<EOF >/etc/apk/repositories\nhttp://$m/alpine/latest-stable/main\nhttp://$m/alpine/latest-stable/community\nEOF\n        if $STD apk -U upgrade; then\n          msg_ok \"CDN set to ${m}: tests passed\"\n          apk_ok=true\n          break\n        else\n          msg_warn \"Mirror ${m} failed\"\n        fi\n      fi\n    done\n    if [[ \"$apk_ok\" != true ]]; then\n      msg_error \"All Alpine mirrors failed. Check network or try again later.\"\n      exit 1\n    fi\n  fi\n  local tools_content\n  tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {\n    msg_error \"Failed to download tools.func\"\n    exit 115\n  }\n  source /dev/stdin <<<\"$tools_content\"\n  if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then\n    msg_error \"tools.func loaded but incomplete — missing expected functions\"\n    exit 115\n  fi\n  msg_ok \"Updated Container OS\"\n  post_progress_to_api\n}\n\n# This function modifies the message of the day (motd) and SSH settings\nmotd_ssh() {\n  echo \"export TERM='xterm-256color'\" >>/root/.bashrc\n\n  PROFILE_FILE=\"/etc/profile.d/00_lxc-details.sh\"\n  echo \"echo -e \\\"\\\"\" >\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${BOLD}${APPLICATION} LXC Container${CL}\"\\\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts & pimox-scripts ${YW}| GitHub: ${GN}https://github.com/asylumexp/ProxmoxVE${CL}\\\"\" >>\"$PROFILE_FILE\"\n  echo \"echo \\\"\\\"\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${OS}${YW} OS: ${GN}\\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\\\"') - Version: \\$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\\\"')${CL}\\\"\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\\$(hostname)${CL}\\\"\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${INFO}${YW} IP Address: ${GN}\\$(ip -4 addr show eth0 | awk '/inet / {print \\$2}' | cut -d/ -f1 | head -n 1)${CL}\\\"\" >>\"$PROFILE_FILE\"\n\n  # Configure SSH if enabled\n  if [[ \"${SSH_ROOT}\" == \"yes\" ]]; then\n    # Enable sshd service\n    $STD rc-update add sshd\n    # Allow root login via SSH\n    sed -i \"s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g\" /etc/ssh/sshd_config\n    # Start the sshd service\n    $STD /etc/init.d/sshd start\n  fi\n  post_progress_to_api\n}\n\n# Validate Timezone for some LXC's\nvalidate_tz() {\n  [[ -f \"/usr/share/zoneinfo/$1\" ]]\n}\n\n# This function customizes the container and enables passwordless login for the root user\ncustomize() {\n  if [[ \"$PASSWORD\" == \"\" ]]; then\n    msg_info \"Customizing Container\"\n    passwd -d root >/dev/null 2>&1\n\n    # Ensure agetty is available\n    apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1\n\n    # Create persistent autologin boot script\n    mkdir -p /etc/local.d\n    cat <<'EOF' >/etc/local.d/autologin.start\n#!/bin/sh\nsed -i 's|^tty1::respawn:.*|tty1::respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab\nkill -HUP 1\nEOF\n    touch /root/.hushlogin\n\n    chmod +x /etc/local.d/autologin.start\n    rc-update add local >/dev/null 2>&1\n\n    # Apply autologin immediately for current session\n    /etc/local.d/autologin.start\n\n    msg_ok \"Customized Container\"\n  fi\n\n  echo \"bash -c \\\"\\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\\\"\" >/usr/bin/update\n  chmod +x /usr/bin/update\n  post_progress_to_api\n}\n"
  },
  {
    "path": "misc/alpine-tools.func",
    "content": "#!/bin/ash\n# shellcheck shell=ash\n\n# Expects existing msg_* functions and optional $STD from the framework.\n\n# ------------------------------\n# helpers\n# ------------------------------\nlower() { printf '%s' \"$1\" | tr '[:upper:]' '[:lower:]'; }\nhas() { command -v \"$1\" >/dev/null 2>&1; }\n\nneed_tool() {\n  # usage: need_tool curl jq unzip ...\n  # setup missing tools via apk\n  local missing=0 t\n  for t in \"$@\"; do\n    if ! has \"$t\"; then missing=1; fi\n  done\n  if [ \"$missing\" -eq 1 ]; then\n    msg_info \"Installing tools: $*\"\n    apk add --no-cache \"$@\" >/dev/null 2>&1 || {\n      msg_error \"apk add failed for: $*\"\n      return 100\n    }\n    msg_ok \"Tools ready: $*\"\n  fi\n}\n\nnet_resolves() {\n  # better handling for missing getent on Alpine\n  # usage: net_resolves api.github.com\n  local host=\"$1\"\n  ping -c1 -W1 \"$host\" >/dev/null 2>&1 || nslookup \"$host\" >/dev/null 2>&1\n}\n\nensure_usr_local_bin_persist() {\n  # Login shells: /etc/profile.d/\n  local PROFILE_FILE=\"/etc/profile.d/10-localbin.sh\"\n  if [ ! -f \"$PROFILE_FILE\" ]; then\n    echo 'case \":$PATH:\" in *:/usr/local/bin:*) ;; *) export PATH=\"/usr/local/bin:$PATH\";; esac' >\"$PROFILE_FILE\"\n    chmod +x \"$PROFILE_FILE\"\n  fi\n\n  # Non-login shells (pct enter): /root/.profile and /root/.bashrc\n  for rc_file in /root/.profile /root/.bashrc; do\n    if [ -f \"$rc_file\" ] && ! grep -q '/usr/local/bin' \"$rc_file\"; then\n      echo 'export PATH=\"/usr/local/bin:$PATH\"' >>\"$rc_file\"\n    fi\n  done\n}\n\ndownload_with_progress() {\n  # $1 url, $2 dest\n  local url=\"$1\" out=\"$2\" cl\n  need_tool curl pv || return 127\n  cl=$(curl -fsSLI \"$url\" 2>/dev/null | awk 'tolower($0) ~ /^content-length:/ {print $2}' | tr -d '\\r')\n  if [ -n \"$cl\" ]; then\n    curl -fsSL \"$url\" | pv -s \"$cl\" >\"$out\" || {\n      msg_error \"Download failed: $url\"\n      return 250\n    }\n  else\n    curl -fL# -o \"$out\" \"$url\" || {\n      msg_error \"Download failed: $url\"\n      return 250\n    }\n  fi\n}\n\n# ------------------------------\n# GitHub: check Release\n# ------------------------------\ncheck_for_gh_release() {\n  # app, repo, [pinned]\n  local app=\"$1\" source=\"$2\" pinned=\"${3:-}\"\n  local app_lc\n  app_lc=\"$(lower \"$app\" | tr -d ' ')\"\n  local current_file=\"$HOME/.${app_lc}\"\n  local current=\"\" release tag\n\n  msg_info \"Check for update: $app\"\n\n  net_resolves api.github.com || {\n    msg_error \"DNS/network error: api.github.com\"\n    return 6\n  }\n  need_tool curl jq || return 127\n\n  tag=$(curl -fsSL \"https://api.github.com/repos/${source}/releases/latest\" | jq -r '.tag_name // empty')\n  [ -z \"$tag\" ] && {\n    msg_error \"Unable to fetch latest tag for $app\"\n    return 22\n  }\n  release=\"${tag#v}\"\n\n  [ -f \"$current_file\" ] && current=\"$(cat \"$current_file\")\"\n\n  if [ -n \"$pinned\" ]; then\n    if [ \"$pinned\" = \"$release\" ]; then\n      msg_ok \"$app pinned to v$pinned (no update)\"\n      return 1\n    fi\n    if [ \"$current\" = \"$pinned\" ]; then\n      msg_ok \"$app pinned v$pinned installed (upstream v$release)\"\n      return 1\n    fi\n    msg_info \"$app pinned v$pinned (upstream v$release) → update/downgrade\"\n    CHECK_UPDATE_RELEASE=\"$pinned\"\n    return 0\n  fi\n\n  if [ \"$release\" != \"$current\" ] || [ ! -f \"$current_file\" ]; then\n    CHECK_UPDATE_RELEASE=\"$release\"\n    msg_info \"New release available: v$release (current: v${current:-none})\"\n    return 0\n  fi\n\n  msg_ok \"$app is up to date (v$release)\"\n  return 1\n}\n\n# ------------------------------\n# GitHub: get Release  & deploy (Alpine)\n# modes: tarball | prebuild | singlefile\n# ------------------------------\nfetch_and_deploy_gh() {\n  # $1 app, $2 repo, [$3 mode], [$4 version], [$5 target], [$6 asset_pattern\n  local app=\"$1\" repo=\"$2\" mode=\"${3:-tarball}\" version=\"${4:-latest}\" target=\"${5:-/opt/$1}\" pattern=\"${6:-}\"\n  local app_lc\n  app_lc=\"$(lower \"$app\" | tr -d ' ')\"\n  local vfile=\"$HOME/.${app_lc}\"\n  local json url filename tmpd unpack\n\n  net_resolves api.github.com || {\n    msg_error \"DNS/network error\"\n    return 6\n  }\n  need_tool curl jq tar || return 127\n  [ \"$mode\" = \"prebuild\" ] || [ \"$mode\" = \"singlefile\" ] && need_tool unzip >/dev/null 2>&1 || true\n\n  tmpd=\"$(mktemp -d)\" || return 252\n  mkdir -p \"$target\"\n\n  # Release JSON\n  if [ \"$version\" = \"latest\" ]; then\n    json=\"$(curl -fsSL \"https://api.github.com/repos/$repo/releases/latest\")\" || {\n      msg_error \"GitHub API failed\"\n      rm -rf \"$tmpd\"\n      return 22\n    }\n  else\n    json=\"$(curl -fsSL \"https://api.github.com/repos/$repo/releases/tags/$version\")\" || {\n      msg_error \"GitHub API failed\"\n      rm -rf \"$tmpd\"\n      return 22\n    }\n  fi\n\n  # correct Version\n  version=\"$(printf '%s' \"$json\" | jq -r '.tag_name // empty')\"\n  version=\"${version#v}\"\n\n  [ -z \"$version\" ] && {\n    msg_error \"No tag in release json\"\n    rm -rf \"$tmpd\"\n    return 65\n  }\n\n  case \"$mode\" in\n  tarball | source)\n    url=\"$(printf '%s' \"$json\" | jq -r '.tarball_url // empty')\"\n    [ -z \"$url\" ] && url=\"https://github.com/$repo/archive/refs/tags/v$version.tar.gz\"\n    filename=\"${app_lc}-${version}.tar.gz\"\n    download_with_progress \"$url\" \"$tmpd/$filename\" || {\n      rm -rf \"$tmpd\"\n      return 250\n    }\n    tar -xzf \"$tmpd/$filename\" -C \"$tmpd\" || {\n      msg_error \"tar extract failed\"\n      rm -rf \"$tmpd\"\n      return 251\n    }\n    unpack=\"$(find \"$tmpd\" -mindepth 1 -maxdepth 1 -type d | head -n1)\"\n    # copy content of unpack to target\n    (cd \"$unpack\" && tar -cf - .) | (cd \"$target\" && tar -xf -) || {\n      msg_error \"copy failed\"\n      rm -rf \"$tmpd\"\n      return 252\n    }\n    ;;\n  prebuild)\n    [ -n \"$pattern\" ] || {\n      msg_error \"prebuild requires asset pattern\"\n      rm -rf \"$tmpd\"\n      return 65\n    }\n    url=\"$(printf '%s' \"$json\" | jq -r '.assets[].browser_download_url' | awk -v p=\"$pattern\" '\n        BEGIN{IGNORECASE=1}\n        $0 ~ p {print; exit}\n      ')\"\n    [ -z \"$url\" ] && {\n      msg_error \"asset not found for pattern: $pattern\"\n      rm -rf \"$tmpd\"\n      return 250\n    }\n    filename=\"${url##*/}\"\n    download_with_progress \"$url\" \"$tmpd/$filename\" || {\n      rm -rf \"$tmpd\"\n      return 250\n    }\n    # unpack archive (Zip or tarball)\n    case \"$filename\" in\n    *.zip)\n      need_tool unzip || {\n        rm -rf \"$tmpd\"\n        return 127\n      }\n      mkdir -p \"$tmpd/unp\"\n      unzip -q \"$tmpd/$filename\" -d \"$tmpd/unp\"\n      ;;\n    *.tar.gz | *.tgz | *.tar.xz | *.tar.zst | *.tar.bz2)\n      mkdir -p \"$tmpd/unp\"\n      tar -xf \"$tmpd/$filename\" -C \"$tmpd/unp\"\n      ;;\n    *)\n      msg_error \"unsupported archive: $filename\"\n      rm -rf \"$tmpd\"\n      return 251\n      ;;\n    esac\n    # top-level folder strippen\n    if [ \"$(find \"$tmpd/unp\" -mindepth 1 -maxdepth 1 -type d | wc -l)\" -eq 1 ] && [ -z \"$(find \"$tmpd/unp\" -mindepth 1 -maxdepth 1 -type f | head -n1)\" ]; then\n      unpack=\"$(find \"$tmpd/unp\" -mindepth 1 -maxdepth 1 -type d)\"\n      (cd \"$unpack\" && tar -cf - .) | (cd \"$target\" && tar -xf -) || {\n        msg_error \"copy failed\"\n        rm -rf \"$tmpd\"\n        return 252\n      }\n    else\n      (cd \"$tmpd/unp\" && tar -cf - .) | (cd \"$target\" && tar -xf -) || {\n        msg_error \"copy failed\"\n        rm -rf \"$tmpd\"\n        return 252\n      }\n    fi\n    ;;\n  singlefile)\n    [ -n \"$pattern\" ] || {\n      msg_error \"singlefile requires asset pattern\"\n      rm -rf \"$tmpd\"\n      return 65\n    }\n    url=\"$(printf '%s' \"$json\" | jq -r '.assets[].browser_download_url' | awk -v p=\"$pattern\" '\n        BEGIN{IGNORECASE=1}\n        $0 ~ p {print; exit}\n      ')\"\n    [ -z \"$url\" ] && {\n      msg_error \"asset not found for pattern: $pattern\"\n      rm -rf \"$tmpd\"\n      return 250\n    }\n    filename=\"${url##*/}\"\n    download_with_progress \"$url\" \"$target/$app\" || {\n      rm -rf \"$tmpd\"\n      return 250\n    }\n    chmod +x \"$target/$app\"\n    ;;\n  *)\n    msg_error \"Unknown mode: $mode\"\n    rm -rf \"$tmpd\"\n    return 65\n    ;;\n  esac\n\n  echo \"$version\" >\"$vfile\"\n  ensure_usr_local_bin_persist\n  rm -rf \"$tmpd\"\n  msg_ok \"Deployed $app ($version) → $target\"\n}\n\n# ------------------------------\n# yq (mikefarah) – Alpine\n# ------------------------------\nsetup_yq() {\n  # prefer apk, unless FORCE_GH=1\n  if [ \"${FORCE_GH:-0}\" != \"1\" ] && apk info -e yq >/dev/null 2>&1; then\n    msg_info \"Updating yq via apk\"\n    apk add --no-cache --upgrade yq >/dev/null 2>&1 || true\n    msg_ok \"yq ready ($(yq --version 2>/dev/null))\"\n    return 0\n  fi\n\n  need_tool curl || return 127\n  local arch bin url tmp\n  case \"$(uname -m)\" in\n  x86_64) arch=\"amd64\" ;;\n  aarch64) arch=\"arm64\" ;;\n  *)\n    msg_error \"Unsupported arch for yq: $(uname -m)\"\n    return 238\n    ;;\n  esac\n  url=\"https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${arch}\"\n  tmp=\"$(mktemp)\"\n  download_with_progress \"$url\" \"$tmp\" || return 250\n  /usr/bin/install -m 0755 \"$tmp\" /usr/local/bin/yq\n  rm -f \"$tmp\"\n  msg_ok \"Setup yq ($(yq --version 2>/dev/null))\"\n}\n\n# ------------------------------\n# Adminer – Alpine\n# ------------------------------\nsetup_adminer() {\n  need_tool curl || return 127\n  msg_info \"Setup Adminer (Alpine)\"\n  mkdir -p /var/www/localhost/htdocs/adminer\n  curl -fsSL https://github.com/vrana/adminer/releases/latest/download/adminer.php \\\n    -o /var/www/localhost/htdocs/adminer/index.php || {\n    msg_error \"Adminer download failed\"\n    return 250\n  }\n  msg_ok \"Adminer at /adminer (served by your webserver)\"\n}\n\n# ------------------------------\n# uv – Alpine (musl tarball)\n# optional: PYTHON_VERSION=\"3.12\"\n# ------------------------------\nsetup_uv() {\n  need_tool curl tar || return 127\n  local UV_BIN=\"/usr/local/bin/uv\"\n  local arch tarball url tmpd ver installed\n\n  case \"$(uname -m)\" in\n  x86_64) arch=\"x86_64-unknown-linux-musl\" ;;\n  aarch64) arch=\"aarch64-unknown-linux-musl\" ;;\n  *)\n    msg_error \"Unsupported arch for uv: $(uname -m)\"\n    return 238\n    ;;\n  esac\n\n  ver=\"$(curl -fsSL https://api.github.com/repos/astral-sh/uv/releases/latest | jq -r '.tag_name' 2>/dev/null)\"\n  ver=\"${ver#v}\"\n  [ -z \"$ver\" ] && {\n    msg_error \"uv: cannot determine latest version\"\n    return 250\n  }\n\n  if has \"$UV_BIN\"; then\n    installed=\"$($UV_BIN -V 2>/dev/null | awk '{print $2}')\"\n    [ \"$installed\" = \"$ver\" ] && {\n      msg_ok \"uv $ver already installed\"\n      return 0\n    }\n    msg_info \"Updating uv $installed → $ver\"\n  else\n    msg_info \"Setup uv $ver\"\n  fi\n\n  tmpd=\"$(mktemp -d)\" || return 252\n  tarball=\"uv-${arch}.tar.gz\"\n  url=\"https://github.com/astral-sh/uv/releases/download/v${ver}/${tarball}\"\n\n  download_with_progress \"$url\" \"$tmpd/uv.tar.gz\" || {\n    rm -rf \"$tmpd\"\n    return 250\n  }\n  tar -xzf \"$tmpd/uv.tar.gz\" -C \"$tmpd\" || {\n    msg_error \"uv: extract failed\"\n    rm -rf \"$tmpd\"\n    return 251\n  }\n\n  # tar contains ./uv\n  if [ -x \"$tmpd/uv\" ]; then\n    /usr/bin/install -m 0755 \"$tmpd/uv\" \"$UV_BIN\"\n  else\n    # fallback: in subfolder\n    /usr/bin/install -m 0755 \"$tmpd\"/*/uv \"$UV_BIN\" 2>/dev/null || {\n      msg_error \"uv binary not found in tar\"\n      rm -rf \"$tmpd\"\n      return 252\n    }\n  fi\n  rm -rf \"$tmpd\"\n  ensure_usr_local_bin_persist\n  msg_ok \"Setup uv $ver\"\n\n  if [ -n \"${PYTHON_VERSION:-}\" ]; then\n    local match\n    match=\"$(uv python list --only-downloads 2>/dev/null | awk -v maj=\"$PYTHON_VERSION\" '\n      $0 ~ \"^cpython-\"maj\"\\\\.\" { print $0 }' | awk -F- '{print $2}' | sort -V | tail -n1)\"\n    [ -z \"$match\" ] && {\n      msg_error \"No matching Python for $PYTHON_VERSION\"\n      return 250\n    }\n    if ! uv python list | grep -q \"cpython-${match}-linux\"; then\n      msg_info \"Installing Python $match via uv\"\n      uv python install \"$match\" || {\n        msg_error \"uv python install failed\"\n        return 150\n      }\n      msg_ok \"Python $match installed (uv)\"\n    fi\n  fi\n}\n\n# ------------------------------\n# Java – Alpine (OpenJDK)\n# JAVA_VERSION: 17|21 (Default 21)\n# ------------------------------\nsetup_java() {\n  local JAVA_VERSION=\"${JAVA_VERSION:-21}\" pkg\n  case \"$JAVA_VERSION\" in\n  17) pkg=\"openjdk17-jdk\" ;;\n  21 | *) pkg=\"openjdk21-jdk\" ;;\n  esac\n  msg_info \"Setup Java (OpenJDK $JAVA_VERSION)\"\n  apk add --no-cache \"$pkg\" >/dev/null 2>&1 || {\n    msg_error \"apk add $pkg failed\"\n    return 100\n  }\n  # set JAVA_HOME\n  local prof=\"/etc/profile.d/20-java.sh\"\n  if [ ! -f \"$prof\" ]; then\n    echo 'export JAVA_HOME=$(dirname $(dirname $(readlink -f $(command -v java))))' >\"$prof\"\n    echo 'case \":$PATH:\" in *:$JAVA_HOME/bin:*) ;; *) export PATH=\"$JAVA_HOME/bin:$PATH\";; esac' >>\"$prof\"\n    chmod +x \"$prof\"\n  fi\n  msg_ok \"Java ready: $(java -version 2>&1 | head -n1)\"\n}\n\n# ------------------------------\n# Go – Alpine (apk prefers, else tarball)\n# ------------------------------\nsetup_go() {\n  if [ -z \"${GO_VERSION:-}\" ]; then\n    msg_info \"Setup Go (apk)\"\n    apk add --no-cache go >/dev/null 2>&1 || {\n      msg_error \"apk add go failed\"\n      return 100\n    }\n    msg_ok \"Go ready: $(go version 2>/dev/null)\"\n    return 0\n  fi\n\n  need_tool curl tar || return 127\n  local ARCH TARBALL URL TMP\n  case \"$(uname -m)\" in\n  x86_64) ARCH=\"amd64\" ;;\n  aarch64) ARCH=\"arm64\" ;;\n  *)\n    msg_error \"Unsupported arch for Go: $(uname -m)\"\n    return 238\n    ;;\n  esac\n  TARBALL=\"go${GO_VERSION}.linux-${ARCH}.tar.gz\"\n  URL=\"https://go.dev/dl/${TARBALL}\"\n  msg_info \"Setup Go $GO_VERSION (tarball)\"\n  TMP=\"$(mktemp)\"\n  download_with_progress \"$URL\" \"$TMP\" || return 250\n  rm -rf /usr/local/go\n  tar -C /usr/local -xzf \"$TMP\" || {\n    msg_error \"extract go failed\"\n    rm -f \"$TMP\"\n    return 251\n  }\n  rm -f \"$TMP\"\n  ln -sf /usr/local/go/bin/go /usr/local/bin/go\n  ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt\n  ensure_usr_local_bin_persist\n  msg_ok \"Go ready: $(go version 2>/dev/null)\"\n}\n\n# ------------------------------\n# Composer – Alpine\n# uses php83-cli + openssl + phar\n# ------------------------------\nsetup_composer() {\n  local COMPOSER_BIN=\"/usr/local/bin/composer\"\n  if ! has php; then\n    # prefers php83\n    msg_info \"Installing PHP CLI for Composer\"\n    apk add --no-cache php83-cli php83-openssl php83-phar php83-iconv >/dev/null 2>&1 || {\n      # Fallback to generic php if 83 not available\n      apk add --no-cache php-cli php-openssl php-phar php-iconv >/dev/null 2>&1 || {\n        msg_error \"Failed to install php-cli for composer\"\n        return 100\n      }\n    }\n    msg_ok \"PHP CLI ready: $(php -v | head -n1)\"\n  fi\n\n  if [ -x \"$COMPOSER_BIN\" ]; then\n    msg_info \"Updating Composer\"\n  else\n    msg_info \"Setup Composer\"\n  fi\n\n  need_tool curl || return 127\n  curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php || {\n    msg_error \"composer installer download failed\"\n    return 250\n  }\n  php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1 || {\n    msg_error \"composer install failed\"\n    return 150\n  }\n  rm -f /tmp/composer-setup.php\n  ensure_usr_local_bin_persist\n  msg_ok \"Composer ready: $(composer --version 2>/dev/null)\"\n}\n"
  },
  {
    "path": "misc/api.func",
    "content": "# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner | MickLesk\n# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE\n\n# ==============================================================================\n# API.FUNC - TELEMETRY & DIAGNOSTICS API\n# ==============================================================================\n#\n# Provides functions for sending anonymous telemetry data via the community\n# telemetry ingest service at telemetry.community-scripts.org.\n#\n# Features:\n#   - Container/VM creation statistics\n#   - Installation success/failure tracking\n#   - Error code mapping and reporting\n#   - Privacy-respecting anonymous telemetry\n#\n# Usage:\n#   source <(curl -fsSL .../api.func)\n#   post_to_api          # Report LXC container creation\n#   post_to_api_vm       # Report VM creation\n#   post_update_to_api   # Report installation status\n#\n# Privacy:\n#   - Only anonymous statistics (no personal data)\n#   - User can opt-out via DIAGNOSTICS=no\n#   - Random UUID for session tracking only\n#   - Data retention: 30 days\n#\n# ==============================================================================\n\n# ==============================================================================\n# Telemetry Configuration\n# ==============================================================================\nTELEMETRY_URL=\"https://telemetry.community-scripts.org/telemetry\"\n\n# Timeout for telemetry requests (seconds)\n# Progress pings (validation/configuring) use the short timeout\nTELEMETRY_TIMEOUT=5\n# Final status updates (success/failed) use the longer timeout\n# PocketBase may need more time under load (FindRecord + UpdateRecord)\nSTATUS_TIMEOUT=10\n\n# ==============================================================================\n# SECTION 0: REPOSITORY SOURCE DETECTION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# detect_repo_source()\n#\n# - Dynamically detects which GitHub/Gitea repo the scripts were loaded from\n# - Inspects /proc/$$/cmdline and $0 to find the source URL\n# - Maps detected repo to one of three canonical values:\n#   * \"ProxmoxVE\"  — official community-scripts/ProxmoxVE (production)\n#   * \"ProxmoxVED\" — official community-scripts/ProxmoxVED (development)\n#   * \"external\"   — any fork or unknown source\n# - Fallback: \"ProxmoxVED\" (CI sed transforms ProxmoxVED → ProxmoxVE on promotion)\n# - Sets and exports REPO_SOURCE global variable\n# - Skips detection if REPO_SOURCE is already set (e.g., by environment)\n# ------------------------------------------------------------------------------\ndetect_repo_source() {\n  # Allow explicit override via environment\n  [[ -n \"${REPO_SOURCE:-}\" ]] && return 0\n\n  local content=\"\" owner_repo=\"\"\n\n  # Method 1: Read from /proc/$$/cmdline\n  # When invoked via: bash -c \"$(curl -fsSL https://.../ct/app.sh)\"\n  # the full CT/VM script content is in /proc/$$/cmdline (same PID through source chain)\n  if [[ -r /proc/$$/cmdline ]]; then\n    content=$(tr '\\0' ' ' </proc/$$/cmdline 2>/dev/null) || true\n  fi\n\n  # Method 2: Read from the original script file (bash ct/app.sh / bash vm/app.sh)\n  if [[ -z \"$content\" ]] || ! echo \"$content\" | grep -qE 'githubusercontent\\.com|community-scripts\\.org' 2>/dev/null; then\n    if [[ -f \"$0\" ]] && [[ \"$0\" != *bash* ]]; then\n      content=$(head -10 \"$0\" 2>/dev/null) || true\n    fi\n  fi\n\n  # Extract owner/repo from URL patterns found in the script content\n  if [[ -n \"$content\" ]]; then\n    # GitHub raw URL: raw.githubusercontent.com/OWNER/REPO/...\n    owner_repo=$(echo \"$content\" | grep -oE 'raw\\.githubusercontent\\.com/[^/]+/[^/]+' | head -1 | sed 's|raw\\.githubusercontent\\.com/||') || true\n\n    # Gitea URL: git.community-scripts.org/OWNER/REPO/...\n    if [[ -z \"$owner_repo\" ]]; then\n      owner_repo=$(echo \"$content\" | grep -oE 'git\\.community-scripts\\.org/[^/]+/[^/]+' | head -1 | sed 's|git\\.community-scripts\\.org/||') || true\n    fi\n  fi\n\n  # Map detected owner/repo to canonical repo_source value\n  case \"$owner_repo\" in\n  community-scripts/ProxmoxVE) REPO_SOURCE=\"ProxmoxVE\" ;;\n  community-scripts/ProxmoxVED) REPO_SOURCE=\"ProxmoxVED\" ;;\n  \"\")\n    # No URL detected — use hardcoded fallback\n    # This value must match the repo: ProxmoxVE for production, ProxmoxVED for dev\n    REPO_SOURCE=\"ProxmoxVE\"\n    ;;\n  *)\n    # Fork or unknown repo\n    REPO_SOURCE=\"external\"\n    ;;\n  esac\n\n  export REPO_SOURCE\n}\n\n# Run detection immediately when api.func is sourced\ndetect_repo_source\n\n# ==============================================================================\n# SECTION 1: ERROR CODE DESCRIPTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# explain_exit_code()\n#\n# - Maps numeric exit codes to human-readable error descriptions\n# - Canonical source of truth for ALL exit code mappings\n# - Used by both api.func (telemetry) and error_handler.func (error display)\n# - Supports:\n#   * Generic/Shell errors (1-3, 10, 124-132, 134, 137, 139, 141, 143-146)\n#   * curl/wget errors (4-8, 16, 18, 22-28, 30, 32-36, 39, 44-48, 51-52, 55-57, 59, 61, 63, 75, 78-79, 92, 95)\n#   * Package manager errors (APT, DPKG: 100-102, 255)\n#   * Script Validation & Setup (103-123)\n#   * BSD sysexits (64-78)\n#   * Systemd/Service errors (150-154)\n#   * Python/pip/uv errors (160-162)\n#   * PostgreSQL errors (170-173)\n#   * MySQL/MariaDB errors (180-183)\n#   * MongoDB errors (190-193)\n#   * Proxmox custom codes (200-231)\n#   * Tools & Addon Scripts (232-238)\n#   * Node.js/npm errors (239, 243, 245-249)\n#   * Application Install/Update errors (250-254)\n# - Returns description string for given exit code\n# ------------------------------------------------------------------------------\nexplain_exit_code() {\n  local code=\"$1\"\n  case \"$code\" in\n  # --- Generic / Shell ---\n  1) echo \"General error / Operation not permitted\" ;;\n  2) echo \"Misuse of shell builtins (e.g. syntax error)\" ;;\n  3) echo \"General syntax or argument error\" ;;\n  10) echo \"Docker / privileged mode required (unsupported environment)\" ;;\n\n  # --- curl / wget errors (commonly seen in downloads) ---\n  4) echo \"curl: Feature not supported or protocol error\" ;;\n  5) echo \"curl: Could not resolve proxy\" ;;\n  6) echo \"curl: DNS resolution failed (could not resolve host)\" ;;\n  7) echo \"curl: Failed to connect (network unreachable / host down)\" ;;\n  8) echo \"curl: Server reply error (FTP/SFTP or apk untrusted key)\" ;;\n  16) echo \"curl: HTTP/2 framing layer error\" ;;\n  18) echo \"curl: Partial file (transfer not completed)\" ;;\n  22) echo \"curl: HTTP error returned (404, 429, 500+)\" ;;\n  23) echo \"curl: Write error (disk full or permissions)\" ;;\n  24) echo \"curl: Write to local file failed\" ;;\n  25) echo \"curl: Upload failed\" ;;\n  26) echo \"curl: Read error on local file (I/O)\" ;;\n  27) echo \"curl: Out of memory (memory allocation failed)\" ;;\n  28) echo \"curl: Operation timeout (network slow or server not responding)\" ;;\n  30) echo \"curl: FTP port command failed\" ;;\n  32) echo \"curl: FTP SIZE command failed\" ;;\n  33) echo \"curl: HTTP range error\" ;;\n  34) echo \"curl: HTTP post error\" ;;\n  35) echo \"curl: SSL/TLS handshake failed (certificate error)\" ;;\n  36) echo \"curl: FTP bad download resume\" ;;\n  39) echo \"curl: LDAP search failed\" ;;\n  44) echo \"curl: Internal error (bad function call order)\" ;;\n  45) echo \"curl: Interface error (failed to bind to specified interface)\" ;;\n  46) echo \"curl: Bad password entered\" ;;\n  47) echo \"curl: Too many redirects\" ;;\n  48) echo \"curl: Unknown command line option specified\" ;;\n  51) echo \"curl: SSL peer certificate or SSH host key verification failed\" ;;\n  52) echo \"curl: Empty reply from server (got nothing)\" ;;\n  55) echo \"curl: Failed sending network data\" ;;\n  56) echo \"curl: Receive error (connection reset by peer)\" ;;\n  57) echo \"curl: Unrecoverable poll/select error (system I/O failure)\" ;;\n  59) echo \"curl: Couldn't use specified SSL cipher\" ;;\n  61) echo \"curl: Bad/unrecognized transfer encoding\" ;;\n  63) echo \"curl: Maximum file size exceeded\" ;;\n  75) echo \"Temporary failure (retry later)\" ;;\n  78) echo \"curl: Remote file not found (404 on FTP/file)\" ;;\n  79) echo \"curl: SSH session error (key exchange/auth failed)\" ;;\n  92) echo \"curl: HTTP/2 stream error (protocol violation)\" ;;\n  95) echo \"curl: HTTP/3 layer error\" ;;\n\n  # --- Package manager / APT / DPKG ---\n  100) echo \"APT: Package manager error (broken packages / dependency problems)\" ;;\n  101) echo \"APT: Configuration error (bad sources.list, malformed config)\" ;;\n  102) echo \"APT: Lock held by another process (dpkg/apt still running)\" ;;\n\n  # --- Script Validation & Setup (103-123) ---\n  103) echo \"Validation: Shell is not Bash\" ;;\n  104) echo \"Validation: Not running as root (or invoked via sudo)\" ;;\n  105) echo \"Validation: Proxmox VE version not supported\" ;;\n  106) echo \"Validation: Architecture not supported (ARM / PiMox)\" ;;\n  107) echo \"Validation: Kernel key parameters unreadable\" ;;\n  108) echo \"Validation: Kernel key limits exceeded\" ;;\n  109) echo \"Proxmox: No available container ID after max attempts\" ;;\n  110) echo \"Proxmox: Failed to apply default.vars\" ;;\n  111) echo \"Proxmox: App defaults file not available\" ;;\n  112) echo \"Proxmox: Invalid install menu option\" ;;\n  113) echo \"LXC: Under-provisioned — user aborted update\" ;;\n  114) echo \"LXC: Storage too low — user aborted update\" ;;\n  115) echo \"Download: install.func download failed or incomplete\" ;;\n  116) echo \"Proxmox: Default bridge vmbr0 not found\" ;;\n  117) echo \"LXC: Container did not reach running state\" ;;\n  118) echo \"LXC: No IP assigned to container after timeout\" ;;\n  119) echo \"Proxmox: No valid storage for rootdir content\" ;;\n  120) echo \"Proxmox: No valid storage for vztmpl content\" ;;\n  121) echo \"LXC: Container network not ready (no IP after retries)\" ;;\n  122) echo \"LXC: No internet connectivity — user declined to continue\" ;;\n  123) echo \"LXC: Local IP detection failed\" ;;\n\n  # --- BSD sysexits.h (64-78) ---\n  64) echo \"Usage error (wrong arguments)\" ;;\n  65) echo \"Data format error (bad input data)\" ;;\n  66) echo \"Input file not found (cannot open input)\" ;;\n  67) echo \"User not found (addressee unknown)\" ;;\n  68) echo \"Host not found (hostname unknown)\" ;;\n  69) echo \"Service unavailable\" ;;\n  70) echo \"Internal software error\" ;;\n  71) echo \"System error (OS-level failure)\" ;;\n  72) echo \"Critical OS file missing\" ;;\n  73) echo \"Cannot create output file\" ;;\n  74) echo \"I/O error\" ;;\n  76) echo \"Remote protocol error\" ;;\n  77) echo \"Permission denied\" ;;\n\n  # --- Common shell/system errors ---\n  124) echo \"Command timed out (timeout command)\" ;;\n  125) echo \"Command failed to start (Docker daemon or execution error)\" ;;\n  126) echo \"Command invoked cannot execute (permission problem?)\" ;;\n  127) echo \"Command not found\" ;;\n  128) echo \"Invalid argument to exit\" ;;\n  129) echo \"Killed by SIGHUP (terminal closed / hangup)\" ;;\n  130) echo \"Aborted by user (SIGINT)\" ;;\n  131) echo \"Killed by SIGQUIT (core dumped)\" ;;\n  132) echo \"Killed by SIGILL (illegal CPU instruction)\" ;;\n  134) echo \"Process aborted (SIGABRT - possibly Node.js heap overflow)\" ;;\n  137) echo \"Killed (SIGKILL / Out of memory?)\" ;;\n  139) echo \"Segmentation fault (core dumped)\" ;;\n  141) echo \"Broken pipe (SIGPIPE - output closed prematurely)\" ;;\n  143) echo \"Terminated (SIGTERM)\" ;;\n  144) echo \"Killed by signal 16 (SIGUSR1 / SIGSTKFLT)\" ;;\n  146) echo \"Killed by signal 18 (SIGTSTP)\" ;;\n\n  # --- Systemd / Service errors (150-154) ---\n  150) echo \"Systemd: Service failed to start\" ;;\n  151) echo \"Systemd: Service unit not found\" ;;\n  152) echo \"Permission denied (EACCES)\" ;;\n  153) echo \"Build/compile failed (make/gcc/cmake)\" ;;\n  154) echo \"Node.js: Native addon build failed (node-gyp)\" ;;\n  # --- Python / pip / uv (160-162) ---\n  160) echo \"Python: Virtualenv / uv environment missing or broken\" ;;\n  161) echo \"Python: Dependency resolution failed\" ;;\n  162) echo \"Python: Installation aborted (permissions or EXTERNALLY-MANAGED)\" ;;\n\n  # --- PostgreSQL (170-173) ---\n  170) echo \"PostgreSQL: Connection failed (server not running / wrong socket)\" ;;\n  171) echo \"PostgreSQL: Authentication failed (bad user/password)\" ;;\n  172) echo \"PostgreSQL: Database does not exist\" ;;\n  173) echo \"PostgreSQL: Fatal error in query / syntax\" ;;\n\n  # --- MySQL / MariaDB (180-183) ---\n  180) echo \"MySQL/MariaDB: Connection failed (server not running / wrong socket)\" ;;\n  181) echo \"MySQL/MariaDB: Authentication failed (bad user/password)\" ;;\n  182) echo \"MySQL/MariaDB: Database does not exist\" ;;\n  183) echo \"MySQL/MariaDB: Fatal error in query / syntax\" ;;\n\n  # --- MongoDB (190-193) ---\n  190) echo \"MongoDB: Connection failed (server not running)\" ;;\n  191) echo \"MongoDB: Authentication failed (bad user/password)\" ;;\n  192) echo \"MongoDB: Database not found\" ;;\n  193) echo \"MongoDB: Fatal query error\" ;;\n\n  # --- Proxmox Custom Codes (200-231) ---\n  200) echo \"Proxmox: Failed to create lock file\" ;;\n  203) echo \"Proxmox: Missing CTID variable\" ;;\n  204) echo \"Proxmox: Missing PCT_OSTYPE variable\" ;;\n  205) echo \"Proxmox: Invalid CTID (<100)\" ;;\n  206) echo \"Proxmox: CTID already in use\" ;;\n  207) echo \"Proxmox: Password contains unescaped special characters\" ;;\n  208) echo \"Proxmox: Invalid configuration (DNS/MAC/Network format)\" ;;\n  209) echo \"Proxmox: Container creation failed\" ;;\n  210) echo \"Proxmox: Cluster not quorate\" ;;\n  211) echo \"Proxmox: Timeout waiting for template lock\" ;;\n  212) echo \"Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)\" ;;\n  213) echo \"Proxmox: Storage type does not support 'rootdir' content\" ;;\n  214) echo \"Proxmox: Not enough storage space\" ;;\n  215) echo \"Proxmox: Container created but not listed (ghost state)\" ;;\n  216) echo \"Proxmox: RootFS entry missing in config\" ;;\n  217) echo \"Proxmox: Storage not accessible\" ;;\n  218) echo \"Proxmox: Template file corrupted or incomplete\" ;;\n  219) echo \"Proxmox: CephFS does not support containers - use RBD\" ;;\n  220) echo \"Proxmox: Unable to resolve template path\" ;;\n  221) echo \"Proxmox: Template file not readable\" ;;\n  222) echo \"Proxmox: Template download failed\" ;;\n  223) echo \"Proxmox: Template not available after download\" ;;\n  224) echo \"Proxmox: PBS storage is for backups only\" ;;\n  225) echo \"Proxmox: No template available for OS/Version\" ;;\n  226) echo \"Proxmox: VM disk import or post-creation setup failed\" ;;\n  231) echo \"Proxmox: LXC stack upgrade failed\" ;;\n\n  # --- Tools & Addon Scripts (232-238) ---\n  232) echo \"Tools: Wrong execution environment (run on PVE host, not inside LXC)\" ;;\n  233) echo \"Tools: Application not installed (update prerequisite missing)\" ;;\n  234) echo \"Tools: No LXC containers found or available\" ;;\n  235) echo \"Tools: Backup or restore operation failed\" ;;\n  236) echo \"Tools: Required hardware not detected\" ;;\n  237) echo \"Tools: Dependency package installation failed\" ;;\n  238) echo \"Tools: OS or distribution not supported for this addon\" ;;\n\n  # --- Node.js / npm / pnpm / yarn (239-249) ---\n  239) echo \"npm/Node.js: Unexpected runtime error or dependency failure\" ;;\n  243) echo \"Node.js: Out of memory (JavaScript heap out of memory)\" ;;\n  245) echo \"Node.js: Invalid command-line option\" ;;\n  246) echo \"Node.js: Internal JavaScript Parse Error\" ;;\n  247) echo \"Node.js: Fatal internal error\" ;;\n  248) echo \"Node.js: Invalid C++ addon / N-API failure\" ;;\n  249) echo \"npm/pnpm/yarn: Unknown fatal error\" ;;\n\n  # --- Application Install/Update Errors (250-254) ---\n  250) echo \"App: Download failed or version not determined\" ;;\n  251) echo \"App: File extraction failed (corrupt or incomplete archive)\" ;;\n  252) echo \"App: Required file or resource not found\" ;;\n  253) echo \"App: Data migration required — update aborted\" ;;\n  254) echo \"App: User declined prompt or input timed out\" ;;\n\n  # --- DPKG ---\n  255) echo \"DPKG: Fatal internal error\" ;;\n\n  # --- Default ---\n  *) echo \"Unknown error\" ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# json_escape()\n#\n# - Escapes a string for safe JSON embedding\n# - Strips ANSI escape sequences and non-printable control characters\n# - Handles backslashes, quotes, newlines, tabs, and carriage returns\n# - Uses jq when available (guaranteed correct), falls back to awk\n# ------------------------------------------------------------------------------\njson_escape() {\n  local input\n  # Pipeline: strip ANSI → remove control chars → escape for JSON\n  input=$(printf '%s' \"$1\" |\n    sed 's/\\x1b\\[[0-9;]*[a-zA-Z]//g' |\n    tr -d '\\000-\\010\\013\\014\\016-\\037\\177\\r')\n\n  # Prefer jq: guaranteed correct JSON string encoding (handles all edge cases)\n  if command -v jq &>/dev/null; then\n    # jq -Rs reads raw stdin as string, outputs JSON-encoded string with quotes.\n    # We strip the surrounding quotes since the heredoc adds them.\n    printf '%s' \"$input\" | jq -Rs '.' | sed 's/^\"//;s/\"$//'\n    return\n  fi\n\n  # Fallback: character-by-character processing with awk (avoids gsub replacement pitfalls)\n  printf '%s' \"$input\" |\n    awk '\n        BEGIN { ORS=\"\" }\n        {\n          if (NR > 1) printf \"%s\", \"\\\\n\"\n          for (i = 1; i <= length($0); i++) {\n            c = substr($0, i, 1)\n            if (c == \"\\\\\") printf \"%s\", \"\\\\\\\\\"\n            else if (c == \"\\\"\") printf \"%s\", \"\\\\\\\"\"\n            else if (c == \"\\t\") printf \"%s\", \"\\\\t\"\n            else printf \"%s\", c\n          }\n        }'\n}\n\n# ------------------------------------------------------------------------------\n# get_error_text()\n#\n# - Returns last 20 lines of the active log (INSTALL_LOG or BUILD_LOG)\n# - Falls back to combined log or BUILD_LOG if primary is not accessible\n# - Handles container paths that don't exist on the host\n# ------------------------------------------------------------------------------\nget_error_text() {\n  local logfile=\"\"\n  if declare -f get_active_logfile >/dev/null 2>&1; then\n    logfile=$(get_active_logfile)\n  elif [[ -n \"${INSTALL_LOG:-}\" ]]; then\n    logfile=\"$INSTALL_LOG\"\n  elif [[ -n \"${BUILD_LOG:-}\" ]]; then\n    logfile=\"$BUILD_LOG\"\n  fi\n\n  # If logfile is inside container (e.g. /root/.install-*), try the host copy\n  if [[ -n \"$logfile\" && ! -s \"$logfile\" ]]; then\n    # Try combined log: /tmp/<app>-<CTID>-<SESSION_ID>.log\n    if [[ -n \"${CTID:-}\" && -n \"${SESSION_ID:-}\" ]]; then\n      local combined_log=\"/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log\"\n      if [[ -s \"$combined_log\" ]]; then\n        logfile=\"$combined_log\"\n      fi\n    fi\n  fi\n\n  # Also try BUILD_LOG as fallback if primary log is empty/missing\n  if [[ -z \"$logfile\" || ! -s \"$logfile\" ]] && [[ -n \"${BUILD_LOG:-}\" && -s \"${BUILD_LOG}\" ]]; then\n    logfile=\"$BUILD_LOG\"\n  fi\n\n  # Try SILENT_LOGFILE as last resort (captures $STD command output)\n  if [[ -z \"$logfile\" || ! -s \"$logfile\" ]] && [[ -n \"${SILENT_LOGFILE:-}\" && -s \"${SILENT_LOGFILE}\" ]]; then\n    logfile=\"$SILENT_LOGFILE\"\n  fi\n\n  if [[ -n \"$logfile\" && -s \"$logfile\" ]]; then\n    tail -n 20 \"$logfile\" 2>/dev/null | sed 's/\\r$//' | sed 's/\\x1b\\[[0-9;]*[a-zA-Z]//g'\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# get_full_log()\n#\n# - Returns the FULL installation log (build + install combined)\n# - Calls ensure_log_on_host() to pull container log if needed\n# - Strips ANSI escape codes and carriage returns\n# - Truncates to max_bytes (default: 120KB) to stay within API limits\n# - Used for the error telemetry field (full trace instead of 20 lines)\n# ------------------------------------------------------------------------------\nget_full_log() {\n  local max_bytes=\"${1:-122880}\" # 120KB default\n  local logfile=\"\"\n\n  # Ensure logs are available on host (pulls from container if needed)\n  if declare -f ensure_log_on_host >/dev/null 2>&1; then\n    ensure_log_on_host\n  fi\n\n  # Try combined log first (most complete)\n  if [[ -n \"${CTID:-}\" && -n \"${SESSION_ID:-}\" ]]; then\n    local combined_log=\"/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log\"\n    if [[ -s \"$combined_log\" ]]; then\n      logfile=\"$combined_log\"\n    fi\n  fi\n\n  # Fall back to INSTALL_LOG\n  if [[ -z \"$logfile\" || ! -s \"$logfile\" ]]; then\n    if [[ -n \"${INSTALL_LOG:-}\" && -s \"${INSTALL_LOG}\" ]]; then\n      logfile=\"$INSTALL_LOG\"\n    fi\n  fi\n\n  # Fall back to BUILD_LOG\n  if [[ -z \"$logfile\" || ! -s \"$logfile\" ]]; then\n    if [[ -n \"${BUILD_LOG:-}\" && -s \"${BUILD_LOG}\" ]]; then\n      logfile=\"$BUILD_LOG\"\n    fi\n  fi\n\n  # Fall back to SILENT_LOGFILE (captures $STD command output)\n  if [[ -z \"$logfile\" || ! -s \"$logfile\" ]]; then\n    if [[ -n \"${SILENT_LOGFILE:-}\" && -s \"${SILENT_LOGFILE}\" ]]; then\n      logfile=\"$SILENT_LOGFILE\"\n    fi\n  fi\n\n  if [[ -n \"$logfile\" && -s \"$logfile\" ]]; then\n    # Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR)\n    sed 's/\\r$//' \"$logfile\" 2>/dev/null |\n      sed 's/\\x1b\\[[0-9;]*[a-zA-Z]//g' |\n      sed -E 's/([0-9]{1,3}\\.)[0-9]{1,3}\\.[0-9]{1,3}/\\1x.x/g' |\n      head -c \"$max_bytes\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# build_error_string()\n#\n# - Builds a structured error string for telemetry reporting\n# - Format: \"exit_code=<N> | <explanation>\\n---\\n<last 20 log lines>\"\n# - If no log lines available, returns just the explanation\n# - Arguments:\n#   * $1: exit_code (numeric)\n#   * $2: log_text (optional, output from get_error_text)\n# - Returns structured error string via stdout\n# ------------------------------------------------------------------------------\nbuild_error_string() {\n  local exit_code=\"${1:-1}\"\n  local log_text=\"${2:-}\"\n  local explanation\n  explanation=$(explain_exit_code \"$exit_code\")\n\n  if [[ -n \"$log_text\" ]]; then\n    # Structured format: header + separator + log lines\n    printf 'exit_code=%s | %s\\n---\\n%s' \"$exit_code\" \"$explanation\" \"$log_text\"\n  else\n    # No log available - just the explanation with exit code\n    printf 'exit_code=%s | %s' \"$exit_code\" \"$explanation\"\n  fi\n}\n\n# ==============================================================================\n# SECTION 2: TELEMETRY FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# detect_gpu()\n#\n# - Detects GPU vendor, model, and passthrough type\n# - Sets GPU_VENDOR, GPU_MODEL, and GPU_PASSTHROUGH globals\n# - Used for GPU analytics\n# ------------------------------------------------------------------------------\ndetect_gpu() {\n  GPU_VENDOR=\"unknown\"\n  GPU_MODEL=\"\"\n  GPU_PASSTHROUGH=\"unknown\"\n\n  local gpu_line\n  gpu_line=$(lspci 2>/dev/null | grep -iE \"VGA|3D|Display\" | head -1 || true)\n\n  if [[ -n \"$gpu_line\" ]]; then\n    # Extract model: everything after the colon, clean up\n    GPU_MODEL=$(echo \"$gpu_line\" | sed 's/.*: //' | sed 's/ (rev .*)$//' | cut -c1-64)\n\n    # Detect vendor and passthrough type\n    if echo \"$gpu_line\" | grep -qi \"Intel\"; then\n      GPU_VENDOR=\"intel\"\n      GPU_PASSTHROUGH=\"igpu\"\n    elif echo \"$gpu_line\" | grep -qi \"AMD\\|ATI\"; then\n      GPU_VENDOR=\"amd\"\n      if echo \"$gpu_line\" | grep -qi \"Radeon RX\\|Radeon Pro\"; then\n        GPU_PASSTHROUGH=\"dgpu\"\n      else\n        GPU_PASSTHROUGH=\"igpu\"\n      fi\n    elif echo \"$gpu_line\" | grep -qi \"NVIDIA\"; then\n      GPU_VENDOR=\"nvidia\"\n      GPU_PASSTHROUGH=\"dgpu\"\n    fi\n  fi\n\n  export GPU_VENDOR GPU_MODEL GPU_PASSTHROUGH\n}\n\n# ------------------------------------------------------------------------------\n# detect_cpu()\n#\n# - Detects CPU vendor and model\n# - Sets CPU_VENDOR (intel/amd/arm/unknown) and CPU_MODEL globals\n# - Used for CPU analytics\n# ------------------------------------------------------------------------------\ndetect_cpu() {\n  CPU_VENDOR=\"unknown\"\n  CPU_MODEL=\"\"\n\n  if [[ -f /proc/cpuinfo ]]; then\n    local vendor_id\n    vendor_id=$(grep -m1 \"vendor_id\" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ' || true)\n\n    case \"$vendor_id\" in\n    GenuineIntel) CPU_VENDOR=\"intel\" ;;\n    AuthenticAMD) CPU_VENDOR=\"amd\" ;;\n    *)\n      # ARM doesn't have vendor_id, check for CPU implementer\n      if grep -qi \"CPU implementer\" /proc/cpuinfo 2>/dev/null; then\n        CPU_VENDOR=\"arm\"\n      fi\n      ;;\n    esac\n\n    # Extract model name and clean it up\n    CPU_MODEL=$(grep -m1 \"model name\" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/  */ /g' | cut -c1-64 || true)\n  fi\n\n  export CPU_VENDOR CPU_MODEL\n}\n\n# ------------------------------------------------------------------------------\n# detect_ram()\n#\n# - Detects RAM speed using dmidecode\n# - Sets RAM_SPEED global (e.g., \"4800\" for DDR5-4800)\n# - Requires root access for dmidecode\n# - Returns empty if not available or if speed is \"Unknown\" (nested VMs)\n# ------------------------------------------------------------------------------\ndetect_ram() {\n  RAM_SPEED=\"\"\n\n  if command -v dmidecode &>/dev/null; then\n    # Get configured memory speed (actual running speed)\n    # Use || true to handle \"Unknown\" values in nested VMs (no numeric match)\n    RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 \"Configured Memory Speed:\" | grep -oE \"[0-9]+\" | head -1) || true\n\n    # Fallback to Speed: if Configured not available\n    if [[ -z \"$RAM_SPEED\" ]]; then\n      RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 \"Speed:\" | grep -oE \"[0-9]+\" | head -1) || true\n    fi\n  fi\n\n  export RAM_SPEED\n}\n\n# ------------------------------------------------------------------------------\n# post_to_api()\n#\n# - Sends LXC container creation statistics to telemetry ingest service\n# - Only executes if:\n#   * curl is available\n#   * DIAGNOSTICS=yes\n#   * RANDOM_UUID is set\n# - Payload includes:\n#   * Container type, disk size, CPU cores, RAM\n#   * OS type and version\n#   * Application name (NSAPP)\n#   * Installation method\n#   * PVE version\n#   * Status: \"installing\"\n#   * Random UUID for session tracking\n# - Anonymous telemetry (no personal data)\n# - Never blocks or fails script execution\n# ------------------------------------------------------------------------------\npost_to_api() {\n  # Prevent duplicate submissions (post_to_api is called from multiple places)\n  [[ \"${POST_TO_API_DONE:-}\" == \"true\" ]] && return 0\n\n  # Silent fail - telemetry should never break scripts\n  command -v curl &>/dev/null || {\n    [[ \"${DEV_MODE:-}\" == \"true\" ]] && echo \"[DEBUG] curl not found, skipping\" >&2\n    return 0\n  }\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && {\n    [[ \"${DEV_MODE:-}\" == \"true\" ]] && echo \"[DEBUG] DIAGNOSTICS=no, skipping\" >&2\n    return 0\n  }\n  [[ -z \"${RANDOM_UUID:-}\" ]] && {\n    [[ \"${DEV_MODE:-}\" == \"true\" ]] && echo \"[DEBUG] RANDOM_UUID empty, skipping\" >&2\n    return 0\n  }\n\n  [[ \"${DEV_MODE:-}\" == \"true\" ]] && echo \"[DEBUG] post_to_api() DIAGNOSTICS=$DIAGNOSTICS RANDOM_UUID=$RANDOM_UUID NSAPP=$NSAPP\" >&2\n\n  # Set type for later status updates (preserve if already set, e.g. turnkey)\n  TELEMETRY_TYPE=\"${TELEMETRY_TYPE:-lxc}\"\n\n  local pve_version=\"\"\n  if command -v pveversion &>/dev/null; then\n    pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true\n  fi\n\n  # Detect GPU if not already set\n  if [[ -z \"${GPU_VENDOR:-}\" ]]; then\n    detect_gpu\n  fi\n  local gpu_vendor=\"${GPU_VENDOR:-unknown}\"\n  local gpu_model\n  gpu_model=$(json_escape \"${GPU_MODEL:-}\")\n  local gpu_passthrough=\"${GPU_PASSTHROUGH:-unknown}\"\n\n  # Detect CPU if not already set\n  if [[ -z \"${CPU_VENDOR:-}\" ]]; then\n    detect_cpu\n  fi\n  local cpu_vendor=\"${CPU_VENDOR:-unknown}\"\n  local cpu_model\n  cpu_model=$(json_escape \"${CPU_MODEL:-}\")\n\n  # Detect RAM if not already set\n  if [[ -z \"${RAM_SPEED:-}\" ]]; then\n    detect_ram\n  fi\n  local ram_speed=\"${RAM_SPEED:-}\"\n\n  local JSON_PAYLOAD\n  JSON_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${RANDOM_UUID}\",\n    \"execution_id\": \"${EXECUTION_ID:-${RANDOM_UUID}}\",\n    \"type\": \"${TELEMETRY_TYPE}\",\n    \"nsapp\": \"${NSAPP:-unknown}\",\n    \"status\": \"installing\",\n    \"ct_type\": ${CT_TYPE:-1},\n    \"disk_size\": ${DISK_SIZE:-0},\n    \"core_count\": ${CORE_COUNT:-0},\n    \"ram_size\": ${RAM_SIZE:-0},\n    \"os_type\": \"${var_os:-}\",\n    \"os_version\": \"${var_version:-}\",\n    \"pve_version\": \"${pve_version}\",\n    \"method\": \"${METHOD:-default}\",\n    \"cpu_vendor\": \"${cpu_vendor}\",\n    \"cpu_model\": \"${cpu_model}\",\n    \"gpu_vendor\": \"${gpu_vendor}\",\n    \"gpu_model\": \"${gpu_model}\",\n    \"gpu_passthrough\": \"${gpu_passthrough}\",\n    \"ram_speed\": \"${ram_speed}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  [[ \"${DEV_MODE:-}\" == \"true\" ]] && echo \"[DEBUG] Sending to: $TELEMETRY_URL\" >&2\n  [[ \"${DEV_MODE:-}\" == \"true\" ]] && echo \"[DEBUG] Payload: $JSON_PAYLOAD\" >&2\n\n  # Send initial \"installing\" record with retry.\n  # This record MUST exist for all subsequent updates to succeed.\n  local http_code=\"\" attempt\n  local _post_success=false\n  for attempt in 1 2 3; do\n    if [[ \"${DEV_MODE:-}\" == \"true\" ]]; then\n      http_code=$(curl -sS -w \"%{http_code}\" -m \"${TELEMETRY_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n        -H \"Content-Type: application/json\" \\\n        -d \"$JSON_PAYLOAD\" -o /dev/stderr 2>&1) || http_code=\"000\"\n      echo \"[DEBUG] post_to_api attempt $attempt HTTP=$http_code\" >&2\n    else\n      http_code=$(curl -sS -w \"%{http_code}\" -m \"${TELEMETRY_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n        -H \"Content-Type: application/json\" \\\n        -d \"$JSON_PAYLOAD\" -o /dev/null 2>/dev/null) || http_code=\"000\"\n    fi\n    if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n      _post_success=true\n      break\n    fi\n    [[ \"$attempt\" -lt 3 ]] && sleep 1\n  done\n\n  # Only mark done if at least one attempt succeeded.\n  # If all 3 failed, POST_TO_API_DONE stays false so post_update_to_api\n  # and on_exit() know the initial record was never created.\n  # The server has fallback logic to create a new record on status updates,\n  # so subsequent calls can still succeed even without the initial record.\n  POST_TO_API_DONE=${_post_success}\n}\n\n# ------------------------------------------------------------------------------\n# post_to_api_vm()\n#\n# - Sends VM creation statistics to telemetry ingest service\n# - Reads DIAGNOSTICS from /usr/local/community-scripts/diagnostics file\n# - Payload differences from LXC:\n#   * ct_type=2 (VM instead of LXC)\n#   * type=\"vm\"\n#   * Disk size without 'G' suffix\n# - Includes hardware detection: CPU, GPU, RAM speed\n# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set\n# - Never blocks or fails script execution\n# ------------------------------------------------------------------------------\npost_to_api_vm() {\n  # Read diagnostics setting from file\n  if [[ -f /usr/local/community-scripts/diagnostics ]]; then\n    DIAGNOSTICS=$(grep -i \"^DIAGNOSTICS=\" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true\n  fi\n\n  # Silent fail - telemetry should never break scripts\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  # Set type for later status updates\n  TELEMETRY_TYPE=\"vm\"\n\n  local pve_version=\"\"\n  if command -v pveversion &>/dev/null; then\n    pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true\n  fi\n\n  # Detect GPU if not already set\n  if [[ -z \"${GPU_VENDOR:-}\" ]]; then\n    detect_gpu\n  fi\n  local gpu_vendor=\"${GPU_VENDOR:-unknown}\"\n  local gpu_model\n  gpu_model=$(json_escape \"${GPU_MODEL:-}\")\n  local gpu_passthrough=\"${GPU_PASSTHROUGH:-unknown}\"\n\n  # Detect CPU if not already set\n  if [[ -z \"${CPU_VENDOR:-}\" ]]; then\n    detect_cpu\n  fi\n  local cpu_vendor=\"${CPU_VENDOR:-unknown}\"\n  local cpu_model\n  cpu_model=$(json_escape \"${CPU_MODEL:-}\")\n\n  # Detect RAM if not already set\n  if [[ -z \"${RAM_SPEED:-}\" ]]; then\n    detect_ram\n  fi\n  local ram_speed=\"${RAM_SPEED:-}\"\n\n  # Remove 'G' suffix from disk size\n  local DISK_SIZE_API=\"${DISK_SIZE%G}\"\n\n  local JSON_PAYLOAD\n  JSON_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${RANDOM_UUID}\",\n    \"execution_id\": \"${EXECUTION_ID:-${RANDOM_UUID}}\",\n    \"type\": \"vm\",\n    \"nsapp\": \"${NSAPP:-unknown}\",\n    \"status\": \"installing\",\n    \"ct_type\": 2,\n    \"disk_size\": ${DISK_SIZE_API:-0},\n    \"core_count\": ${CORE_COUNT:-0},\n    \"ram_size\": ${RAM_SIZE:-0},\n    \"os_type\": \"${var_os:-}\",\n    \"os_version\": \"${var_version:-}\",\n    \"pve_version\": \"${pve_version}\",\n    \"method\": \"${METHOD:-default}\",\n    \"cpu_vendor\": \"${cpu_vendor}\",\n    \"cpu_model\": \"${cpu_model}\",\n    \"gpu_vendor\": \"${gpu_vendor}\",\n    \"gpu_model\": \"${gpu_model}\",\n    \"gpu_passthrough\": \"${gpu_passthrough}\",\n    \"ram_speed\": \"${ram_speed}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  # Send initial \"installing\" record with retry (must succeed for updates to work)\n  local http_code=\"\" attempt\n  local _post_success=false\n  for attempt in 1 2 3; do\n    http_code=$(curl -sS -w \"%{http_code}\" -m \"${TELEMETRY_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n      -H \"Content-Type: application/json\" \\\n      -d \"$JSON_PAYLOAD\" -o /dev/null 2>/dev/null) || http_code=\"000\"\n    if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n      _post_success=true\n      break\n    fi\n    [[ \"$attempt\" -lt 3 ]] && sleep 1\n  done\n\n  POST_TO_API_DONE=${_post_success}\n}\n\n# ------------------------------------------------------------------------------\n# post_progress_to_api()\n#\n# - Lightweight progress ping from host or container\n# - Updates the existing telemetry record status\n# - Arguments:\n#   * $1: status (optional, default: \"configuring\")\n#         Valid values: \"validation\", \"configuring\"\n# - Signals that the installation is actively progressing (not stuck)\n# - Fire-and-forget: never blocks or fails the script\n# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set\n# - Can be called multiple times safely\n# ------------------------------------------------------------------------------\npost_progress_to_api() {\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  local progress_status=\"${1:-configuring}\"\n  local app_name=\"${NSAPP:-${app:-unknown}}\"\n  local telemetry_type=\"${TELEMETRY_TYPE:-lxc}\"\n\n  curl -fsS -m 5 -X POST \"${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"{\\\"random_id\\\":\\\"${RANDOM_UUID}\\\",\\\"execution_id\\\":\\\"${EXECUTION_ID:-${RANDOM_UUID}}\\\",\\\"type\\\":\\\"${telemetry_type}\\\",\\\"nsapp\\\":\\\"${app_name}\\\",\\\"status\\\":\\\"${progress_status}\\\"}\" &>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# post_update_to_api()\n#\n# - Reports installation completion status to telemetry ingest service\n# - Prevents duplicate submissions via POST_UPDATE_DONE flag\n# - Arguments:\n#   * $1: status (\"done\" or \"failed\")\n#   * $2: exit_code (numeric, default: 1 for failed, 0 for done)\n# - Payload includes:\n#   * Final status (mapped: \"done\"→\"success\", \"failed\"→\"failed\")\n#   * Error description via explain_exit_code()\n#   * Numeric exit code\n# - Only executes once per session\n# - Never blocks or fails script execution\n# ------------------------------------------------------------------------------\npost_update_to_api() {\n  # Silent fail - telemetry should never break scripts\n  command -v curl &>/dev/null || return 0\n\n  # Support \"force\" mode (3rd arg) to bypass duplicate check for retries after cleanup\n  local force=\"${3:-}\"\n  POST_UPDATE_DONE=${POST_UPDATE_DONE:-false}\n  if [[ \"$POST_UPDATE_DONE\" == \"true\" && \"$force\" != \"force\" ]]; then\n    return 0\n  fi\n\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  local status=\"${1:-failed}\"\n  local raw_exit_code=\"${2:-1}\"\n  local exit_code=0 error=\"\" pb_status error_category=\"\"\n\n  # Get GPU info (if detected)\n  local gpu_vendor=\"${GPU_VENDOR:-unknown}\"\n  local gpu_model\n  gpu_model=$(json_escape \"${GPU_MODEL:-}\")\n  local gpu_passthrough=\"${GPU_PASSTHROUGH:-unknown}\"\n\n  # Get CPU info (if detected)\n  local cpu_vendor=\"${CPU_VENDOR:-unknown}\"\n  local cpu_model\n  cpu_model=$(json_escape \"${CPU_MODEL:-}\")\n\n  # Get RAM info (if detected)\n  local ram_speed=\"${RAM_SPEED:-}\"\n\n  # Map status to telemetry values: installing, success, failed, unknown\n  case \"$status\" in\n  done | success)\n    pb_status=\"success\"\n    exit_code=0\n    error=\"\"\n    error_category=\"\"\n    ;;\n  failed)\n    pb_status=\"failed\"\n    ;;\n  *)\n    pb_status=\"unknown\"\n    ;;\n  esac\n\n  # For failed/unknown status, resolve exit code and error description\n  local short_error=\"\" medium_error=\"\"\n  if [[ \"$pb_status\" == \"failed\" ]] || [[ \"$pb_status\" == \"unknown\" ]]; then\n    if [[ \"$raw_exit_code\" =~ ^[0-9]+$ ]]; then\n      exit_code=\"$raw_exit_code\"\n    else\n      exit_code=1\n    fi\n    # Get full installation log for error field\n    local log_text=\"\"\n    log_text=$(get_full_log 122880) || true # 120KB max\n    if [[ -z \"$log_text\" ]]; then\n      # Fallback to last 20 lines\n      log_text=$(get_error_text)\n    fi\n    local full_error\n    full_error=$(build_error_string \"$exit_code\" \"$log_text\")\n    error=$(json_escape \"$full_error\")\n    short_error=$(json_escape \"$(explain_exit_code \"$exit_code\")\")\n    error_category=$(categorize_error \"$exit_code\")\n    [[ -z \"$error\" ]] && error=\"Unknown error\"\n\n    # Build medium error for attempt 2: explanation + last 100 log lines (≤16KB)\n    # This is the critical middle ground between full 120KB log and generic-only description\n    local medium_log=\"\"\n    medium_log=$(get_full_log 16384) || true # 16KB max\n    if [[ -z \"$medium_log\" ]]; then\n      medium_log=$(get_error_text) || true\n    fi\n    local medium_full\n    medium_full=$(build_error_string \"$exit_code\" \"$medium_log\")\n    medium_error=$(json_escape \"$medium_full\")\n    [[ -z \"$medium_error\" ]] && medium_error=\"$short_error\"\n  fi\n\n  # Calculate duration if timer was started\n  local duration=0\n  if [[ -n \"${INSTALL_START_TIME:-}\" ]]; then\n    duration=$(($(date +%s) - INSTALL_START_TIME))\n  fi\n\n  # Get PVE version\n  local pve_version=\"\"\n  if command -v pveversion &>/dev/null; then\n    pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true\n  fi\n\n  local http_code=\"\"\n\n  # Strip 'G' suffix from disk size (VMs set DISK_SIZE=32G)\n  local DISK_SIZE_API=\"${DISK_SIZE:-0}\"\n  DISK_SIZE_API=\"${DISK_SIZE_API%G}\"\n  [[ ! \"$DISK_SIZE_API\" =~ ^[0-9]+$ ]] && DISK_SIZE_API=0\n\n  # ── Attempt 1: Full payload with complete error text (includes full log) ──\n  local JSON_PAYLOAD\n  JSON_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${RANDOM_UUID}\",\n    \"execution_id\": \"${EXECUTION_ID:-${RANDOM_UUID}}\",\n    \"type\": \"${TELEMETRY_TYPE:-lxc}\",\n    \"nsapp\": \"${NSAPP:-unknown}\",\n    \"status\": \"${pb_status}\",\n    \"ct_type\": ${CT_TYPE:-1},\n    \"disk_size\": ${DISK_SIZE_API},\n    \"core_count\": ${CORE_COUNT:-0},\n    \"ram_size\": ${RAM_SIZE:-0},\n    \"os_type\": \"${var_os:-}\",\n    \"os_version\": \"${var_version:-}\",\n    \"pve_version\": \"${pve_version}\",\n    \"method\": \"${METHOD:-default}\",\n    \"exit_code\": ${exit_code},\n    \"error\": \"${error}\",\n    \"error_category\": \"${error_category}\",\n    \"install_duration\": ${duration},\n    \"cpu_vendor\": \"${cpu_vendor}\",\n    \"cpu_model\": \"${cpu_model}\",\n    \"gpu_vendor\": \"${gpu_vendor}\",\n    \"gpu_model\": \"${gpu_model}\",\n    \"gpu_passthrough\": \"${gpu_passthrough}\",\n    \"ram_speed\": \"${ram_speed}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  http_code=$(curl -sS -w \"%{http_code}\" -m \"${STATUS_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$JSON_PAYLOAD\" -o /dev/null 2>/dev/null) || http_code=\"000\"\n\n  if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n    POST_UPDATE_DONE=true\n    return 0\n  fi\n\n  # ── Attempt 2: Medium error text (truncated log ≤16KB instead of full 120KB) ──\n  sleep 1\n  local RETRY_PAYLOAD\n  RETRY_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${RANDOM_UUID}\",\n    \"execution_id\": \"${EXECUTION_ID:-${RANDOM_UUID}}\",\n    \"type\": \"${TELEMETRY_TYPE:-lxc}\",\n    \"nsapp\": \"${NSAPP:-unknown}\",\n    \"status\": \"${pb_status}\",\n    \"ct_type\": ${CT_TYPE:-1},\n    \"disk_size\": ${DISK_SIZE_API},\n    \"core_count\": ${CORE_COUNT:-0},\n    \"ram_size\": ${RAM_SIZE:-0},\n    \"os_type\": \"${var_os:-}\",\n    \"os_version\": \"${var_version:-}\",\n    \"pve_version\": \"${pve_version}\",\n    \"method\": \"${METHOD:-default}\",\n    \"exit_code\": ${exit_code},\n    \"error\": \"${medium_error}\",\n    \"error_category\": \"${error_category}\",\n    \"install_duration\": ${duration},\n    \"cpu_vendor\": \"${cpu_vendor}\",\n    \"cpu_model\": \"${cpu_model}\",\n    \"gpu_vendor\": \"${gpu_vendor}\",\n    \"gpu_model\": \"${gpu_model}\",\n    \"gpu_passthrough\": \"${gpu_passthrough}\",\n    \"ram_speed\": \"${ram_speed}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  http_code=$(curl -sS -w \"%{http_code}\" -m \"${STATUS_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$RETRY_PAYLOAD\" -o /dev/null 2>/dev/null) || http_code=\"000\"\n\n  if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n    POST_UPDATE_DONE=true\n    return 0\n  fi\n\n  # ── Attempt 3: Minimal payload with medium error (bare minimum to set status) ──\n  sleep 2\n  local MINIMAL_PAYLOAD\n  MINIMAL_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${RANDOM_UUID}\",\n    \"execution_id\": \"${EXECUTION_ID:-${RANDOM_UUID}}\",\n    \"type\": \"${TELEMETRY_TYPE:-lxc}\",\n    \"nsapp\": \"${NSAPP:-unknown}\",\n    \"status\": \"${pb_status}\",\n    \"exit_code\": ${exit_code},\n    \"error\": \"${medium_error}\",\n    \"error_category\": \"${error_category}\",\n    \"install_duration\": ${duration}\n}\nEOF\n  )\n\n  http_code=$(curl -sS -w \"%{http_code}\" -m \"${STATUS_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$MINIMAL_PAYLOAD\" -o /dev/null 2>/dev/null) || http_code=\"000\"\n\n  if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n    POST_UPDATE_DONE=true\n    return 0\n  fi\n\n  # All 3 attempts failed — do NOT set POST_UPDATE_DONE=true.\n  # This allows the EXIT trap (on_exit in error_handler.func) to retry.\n  # No infinite loop risk: EXIT trap fires exactly once.\n}\n\n# ==============================================================================\n# SECTION 3: EXTENDED TELEMETRY FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# categorize_error()\n#\n# - Maps exit codes to error categories for better analytics\n# - Categories: network, storage, dependency, permission, timeout, config, resource, unknown\n# - Used to group errors in dashboard\n# ------------------------------------------------------------------------------\ncategorize_error() {\n  # Allow build.func to override category based on log analysis (exit code 1 subclassification)\n  if [[ -n \"${ERROR_CATEGORY_OVERRIDE:-}\" ]]; then\n    echo \"$ERROR_CATEGORY_OVERRIDE\"\n    return\n  fi\n\n  local code=\"$1\"\n  case \"$code\" in\n  # Network errors (curl/wget)\n  6 | 7 | 22 | 35) echo \"network\" ;;\n\n  # Docker / Privileged mode required\n  10) echo \"config\" ;;\n\n  # Timeout errors\n  28 | 124 | 211) echo \"timeout\" ;;\n\n  # Storage errors (Proxmox storage)\n  214 | 217 | 219 | 224) echo \"storage\" ;;\n\n  # Dependency/Package errors (APT, DPKG, pip, commands)\n  100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo \"dependency\" ;;\n\n  # Permission errors\n  126 | 152) echo \"permission\" ;;\n\n  # Configuration errors (Proxmox config, invalid args)\n  128 | 203 | 204 | 205 | 206 | 207 | 208) echo \"config\" ;;\n\n  # Proxmox container/template errors\n  200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo \"proxmox\" ;;\n\n  # Service/Systemd errors\n  150 | 151 | 153 | 154) echo \"service\" ;;\n\n  # Database errors (PostgreSQL, MySQL, MongoDB)\n  170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo \"database\" ;;\n\n  # Node.js / JavaScript runtime errors\n  243 | 245 | 246 | 247 | 248 | 249) echo \"runtime\" ;;\n\n  # Python environment errors\n  # (already covered: 160-162 under dependency)\n\n  # Aborted by user (SIGHUP=terminal closed, SIGINT=Ctrl+C, SIGTERM=killed)\n  129 | 130 | 143) echo \"user_aborted\" ;;\n\n  # Resource errors (OOM, SIGKILL, SIGABRT)\n  134 | 137) echo \"resource\" ;;\n\n  # Signal/Process errors (SIGPIPE, SIGSEGV)\n  139 | 141) echo \"signal\" ;;\n\n  # Shell errors (general error, syntax error)\n  1 | 2) echo \"shell\" ;;\n\n  # Default - truly unknown\n  *) echo \"unknown\" ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# start_install_timer()\n#\n# - Captures start time for installation duration tracking\n# - Call at the beginning of installation\n# - Sets INSTALL_START_TIME global variable\n# ------------------------------------------------------------------------------\nstart_install_timer() {\n  INSTALL_START_TIME=$(date +%s)\n  export INSTALL_START_TIME\n}\n\n# ------------------------------------------------------------------------------\n# get_install_duration()\n#\n# - Returns elapsed seconds since start_install_timer() was called\n# - Returns 0 if timer was not started\n# ------------------------------------------------------------------------------\nget_install_duration() {\n  if [[ -z \"${INSTALL_START_TIME:-}\" ]]; then\n    echo \"0\"\n    return\n  fi\n  local now=$(date +%s)\n  echo $((now - INSTALL_START_TIME))\n}\n\n# ------------------------------------------------------------------------------\n# _telemetry_report_exit()\n#\n# - Internal handler called by EXIT trap set in init_tool_telemetry()\n# - Determines success/failure from exit code and reports via appropriate API\n# - Arguments:\n#   * $1: exit_code from the script\n# ------------------------------------------------------------------------------\n_telemetry_report_exit() {\n  local ec=\"${1:-0}\"\n  local status=\"success\"\n  [[ \"$ec\" -ne 0 ]] && status=\"failed\"\n\n  # Lazy name resolution: use explicit name, fall back to $APP, then \"unknown\"\n  local name=\"${TELEMETRY_TOOL_NAME:-${APP:-unknown}}\"\n\n  if [[ \"${TELEMETRY_TOOL_TYPE:-pve}\" == \"addon\" ]]; then\n    post_addon_to_api \"$name\" \"$status\" \"$ec\"\n  else\n    post_tool_to_api \"$name\" \"$status\" \"$ec\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# init_tool_telemetry()\n#\n# - One-line telemetry setup for tools/addon scripts\n# - Reads DIAGNOSTICS from /usr/local/community-scripts/diagnostics\n#   (persisted on PVE host during first build, and inside containers by install.func)\n# - Starts install timer for duration tracking\n# - Sets EXIT trap to automatically report success/failure on script exit\n# - Arguments:\n#   * $1: tool_name (optional, falls back to $APP at exit time)\n#   * $2: type (\"pve\" for PVE host scripts, \"addon\" for container addons)\n# - Usage:\n#   source <(curl -fsSL .../misc/api.func) 2>/dev/null || true\n#   init_tool_telemetry \"post-pve-install\" \"pve\"\n#   init_tool_telemetry \"\" \"addon\"  # uses $APP at exit time\n# ------------------------------------------------------------------------------\ninit_tool_telemetry() {\n  local name=\"${1:-}\"\n  local type=\"${2:-pve}\"\n\n  [[ -n \"$name\" ]] && TELEMETRY_TOOL_NAME=\"$name\"\n  TELEMETRY_TOOL_TYPE=\"$type\"\n\n  # Read diagnostics opt-in/opt-out\n  if [[ -f /usr/local/community-scripts/diagnostics ]]; then\n    DIAGNOSTICS=$(grep -i \"^DIAGNOSTICS=\" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true\n  fi\n\n  start_install_timer\n\n  # EXIT trap: automatically report telemetry when script ends\n  trap '_telemetry_report_exit \"$?\"' EXIT\n}\n\n# ------------------------------------------------------------------------------\n# post_tool_to_api()\n#\n# - Reports tool usage to telemetry\n# - Arguments:\n#   * $1: tool_name (e.g., \"microcode\", \"lxc-update\", \"post-pve-install\")\n#   * $2: status (\"success\" or \"failed\")\n#   * $3: exit_code (optional, default: 0 for success, 1 for failed)\n# - For PVE host tools, not container installations\n# ------------------------------------------------------------------------------\npost_tool_to_api() {\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n\n  local tool_name=\"${1:-unknown}\"\n  local status=\"${2:-success}\"\n  local exit_code=\"${3:-0}\"\n  local error=\"\" error_category=\"\"\n  local uuid duration\n\n  # Generate UUID for this tool execution\n  uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo \"tool-$(date +%s)\")\n  duration=$(get_install_duration)\n\n  # Map status\n  [[ \"$status\" == \"done\" ]] && status=\"success\"\n\n  if [[ \"$status\" == \"failed\" ]]; then\n    [[ ! \"$exit_code\" =~ ^[0-9]+$ ]] && exit_code=1\n    local error_text=\"\"\n    error_text=$(get_error_text)\n    local full_error\n    full_error=$(build_error_string \"$exit_code\" \"$error_text\")\n    error=$(json_escape \"$full_error\")\n    error_category=$(categorize_error \"$exit_code\")\n  fi\n\n  local pve_version=\"\"\n  if command -v pveversion &>/dev/null; then\n    pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true\n  fi\n\n  local JSON_PAYLOAD\n  JSON_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${uuid}\",\n    \"execution_id\": \"${EXECUTION_ID:-${uuid}}\",\n    \"type\": \"pve\",\n    \"nsapp\": \"${tool_name}\",\n    \"status\": \"${status}\",\n    \"exit_code\": ${exit_code},\n    \"error\": \"${error}\",\n    \"error_category\": \"${error_category}\",\n    \"install_duration\": ${duration:-0},\n    \"pve_version\": \"${pve_version}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  curl -fsS -m \"${TELEMETRY_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$JSON_PAYLOAD\" &>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# post_addon_to_api()\n#\n# - Reports addon installation to telemetry\n# - Arguments:\n#   * $1: addon_name (e.g., \"filebrowser\", \"netdata\")\n#   * $2: status (\"success\" or \"failed\")\n#   * $3: exit_code (optional)\n# - For addons installed inside containers\n# ------------------------------------------------------------------------------\npost_addon_to_api() {\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n\n  local addon_name=\"${1:-unknown}\"\n  local status=\"${2:-success}\"\n  local exit_code=\"${3:-0}\"\n  local error=\"\" error_category=\"\"\n  local uuid duration\n\n  # Generate UUID for this addon installation\n  uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo \"addon-$(date +%s)\")\n  duration=$(get_install_duration)\n\n  # Map status\n  [[ \"$status\" == \"done\" ]] && status=\"success\"\n\n  if [[ \"$status\" == \"failed\" ]]; then\n    [[ ! \"$exit_code\" =~ ^[0-9]+$ ]] && exit_code=1\n    local error_text=\"\"\n    error_text=$(get_error_text)\n    local full_error\n    full_error=$(build_error_string \"$exit_code\" \"$error_text\")\n    error=$(json_escape \"$full_error\")\n    error_category=$(categorize_error \"$exit_code\")\n  fi\n\n  # Detect OS info\n  local os_type=\"\" os_version=\"\"\n  if [[ -f /etc/os-release ]]; then\n    os_type=$(grep \"^ID=\" /etc/os-release | cut -d= -f2 | tr -d '\"' || true)\n    os_version=$(grep \"^VERSION_ID=\" /etc/os-release | cut -d= -f2 | tr -d '\"' || true)\n  fi\n\n  local JSON_PAYLOAD\n  JSON_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${uuid}\",\n    \"execution_id\": \"${EXECUTION_ID:-${uuid}}\",\n    \"type\": \"addon\",\n    \"nsapp\": \"${addon_name}\",\n    \"status\": \"${status}\",\n    \"exit_code\": ${exit_code},\n    \"error\": \"${error}\",\n    \"error_category\": \"${error_category}\",\n    \"install_duration\": ${duration:-0},\n    \"os_type\": \"${os_type}\",\n    \"os_version\": \"${os_version}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  curl -fsS -m \"${TELEMETRY_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$JSON_PAYLOAD\" &>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# post_update_to_api_extended()\n#\n# - Extended version of post_update_to_api with duration, GPU, and error category\n# - Same arguments as post_update_to_api:\n#   * $1: status (\"done\" or \"failed\")\n#   * $2: exit_code (numeric)\n# - Automatically includes:\n#   * Install duration (if start_install_timer was called)\n#   * Error category (for failed status)\n#   * GPU info (if detect_gpu was called)\n# ------------------------------------------------------------------------------\npost_update_to_api_extended() {\n  # Silent fail - telemetry should never break scripts\n  command -v curl &>/dev/null || return 0\n\n  # Prevent duplicate submissions\n  POST_UPDATE_DONE=${POST_UPDATE_DONE:-false}\n  [[ \"$POST_UPDATE_DONE\" == \"true\" ]] && return 0\n\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  local status=\"${1:-failed}\"\n  local raw_exit_code=\"${2:-1}\"\n  local exit_code=0 error=\"\" pb_status error_category=\"\"\n  local duration gpu_vendor gpu_passthrough\n\n  # Get duration\n  duration=$(get_install_duration)\n\n  # Get GPU info (if detected)\n  gpu_vendor=\"${GPU_VENDOR:-}\"\n  gpu_passthrough=\"${GPU_PASSTHROUGH:-}\"\n\n  # Map status to telemetry values\n  case \"$status\" in\n  done | success)\n    pb_status=\"success\"\n    exit_code=0\n    error=\"\"\n    error_category=\"\"\n    ;;\n  failed)\n    pb_status=\"failed\"\n    ;;\n  *)\n    pb_status=\"unknown\"\n    ;;\n  esac\n\n  # For failed/unknown status, resolve exit code and error description\n  if [[ \"$pb_status\" == \"failed\" ]] || [[ \"$pb_status\" == \"unknown\" ]]; then\n    if [[ \"$raw_exit_code\" =~ ^[0-9]+$ ]]; then\n      exit_code=\"$raw_exit_code\"\n    else\n      exit_code=1\n    fi\n    local error_text=\"\"\n    error_text=$(get_error_text)\n    local full_error\n    full_error=$(build_error_string \"$exit_code\" \"$error_text\")\n    error=$(json_escape \"$full_error\")\n    error_category=$(categorize_error \"$exit_code\")\n    [[ -z \"$error\" ]] && error=\"Unknown error\"\n  fi\n\n  local JSON_PAYLOAD\n  JSON_PAYLOAD=$(\n    cat <<EOF\n{\n    \"random_id\": \"${RANDOM_UUID}\",\n    \"execution_id\": \"${EXECUTION_ID:-${RANDOM_UUID}}\",\n    \"type\": \"${TELEMETRY_TYPE:-lxc}\",\n    \"nsapp\": \"${NSAPP:-unknown}\",\n    \"status\": \"${pb_status}\",\n    \"exit_code\": ${exit_code},\n    \"error\": \"${error}\",\n    \"error_category\": \"${error_category}\",\n    \"install_duration\": ${duration:-0},\n    \"gpu_vendor\": \"${gpu_vendor}\",\n    \"gpu_passthrough\": \"${gpu_passthrough}\",\n    \"repo_source\": \"${REPO_SOURCE}\"\n}\nEOF\n  )\n\n  local http_code\n  http_code=$(curl -sS -w \"%{http_code}\" -m \"${STATUS_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$JSON_PAYLOAD\" -o /dev/null 2>/dev/null) || http_code=\"000\"\n\n  if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n    POST_UPDATE_DONE=true\n    return 0\n  fi\n\n  # Retry with minimal payload\n  sleep 1\n  http_code=$(curl -sS -w \"%{http_code}\" -m \"${STATUS_TIMEOUT}\" -X POST \"${TELEMETRY_URL}\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"{\\\"random_id\\\":\\\"${RANDOM_UUID}\\\",\\\"execution_id\\\":\\\"${EXECUTION_ID:-${RANDOM_UUID}}\\\",\\\"type\\\":\\\"${TELEMETRY_TYPE:-lxc}\\\",\\\"nsapp\\\":\\\"${NSAPP:-unknown}\\\",\\\"status\\\":\\\"${pb_status}\\\",\\\"exit_code\\\":${exit_code},\\\"install_duration\\\":${duration:-0}}\" \\\n    -o /dev/null 2>/dev/null) || http_code=\"000\"\n\n  if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n    POST_UPDATE_DONE=true\n    return 0\n  fi\n\n  # Do NOT set POST_UPDATE_DONE=true — let EXIT trap retry\n}\n"
  },
  {
    "path": "misc/auto-build.func",
    "content": "# Copyright (c) 2021-2025 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# AUTOMATED TESTING VERSION - Non-interactive build function\n\nvariables() {\n  NSAPP=$(echo \"${APP,,}\" | tr -d ' ')\n  var_install=\"${NSAPP}-install\"\n  INTEGER='^[0-9]+([.][0-9]+)?$'\n  PVEHOST_NAME=$(hostname)\n  DIAGNOSTICS=\"no\"  # Disable diagnostics for automated testing\n  METHOD=\"default\"\n  RANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\n  CT_TYPE=${var_unprivileged:-$CT_TYPE}\n}\n\n# For automated testing, we use the online versions\n# This ensures we're testing with the actual deployed code\nsource <(curl -s https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\nif command -v curl >/dev/null 2>&1; then\n  source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\n  load_functions\nelif command -v wget >/dev/null 2>&1; then\n  source <(wget -qO- https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\n  load_functions\nfi\n\ncatch_errors() {\n  set -Eeo pipefail\n  trap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\n}\n\nerror_handler() {\n  source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n  printf \"\\e[?25h\"\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${command}\"\n  echo -e \"\\n$error_message\\n\"\n}\n\nshell_check() {\n  if [[ \"$(ps -p $$ -o comm=)\" != \"bash\" ]]; then\n    clear\n    msg_error \"Your default shell is not bash. Please report this to our github issues or discord.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nroot_check() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 1\n    fi\n    return 0\n  fi\n\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR != 0)); then\n      msg_error \"This version of Proxmox VE is not yet supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0\"\n      exit 1\n    fi\n    return 0\n  fi\n\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0\"\n  exit 1\n}\n\nmaxkeys_check() {\n  per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0)\n  per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0)\n\n  if [[ \"$per_user_maxkeys\" -eq 0 || \"$per_user_maxbytes\" -eq 0 ]]; then\n    echo -e \"${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}\"\n    exit 1\n  fi\n\n  used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0)\n  used_lxc_bytes=$(awk '/100000:/ {split($5, a, \"/\"); print a[1]}' /proc/key-users 2>/dev/null || echo 0)\n\n  threshold_keys=$((per_user_maxkeys - 100))\n  threshold_bytes=$((per_user_maxbytes - 1000))\n  new_limit_keys=$((per_user_maxkeys * 2))\n  new_limit_bytes=$((per_user_maxbytes * 2))\n\n  failure=0\n  if [[ \"$used_lxc_keys\" -gt \"$threshold_keys\" ]]; then\n    echo -e \"${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}\"\n    echo -e \"${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}.\"\n    failure=1\n  fi\n  if [[ \"$used_lxc_bytes\" -gt \"$threshold_bytes\" ]]; then\n    echo -e \"${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}\"\n    echo -e \"${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}.\"\n    failure=1\n  fi\n\n  if [[ \"$failure\" -eq 1 ]]; then\n    echo -e \"${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}\"\n    exit 1\n  fi\n\n  echo -e \"${CM}${GN} All kernel key limits are within safe thresholds.${CL}\"\n}\n\narch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work on non arm64 systems! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/community-scripts/ProxmoxVE for AMD64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nget_current_ip() {\n  if [ -f /etc/os-release ]; then\n    if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n      CURRENT_IP=$(hostname -I | awk '{print $1}')\n    elif grep -q 'ID=alpine' /etc/os-release; then\n      CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)\n    else\n      CURRENT_IP=\"Unknown\"\n    fi\n  fi\n  echo \"$CURRENT_IP\"\n}\n\nupdate_motd_ip() {\n  MOTD_FILE=\"/etc/motd\"\n\n  if [ -f \"$MOTD_FILE\" ]; then\n    sed -i '/IP Address:/d' \"$MOTD_FILE\"\n    IP=$(get_current_ip)\n    echo -e \"${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}\" >>\"$MOTD_FILE\"\n  fi\n}\n\n# AUTOMATED: Skip SSH check\nssh_check() {\n  return 0\n}\n\nbase_settings() {\n  CT_TYPE=${var_unprivileged:-\"1\"}\n  DISK_SIZE=${var_disk:-\"4\"}\n  CORE_COUNT=${var_cpu:-\"1\"}\n  RAM_SIZE=${var_ram:-\"1024\"}\n  VERBOSE=\"yes\"  # Force verbose for automated testing\n  PW=${var_pw:-\"\"}\n  CT_ID=${var_ctid:-$NEXTID}\n  HN=${var_hostname:-$NSAPP}\n  BRG=${var_brg:-\"vmbr0\"}\n  NET=${var_net:-\"dhcp\"}\n  IPV6_METHOD=${var_ipv6_method:-\"none\"}\n  IPV6_STATIC=${var_ipv6_static:-\"\"}\n  GATE=${var_gateway:-\"\"}\n  APT_CACHER=${var_apt_cacher:-\"\"}\n  APT_CACHER_IP=${var_apt_cacher_ip:-\"\"}\n  MTU=${var_mtu:-\"\"}\n  SD=${var_storage:-\"\"}\n  NS=${var_ns:-\"\"}\n  MAC=${var_mac:-\"\"}\n  VLAN=${var_vlan:-\"\"}\n  SSH=${var_ssh:-\"no\"}\n  SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-\"\"}\n  UDHCPC_FIX=${var_udhcpc_fix:-\"\"}\n  TAGS=\"community-script;auto-test;${var_tags:-}\"\n  ENABLE_FUSE=${var_fuse:-\"no\"}\n  ENABLE_TUN=${var_tun:-\"no\"}\n\n  if [ -z \"$var_os\" ]; then\n    var_os=\"debian\"\n  fi\n  if [ -z \"$var_version\" ]; then\n    var_version=\"12\"\n  fi\n}\n\necho_default() {\n  CT_TYPE_DESC=\"Unprivileged\"\n  if [ \"$CT_TYPE\" -eq 0 ]; then\n    CT_TYPE_DESC=\"Privileged\"\n  fi\n\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}\"\n  echo -e \"${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled (Auto-Test)${CL}\"\n  echo -e \"${CREATING}${BOLD}${BL}Creating a ${APP} LXC using automated test settings${CL}\"\n  echo -e \"  \"\n}\n\nexit_script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\n# AUTOMATED: Skip diagnostics check\ndiagnostics_check() {\n  DIAGNOSTICS=\"no\"\n}\n\n# AUTOMATED: Non-interactive install\ninstall_script() {\n  pve_check\n  shell_check\n  root_check\n  arch_check\n  maxkeys_check\n  diagnostics_check\n\n  if systemctl is-active -q ping-instances.service; then\n    systemctl -q stop ping-instances.service\n  fi\n  \n  NEXTID=$(pvesh get /cluster/nextid)\n  timezone=$( (cat /etc/timezone 2>/dev/null || readlink -f /etc/localtime | sed 's|.*/zoneinfo/||') )  \n  \n  # Use default settings automatically\n  header_info\n  echo -e \"${DEFAULT}${BOLD}${BL}Using Automated Test Settings on node $PVEHOST_NAME${CL}\"\n  VERBOSE=\"yes\"\n  METHOD=\"default\"\n  base_settings \"$VERBOSE\"\n  echo_default\n}\n\ncheck_container_resources() {\n  current_ram=$(free -m | awk 'NR==2{print $2}')\n  current_cpu=$(nproc)\n\n  if [[ \"$current_ram\" -lt \"$var_ram\" ]] || [[ \"$current_cpu\" -lt \"$var_cpu\" ]]; then\n    echo -e \"\\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}\"\n    echo -e \"${YWB}Container may be under-provisioned for ${APP} LXC.${CL}\\n\"\n  fi\n}\n\ncheck_container_storage() {\n  total_size=$(df /boot --output=size | tail -n 1)\n  local used_size=$(df /boot --output=used | tail -n 1)\n  usage=$((100 * used_size / total_size))\n  if ((usage > 80)); then\n    echo -e \"${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}\"\n  fi\n}\n\nstart() {\n  source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func)\n  if ! command -v pveversion >/dev/null 2>&1; then\n    if [ -f /etc/debian_version ] || [ -f /etc/lsb-release ]; then\n      apt-get install -y whiptail &>/dev/null\n    elif [ -f /etc/alpine-release ]; then\n      apk add --no-cache whiptail &>/dev/null\n    fi\n  fi\n  \n  if command -v pveversion >/dev/null 2>&1; then\n    install_script\n  else\n    echo -e \"${RD}Not running on Proxmox VE host${CL}\"\n    exit 1\n  fi\n}\n\nbuild_container() {\n  NET_STRING=\"-net0 name=eth0,bridge=$BRG$MAC,ip=$NET$GATE$VLAN$MTU\"\n  case \"$IPV6_METHOD\" in\n  auto) NET_STRING=\"$NET_STRING,ip6=auto\" ;;\n  dhcp) NET_STRING=\"$NET_STRING,ip6=dhcp\" ;;\n  static)\n    NET_STRING=\"$NET_STRING,ip6=$IPV6_ADDR\"\n    [ -n \"$IPV6_GATE\" ] && NET_STRING=\"$NET_STRING,gw6=$IPV6_GATE\"\n    ;;\n  none) ;;\n  esac\n  \n  if [ \"$CT_TYPE\" == \"1\" ]; then\n    FEATURES=\"keyctl=1,nesting=1\"\n  else\n    FEATURES=\"nesting=1\"\n  fi\n\n  if [ \"$ENABLE_FUSE\" == \"yes\" ]; then\n    FEATURES=\"$FEATURES,fuse=1\"\n  fi\n\n  if [[ $DIAGNOSTICS == \"yes\" ]]; then\n    post_to_api\n  fi\n\n  TEMP_DIR=$(mktemp -d)\n  pushd \"$TEMP_DIR\" >/dev/null\n  if [ \"$var_os\" == \"alpine\" ]; then\n    export FUNCTIONS_FILE_PATH=\"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/alpine-install.func)\"\n  else\n    export FUNCTIONS_FILE_PATH=\"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/install.func)\"\n  fi\n\n  export DIAGNOSTICS=\"$DIAGNOSTICS\"\n  export RANDOM_UUID=\"$RANDOM_UUID\"\n  export CACHER=\"$APT_CACHER\"\n  export CACHER_IP=\"$APT_CACHER_IP\"\n  export tz=\"$timezone\"\n  export APPLICATION=\"$APP\"\n  export app=\"$NSAPP\"\n  export PASSWORD=\"$PW\"\n  export VERBOSE=\"$VERBOSE\"\n  export SSH_ROOT=\"${SSH}\"\n  export SSH_AUTHORIZED_KEY\n  export CTID=\"$CT_ID\"\n  export CTTYPE=\"$CT_TYPE\"\n  export ENABLE_FUSE=\"$ENABLE_FUSE\"\n  export ENABLE_TUN=\"$ENABLE_TUN\"\n  export PCT_OSTYPE=\"$var_os\"\n  export PCT_OSVERSION=\"$var_version\"\n  export PCT_DISK_SIZE=\"$DISK_SIZE\"\n  export PCT_OPTIONS=\"\n    -features $FEATURES\n    -hostname $HN\n    -tags $TAGS\n    $SD\n    $NS\n    $NET_STRING\n    -onboot 1\n    -cores $CORE_COUNT\n    -memory $RAM_SIZE\n    -unprivileged $CT_TYPE\n    $PW\n  \"\n  \n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/create_lxc.sh)\" $?\n\n  LXC_CONFIG=\"/etc/pve/lxc/${CTID}.conf\"\n\n  if [ \"$CT_TYPE\" == \"0\" ]; then\n    cat <<EOF >>\"$LXC_CONFIG\"\n# USB passthrough\nlxc.cgroup2.devices.allow: a\nlxc.cap.drop:\nlxc.cgroup2.devices.allow: c 188:* rwm\nlxc.cgroup2.devices.allow: c 189:* rwm\nlxc.mount.entry: /dev/serial/by-id  dev/serial/by-id  none bind,optional,create=dir\nlxc.mount.entry: /dev/ttyUSB0       dev/ttyUSB0       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyUSB1       dev/ttyUSB1       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyACM0       dev/ttyACM0       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyACM1       dev/ttyACM1       none bind,optional,create=file\nEOF\n  fi\n\n  # Skip VAAPI interactive prompts in automated mode\n  \n  if [ \"$ENABLE_TUN\" == \"yes\" ]; then\n    cat <<EOF >>\"$LXC_CONFIG\"\nlxc.cgroup2.devices.allow: c 10:200 rwm\nlxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file\nEOF\n  fi\n\n  msg_info \"Starting LXC Container\"\n  pct start \"$CTID\"\n\n  for i in {1..10}; do\n    if pct status \"$CTID\" | grep -q \"status: running\"; then\n      msg_ok \"Started LXC Container\"\n      break\n    fi\n    sleep 1\n    if [ \"$i\" -eq 10 ]; then\n      msg_error \"LXC Container did not reach running state\"\n      exit 1\n    fi\n  done\n\n  if [ \"$var_os\" != \"alpine\" ]; then\n    msg_info \"Waiting for network in LXC container\"\n    for i in {1..10}; do\n      if pct exec \"$CTID\" -- ping -c1 -W1 deb.debian.org >/dev/null 2>&1; then\n        msg_ok \"Network in LXC is reachable (ping)\"\n        break\n      fi\n      \n      if [ \"$i\" -lt 10 ]; then\n        msg_warn \"No network in LXC yet (try $i/10) – waiting...\"\n        sleep 3\n      else\n        msg_warn \"Ping failed 10 times. Trying HTTP connectivity check (wget) as fallback...\"\n        if pct exec \"$CTID\" -- wget -q --spider http://deb.debian.org; then\n          msg_ok \"Network in LXC is reachable (wget fallback)\"\n        else\n          msg_error \"No network in LXC after all checks - using fallback DNS\"\n          pct set \"$CTID\" --nameserver 1.1.1.1\n          pct set \"$CTID\" --nameserver 8.8.8.8\n        fi\n        break\n      fi\n    done\n  fi\n\n  msg_info \"Customizing LXC Container\"\n  : \"${tz:=Etc/UTC}\"\n  if [ \"$var_os\" == \"alpine\" ]; then\n    sleep 3\n    pct exec \"$CTID\" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories\nhttp://dl-cdn.alpinelinux.org/alpine/latest-stable/main\nhttp://dl-cdn.alpinelinux.org/alpine/latest-stable/community\nEOF'\n    pct exec \"$CTID\" -- ash -c \"apk add bash newt curl openssh nano mc ncurses jq >/dev/null\"\n  else\n    sleep 3\n    pct exec \"$CTID\" -- bash -c \"sed -i '/$LANG/ s/^# //' /etc/locale.gen\"\n    pct exec \"$CTID\" -- bash -c \"locale_line=\\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \\$1}' | head -n 1) && \\\n    echo LANG=\\$locale_line >/etc/default/locale && \\\n    locale-gen >/dev/null && \\\n    export LANG=\\$locale_line\"\n\n    if [[ -z \"${tz:-}\" ]]; then\n      tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo \"Etc/UTC\")\n    fi\n    if pct exec \"$CTID\" -- test -e \"/usr/share/zoneinfo/$tz\"; then\n      pct exec \"$CTID\" -- bash -c \"tz='$tz'; echo \\\"\\$tz\\\" >/etc/timezone && ln -sf \\\"/usr/share/zoneinfo/\\$tz\\\" /etc/localtime\"\n    else\n      msg_warn \"Skipping timezone setup – zone '$tz' not found in container\"\n    fi\n\n    pct exec \"$CTID\" -- bash -c \"apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 jq >/dev/null\"\n  fi\n  msg_ok \"Customized LXC Container\"\n\n  lxc-attach -n \"$CTID\" -- bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/install/\"$var_install\".sh)\"\n}\n\ndescription() {\n  IP=$(pct exec \"$CTID\" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n\n  DESCRIPTION=$(\n    cat <<EOF\n<div align='center'>\n  <a href='https://pimox-scripts.com' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC (Auto-Test)</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/asylumexp/Proxmox' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n</div>\nEOF\n  )\n\n  pct set \"$CTID\" -description \"$DESCRIPTION\"\n\n  if [[ -f /etc/systemd/system/ping-instances.service ]]; then\n    systemctl start ping-instances.service\n  fi\n\n  post_update_to_api \"done\" \"none\"\n}\n\napi_exit_script() {\n  exit_code=$?\n\n  if [ $exit_code -ne 0 ]; then\n    case $exit_code in\n    100) post_update_to_api \"failed\" \"100: Unexpected error in create_lxc.sh\" ;;\n    101) post_update_to_api \"failed\" \"101: No network connection detected in create_lxc.sh\" ;;\n    200) post_update_to_api \"failed\" \"200: LXC creation failed in create_lxc.sh\" ;;\n    203) post_update_to_api \"failed\" \"203: CTID not set in create_lxc.sh\" ;;\n    204) post_update_to_api \"failed\" \"204: PCT_OSTYPE not set in create_lxc.sh\" ;;\n    205) post_update_to_api \"failed\" \"205: CTID cannot be less than 100 in create_lxc.sh\" ;;\n    206) post_update_to_api \"failed\" \"206: CTID already in use in create_lxc.sh\" ;;\n    207) post_update_to_api \"failed\" \"207: Template not found in create_lxc.sh\" ;;\n    208) post_update_to_api \"failed\" \"208: Error downloading template in create_lxc.sh\" ;;\n    209) post_update_to_api \"failed\" \"209: Container creation failed in create_lxc.sh\" ;;\n    *) post_update_to_api \"failed\" \"Unknown error, exit code: $exit_code\" ;;\n    esac\n  fi\n}\n\nif command -v pveversion >/dev/null 2>&1; then\n  trap 'api_exit_script' EXIT\nfi\ntrap 'post_update_to_api \"failed\" \"$BASH_COMMAND\"' ERR\ntrap 'post_update_to_api \"failed\" \"INTERRUPTED\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"TERMINATED\"' SIGTERM\n\n"
  },
  {
    "path": "misc/build.func",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | MickLesk | michelroegl-brunner\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/branch/main/LICENSE\n\n# ==============================================================================\n# BUILD.FUNC - LXC CONTAINER BUILD & CONFIGURATION\n# ==============================================================================\n#\n# This file provides the main build functions for creating and configuring\n# LXC containers in Proxmox VE. It handles:\n#\n#   - Variable initialization and defaults\n#   - Container creation and resource allocation\n#   - Storage selection and management\n#   - Advanced configuration and customization\n#   - User interaction menus and prompts\n#\n# Usage:\n#   - Sourced automatically by CT creation scripts\n#   - Requires core.func and error_handler.func to be loaded first\n#\n# ==============================================================================\n\n# ==============================================================================\n# SECTION 1: INITIALIZATION & CORE VARIABLES\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# variables()\n#\n# - Initializes core variables for container creation\n# - Normalizes application name (NSAPP = lowercase, no spaces)\n# - Builds installer filename (var_install)\n# - Defines regex patterns for validation\n# - Fetches Proxmox hostname and version\n# - Generates unique session ID for tracking and logging\n# - Captures app-declared resource defaults (CPU, RAM, Disk)\n# ------------------------------------------------------------------------------\nvariables() {\n  NSAPP=$(echo \"${APP,,}\" | tr -d ' ')              # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.\n  var_install=\"${NSAPP}-install\"                    # sets the var_install variable by appending \"-install\" to the value of NSAPP.\n  INTEGER='^[0-9]+([.][0-9]+)?$'                    # it defines the INTEGER regular expression pattern.\n  PVEHOST_NAME=$(hostname)                          # gets the Proxmox Hostname and sets it to Uppercase\n  DIAGNOSTICS=\"no\"                                  # Safe default: no telemetry until user consents via diagnostics_check()\n  METHOD=\"default\"                                  # sets the METHOD variable to \"default\", used for the API call.\n  RANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\" # generates a random UUID and sets it to the RANDOM_UUID variable.\n  EXECUTION_ID=\"${RANDOM_UUID}\"                     # Unique execution ID for telemetry record identification (unique-indexed in PocketBase)\n  SESSION_ID=\"${RANDOM_UUID:0:8}\"                   # Short session ID (first 8 chars of UUID) for log files\n  BUILD_LOG=\"/tmp/create-lxc-${SESSION_ID}.log\"     # Host-side container creation log\n  # NOTE: combined_log is constructed locally in build_container() and ensure_log_on_host()\n  # as \"/tmp/${NSAPP}-${CTID}-${SESSION_ID}.log\" (requires CTID, not available here)\n  CTTYPE=\"${CTTYPE:-${CT_TYPE:-1}}\"\n\n  # Parse dev_mode early\n  parse_dev_mode\n\n  # Setup persistent log directory if logs mode active\n  if [[ \"${DEV_MODE_LOGS:-false}\" == \"true\" ]]; then\n    mkdir -p /var/log/community-scripts\n    BUILD_LOG=\"/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log\"\n  fi\n\n  # Get Proxmox VE version and kernel version\n  if command -v pveversion >/dev/null 2>&1; then\n    PVEVERSION=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n  else\n    PVEVERSION=\"N/A\"\n  fi\n  KERNEL_VERSION=$(uname -r)\n\n  # Capture app-declared defaults (for precedence logic)\n  # These values are set by the app script BEFORE default.vars is loaded\n  # If app declares higher values than default.vars, app values take precedence\n  if [[ -n \"${var_cpu:-}\" && \"${var_cpu}\" =~ ^[0-9]+$ ]]; then\n    export APP_DEFAULT_CPU=\"${var_cpu}\"\n  fi\n  if [[ -n \"${var_ram:-}\" && \"${var_ram}\" =~ ^[0-9]+$ ]]; then\n    export APP_DEFAULT_RAM=\"${var_ram}\"\n  fi\n  if [[ -n \"${var_disk:-}\" && \"${var_disk}\" =~ ^[0-9]+$ ]]; then\n    export APP_DEFAULT_DISK=\"${var_disk}\"\n  fi\n}\n\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\nif command -v curl >/dev/null 2>&1; then\n  source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\n  source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\n  load_functions\n  catch_errors\nelif command -v wget >/dev/null 2>&1; then\n  source <(wget -qO- https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\n  source <(wget -qO- https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\n  load_functions\n  catch_errors\nfi\n\n# ==============================================================================\n# SECTION 2: PRE-FLIGHT CHECKS & SYSTEM VALIDATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# maxkeys_check()\n#\n# - Reads kernel keyring limits (maxkeys, maxbytes)\n# - Checks current usage for LXC user (UID 100000)\n# - Warns if usage is close to limits and suggests sysctl tuning\n# - Exits if thresholds are exceeded\n# - https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html\n# ------------------------------------------------------------------------------\n\nmaxkeys_check() {\n  # Read kernel parameters\n  per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0)\n  per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0)\n\n  # Exit if kernel parameters are unavailable\n  if [[ \"$per_user_maxkeys\" -eq 0 || \"$per_user_maxbytes\" -eq 0 ]]; then\n    msg_error \"Unable to read kernel key parameters. Ensure proper permissions.\"\n    exit 107\n  fi\n\n  # Fetch key usage for user ID 100000 (typical for containers)\n  used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0)\n  used_lxc_bytes=$(awk '/100000:/ {split($5, a, \"/\"); print a[1]}' /proc/key-users 2>/dev/null || echo 0)\n\n  # Calculate thresholds and suggested new limits\n  threshold_keys=$((per_user_maxkeys - 100))\n  threshold_bytes=$((per_user_maxbytes - 1000))\n  new_limit_keys=$((per_user_maxkeys * 2))\n  new_limit_bytes=$((per_user_maxbytes * 2))\n\n  # Check if key or byte usage is near limits\n  failure=0\n  if [[ \"$used_lxc_keys\" -gt \"$threshold_keys\" ]]; then\n    msg_warn \"Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys})\"\n    echo -e \"${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}.\"\n    failure=1\n  fi\n  if [[ \"$used_lxc_bytes\" -gt \"$threshold_bytes\" ]]; then\n    msg_warn \"Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes})\"\n    echo -e \"${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}.\"\n    failure=1\n  fi\n\n  # Provide next steps if issues are detected\n  if [[ \"$failure\" -eq 1 ]]; then\n    msg_error \"Kernel key limits exceeded - see suggestions above\"\n    exit 108\n  fi\n\n  # Silent success - only show errors if they exist\n}\n\n# ==============================================================================\n# SECTION 3: CONTAINER SETUP UTILITIES\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# get_current_ip()\n#\n# - Returns current container IP depending on OS type\n# - Debian/Ubuntu: uses `hostname -I`\n# - Alpine: parses eth0 via `ip -4 addr` or `ip -6 addr`\n# - Supports IPv6-only environments as fallback\n# - Returns \"Unknown\" if OS type cannot be determined\n# ------------------------------------------------------------------------------\nget_current_ip() {\n  CURRENT_IP=\"\"\n  if [ -f /etc/os-release ]; then\n    # Check for Debian/Ubuntu (uses hostname -I)\n    if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n      # Try IPv4 first\n      CURRENT_IP=$(hostname -I 2>/dev/null | tr ' ' '\\n' | grep -E '^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$' | head -n1 || true)\n      # Fallback to IPv6 if no IPv4\n      if [[ -z \"$CURRENT_IP\" ]]; then\n        CURRENT_IP=$(hostname -I 2>/dev/null | tr ' ' '\\n' | grep -E ':' | head -n1 || true)\n      fi\n    # Check for Alpine (uses ip command)\n    elif grep -q 'ID=alpine' /etc/os-release; then\n      # Try IPv4 first\n      CURRENT_IP=$(ip -4 addr show eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)\n      # Fallback to IPv6 if no IPv4\n      if [[ -z \"$CURRENT_IP\" ]]; then\n        CURRENT_IP=$(ip -6 addr show eth0 scope global 2>/dev/null | awk '/inet6 / {print $2}' | cut -d/ -f1 | head -n 1)\n      fi\n    else\n      CURRENT_IP=\"Unknown\"\n    fi\n  fi\n  echo \"$CURRENT_IP\"\n}\n\n# ------------------------------------------------------------------------------\n# update_motd_ip()\n#\n# - Updates /etc/motd with current container IP\n# - Removes old IP entries to avoid duplicates\n# - Regenerates /etc/profile.d/00_lxc-details.sh with dynamic OS/IP info\n# ------------------------------------------------------------------------------\nupdate_motd_ip() {\n  MOTD_FILE=\"/etc/motd\"\n  PROFILE_FILE=\"/etc/profile.d/00_lxc-details.sh\"\n\n  if [ -f \"$MOTD_FILE\" ]; then\n    # Remove existing IP Address lines to prevent duplication\n    sed -i '/IP Address:/d' \"$MOTD_FILE\"\n\n    IP=$(get_current_ip)\n    # Add the new IP address\n    echo -e \"${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}\" >>\"$MOTD_FILE\"\n  fi\n\n  # Update dynamic LXC details profile if values changed (e.g., after OS upgrade)\n  # Only update if file exists and is from community-scripts\n  if [ -f \"$PROFILE_FILE\" ] && grep -q \"community-scripts\" \"$PROFILE_FILE\" 2>/dev/null; then\n    # Get current values\n    local current_os=\"$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')\"\n    local current_hostname=\"$(hostname)\"\n    local current_ip=\"$(hostname -I | awk '{print $1}')\"\n\n    # Escape sed special chars in replacement strings (& \\ |)\n    current_os=\"${current_os//\\\\/\\\\\\\\}\"\n    current_os=\"${current_os//&/\\\\&}\"\n    current_hostname=\"${current_hostname//\\\\/\\\\\\\\}\"\n    current_hostname=\"${current_hostname//&/\\\\&}\"\n    current_ip=\"${current_ip//\\\\/\\\\\\\\}\"\n    current_ip=\"${current_ip//&/\\\\&}\"\n\n    # Update only if values actually changed\n    if ! grep -q \"OS:.*$current_os\" \"$PROFILE_FILE\" 2>/dev/null; then\n      sed -i \"s|OS:.*|OS: \\${GN}$current_os\\${CL}\\\\\\\"|\" \"$PROFILE_FILE\"\n    fi\n    if ! grep -q \"Hostname:.*$current_hostname\" \"$PROFILE_FILE\" 2>/dev/null; then\n      sed -i \"s|Hostname:.*|Hostname: \\${GN}$current_hostname\\${CL}\\\\\\\"|\" \"$PROFILE_FILE\"\n    fi\n    if ! grep -q \"IP Address:.*$current_ip\" \"$PROFILE_FILE\" 2>/dev/null; then\n      sed -i \"s|IP Address:.*|IP Address: \\${GN}$current_ip\\${CL}\\\\\\\"|\" \"$PROFILE_FILE\"\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# install_ssh_keys_into_ct()\n#\n# - Installs SSH keys into container root account if SSH is enabled\n# - Uses pct push or direct input to authorized_keys\n# - Supports both SSH_KEYS_FILE (from advanced settings) and SSH_AUTHORIZED_KEY (from user defaults)\n# - Falls back to warning if no keys provided\n# ------------------------------------------------------------------------------\ninstall_ssh_keys_into_ct() {\n  [[ \"${SSH:-no}\" != \"yes\" ]] && return 0\n\n  # Ensure SSH_KEYS_FILE is defined (may not be set if advanced_settings was skipped)\n  : \"${SSH_KEYS_FILE:=}\"\n\n  # If SSH_KEYS_FILE doesn't exist but SSH_AUTHORIZED_KEY is set (from user defaults),\n  # create a temporary SSH_KEYS_FILE with the key\n  if [[ -z \"$SSH_KEYS_FILE\" || ! -s \"$SSH_KEYS_FILE\" ]] && [[ -n \"${SSH_AUTHORIZED_KEY:-}\" ]]; then\n    SSH_KEYS_FILE=\"$(mktemp)\"\n    printf '%s\\n' \"$SSH_AUTHORIZED_KEY\" >\"$SSH_KEYS_FILE\"\n  fi\n\n  if [[ -n \"$SSH_KEYS_FILE\" && -s \"$SSH_KEYS_FILE\" ]]; then\n    msg_info \"Installing selected SSH keys into CT ${CTID}\"\n    pct exec \"$CTID\" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || {\n      msg_error \"prepare /root/.ssh failed\"\n      return 252\n    }\n    pct push \"$CTID\" \"$SSH_KEYS_FILE\" /root/.ssh/authorized_keys >/dev/null 2>&1 ||\n      pct exec \"$CTID\" -- sh -c \"cat > /root/.ssh/authorized_keys\" <\"$SSH_KEYS_FILE\" || {\n      msg_error \"write authorized_keys failed\"\n      return 252\n    }\n    pct exec \"$CTID\" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true\n    msg_ok \"Installed SSH keys into CT ${CTID}\"\n    return 0\n  fi\n\n  # Fallback\n  msg_warn \"No SSH keys to install (skipping).\"\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_container_id()\n#\n# - Validates if a container ID is available for use (CLUSTER-WIDE)\n# - Checks cluster resources via pvesh for VMs/CTs on ALL nodes\n# - Falls back to local config file check if pvesh unavailable\n# - Checks if ID is used in LVM logical volumes\n# - Returns 0 if ID is available, 1 if already in use\n# ------------------------------------------------------------------------------\nvalidate_container_id() {\n  local ctid=\"$1\"\n\n  # Check if ID is numeric\n  if ! [[ \"$ctid\" =~ ^[0-9]+$ ]]; then\n    return 1\n  fi\n\n  # CLUSTER-WIDE CHECK: Query all VMs/CTs across all nodes\n  # This catches IDs used on other nodes in the cluster\n  # NOTE: Works on single-node too - Proxmox always has internal cluster structure\n  # Falls back gracefully if pvesh unavailable or returns empty\n  if command -v pvesh &>/dev/null; then\n    local cluster_ids\n    cluster_ids=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null |\n      grep -oP '\"vmid\":\\s*\\K[0-9]+' 2>/dev/null || true)\n    if [[ -n \"$cluster_ids\" ]] && echo \"$cluster_ids\" | grep -qw \"$ctid\"; then\n      return 1\n    fi\n  fi\n\n  # LOCAL FALLBACK: Check if config file exists for VM or LXC\n  # This handles edge cases where pvesh might not return all info\n  if [[ -f \"/etc/pve/qemu-server/${ctid}.conf\" ]] || [[ -f \"/etc/pve/lxc/${ctid}.conf\" ]]; then\n    return 1\n  fi\n\n  # Check ALL nodes in cluster for config files (handles pmxcfs sync delays)\n  # NOTE: On single-node, /etc/pve/nodes/ contains just the one node - still works\n  if [[ -d \"/etc/pve/nodes\" ]]; then\n    for node_dir in /etc/pve/nodes/*/; do\n      if [[ -f \"${node_dir}qemu-server/${ctid}.conf\" ]] || [[ -f \"${node_dir}lxc/${ctid}.conf\" ]]; then\n        return 1\n      fi\n    done\n  fi\n\n  # Check if ID is used in LVM logical volumes\n  if lvs --noheadings -o lv_name 2>/dev/null | grep -qE \"(^|[-_])${ctid}($|[-_])\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# get_valid_container_id()\n#\n# - Returns a valid, unused container ID (CLUSTER-AWARE)\n# - Uses pvesh /cluster/nextid as starting point (already cluster-aware)\n# - If provided ID is valid, returns it\n# - Otherwise increments until a free one is found across entire cluster\n# - Calls validate_container_id() to check availability\n# ------------------------------------------------------------------------------\nget_valid_container_id() {\n  local suggested_id=\"${1:-$(pvesh get /cluster/nextid 2>/dev/null || echo 100)}\"\n\n  # Ensure we have a valid starting ID\n  if ! [[ \"$suggested_id\" =~ ^[0-9]+$ ]]; then\n    suggested_id=$(pvesh get /cluster/nextid 2>/dev/null || echo 100)\n  fi\n\n  local max_attempts=1000\n  local attempts=0\n\n  while ! validate_container_id \"$suggested_id\"; do\n    suggested_id=$((suggested_id + 1))\n    attempts=$((attempts + 1))\n    if [[ $attempts -ge $max_attempts ]]; then\n      msg_error \"Could not find available container ID after $max_attempts attempts\"\n      exit 109\n    fi\n  done\n\n  echo \"$suggested_id\"\n}\n\n# ------------------------------------------------------------------------------\n# validate_hostname()\n#\n# - Validates hostname/FQDN according to RFC 1123/952\n# - Checks total length (max 253 characters for FQDN)\n# - Validates each label (max 63 chars, alphanumeric + hyphens)\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_hostname() {\n  local hostname=\"$1\"\n\n  # Check total length (max 253 for FQDN)\n  if [[ ${#hostname} -gt 253 ]] || [[ -z \"$hostname\" ]]; then\n    return 1\n  fi\n\n  # Split by dots and validate each label\n  local IFS='.'\n  read -ra labels <<<\"$hostname\"\n  for label in \"${labels[@]}\"; do\n    # Each label: 1-63 chars, alphanumeric, hyphens allowed (not at start/end)\n    if [[ -z \"$label\" ]] || [[ ${#label} -gt 63 ]]; then\n      return 1\n    fi\n    if [[ ! \"$label\" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]] && [[ ! \"$label\" =~ ^[a-z0-9]$ ]]; then\n      return 1\n    fi\n  done\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_mac_address()\n#\n# - Validates MAC address format (XX:XX:XX:XX:XX:XX)\n# - Empty value is allowed (auto-generated)\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_mac_address() {\n  local mac=\"$1\"\n  [[ -z \"$mac\" ]] && return 0\n  if [[ ! \"$mac\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n    return 1\n  fi\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_vlan_tag()\n#\n# - Validates VLAN tag (1-4094)\n# - Empty value is allowed (no VLAN)\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_vlan_tag() {\n  local vlan=\"$1\"\n  [[ -z \"$vlan\" ]] && return 0\n  if ! [[ \"$vlan\" =~ ^[0-9]+$ ]] || ((vlan < 1 || vlan > 4094)); then\n    return 1\n  fi\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_mtu()\n#\n# - Validates MTU size (576-65535, common values: 1500, 9000)\n# - Empty value is allowed (default 1500)\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_mtu() {\n  local mtu=\"$1\"\n  [[ -z \"$mtu\" ]] && return 0\n  if ! [[ \"$mtu\" =~ ^[0-9]+$ ]] || ((mtu < 576 || mtu > 65535)); then\n    return 1\n  fi\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_ipv6_address()\n#\n# - Validates IPv6 address with optional CIDR notation\n# - Supports compressed (::) and full notation\n# - Empty value is allowed\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_ipv6_address() {\n  local ipv6=\"$1\"\n  [[ -z \"$ipv6\" ]] && return 0\n\n  # Extract address and CIDR\n  local addr=\"${ipv6%%/*}\"\n  local cidr=\"${ipv6##*/}\"\n\n  # Validate CIDR if present (1-128)\n  if [[ \"$ipv6\" == */* ]]; then\n    if ! [[ \"$cidr\" =~ ^[0-9]+$ ]] || ((cidr < 1 || cidr > 128)); then\n      return 1\n    fi\n  fi\n\n  # Basic IPv6 validation - check for valid characters and structure\n  # Must contain only hex digits and colons\n  if [[ ! \"$addr\" =~ ^[0-9a-fA-F:]+$ ]]; then\n    return 1\n  fi\n\n  # Must contain at least one colon\n  if [[ ! \"$addr\" == *:* ]]; then\n    return 1\n  fi\n\n  # Check for valid double-colon usage (only one :: allowed)\n  if [[ \"$addr\" == *::*::* ]]; then\n    return 1\n  fi\n\n  # Check that no segment exceeds 4 hex chars\n  local IFS=':'\n  local -a segments\n  read -ra segments <<<\"$addr\"\n  for seg in \"${segments[@]}\"; do\n    if [[ ${#seg} -gt 4 ]]; then\n      return 1\n    fi\n  done\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_bridge()\n#\n# - Validates that network bridge exists and is active\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_bridge() {\n  local bridge=\"$1\"\n  [[ -z \"$bridge\" ]] && return 1\n\n  # Check if bridge interface exists\n  if ! ip link show dev \"$bridge\" &>/dev/null; then\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_gateway_in_subnet()\n#\n# - Validates that gateway IP is in the same subnet as static IP\n# - Arguments: static_ip (with CIDR), gateway_ip\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_gateway_in_subnet() {\n  local static_ip=\"$1\"\n  local gateway=\"$2\"\n\n  [[ -z \"$static_ip\" || -z \"$gateway\" ]] && return 0\n\n  # Extract IP and CIDR\n  local ip=\"${static_ip%%/*}\"\n  local cidr=\"${static_ip##*/}\"\n\n  # /31 and /32 are valid point-to-point / zero-trust DMZ configurations\n  # where the gateway is technically outside the subnet — skip validation\n  ((cidr >= 31)) && return 0\n\n  # Convert CIDR to netmask bits\n  local mask=$((0xFFFFFFFF << (32 - cidr) & 0xFFFFFFFF))\n\n  # Convert IPs to integers\n  local IFS='.'\n  read -r i1 i2 i3 i4 <<<\"$ip\"\n  read -r g1 g2 g3 g4 <<<\"$gateway\"\n\n  local ip_int=$(((i1 << 24) + (i2 << 16) + (i3 << 8) + i4))\n  local gw_int=$(((g1 << 24) + (g2 << 16) + (g3 << 8) + g4))\n\n  # Check if both are in same network\n  if (((ip_int & mask) != (gw_int & mask))); then\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_ip_address()\n#\n# - Validates IPv4 address with CIDR notation\n# - Checks each octet is 0-255\n# - Checks CIDR is 1-32\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_ip_address() {\n  local ip=\"$1\"\n  [[ -z \"$ip\" ]] && return 1\n\n  # Check format with CIDR\n  if [[ ! \"$ip\" =~ ^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})/([0-9]{1,2})$ ]]; then\n    return 1\n  fi\n\n  local o1=\"${BASH_REMATCH[1]}\"\n  local o2=\"${BASH_REMATCH[2]}\"\n  local o3=\"${BASH_REMATCH[3]}\"\n  local o4=\"${BASH_REMATCH[4]}\"\n  local cidr=\"${BASH_REMATCH[5]}\"\n\n  # Validate octets (0-255)\n  for octet in \"$o1\" \"$o2\" \"$o3\" \"$o4\"; do\n    if ((octet > 255)); then\n      return 1\n    fi\n  done\n\n  # Validate CIDR (1-32)\n  if ((cidr < 1 || cidr > 32)); then\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_gateway_ip()\n#\n# - Validates gateway IPv4 address (without CIDR)\n# - Checks each octet is 0-255\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_gateway_ip() {\n  local ip=\"$1\"\n  [[ -z \"$ip\" ]] && return 0\n\n  # Check format without CIDR\n  if [[ ! \"$ip\" =~ ^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$ ]]; then\n    return 1\n  fi\n\n  local o1=\"${BASH_REMATCH[1]}\"\n  local o2=\"${BASH_REMATCH[2]}\"\n  local o3=\"${BASH_REMATCH[3]}\"\n  local o4=\"${BASH_REMATCH[4]}\"\n\n  # Validate octets (0-255)\n  for octet in \"$o1\" \"$o2\" \"$o3\" \"$o4\"; do\n    if ((octet > 255)); then\n      return 1\n    fi\n  done\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_timezone()\n#\n# - Validates timezone string against system zoneinfo\n# - Empty value or \"host\" is allowed\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_timezone() {\n  local tz=\"$1\"\n  [[ -z \"$tz\" || \"$tz\" == \"host\" ]] && return 0\n\n  # Check if timezone file exists\n  if [[ ! -f \"/usr/share/zoneinfo/$tz\" ]]; then\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# validate_tags()\n#\n# - Validates Proxmox tags format\n# - Only alphanumeric, hyphens, underscores, and semicolons allowed\n# - Empty value is allowed\n# - Returns 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nvalidate_tags() {\n  local tags=\"$1\"\n  [[ -z \"$tags\" ]] && return 0\n\n  # Tags can only contain alphanumeric, -, _, and ; (separator)\n  if [[ ! \"$tags\" =~ ^[a-zA-Z0-9_\\;-]+$ ]]; then\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# find_host_ssh_keys()\n#\n# - Scans system for available SSH keys\n# - Supports defaults (~/.ssh, /etc/ssh/authorized_keys)\n# - Returns list of files containing valid SSH public keys\n# - Sets FOUND_HOST_KEY_COUNT to number of keys found\n# ------------------------------------------------------------------------------\nfind_host_ssh_keys() {\n  local re='(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))'\n  local -a files=() cand=()\n  local g=\"${var_ssh_import_glob:-}\"\n  local total=0 f base c\n\n  shopt -s nullglob\n  if [[ -n \"$g\" ]]; then\n    for pat in $g; do cand+=($pat); done\n  else\n    cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)\n    cand+=(/root/.ssh/*.pub)\n    cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)\n  fi\n  shopt -u nullglob\n\n  for f in \"${cand[@]}\"; do\n    [[ -f \"$f\" && -r \"$f\" ]] || continue\n    base=\"$(basename -- \"$f\")\"\n    case \"$base\" in\n    known_hosts | known_hosts.* | config) continue ;;\n    id_*) [[ \"$f\" != *.pub ]] && continue ;;\n    esac\n\n    # CRLF safe check for host keys\n    c=$(tr -d '\\r' <\"$f\" | awk '\n      /^[[:space:]]*#/ {next}\n      /^[[:space:]]*$/ {next}\n      {print}\n    ' | grep -E -c \"$re\" || true)\n\n    if ((c > 0)); then\n      files+=(\"$f\")\n      total=$((total + c))\n    fi\n  done\n\n  # Fallback to /root/.ssh/authorized_keys\n  if ((${#files[@]} == 0)) && [[ -r /root/.ssh/authorized_keys ]]; then\n    if grep -E -q \"$re\" /root/.ssh/authorized_keys; then\n      files+=(/root/.ssh/authorized_keys)\n      total=$((total + $(grep -E -c \"$re\" /root/.ssh/authorized_keys || echo 0)))\n    fi\n  fi\n\n  FOUND_HOST_KEY_COUNT=\"$total\"\n  (\n    IFS=:\n    echo \"${files[*]}\"\n  )\n}\n\n# ==============================================================================\n# SECTION 3B: IP RANGE SCANNING\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# ip_to_int() / int_to_ip()\n#\n# - Converts IP address to integer and vice versa for range iteration\n# ------------------------------------------------------------------------------\nip_to_int() {\n  local IFS=.\n  read -r i1 i2 i3 i4 <<<\"$1\"\n  echo $(((i1 << 24) + (i2 << 16) + (i3 << 8) + i4))\n}\n\nint_to_ip() {\n  local ip=$1\n  echo \"$(((ip >> 24) & 0xFF)).$(((ip >> 16) & 0xFF)).$(((ip >> 8) & 0xFF)).$((ip & 0xFF))\"\n}\n\n# ------------------------------------------------------------------------------\n# resolve_ip_from_range()\n#\n# - Takes an IP range in format \"10.0.0.1/24-10.0.0.10/24\"\n# - Pings each IP in the range to find the first available one\n# - Returns the first free IP with CIDR notation\n# - Sets NET_RESOLVED to the resolved IP or empty on failure\n# ------------------------------------------------------------------------------\nresolve_ip_from_range() {\n  local range=\"$1\"\n  local ip_cidr_regex='^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})/([0-9]{1,2})$'\n  local ip_start ip_end\n\n  # Parse range: \"10.0.0.1/24-10.0.0.10/24\"\n  ip_start=\"${range%%-*}\"\n  ip_end=\"${range##*-}\"\n\n  if [[ ! \"$ip_start\" =~ $ip_cidr_regex ]] || [[ ! \"$ip_end\" =~ $ip_cidr_regex ]]; then\n    NET_RESOLVED=\"\"\n    return 1\n  fi\n\n  local ip1=\"${ip_start%%/*}\"\n  local ip2=\"${ip_end%%/*}\"\n  local cidr=\"${ip_start##*/}\"\n\n  local start_int=$(ip_to_int \"$ip1\")\n  local end_int=$(ip_to_int \"$ip2\")\n\n  for ((ip_int = start_int; ip_int <= end_int; ip_int++)); do\n    local ip=$(int_to_ip $ip_int)\n    msg_info \"Checking IP: $ip\"\n    if ! ping -c 1 -W 1 \"$ip\" >/dev/null 2>&1; then\n      NET_RESOLVED=\"$ip/$cidr\"\n      msg_ok \"Found free IP: ${BGN}$NET_RESOLVED${CL}\"\n      return 0\n    fi\n  done\n\n  NET_RESOLVED=\"\"\n  msg_error \"No free IP found in range $range\"\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# is_ip_range()\n#\n# - Checks if a string is an IP range (contains - and looks like IP/CIDR)\n# - Returns 0 if it's a range, 1 otherwise\n# ------------------------------------------------------------------------------\nis_ip_range() {\n  local value=\"$1\"\n  local ip_start ip_end\n  if [[ \"$value\" == *-* ]] && [[ \"$value\" != \"dhcp\" ]]; then\n    local ip_cidr_regex='^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})/([0-9]{1,2})$'\n    ip_start=\"${value%%-*}\"\n    ip_end=\"${value##*-}\"\n    if [[ \"$ip_start\" =~ $ip_cidr_regex ]] && [[ \"$ip_end\" =~ $ip_cidr_regex ]]; then\n      return 0\n    fi\n  fi\n  return 1\n}\n\n# ==============================================================================\n# SECTION 4: STORAGE & RESOURCE MANAGEMENT\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# _write_storage_to_vars()\n#\n# - Writes storage selection to vars file\n# - Removes old entries (commented and uncommented) to avoid duplicates\n# - Arguments: vars_file, key (var_container_storage/var_template_storage), value\n# ------------------------------------------------------------------------------\n_write_storage_to_vars() {\n  # $1 = vars_file, $2 = key (var_container_storage / var_template_storage), $3 = value\n  local vf=\"$1\" key=\"$2\" val=\"$3\"\n  # remove uncommented and commented versions to avoid duplicates\n  sed -i \"/^[#[:space:]]*${key}=/d\" \"$vf\"\n  echo \"${key}=${val}\" >>\"$vf\"\n}\n\nchoose_and_set_storage_for_file() {\n  # $1 = vars_file, $2 = class ('container'|'template')\n  local vf=\"$1\" class=\"$2\" key=\"\" current=\"\"\n  case \"$class\" in\n  container) key=\"var_container_storage\" ;;\n  template) key=\"var_template_storage\" ;;\n  *)\n    msg_error \"Unknown storage class: $class\"\n    return 65\n    ;;\n  esac\n\n  current=$(awk -F= -v k=\"^${key}=\" '$0 ~ k {print $2; exit}' \"$vf\")\n\n  # If only one storage exists for the content type, auto-pick. Else always ask (your wish #4).\n  local content=\"rootdir\"\n  [[ \"$class\" == \"template\" ]] && content=\"vztmpl\"\n  local count\n  count=$(pvesm status -content \"$content\" | awk 'NR>1{print $1}' | wc -l)\n\n  if [[ \"$count\" -eq 1 ]]; then\n    STORAGE_RESULT=$(pvesm status -content \"$content\" | awk 'NR>1{print $1; exit}')\n    STORAGE_INFO=\"\"\n\n    # Validate storage space for auto-picked container storage\n    if [[ \"$class\" == \"container\" && -n \"${DISK_SIZE:-}\" ]]; then\n      validate_storage_space \"$STORAGE_RESULT\" \"$DISK_SIZE\" \"yes\"\n      # Continue even if validation fails - user was warned\n    fi\n  else\n    # If the current value is preselectable, we could show it, but per your requirement we always offer selection\n    select_storage \"$class\" || return 150\n  fi\n\n  _write_storage_to_vars \"$vf\" \"$key\" \"$STORAGE_RESULT\"\n\n  # Keep environment in sync for later steps (e.g. app-default save)\n  if [[ \"$class\" == \"container\" ]]; then\n    export var_container_storage=\"$STORAGE_RESULT\"\n    export CONTAINER_STORAGE=\"$STORAGE_RESULT\"\n  else\n    export var_template_storage=\"$STORAGE_RESULT\"\n    export TEMPLATE_STORAGE=\"$STORAGE_RESULT\"\n  fi\n\n  # Silent operation - no output message\n}\n\n# ==============================================================================\n# SECTION 5: CONFIGURATION & DEFAULTS MANAGEMENT\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# base_settings()\n#\n# - Defines all base/default variables for container creation\n# - Reads from environment variables (var_*)\n# - Provides fallback defaults for OS type/version\n# - App-specific values take precedence when they are HIGHER (for CPU, RAM, DISK)\n# - Sets up container type, resources, network, SSH, features, and tags\n# ------------------------------------------------------------------------------\nbase_settings() {\n  # Default Settings\n  CT_TYPE=${var_unprivileged:-\"1\"}\n\n  # Resource allocation: App defaults take precedence if HIGHER\n  # Compare app-declared values (saved in APP_DEFAULT_*) with current var_* values\n  local final_disk=\"${var_disk:-4}\"\n  local final_cpu=\"${var_cpu:-1}\"\n  local final_ram=\"${var_ram:-1024}\"\n\n  # If app declared higher values, use those instead\n  if [[ -n \"${APP_DEFAULT_DISK:-}\" && \"${APP_DEFAULT_DISK}\" =~ ^[0-9]+$ ]]; then\n    if [[ \"${APP_DEFAULT_DISK}\" -gt \"${final_disk}\" ]]; then\n      final_disk=\"${APP_DEFAULT_DISK}\"\n    fi\n  fi\n\n  if [[ -n \"${APP_DEFAULT_CPU:-}\" && \"${APP_DEFAULT_CPU}\" =~ ^[0-9]+$ ]]; then\n    if [[ \"${APP_DEFAULT_CPU}\" -gt \"${final_cpu}\" ]]; then\n      final_cpu=\"${APP_DEFAULT_CPU}\"\n    fi\n  fi\n\n  if [[ -n \"${APP_DEFAULT_RAM:-}\" && \"${APP_DEFAULT_RAM}\" =~ ^[0-9]+$ ]]; then\n    if [[ \"${APP_DEFAULT_RAM}\" -gt \"${final_ram}\" ]]; then\n      final_ram=\"${APP_DEFAULT_RAM}\"\n    fi\n  fi\n\n  DISK_SIZE=\"${final_disk}\"\n  CORE_COUNT=\"${final_cpu}\"\n  RAM_SIZE=\"${final_ram}\"\n  VERBOSE=${var_verbose:-\"${1:-no}\"}\n  PW=\"\"\n  if [[ -n \"${var_pw:-}\" ]]; then\n    local _pw_raw=\"${var_pw}\"\n    case \"$_pw_raw\" in\n    --password\\ *) _pw_raw=\"${_pw_raw#--password }\" ;;\n    -password\\ *) _pw_raw=\"${_pw_raw#-password }\" ;;\n    esac\n    while [[ \"$_pw_raw\" == -* ]]; do\n      _pw_raw=\"${_pw_raw#-}\"\n    done\n    if [[ -z \"$_pw_raw\" ]]; then\n      msg_warn \"Password was only dashes after cleanup; leaving empty.\"\n    else\n      PW=\"--password $_pw_raw\"\n    fi\n  fi\n\n  # Validate and set Container ID\n  local requested_id=\"${var_ctid:-$NEXTID}\"\n  if ! validate_container_id \"$requested_id\"; then\n    # Only show warning if user manually specified an ID (not auto-assigned)\n    if [[ -n \"${var_ctid:-}\" ]]; then\n      msg_warn \"Container ID $requested_id is already in use. Using next available ID: $(get_valid_container_id \"$requested_id\")\"\n    fi\n    requested_id=$(get_valid_container_id \"$requested_id\")\n  fi\n  CT_ID=\"$requested_id\"\n\n  # Validate and set Hostname/FQDN\n  local requested_hostname=\"${var_hostname:-$NSAPP}\"\n  requested_hostname=$(echo \"${requested_hostname,,}\" | tr -d ' ')\n  if ! validate_hostname \"$requested_hostname\"; then\n    if [[ -n \"${var_hostname:-}\" ]]; then\n      msg_warn \"Invalid hostname '$requested_hostname'. Using default: $NSAPP\"\n    fi\n    requested_hostname=\"$NSAPP\"\n  fi\n  HN=\"$requested_hostname\"\n\n  BRG=${var_brg:-\"vmbr0\"}\n  NET=${var_net:-\"dhcp\"}\n\n  # Resolve IP range if NET contains a range (e.g., 192.168.1.100/24-192.168.1.200/24)\n  if is_ip_range \"$NET\"; then\n    msg_info \"Scanning IP range: $NET\"\n    if resolve_ip_from_range \"$NET\"; then\n      NET=\"$NET_RESOLVED\"\n    else\n      msg_error \"Could not find free IP in range. Falling back to DHCP.\"\n      NET=\"dhcp\"\n    fi\n  fi\n\n  IPV6_METHOD=${var_ipv6_method:-\"none\"}\n  GATE=${var_gateway:-\"\"}\n  APT_CACHER=${var_apt_cacher:-\"\"}\n  APT_CACHER_IP=${var_apt_cacher_ip:-\"\"}\n\n  # Runtime check: Verify APT cacher is reachable if configured\n  if [[ -n \"$APT_CACHER_IP\" && \"$APT_CACHER\" == \"yes\" ]]; then\n    local _check_host _check_port _check_url\n    _check_host=$(echo \"$APT_CACHER_IP\" | sed -e 's|https\\?://||' -e 's|/.*||' | cut -d: -f1)\n    _check_port=$(echo \"$APT_CACHER_IP\" | sed -e 's|https\\?://||' -e 's|/.*||' | cut -s -d: -f2)\n    if [[ \"$APT_CACHER_IP\" =~ ^https?:// ]]; then\n      _check_url=\"$APT_CACHER_IP\"\n      _check_port=\"${_check_port:-80}\"\n    else\n      _check_port=\"${_check_port:-3142}\"\n      _check_url=\"http://${APT_CACHER_IP}:${_check_port}\"\n    fi\n    if ! curl -s --connect-timeout 2 \"${_check_url}\" >/dev/null 2>&1; then\n      msg_warn \"APT Cacher configured but not reachable at ${_check_url}\"\n      msg_custom \"⚠️\" \"${YW}\" \"Disabling APT Cacher for this installation\"\n      APT_CACHER=\"\"\n      APT_CACHER_IP=\"\"\n    else\n      msg_ok \"APT Cacher verified at ${_check_url}\"\n    fi\n  fi\n\n  MTU=${var_mtu:-\"\"}\n  _sd_val=\"${var_searchdomain:-\"\"}\"\n  [[ -n \"$_sd_val\" ]] && SD=\"-searchdomain=$_sd_val\" || SD=\"\"\n  _ns_val=\"${var_ns:-\"\"}\"\n  [[ -n \"$_ns_val\" ]] && NS=\"-nameserver=$_ns_val\" || NS=\"\"\n  MAC=${var_mac:-\"\"}\n  VLAN=${var_vlan:-\"\"}\n  SSH=${var_ssh:-\"no\"}\n  SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-\"\"}\n  # Build TAGS: ensure community-script prefix, use semicolons (pct format), no duplicates\n  if [[ \"${var_tags:-}\" == *community-script* ]]; then\n    TAGS=\"${var_tags:-community-script}\"\n  else\n    TAGS=\"community-script${var_tags:+;${var_tags}}\"\n  fi\n  ENABLE_FUSE=${var_fuse:-\"${1:-no}\"}\n  ENABLE_TUN=${var_tun:-\"${1:-no}\"}\n\n  # Additional settings that may be skipped if advanced_settings is not run (e.g., App Defaults)\n  ENABLE_GPU=${var_gpu:-\"no\"}\n  ENABLE_NESTING=${var_nesting:-\"1\"}\n  ENABLE_KEYCTL=${var_keyctl:-\"0\"}\n  ENABLE_MKNOD=${var_mknod:-\"0\"}\n  ALLOW_MOUNT_FS=${var_mount_fs:-\"\"}\n  PROTECT_CT=${var_protection:-\"no\"}\n  CT_TIMEZONE=${var_timezone:-\"$timezone\"}\n  [[ \"${CT_TIMEZONE:-}\" == Etc/* ]] && CT_TIMEZONE=\"host\" # pct doesn't accept Etc/* zones\n\n  # Since these 2 are only defined outside of default_settings function, we add a temporary fallback. TODO: To align everything, we should add these as constant variables (e.g. OSTYPE and OSVERSION), but that would currently require updating the default_settings function for all existing scripts\n  if [ -z \"$var_os\" ]; then\n    var_os=\"debian\"\n  fi\n  if [ -z \"$var_version\" ]; then\n    var_version=\"12\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# load_vars_file()\n#\n# - Safe parser for KEY=VALUE lines from vars files\n# - Used by default_var_settings and app defaults loading\n# - Only loads whitelisted var_* keys\n# - Optional force parameter to override existing values (for app defaults)\n# ------------------------------------------------------------------------------\nload_vars_file() {\n  local file=\"$1\"\n  local force=\"${2:-no}\" # If \"yes\", override existing variables\n  [ -f \"$file\" ] || return 0\n  msg_info \"Loading defaults from ${file}\"\n\n  # Allowed var_* keys\n  local VAR_WHITELIST=(\n    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl\n    var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu\n    var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged\n    var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain\n    var_post_install\n  )\n\n  # Whitelist check helper\n  _is_whitelisted() {\n    local k=\"$1\" w\n    for w in \"${VAR_WHITELIST[@]}\"; do [ \"$k\" = \"$w\" ] && return 0; done\n    return 1\n  }\n\n  local line key val\n  while IFS= read -r line || [ -n \"$line\" ]; do\n    line=\"${line#\"${line%%[![:space:]]*}\"}\"\n    line=\"${line%\"${line##*[![:space:]]}\"}\"\n    [[ -z \"$line\" || \"$line\" == \\#* ]] && continue\n    if [[ \"$line\" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then\n      local var_key=\"${BASH_REMATCH[1]}\"\n      local var_val=\"${BASH_REMATCH[2]}\"\n\n      [[ \"$var_key\" != var_* ]] && continue\n      _is_whitelisted \"$var_key\" || continue\n\n      # Strip inline comments (anything after unquoted #)\n      # Only strip if not inside quotes\n      if [[ ! \"$var_val\" =~ ^[\\\"\\'] ]]; then\n        var_val=\"${var_val%%#*}\"\n      fi\n\n      # Strip quotes\n      if [[ \"$var_val\" =~ ^\\\"(.*)\\\"$ ]]; then\n        var_val=\"${BASH_REMATCH[1]}\"\n      elif [[ \"$var_val\" =~ ^\\'(.*)\\'$ ]]; then\n        var_val=\"${BASH_REMATCH[1]}\"\n      fi\n\n      # Trim trailing whitespace\n      var_val=\"${var_val%\"${var_val##*[![:space:]]}\"}\"\n\n      # Validate values before setting (skip empty values - they use defaults)\n      if [[ -n \"$var_val\" ]]; then\n        case \"$var_key\" in\n        var_mac)\n          if ! validate_mac_address \"$var_val\"; then\n            msg_warn \"Invalid MAC address '$var_val' in $file, ignoring\"\n            continue\n          fi\n          ;;\n        var_vlan)\n          if ! validate_vlan_tag \"$var_val\"; then\n            msg_warn \"Invalid VLAN tag '$var_val' in $file (must be 1-4094), ignoring\"\n            continue\n          fi\n          ;;\n        var_mtu)\n          if ! validate_mtu \"$var_val\"; then\n            msg_warn \"Invalid MTU '$var_val' in $file (must be 576-65535), ignoring\"\n            continue\n          fi\n          ;;\n        var_tags)\n          if ! validate_tags \"$var_val\"; then\n            msg_warn \"Invalid tags '$var_val' in $file (alphanumeric, -, _, ; only), ignoring\"\n            continue\n          fi\n          ;;\n        var_timezone)\n          if ! validate_timezone \"$var_val\"; then\n            msg_warn \"Invalid timezone '$var_val' in $file, ignoring\"\n            continue\n          fi\n          ;;\n        var_brg)\n          if ! validate_bridge \"$var_val\"; then\n            msg_warn \"Bridge '$var_val' not found in $file, ignoring\"\n            continue\n          fi\n          ;;\n        var_gateway)\n          if ! validate_gateway_ip \"$var_val\"; then\n            msg_warn \"Invalid gateway IP '$var_val' in $file, ignoring\"\n            continue\n          fi\n          ;;\n        var_hostname)\n          if ! validate_hostname \"$var_val\"; then\n            msg_warn \"Invalid hostname '$var_val' in $file, ignoring\"\n            continue\n          fi\n          ;;\n        var_cpu)\n          if ! [[ \"$var_val\" =~ ^[0-9]+$ ]] || ((var_val < 1 || var_val > 128)); then\n            msg_warn \"Invalid CPU count '$var_val' in $file (must be 1-128), ignoring\"\n            continue\n          fi\n          ;;\n        var_ram)\n          if ! [[ \"$var_val\" =~ ^[0-9]+$ ]] || ((var_val < 256)); then\n            msg_warn \"Invalid RAM '$var_val' in $file (must be >= 256 MiB), ignoring\"\n            continue\n          fi\n          ;;\n        var_disk)\n          if ! [[ \"$var_val\" =~ ^[0-9]+$ ]] || ((var_val < 1)); then\n            msg_warn \"Invalid disk size '$var_val' in $file (must be >= 1 GB), ignoring\"\n            continue\n          fi\n          ;;\n        var_unprivileged)\n          if [[ \"$var_val\" != \"0\" && \"$var_val\" != \"1\" ]]; then\n            msg_warn \"Invalid unprivileged value '$var_val' in $file (must be 0 or 1), ignoring\"\n            continue\n          fi\n          ;;\n        var_nesting)\n          if [[ \"$var_val\" != \"0\" && \"$var_val\" != \"1\" ]]; then\n            msg_warn \"Invalid nesting value '$var_val' in $file (must be 0 or 1), ignoring\"\n            continue\n          fi\n          # Warn about potential issues with systemd-based OS when nesting is disabled via vars file\n          if [[ \"$var_val\" == \"0\" && \"${var_os:-debian}\" != \"alpine\" ]]; then\n            msg_warn \"Nesting disabled in $file - modern systemd-based distributions may require nesting for proper operation\"\n          fi\n          ;;\n        var_keyctl)\n          if [[ \"$var_val\" != \"0\" && \"$var_val\" != \"1\" ]]; then\n            msg_warn \"Invalid keyctl value '$var_val' in $file (must be 0 or 1), ignoring\"\n            continue\n          fi\n          ;;\n        var_net)\n          # var_net can be: dhcp, static IP/CIDR, or IP range\n          if [[ \"$var_val\" != \"dhcp\" ]]; then\n            if is_ip_range \"$var_val\"; then\n              : # IP range is valid, will be resolved at runtime\n            elif ! validate_ip_address \"$var_val\"; then\n              msg_warn \"Invalid network '$var_val' in $file (must be dhcp or IP/CIDR), ignoring\"\n              continue\n            fi\n          fi\n          ;;\n        var_fuse | var_tun | var_gpu | var_ssh | var_verbose | var_protection)\n          if [[ \"$var_val\" != \"yes\" && \"$var_val\" != \"no\" ]]; then\n            msg_warn \"Invalid boolean '$var_val' for $var_key in $file (must be yes/no), ignoring\"\n            continue\n          fi\n          ;;\n        var_mknod)\n          if [[ \"$var_val\" != \"0\" && \"$var_val\" != \"1\" ]]; then\n            msg_warn \"Invalid mknod value '$var_val' in $file (must be 0 or 1), ignoring\"\n            continue\n          fi\n          ;;\n        var_mount_fs)\n          # Normalize: strip spaces, trailing commas\n          var_val=\"${var_val// /}\"\n          var_val=\"${var_val%%,}\"\n          var_val=\"${var_val##,}\"\n          if [[ -n \"$var_val\" ]] && [[ ! \"$var_val\" =~ ^[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$ ]]; then\n            msg_warn \"Invalid mount_fs value '$var_val' in $file (comma-separated fs names only, e.g. nfs,cifs), ignoring\"\n            continue\n          fi\n          ;;\n        var_ipv6_method)\n          if [[ \"$var_val\" != \"auto\" && \"$var_val\" != \"dhcp\" && \"$var_val\" != \"static\" && \"$var_val\" != \"none\" ]]; then\n            msg_warn \"Invalid IPv6 method '$var_val' in $file (must be auto/dhcp/static/none), ignoring\"\n            continue\n          fi\n          ;;\n        var_apt_cacher_ip)\n          # Allow: plain IP/hostname, http://host, https://host:port\n          if [[ -n \"$var_val\" ]] && ! [[ \"$var_val\" =~ ^(https?://)?[a-zA-Z0-9._-]+(:[0-9]+)?(/.*)?$ ]]; then\n            msg_warn \"Invalid APT Cacher address '$var_val' in $file, ignoring\"\n            continue\n          fi\n          ;;\n        var_container_storage | var_template_storage)\n          # Validate that the storage exists and is active on the current node\n          local _storage_status\n          _storage_status=$(pvesm status 2>/dev/null | awk -v s=\"$var_val\" '$1 == s { print $3 }')\n          if [[ -z \"$_storage_status\" ]]; then\n            msg_warn \"Storage '$var_val' from $file not found on this node, ignoring\"\n            continue\n          elif [[ \"$_storage_status\" == \"disabled\" ]]; then\n            msg_warn \"Storage '$var_val' from $file is disabled on this node, ignoring\"\n            continue\n          fi\n          ;;\n        esac\n      fi\n\n      # Set variable: force mode overrides existing, otherwise only set if empty\n      if [[ \"$force\" == \"yes\" ]]; then\n        export \"${var_key}=${var_val}\"\n      else\n        [[ -z \"${!var_key+x}\" ]] && export \"${var_key}=${var_val}\"\n      fi\n    fi\n  done <\"$file\"\n  msg_ok \"Loaded ${file}\"\n}\n\n# ------------------------------------------------------------------------------\n# default_var_settings\n#\n# - Ensures /usr/local/community-scripts/default.vars exists (creates if missing)\n# - Loads var_* values from default.vars (safe parser, no source/eval)\n# - Precedence: ENV var_* > default.vars > built-in defaults\n# - Maps var_verbose → VERBOSE\n# - Calls base_settings \"$VERBOSE\" and echo_default\n# ------------------------------------------------------------------------------\ndefault_var_settings() {\n  # Allowed var_* keys (alphabetically sorted)\n  # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)\n  local VAR_WHITELIST=(\n    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl\n    var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu\n    var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged\n    var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage\n    var_post_install\n  )\n\n  # Snapshot: environment variables (highest precedence)\n  declare -A _HARD_ENV=()\n  local _k\n  for _k in \"${VAR_WHITELIST[@]}\"; do\n    if printenv \"$_k\" >/dev/null 2>&1; then _HARD_ENV[\"$_k\"]=1; fi\n  done\n\n  # Find default.vars location\n  local _find_default_vars\n  _find_default_vars() {\n    local f\n    for f in \\\n      /usr/local/community-scripts/default.vars \\\n      \"$HOME/.config/community-scripts/default.vars\" \\\n      \"./default.vars\"; do\n      [ -f \"$f\" ] && {\n        echo \"$f\"\n        return 0\n      }\n    done\n    return 252\n  }\n  # Allow override of storages via env (for non-interactive use cases)\n  [ -n \"${var_template_storage:-}\" ] && TEMPLATE_STORAGE=\"$var_template_storage\"\n  [ -n \"${var_container_storage:-}\" ] && CONTAINER_STORAGE=\"$var_container_storage\"\n\n  # Create once, with storages already selected, no var_ctid/var_hostname lines\n  local _ensure_default_vars\n  _ensure_default_vars() {\n    _find_default_vars >/dev/null 2>&1 && return 0\n\n    local canonical=\"/usr/local/community-scripts/default.vars\"\n    # Silent creation - no msg_info output\n    mkdir -p /usr/local/community-scripts\n\n    # Pick storages before writing the file (always ask unless only one)\n    # Create a minimal temp file to write into\n    : >\"$canonical\"\n\n    # Base content (no var_ctid / var_hostname here)\n    cat >\"$canonical\" <<'EOF'\n# Community-Scripts defaults (var_* only). Lines starting with # are comments.\n# Precedence: ENV var_* > default.vars > built-ins.\n# Keep keys alphabetically sorted.\n\n# Container type\nvar_unprivileged=1\n\n# Resources\nvar_cpu=1\nvar_disk=4\nvar_ram=1024\n\n# Network\nvar_brg=vmbr0\nvar_net=dhcp\nvar_ipv6_method=none\n# var_gateway=\n# var_vlan=\n# var_mtu=\n# var_mac=\n# var_ns=\n\n# SSH\nvar_ssh=no\n# var_ssh_authorized_key=\n\n# APT cacher (optional - IP or URL)\n# var_apt_cacher=yes\n# var_apt_cacher_ip=192.168.1.10\n# var_apt_cacher_ip=http://proxy.local\n# var_apt_cacher_ip=https://proxy.local:443\n\n# Features/Tags/verbosity\nvar_fuse=no\nvar_tun=no\n\n# Advanced Settings (Proxmox-official features)\nvar_nesting=1          # Allow nesting (required for Docker/LXC in CT)\nvar_keyctl=0           # Allow keyctl() - needed for Docker (systemd-networkd workaround)\nvar_mknod=0            # Allow device node creation (requires kernel 5.3+, experimental)\nvar_mount_fs=          # Allow specific filesystems: nfs,fuse,ext4,etc (leave empty for defaults)\nvar_protection=no      # Prevent accidental deletion of container\nvar_timezone=          # Container timezone (e.g. Europe/Berlin, leave empty for host timezone)\nvar_tags=community-script\nvar_verbose=no\n\n# Security (root PW) – empty => autologin\n# var_pw=\n\n# GitHub Personal Access Token (optional – avoids API rate limits during installs)\n# Create at https://github.com/settings/tokens – read-only public access is sufficient\n# var_github_token=ghp_your_token_here\n\n# Optional post-install script (host-side path to a *.sh on the Proxmox host)\n# Runs ON THE HOST after the container is fully provisioned.\n# Available env vars: APP, NSAPP, CTID, IP, HN, STORAGE, BRG\n# var_post_install=/opt/post-install/myhook.sh\nEOF\n\n    # Now choose storages (always prompt unless just one exists)\n    choose_and_set_storage_for_file \"$canonical\" template\n    choose_and_set_storage_for_file \"$canonical\" container\n\n    chmod 0644 \"$canonical\"\n    # Silent creation - no output message\n  }\n\n  # Whitelist check\n  local _is_whitelisted_key\n  _is_whitelisted_key() {\n    local k=\"$1\"\n    local w\n    for w in \"${VAR_WHITELIST[@]}\"; do [ \"$k\" = \"$w\" ] && return 0; done\n    return 1\n  }\n\n  # 1) Ensure file exists\n  _ensure_default_vars\n\n  # 2) Load file\n  local dv\n  dv=\"$(_find_default_vars)\" || {\n    msg_error \"default.vars not found after ensure step\"\n    return 252\n  }\n  load_vars_file \"$dv\"\n\n  # 3) Map var_verbose → VERBOSE\n  if [[ -n \"${var_verbose:-}\" ]]; then\n    case \"${var_verbose,,}\" in 1 | yes | true | on) VERBOSE=\"yes\" ;; 0 | no | false | off) VERBOSE=\"no\" ;; *) VERBOSE=\"${var_verbose}\" ;; esac\n  else\n    VERBOSE=\"no\"\n  fi\n\n  # 4) Map var_github_token → GITHUB_TOKEN (only if not already set in environment)\n  if [[ -z \"${GITHUB_TOKEN:-}\" && -n \"${var_github_token:-}\" ]]; then\n    export GITHUB_TOKEN=\"${var_github_token}\"\n  fi\n\n  # 4) Apply base settings and show summary\n  METHOD=\"mydefaults-global\"\n  base_settings \"$VERBOSE\"\n  header_info\n  echo -e \"${DEFAULT}${BOLD}${BL}Using User Defaults (default.vars) on node $PVEHOST_NAME${CL}\"\n  echo_default\n}\n\n# ------------------------------------------------------------------------------\n# get_app_defaults_path()\n#\n# - Returns full path for app-specific defaults file\n# - Example: /usr/local/community-scripts/defaults/<app>.vars\n# ------------------------------------------------------------------------------\n\nget_app_defaults_path() {\n  local n=\"${NSAPP:-${APP,,}}\"\n  echo \"/usr/local/community-scripts/defaults/${n}.vars\"\n}\n\n# ------------------------------------------------------------------------------\n# maybe_offer_save_app_defaults\n#\n# - Called after advanced_settings returned with fully chosen values.\n# - If no <nsapp>.vars exists, offers to persist current advanced settings\n#   into /usr/local/community-scripts/defaults/<nsapp>.vars\n# - Only writes whitelisted var_* keys.\n# - Extracts raw values from flags like \",gw=...\" \",mtu=...\" etc.\n# ------------------------------------------------------------------------------\nif ! declare -p VAR_WHITELIST >/dev/null 2>&1; then\n  # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)\n  declare -ag VAR_WHITELIST=(\n    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl\n    var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu\n    var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged\n    var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain\n    var_post_install\n  )\nfi\n\n# Global whitelist check function (used by _load_vars_file_to_map and others)\n_is_whitelisted_key() {\n  local k=\"$1\"\n  local w\n  for w in \"${VAR_WHITELIST[@]}\"; do [ \"$k\" = \"$w\" ] && return 0; done\n  return 1\n}\n\n_sanitize_value() {\n  # Disallow Command-Substitution / Shell-Meta\n  case \"$1\" in\n  *'$('* | *'`'* | *';'* | *'&'* | *'<('*)\n    echo \"\"\n    return 0\n    ;;\n  esac\n  echo \"$1\"\n}\n\n# Map-Parser: read var_* from file into _VARS_IN associative array\n# Note: Main _load_vars_file() with full validation is defined in default_var_settings section\n# This simplified version is used specifically for diff operations via _VARS_IN array\ndeclare -A _VARS_IN\n_load_vars_file_to_map() {\n  local file=\"$1\"\n  [ -f \"$file\" ] || return 0\n  _VARS_IN=() # Clear array\n  local line key val\n  while IFS= read -r line || [ -n \"$line\" ]; do\n    line=\"${line#\"${line%%[![:space:]]*}\"}\"\n    line=\"${line%\"${line##*[![:space:]]}\"}\"\n    [ -z \"$line\" ] && continue\n    case \"$line\" in\n    \\#*) continue ;;\n    esac\n    key=$(printf \"%s\" \"$line\" | cut -d= -f1)\n    val=$(printf \"%s\" \"$line\" | cut -d= -f2-)\n    case \"$key\" in\n    var_*)\n      if _is_whitelisted_key \"$key\"; then\n        _VARS_IN[\"$key\"]=\"$val\"\n      fi\n      ;;\n    esac\n  done <\"$file\"\n}\n\n# Diff function for two var_* files -> produces human-readable diff list for $1 (old) vs $2 (new)\n_build_vars_diff() {\n  local oldf=\"$1\" newf=\"$2\"\n  local k\n  local -A OLD=() NEW=()\n  _load_vars_file_to_map \"$oldf\"\n  for k in \"${!_VARS_IN[@]}\"; do OLD[\"$k\"]=\"${_VARS_IN[$k]}\"; done\n  _load_vars_file_to_map \"$newf\"\n  for k in \"${!_VARS_IN[@]}\"; do NEW[\"$k\"]=\"${_VARS_IN[$k]}\"; done\n\n  local out\n  out+=\"# Diff for ${APP} (${NSAPP})\\n\"\n  out+=\"# Old: ${oldf}\\n# New: ${newf}\\n\\n\"\n\n  local found_change=0\n\n  # Changed & Removed\n  for k in \"${!OLD[@]}\"; do\n    if [[ -v NEW[\"$k\"] ]]; then\n      if [[ \"${OLD[$k]}\" != \"${NEW[$k]}\" ]]; then\n        out+=\"~ ${k}\\n    - old: ${OLD[$k]}\\n    + new: ${NEW[$k]}\\n\"\n        found_change=1\n      fi\n    else\n      out+=\"- ${k}\\n    - old: ${OLD[$k]}\\n\"\n      found_change=1\n    fi\n  done\n\n  # Added\n  for k in \"${!NEW[@]}\"; do\n    if [[ ! -v OLD[\"$k\"] ]]; then\n      out+=\"+ ${k}\\n    + new: ${NEW[$k]}\\n\"\n      found_change=1\n    fi\n  done\n\n  if [[ $found_change -eq 0 ]]; then\n    out+=\"(No differences)\\n\"\n  fi\n\n  printf \"%b\" \"$out\"\n}\n\n# Build a temporary <app>.vars file from current advanced settings\n_build_current_app_vars_tmp() {\n  tmpf=\"$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)\"\n\n  # NET/GW\n  _net=\"${NET:-}\"\n  _gate=\"\"\n  case \"${GATE:-}\" in\n  ,gw=*) _gate=$(echo \"$GATE\" | sed 's/^,gw=//') ;;\n  esac\n\n  # IPv6\n  _ipv6_method=\"${IPV6_METHOD:-auto}\"\n  _ipv6_static=\"\"\n  _ipv6_gateway=\"\"\n  if [ \"$_ipv6_method\" = \"static\" ]; then\n    _ipv6_static=\"${IPV6_ADDR:-}\"\n    _ipv6_gateway=\"${IPV6_GATE:-}\"\n  fi\n\n  # MTU/VLAN/MAC\n  _mtu=\"\"\n  _vlan=\"\"\n  _mac=\"\"\n  case \"${MTU:-}\" in\n  ,mtu=*) _mtu=$(echo \"$MTU\" | sed 's/^,mtu=//') ;;\n  esac\n  case \"${VLAN:-}\" in\n  ,tag=*) _vlan=$(echo \"$VLAN\" | sed 's/^,tag=//') ;;\n  esac\n  case \"${MAC:-}\" in\n  ,hwaddr=*) _mac=$(echo \"$MAC\" | sed 's/^,hwaddr=//') ;;\n  esac\n\n  # DNS / Searchdomain\n  _ns=\"\"\n  _searchdomain=\"\"\n  case \"${NS:-}\" in\n  -nameserver=*) _ns=$(echo \"$NS\" | sed 's/^-nameserver=//') ;;\n  esac\n  case \"${SD:-}\" in\n  -searchdomain=*) _searchdomain=$(echo \"$SD\" | sed 's/^-searchdomain=//') ;;\n  esac\n\n  # SSH / APT / Features\n  _ssh=\"${SSH:-no}\"\n  _ssh_auth=\"${SSH_AUTHORIZED_KEY:-}\"\n  _apt_cacher=\"${APT_CACHER:-}\"\n  _apt_cacher_ip=\"${APT_CACHER_IP:-}\"\n  _fuse=\"${ENABLE_FUSE:-no}\"\n  _tun=\"${ENABLE_TUN:-no}\"\n  _gpu=\"${ENABLE_GPU:-no}\"\n  _nesting=\"${ENABLE_NESTING:-1}\"\n  _keyctl=\"${ENABLE_KEYCTL:-0}\"\n  _mknod=\"${ENABLE_MKNOD:-0}\"\n  _mount_fs=\"${ALLOW_MOUNT_FS:-}\"\n  _protect=\"${PROTECT_CT:-no}\"\n  _timezone=\"${CT_TIMEZONE:-}\"\n  _tags=\"${TAGS:-}\"\n  _verbose=\"${VERBOSE:-no}\"\n\n  # Type / Resources / Identity\n  _unpriv=\"${CT_TYPE:-1}\"\n  _cpu=\"${CORE_COUNT:-1}\"\n  _ram=\"${RAM_SIZE:-1024}\"\n  _disk=\"${DISK_SIZE:-4}\"\n  _hostname=\"${HN:-$NSAPP}\"\n\n  # Storage\n  _tpl_storage=\"${TEMPLATE_STORAGE:-${var_template_storage:-}}\"\n  _ct_storage=\"${CONTAINER_STORAGE:-${var_container_storage:-}}\"\n\n  {\n    echo \"# App-specific defaults for ${APP} (${NSAPP})\"\n    echo \"# Generated on $(date -u '+%Y-%m-%dT%H:%M:%SZ')\"\n    echo\n\n    echo \"var_os=$(_sanitize_value \"${var_os:-}\")\"\n    echo \"var_version=$(_sanitize_value \"${var_version:-}\")\"\n    echo \"var_unprivileged=$(_sanitize_value \"$_unpriv\")\"\n    echo \"var_cpu=$(_sanitize_value \"$_cpu\")\"\n    echo \"var_ram=$(_sanitize_value \"$_ram\")\"\n    echo \"var_disk=$(_sanitize_value \"$_disk\")\"\n\n    [ -n \"${BRG:-}\" ] && echo \"var_brg=$(_sanitize_value \"$BRG\")\"\n    [ -n \"$_net\" ] && echo \"var_net=$(_sanitize_value \"$_net\")\"\n    [ -n \"$_gate\" ] && echo \"var_gateway=$(_sanitize_value \"$_gate\")\"\n    [ -n \"$_mtu\" ] && echo \"var_mtu=$(_sanitize_value \"$_mtu\")\"\n    [ -n \"$_vlan\" ] && echo \"var_vlan=$(_sanitize_value \"$_vlan\")\"\n    [ -n \"$_mac\" ] && echo \"var_mac=$(_sanitize_value \"$_mac\")\"\n    [ -n \"$_ns\" ] && echo \"var_ns=$(_sanitize_value \"$_ns\")\"\n\n    [ -n \"$_ipv6_method\" ] && echo \"var_ipv6_method=$(_sanitize_value \"$_ipv6_method\")\"\n    # var_ipv6_static removed - static IPs are unique, can't be default\n\n    [ -n \"$_ssh\" ] && echo \"var_ssh=$(_sanitize_value \"$_ssh\")\"\n    [ -n \"$_ssh_auth\" ] && echo \"var_ssh_authorized_key=$(_sanitize_value \"$_ssh_auth\")\"\n\n    [ -n \"$_apt_cacher\" ] && echo \"var_apt_cacher=$(_sanitize_value \"$_apt_cacher\")\"\n    [ -n \"$_apt_cacher_ip\" ] && echo \"var_apt_cacher_ip=$(_sanitize_value \"$_apt_cacher_ip\")\"\n\n    [ -n \"$_fuse\" ] && echo \"var_fuse=$(_sanitize_value \"$_fuse\")\"\n    [ -n \"$_tun\" ] && echo \"var_tun=$(_sanitize_value \"$_tun\")\"\n    [ -n \"$_gpu\" ] && echo \"var_gpu=$(_sanitize_value \"$_gpu\")\"\n    [ -n \"$_nesting\" ] && echo \"var_nesting=$(_sanitize_value \"$_nesting\")\"\n    [ -n \"$_keyctl\" ] && echo \"var_keyctl=$(_sanitize_value \"$_keyctl\")\"\n    [ -n \"$_mknod\" ] && echo \"var_mknod=$(_sanitize_value \"$_mknod\")\"\n    [ -n \"$_mount_fs\" ] && echo \"var_mount_fs=$(_sanitize_value \"$_mount_fs\")\"\n    [ -n \"$_protect\" ] && echo \"var_protection=$(_sanitize_value \"$_protect\")\"\n    [ -n \"$_timezone\" ] && echo \"var_timezone=$(_sanitize_value \"$_timezone\")\"\n    [ -n \"$_tags\" ] && echo \"var_tags=$(_sanitize_value \"$_tags\")\"\n    [ -n \"$_verbose\" ] && echo \"var_verbose=$(_sanitize_value \"$_verbose\")\"\n\n    [ -n \"$_hostname\" ] && echo \"var_hostname=$(_sanitize_value \"$_hostname\")\"\n    [ -n \"$_searchdomain\" ] && echo \"var_searchdomain=$(_sanitize_value \"$_searchdomain\")\"\n\n    [ -n \"$_tpl_storage\" ] && echo \"var_template_storage=$(_sanitize_value \"$_tpl_storage\")\"\n    [ -n \"$_ct_storage\" ] && echo \"var_container_storage=$(_sanitize_value \"$_ct_storage\")\"\n    [ -n \"${var_post_install:-}\" ] && echo \"var_post_install=$(_sanitize_value \"${var_post_install}\")\"\n  } >\"$tmpf\"\n\n  echo \"$tmpf\"\n}\n\n# ------------------------------------------------------------------------------\n# maybe_offer_save_app_defaults()\n#\n# - Called after advanced_settings()\n# - Offers to save current values as app defaults if not existing\n# - If file exists: shows diff and allows Update, Keep, View Diff, or Cancel\n# ------------------------------------------------------------------------------\nmaybe_offer_save_app_defaults() {\n  local app_vars_path\n  app_vars_path=\"$(get_app_defaults_path)\"\n\n  # always build from current settings\n  local new_tmp diff_tmp\n  new_tmp=\"$(_build_current_app_vars_tmp)\"\n  diff_tmp=\"$(mktemp -p /tmp \"${NSAPP:-app}.vars.diff.XXXXXX\")\"\n\n  # 1) if no file → offer to create\n  if [[ ! -f \"$app_vars_path\" ]]; then\n    if whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --yesno \"Save these advanced settings as defaults for ${APP}?\\n\\nThis will create:\\n${app_vars_path}\" 12 72; then\n      mkdir -p \"$(dirname \"$app_vars_path\")\"\n      /usr/bin/install -m 0644 \"$new_tmp\" \"$app_vars_path\"\n      msg_ok \"Saved app defaults: ${app_vars_path}\"\n    fi\n    rm -f \"$new_tmp\" \"$diff_tmp\"\n    return 0\n  fi\n\n  # 2) if file exists → build diff\n  _build_vars_diff \"$app_vars_path\" \"$new_tmp\" >\"$diff_tmp\"\n\n  # if no differences → do nothing\n  if grep -q \"^(No differences)$\" \"$diff_tmp\"; then\n    rm -f \"$new_tmp\" \"$diff_tmp\"\n    return 0\n  fi\n\n  # 3) if file exists → show menu with default selection \"Update Defaults\"\n  local app_vars_file\n  app_vars_file=\"$(basename \"$app_vars_path\")\"\n\n  while true; do\n    local sel\n    sel=\"$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"APP DEFAULTS – ${APP}\" \\\n      --menu \"Differences detected. What do you want to do?\" 20 78 10 \\\n      \"Update Defaults\" \"Write new values to ${app_vars_file}\" \\\n      \"Keep Current\" \"Keep existing defaults (no changes)\" \\\n      \"View Diff\" \"Show a detailed diff\" \\\n      \"Cancel\" \"Abort without changes\" \\\n      --default-item \"Update Defaults\" \\\n      3>&1 1>&2 2>&3)\" || { sel=\"Cancel\"; }\n\n    case \"$sel\" in\n    \"Update Defaults\")\n      /usr/bin/install -m 0644 \"$new_tmp\" \"$app_vars_path\"\n      msg_ok \"Updated app defaults: ${app_vars_path}\"\n      break\n      ;;\n    \"Keep Current\")\n      msg_custom \"ℹ️\" \"${BL}\" \"Keeping current app defaults: ${app_vars_path}\"\n      break\n      ;;\n    \"View Diff\")\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n        --title \"Diff – ${APP}\" \\\n        --scrolltext --textbox \"$diff_tmp\" 25 100\n      ;;\n    \"Cancel\" | *)\n      msg_custom \"🚫\" \"${YW}\" \"Canceled. No changes to app defaults.\"\n      break\n      ;;\n    esac\n  done\n\n  rm -f \"$new_tmp\" \"$diff_tmp\"\n}\n\nensure_storage_selection_for_vars_file() {\n  local vf=\"$1\"\n\n  # Read stored values (if any)\n  local tpl ct\n  tpl=$(grep -E '^var_template_storage=' \"$vf\" | cut -d= -f2- || true)\n  ct=$(grep -E '^var_container_storage=' \"$vf\" | cut -d= -f2- || true)\n\n  if [[ -n \"$tpl\" && -n \"$ct\" ]]; then\n    TEMPLATE_STORAGE=\"$tpl\"\n    CONTAINER_STORAGE=\"$ct\"\n\n    # Validate storage space for loaded container storage\n    if [[ -n \"${DISK_SIZE:-}\" ]]; then\n      validate_storage_space \"$ct\" \"$DISK_SIZE\" \"yes\"\n      # Continue even if validation fails - user was warned\n    fi\n\n    return 0\n  fi\n\n  choose_and_set_storage_for_file \"$vf\" template\n  choose_and_set_storage_for_file \"$vf\" container\n\n  # Silent operation - no output message\n}\n\nensure_global_default_vars_file() {\n  local vars_path=\"/usr/local/community-scripts/default.vars\"\n  if [[ ! -f \"$vars_path\" ]]; then\n    mkdir -p \"$(dirname \"$vars_path\")\"\n    touch \"$vars_path\"\n  fi\n  echo \"$vars_path\"\n}\n\n# ==============================================================================\n# SECTION 6: ADVANCED INTERACTIVE CONFIGURATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# advanced_settings()\n#\n# - Interactive wizard-style configuration with BACK navigation\n# - State-machine approach: each step can go forward or backward\n# - Cancel at Step 1 = Exit Script, Cancel at other steps = Go Back\n# - Allows user to customize all container settings\n# ------------------------------------------------------------------------------\nadvanced_settings() {\n  # Enter alternate screen buffer to prevent flicker between dialogs\n  tput smcup 2>/dev/null || true\n  trap 'tput rmcup 2>/dev/null || true' RETURN\n\n  # Initialize defaults\n  # Build TAGS: ensure community-script prefix, use semicolons (pct format), no duplicates\n  if [[ \"${var_tags:-}\" == *community-script* ]]; then\n    TAGS=\"${var_tags:-community-script}\"\n  else\n    TAGS=\"community-script${var_tags:+;${var_tags}}\"\n  fi\n  local STEP=1\n  local MAX_STEP=29\n\n  # Store values for back navigation - inherit from var_* app defaults\n  local _ct_type=\"${var_unprivileged:-1}\"\n  local _pw=\"\"\n  local _pw_display=\"Automatic Login\"\n  local _ct_id=\"$NEXTID\"\n  local _hostname=\"$NSAPP\"\n  local _disk_size=\"${var_disk:-4}\"\n  local _core_count=\"${var_cpu:-1}\"\n  local _ram_size=\"${var_ram:-1024}\"\n  local _bridge=\"${var_brg:-vmbr0}\"\n  local _net=\"${var_net:-dhcp}\"\n  local _gate=\"${var_gateway:-}\"\n  local _ipv6_method=\"${var_ipv6_method:-auto}\"\n  local _ipv6_addr=\"\"\n  local _ipv6_gate=\"\"\n  local _apt_cacher=\"${var_apt_cacher:-no}\"\n  local _apt_cacher_ip=\"${var_apt_cacher_ip:-}\"\n  local _mtu=\"${var_mtu:-}\"\n  local _sd=\"${var_searchdomain:-}\"\n  local _ns=\"${var_ns:-}\"\n  local _mac=\"${var_mac:-}\"\n  local _vlan=\"${var_vlan:-}\"\n  local _tags=\"$TAGS\"\n  local _enable_fuse=\"${var_fuse:-no}\"\n  local _enable_tun=\"${var_tun:-no}\"\n  local _enable_gpu=\"${var_gpu:-no}\"\n  local _enable_nesting=\"${var_nesting:-1}\"\n  local _verbose=\"${var_verbose:-no}\"\n  local _enable_keyctl=\"${var_keyctl:-0}\"\n  local _enable_mknod=\"${var_mknod:-0}\"\n  local _mount_fs=\"${var_mount_fs:-}\"\n  local _protect_ct=\"${var_protection:-no}\"\n  local _post_install=\"${var_post_install:-}\"\n\n  # Detect host timezone for default (if not set via var_timezone)\n  local _host_timezone=\"\"\n  if command -v timedatectl >/dev/null 2>&1; then\n    _host_timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo \"\")\n  elif [ -f /etc/timezone ]; then\n    _host_timezone=$(cat /etc/timezone 2>/dev/null || echo \"\")\n  fi\n  # Map Etc/* timezones to \"host\" (pct doesn't accept Etc/* zones)\n  [[ \"${_host_timezone:-}\" == Etc/* ]] && _host_timezone=\"host\"\n  local _ct_timezone=\"${var_timezone:-$_host_timezone}\"\n  [[ \"${_ct_timezone:-}\" == Etc/* ]] && _ct_timezone=\"host\"\n\n  # Helper to show current progress\n  show_progress() {\n    local current=$1\n    local total=$MAX_STEP\n    echo -e \"\\n${INFO}${BOLD}${DGN}Step $current of $total${CL}\"\n  }\n\n  # Detect available bridges (do this once)\n  local BRIDGES=\"\"\n  local BRIDGE_MENU_OPTIONS=()\n  _detect_bridges() {\n    IFACE_FILEPATH_LIST=\"/etc/network/interfaces\"$'\\n'$(find \"/etc/network/interfaces.d/\" -type f 2>/dev/null)\n    BRIDGES=\"\"\n    local OLD_IFS=$IFS\n    IFS=$'\\n'\n    for iface_filepath in ${IFACE_FILEPATH_LIST}; do\n      local iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX')\n      (grep -Pn '^\\s*iface' \"${iface_filepath}\" 2>/dev/null | cut -d':' -f1 && wc -l \"${iface_filepath}\" 2>/dev/null | cut -d' ' -f1) | awk 'FNR==1 {line=$0; next} {print line\":\"$0-1; line=$0}' >\"${iface_indexes_tmpfile}\" 2>/dev/null || true\n      if [ -f \"${iface_indexes_tmpfile}\" ]; then\n        while read -r pair; do\n          local start=$(echo \"${pair}\" | cut -d':' -f1)\n          local end=$(echo \"${pair}\" | cut -d':' -f2)\n          if awk \"NR >= ${start} && NR <= ${end}\" \"${iface_filepath}\" 2>/dev/null | grep -qP '^\\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\\s+OVSBridge)\\b'; then\n            local iface_name=$(sed \"${start}q;d\" \"${iface_filepath}\" | awk '{print $2}')\n            BRIDGES=\"${iface_name}\"$'\\n'\"${BRIDGES}\"\n          fi\n        done <\"${iface_indexes_tmpfile}\"\n        rm -f \"${iface_indexes_tmpfile}\"\n      fi\n    done\n    IFS=$OLD_IFS\n    BRIDGES=$(echo \"$BRIDGES\" | grep -v '^\\s*$' | sort | uniq)\n\n    # Build bridge menu\n    BRIDGE_MENU_OPTIONS=()\n    if [[ -n \"$BRIDGES\" ]]; then\n      while IFS= read -r bridge; do\n        if [[ -n \"$bridge\" ]]; then\n          local description=$(grep -A 10 \"iface $bridge\" /etc/network/interfaces 2>/dev/null | grep '^#' | head -n1 | sed 's/^#\\s*//;s/^[- ]*//' || true)\n          BRIDGE_MENU_OPTIONS+=(\"$bridge\" \"${description:- }\")\n        fi\n      done <<<\"$BRIDGES\"\n    fi\n  }\n  _detect_bridges\n\n  # Main wizard loop\n  while [ $STEP -le $MAX_STEP ]; do\n    case $STEP in\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 1: Container Type\n    # ═══════════════════════════════════════════════════════════════════════════\n    1)\n      local default_on=\"ON\"\n      local default_off=\"OFF\"\n      [[ \"$_ct_type\" == \"0\" ]] && {\n        default_on=\"OFF\"\n        default_off=\"ON\"\n      }\n\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CONTAINER TYPE\" \\\n        --ok-button \"Next\" --cancel-button \"Exit\" \\\n        --radiolist \"\\nChoose container type:\\n\\nUse SPACE to select, ENTER to confirm.\" 14 58 2 \\\n        \"1\" \"Unprivileged (recommended)\" $default_on \\\n        \"0\" \"Privileged\" $default_off \\\n        3>&1 1>&2 2>&3); then\n        [[ -n \"$result\" ]] && _ct_type=\"$result\"\n        ((STEP++))\n      else\n        exit_script\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 2: Root Password\n    # ════════════════════════════════════════���═══════════════════════════════���══\n    2)\n      if PW1=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"ROOT PASSWORD\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --passwordbox \"\\nSet Root Password (needed for root ssh access)\\n\\nLeave blank for automatic login (no password)\" 12 58 \\\n        3>&1 1>&2 2>&3); then\n\n        if [[ -z \"$PW1\" ]]; then\n          _pw=\"\"\n          _pw_display=\"Automatic Login\"\n          ((STEP++))\n        elif [[ \"$PW1\" == *\" \"* ]]; then\n          whiptail --msgbox \"Password cannot contain spaces.\" 8 58\n        else\n          local _pw1_clean=\"$PW1\"\n          while [[ \"$_pw1_clean\" == -* ]]; do\n            _pw1_clean=\"${_pw1_clean#-}\"\n          done\n          if [[ -z \"$_pw1_clean\" ]]; then\n            whiptail --msgbox \"Password cannot be only '-' characters.\" 8 58\n            continue\n          elif ((${#_pw1_clean} < 5)); then\n            whiptail --msgbox \"Password must be at least 5 characters (after removing leading '-').\" 8 70\n            continue\n          fi\n          # Verify password\n          if PW2=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n            --title \"PASSWORD VERIFICATION\" \\\n            --ok-button \"Confirm\" --cancel-button \"Back\" \\\n            --passwordbox \"\\nVerify Root Password\" 10 58 \\\n            3>&1 1>&2 2>&3); then\n            local _pw2_clean=\"$PW2\"\n            while [[ \"$_pw2_clean\" == -* ]]; do\n              _pw2_clean=\"${_pw2_clean#-}\"\n            done\n            if [[ \"$_pw1_clean\" == \"$_pw2_clean\" ]]; then\n              _pw=\"--password $_pw1_clean\"\n              _pw_display=\"********\"\n              ((STEP++))\n            else\n              whiptail --msgbox \"Passwords do not match. Please try again.\" 8 58\n            fi\n          else\n            ((STEP--))\n          fi\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 3: Container ID\n    # ═══════════════════════════════════════════════════════════════════════════\n    3)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CONTAINER ID\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet Container ID\" 10 58 \"$_ct_id\" \\\n        3>&1 1>&2 2>&3); then\n        local input_id=\"${result:-$NEXTID}\"\n\n        # Validate that ID is numeric\n        if ! [[ \"$input_id\" =~ ^[0-9]+$ ]]; then\n          whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Invalid ID\" --msgbox \"Container ID must be numeric.\" 8 58\n          continue\n        fi\n\n        # Check if ID is already in use\n        if ! validate_container_id \"$input_id\"; then\n          if whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ID Already In Use\" \\\n            --yesno \"Container/VM ID $input_id is already in use.\\n\\nWould you like to use the next available ID ($(get_valid_container_id \"$input_id\"))?\" 10 58; then\n            _ct_id=$(get_valid_container_id \"$input_id\")\n          else\n            continue\n          fi\n        else\n          _ct_id=\"$input_id\"\n        fi\n        ((STEP++))\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 4: Hostname\n    # ═══════════════════════════════════════════════════════════════════════════\n    4)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"HOSTNAME\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet Hostname (or FQDN, e.g. host.example.com)\" 10 58 \"$_hostname\" \\\n        3>&1 1>&2 2>&3); then\n        local hn_test=\"${result:-$NSAPP}\"\n        hn_test=$(echo \"${hn_test,,}\" | tr -d ' ')\n\n        if validate_hostname \"$hn_test\"; then\n          _hostname=\"$hn_test\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Invalid hostname: '$hn_test'\\n\\nRules:\\n- Only lowercase letters, digits, dots and hyphens\\n- Labels separated by dots (max 63 chars each)\\n- No leading/trailing hyphens or dots\\n- No consecutive dots\\n- Total max 253 characters\" 14 60\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 5: Disk Size\n    # ═══════════════════════════════════════════════════════════════════════════\n    5)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"DISK SIZE\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet Disk Size in GB\" 10 58 \"$_disk_size\" \\\n        3>&1 1>&2 2>&3); then\n        local disk_test=\"${result:-$var_disk}\"\n        if [[ \"$disk_test\" =~ ^[1-9][0-9]*$ ]]; then\n          _disk_size=\"$disk_test\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Disk size must be a positive integer!\" 8 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 6: CPU Cores\n    # ═══════════════════════════════════════════════════════════════════════════\n    6)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CPU CORES\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nAllocate CPU Cores\" 10 58 \"$_core_count\" \\\n        3>&1 1>&2 2>&3); then\n        local cpu_test=\"${result:-$var_cpu}\"\n        if [[ \"$cpu_test\" =~ ^[1-9][0-9]*$ ]]; then\n          _core_count=\"$cpu_test\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"CPU core count must be a positive integer!\" 8 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 7: RAM Size\n    # ═══════════════════════════════════════════════════════════════════════════\n    7)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"RAM SIZE\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nAllocate RAM in MiB\" 10 58 \"$_ram_size\" \\\n        3>&1 1>&2 2>&3); then\n        local ram_test=\"${result:-$var_ram}\"\n        if [[ \"$ram_test\" =~ ^[1-9][0-9]*$ ]]; then\n          _ram_size=\"$ram_test\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"RAM size must be a positive integer!\" 8 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 8: Network Bridge\n    # ═══════════════════════════════════════════════════════════════════════════\n    8)\n      if [[ ${#BRIDGE_MENU_OPTIONS[@]} -eq 0 ]]; then\n        # Validate default bridge exists\n        if validate_bridge \"vmbr0\"; then\n          _bridge=\"vmbr0\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Default bridge 'vmbr0' not found!\\n\\nPlease configure a network bridge in Proxmox first.\" 10 58\n          msg_error \"Default bridge 'vmbr0' not found\"\n          exit 116\n        fi\n      else\n        if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n          --title \"NETWORK BRIDGE\" \\\n          --ok-button \"Next\" --cancel-button \"Back\" \\\n          --menu \"\\nSelect network bridge:\" 16 58 6 \\\n          \"${BRIDGE_MENU_OPTIONS[@]}\" \\\n          3>&1 1>&2 2>&3); then\n          local bridge_test=\"${result:-vmbr0}\"\n          # Skip separator entries (e.g., __other__) - re-display menu\n          if [[ \"$bridge_test\" == \"__other__\" || \"$bridge_test\" == -* ]]; then\n            continue\n          fi\n          if validate_bridge \"$bridge_test\"; then\n            _bridge=\"$bridge_test\"\n            ((STEP++))\n          else\n            whiptail --msgbox \"Bridge '$bridge_test' is not available or not active.\" 8 58\n          fi\n        else\n          ((STEP--))\n        fi\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 9: IPv4 Configuration\n    # ═══════════════════════════════════════════════════════════════════════════\n    9)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"IPv4 CONFIGURATION\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --menu \"\\nSelect IPv4 Address Assignment:\" 16 65 3 \\\n        \"dhcp\" \"Automatic (DHCP, recommended)\" \\\n        \"static\" \"Static (manual entry)\" \\\n        \"range\" \"IP Range Scan (find first free IP)\" \\\n        3>&1 1>&2 2>&3); then\n\n        if [[ \"$result\" == \"static\" ]]; then\n          # Get static IP\n          local static_ip\n          if static_ip=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n            --title \"STATIC IPv4 ADDRESS\" \\\n            --ok-button \"Next\" --cancel-button \"Back\" \\\n            --inputbox \"\\nEnter Static IPv4 CIDR Address\\n(e.g. 192.168.1.100/24)\" 12 58 \"\" \\\n            3>&1 1>&2 2>&3); then\n            if validate_ip_address \"$static_ip\"; then\n              # Get gateway\n              local gateway_ip\n              if gateway_ip=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n                --title \"GATEWAY IP\" \\\n                --ok-button \"Next\" --cancel-button \"Back\" \\\n                --inputbox \"\\nEnter Gateway IP address\" 10 58 \"\" \\\n                3>&1 1>&2 2>&3); then\n                if validate_gateway_ip \"$gateway_ip\"; then\n                  # Validate gateway is in same subnet\n                  if validate_gateway_in_subnet \"$static_ip\" \"$gateway_ip\"; then\n                    _net=\"$static_ip\"\n                    _gate=\",gw=$gateway_ip\"\n                    ((STEP++))\n                  else\n                    whiptail --msgbox \"Gateway is not in the same subnet as the static IP.\\n\\nStatic IP: $static_ip\\nGateway: $gateway_ip\" 10 58\n                  fi\n                else\n                  whiptail --msgbox \"Invalid Gateway IP format.\\n\\nEach octet must be 0-255.\\nExample: 192.168.1.1\" 10 58\n                fi\n              fi\n            else\n              whiptail --msgbox \"Invalid IPv4 CIDR format.\\n\\nEach octet must be 0-255.\\nCIDR must be 1-32.\\nExample: 192.168.1.100/24\" 12 58\n            fi\n          fi\n        elif [[ \"$result\" == \"range\" ]]; then\n          # IP Range Scan\n          local ip_range\n          if ip_range=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n            --title \"IP RANGE SCAN\" \\\n            --ok-button \"Scan\" --cancel-button \"Back\" \\\n            --inputbox \"\\nEnter IP range to scan for free address\\n(e.g. 192.168.1.100/24-192.168.1.200/24)\" 12 65 \"\" \\\n            3>&1 1>&2 2>&3); then\n            if is_ip_range \"$ip_range\"; then\n              # Exit whiptail screen temporarily to show scan progress\n              clear\n              header_info\n              echo -e \"${INFO}${BOLD}${DGN}Scanning IP range for free address...${CL}\\n\"\n              if resolve_ip_from_range \"$ip_range\"; then\n                # Get gateway\n                local gateway_ip\n                if gateway_ip=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n                  --title \"GATEWAY IP\" \\\n                  --ok-button \"Next\" --cancel-button \"Back\" \\\n                  --inputbox \"\\nFound free IP: $NET_RESOLVED\\n\\nEnter Gateway IP address\" 12 58 \"\" \\\n                  3>&1 1>&2 2>&3); then\n                  if validate_gateway_ip \"$gateway_ip\"; then\n                    # Validate gateway is in same subnet\n                    if validate_gateway_in_subnet \"$NET_RESOLVED\" \"$gateway_ip\"; then\n                      _net=\"$NET_RESOLVED\"\n                      _gate=\",gw=$gateway_ip\"\n                      ((STEP++))\n                    else\n                      whiptail --msgbox \"Gateway is not in the same subnet as the IP.\\n\\nIP: $NET_RESOLVED\\nGateway: $gateway_ip\" 10 58\n                    fi\n                  else\n                    whiptail --msgbox \"Invalid Gateway IP format.\\n\\nEach octet must be 0-255.\\nExample: 192.168.1.1\" 10 58\n                  fi\n                fi\n              else\n                whiptail --msgbox \"No free IP found in the specified range.\\nAll IPs responded to ping.\" 10 58\n              fi\n            else\n              whiptail --msgbox \"Invalid IP range format.\\n\\nExample: 192.168.1.100/24-192.168.1.200/24\" 10 58\n            fi\n          fi\n        else\n          _net=\"dhcp\"\n          _gate=\"\"\n          ((STEP++))\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 10: IPv6 Configuration\n    # ═══════════════════════════════════════════════════════════════════════════\n    10)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"IPv6 CONFIGURATION\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --menu \"\\nSelect IPv6 Address Management:\" 16 70 5 \\\n        \"auto\" \"SLAAC/AUTO (recommended) - Dynamic IPv6 from network\" \\\n        \"dhcp\" \"DHCPv6 - DHCP-assigned IPv6 address\" \\\n        \"static\" \"Static - Manual IPv6 address configuration\" \\\n        \"none\" \"None - No IPv6 assignment (most containers)\" \\\n        \"disable\" \"Fully Disabled - (breaks some services)\" \\\n        3>&1 1>&2 2>&3); then\n\n        _ipv6_method=\"$result\"\n        case \"$result\" in\n        static)\n          local ipv6_addr\n          if ipv6_addr=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n            --title \"STATIC IPv6 ADDRESS\" \\\n            --inputbox \"\\nEnter IPv6 CIDR address\\n(e.g. 2001:db8::1/64)\" 12 58 \"\" \\\n            3>&1 1>&2 2>&3); then\n            if validate_ipv6_address \"$ipv6_addr\"; then\n              _ipv6_addr=\"$ipv6_addr\"\n              # Optional gateway - loop until valid or empty\n              local ipv6_gw_valid=false\n              while [[ \"$ipv6_gw_valid\" == \"false\" ]]; do\n                local ipv6_gw\n                ipv6_gw=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n                  --title \"IPv6 GATEWAY\" \\\n                  --inputbox \"\\nEnter IPv6 gateway (optional, leave blank for none)\" 10 58 \"\" \\\n                  3>&1 1>&2 2>&3) || true\n                # Validate gateway if provided\n                if [[ -n \"$ipv6_gw\" ]]; then\n                  if validate_ipv6_address \"$ipv6_gw\"; then\n                    _ipv6_gate=\"$ipv6_gw\"\n                    ipv6_gw_valid=true\n                    ((STEP++))\n                  else\n                    whiptail --msgbox \"Invalid IPv6 gateway format.\\n\\nExample: 2001:db8::1\" 8 58\n                  fi\n                else\n                  _ipv6_gate=\"\"\n                  ipv6_gw_valid=true\n                  ((STEP++))\n                fi\n              done\n            else\n              whiptail --msgbox \"Invalid IPv6 CIDR format.\\n\\nExample: 2001:db8::1/64\\nCIDR must be 1-128.\" 10 58\n            fi\n          fi\n          ;;\n        dhcp)\n          _ipv6_addr=\"dhcp\"\n          _ipv6_gate=\"\"\n          ((STEP++))\n          ;;\n\n        none)\n          _ipv6_addr=\"none\"\n          _ipv6_gate=\"\"\n          ((STEP++))\n          ;;\n        *)\n          _ipv6_addr=\"\"\n          _ipv6_gate=\"\"\n          ((STEP++))\n          ;;\n        esac\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 11: MTU Size\n    # ═══════════════════════════════════════════════════════════════════════════\n    11)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"MTU SIZE\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet Interface MTU Size\\n(leave blank for default 1500, common values: 1500, 9000)\" 12 62 \"\" \\\n        3>&1 1>&2 2>&3); then\n        if validate_mtu \"$result\"; then\n          _mtu=\"$result\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Invalid MTU size.\\n\\nMTU must be between 576 and 65535.\\nCommon values: 1500 (default), 9000 (jumbo frames)\" 10 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 12: DNS Search Domain\n    # ═══════════════════════════════════════════════════════════════════════════\n    12)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"DNS SEARCH DOMAIN\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet DNS Search Domain\\n(leave blank to use host setting)\" 12 58 \"\" \\\n        3>&1 1>&2 2>&3); then\n        _sd=\"$result\"\n        ((STEP++))\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 13: DNS Server\n    # ═══════════════════════════════════════════════════════════════════════════\n    13)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"DNS SERVER\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet DNS Server IP\\n(leave blank to use host setting)\" 12 58 \"\" \\\n        3>&1 1>&2 2>&3); then\n        _ns=\"$result\"\n        ((STEP++))\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 14: MAC Address\n    # ═══════════════════════════════════════════════════════════════════════════\n    14)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"MAC ADDRESS\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet MAC Address\\n(leave blank for auto-generated, format: XX:XX:XX:XX:XX:XX)\" 12 62 \"\" \\\n        3>&1 1>&2 2>&3); then\n        if validate_mac_address \"$result\"; then\n          _mac=\"$result\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Invalid MAC address format.\\n\\nRequired format: XX:XX:XX:XX:XX:XX\\nExample: 02:00:00:00:00:01\" 10 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 15: VLAN Tag\n    # ═══════════════════════════════════════════════════════════════════════════\n    15)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"VLAN TAG\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet VLAN Tag (1-4094)\\n(leave blank for no VLAN)\" 12 58 \"\" \\\n        3>&1 1>&2 2>&3); then\n        if validate_vlan_tag \"$result\"; then\n          _vlan=\"$result\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Invalid VLAN tag.\\n\\nVLAN must be a number between 1 and 4094.\" 8 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 16: Tags\n    # ═══════════════════════════════════════════════════════════════════════════\n    16)\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CONTAINER TAGS\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet Custom Tags (semicolon-separated)\\n(alphanumeric, hyphens, underscores only)\" 12 58 \"$_tags\" \\\n        3>&1 1>&2 2>&3); then\n        local tags_test=\"${result:-}\"\n        tags_test=$(echo \"$tags_test\" | tr -d '[:space:]')\n        if validate_tags \"$tags_test\"; then\n          _tags=\"$tags_test\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Invalid tag format.\\n\\nTags can only contain:\\n- Letters (a-z, A-Z)\\n- Numbers (0-9)\\n- Hyphens (-)\\n- Underscores (_)\\n- Semicolons (;) as separator\" 14 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 17: SSH Settings\n    # ═══════════════════════════════════════════════════════════════════════════\n    17)\n      configure_ssh_settings \"Step $STEP/$MAX_STEP\"\n      # configure_ssh_settings handles its own flow, always advance\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 18: FUSE Support\n    # ═══════════════════════════════════════════════════════════════════════════\n    18)\n      local fuse_default_flag=\"--defaultno\"\n      [[ \"$_enable_fuse\" == \"yes\" || \"$_enable_fuse\" == \"1\" ]] && fuse_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"FUSE SUPPORT\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $fuse_default_flag \\\n        --yesno \"\\nEnable FUSE support?\\n\\nRequired for: rclone, mergerfs, AppImage, etc.\\n\\n(App default: ${var_fuse:-no})\" 14 58; then\n        _enable_fuse=\"yes\"\n      else\n        if [ $? -eq 1 ]; then\n          _enable_fuse=\"no\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 19: TUN/TAP Support\n    # ═══════════════════════════════════════════════════════════════════════════\n    19)\n      local tun_default_flag=\"--defaultno\"\n      [[ \"$_enable_tun\" == \"yes\" || \"$_enable_tun\" == \"1\" ]] && tun_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"TUN/TAP SUPPORT\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $tun_default_flag \\\n        --yesno \"\\nEnable TUN/TAP device support?\\n\\nRequired for: VPN apps (WireGuard, OpenVPN, Tailscale),\\nnetwork tunneling, and containerized networking.\\n\\n(App default: ${var_tun:-no})\" 14 62; then\n        _enable_tun=\"yes\"\n      else\n        if [ $? -eq 1 ]; then\n          _enable_tun=\"no\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 20: Nesting Support\n    # ═══════════════════════════════════════════════════════════════════════════\n    20)\n      local nesting_default_flag=\"\"\n      [[ \"$_enable_nesting\" == \"0\" || \"$_enable_nesting\" == \"no\" ]] && nesting_default_flag=\"--defaultno\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"NESTING SUPPORT\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $nesting_default_flag \\\n        --yesno \"\\nEnable Nesting?\\n\\nRequired for: Docker, LXC inside LXC, Podman,\\nand other containerization tools.\\n\\n(App default: ${var_nesting:-1})\" 14 58; then\n        _enable_nesting=\"1\"\n      else\n        if [ $? -eq 1 ]; then\n          _enable_nesting=\"0\"\n          # Warn about potential issues with systemd-based OS when nesting is disabled\n          if [[ \"$var_os\" != \"alpine\" ]]; then\n            whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n              --title \"⚠️ NESTING WARNING\" \\\n              --msgbox \"Modern systemd-based distributions (Debian 13+, Ubuntu 24.04+, etc.) may require nesting to be enabled for proper operation.\\n\\nWithout nesting, the container may start in a degraded state with failing services (error 243/CREDENTIALS).\\n\\nIf you experience issues, enable nesting in the container options.\" 14 68\n          fi\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 21: GPU Passthrough\n    # ═══════════════════════════════════════════════════════════════════════════\n    21)\n      local gpu_default_flag=\"--defaultno\"\n      [[ \"$_enable_gpu\" == \"yes\" ]] && gpu_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"GPU PASSTHROUGH\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $gpu_default_flag \\\n        --yesno \"\\nEnable GPU Passthrough?\\n\\nAutomatically detects and passes through available GPUs\\n(Intel/AMD/NVIDIA) for hardware acceleration.\\n\\nRecommended for: Media servers, AI/ML, Transcoding\\n\\n(App default: ${var_gpu:-no})\" 16 62; then\n        _enable_gpu=\"yes\"\n      else\n        if [ $? -eq 1 ]; then\n          _enable_gpu=\"no\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 22: Keyctl Support (Docker/systemd)\n    # ═══════════════════════════════════════════════════════════════════════════\n    22)\n      # Keyctl is always required for unprivileged containers — skip dialog\n      if [[ \"$_ct_type\" == \"1\" ]]; then\n        _enable_keyctl=\"1\"\n        ((STEP++))\n        continue\n      fi\n\n      local keyctl_default_flag=\"--defaultno\"\n      [[ \"$_enable_keyctl\" == \"1\" ]] && keyctl_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"KEYCTL SUPPORT\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $keyctl_default_flag \\\n        --yesno \"\\nEnable Keyctl support?\\n\\nRequired for: Docker containers, systemd-networkd,\\nand kernel keyring operations.\\n\\n(App default: ${var_keyctl:-0})\" 14 62; then\n        _enable_keyctl=\"1\"\n      else\n        if [ $? -eq 1 ]; then\n          _enable_keyctl=\"0\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 23: APT Cacher Proxy\n    # ═══════════════════════════════════════════════════════════════════════════\n    23)\n      local apt_cacher_default_flag=\"--defaultno\"\n      [[ \"$_apt_cacher\" == \"yes\" ]] && apt_cacher_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"APT CACHER PROXY\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $apt_cacher_default_flag \\\n        --yesno \"\\nUse APT Cacher-NG proxy?\\n\\nSpeeds up package downloads by caching them locally.\\nRequires apt-cacher-ng running on your network.\\n\\n(App default: ${var_apt_cacher:-no})\" 14 62; then\n        _apt_cacher=\"yes\"\n        # Ask for IP if enabled\n        if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n          --title \"APT CACHER IP\" \\\n          --inputbox \"\\nEnter APT Cacher-NG IP or URL:\\n(e.g. 192.168.1.10, http://host, https://host:443)\" 12 62 \"$_apt_cacher_ip\" \\\n          3>&1 1>&2 2>&3); then\n          _apt_cacher_ip=\"$result\"\n        fi\n      else\n        if [ $? -eq 1 ]; then\n          _apt_cacher=\"no\"\n          _apt_cacher_ip=\"\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 24: Container Timezone\n    # ═══════════════════════════════════════════════════════════════════════════\n    24)\n      local tz_hint=\"$_ct_timezone\"\n      [[ -z \"$tz_hint\" ]] && tz_hint=\"(empty - will use host timezone)\"\n\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CONTAINER TIMEZONE\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nSet container timezone.\\n\\nExamples: Europe/Berlin, America/New_York, Asia/Tokyo\\n\\nHost timezone: ${_host_timezone:-unknown}\\n\\nLeave empty to inherit from host.\" 16 62 \"$_ct_timezone\" \\\n        3>&1 1>&2 2>&3); then\n        local tz_test=\"$result\"\n        [[ \"${tz_test:-}\" == Etc/* ]] && tz_test=\"host\" # pct doesn't accept Etc/* zones\n        if validate_timezone \"$tz_test\"; then\n          _ct_timezone=\"$tz_test\"\n          ((STEP++))\n        else\n          whiptail --msgbox \"Invalid timezone: '$result'\\n\\nTimezone must exist in /usr/share/zoneinfo/\\n\\nExamples:\\n- Europe/Berlin\\n- America/New_York\\n- Asia/Tokyo\\n- UTC\" 14 58\n        fi\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 25: Container Protection\n    # ═══════════════════════════════════════════════════════════════════════════\n    25)\n      local protect_default_flag=\"--defaultno\"\n      [[ \"$_protect_ct\" == \"yes\" || \"$_protect_ct\" == \"1\" ]] && protect_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CONTAINER PROTECTION\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $protect_default_flag \\\n        --yesno \"\\nEnable Container Protection?\\n\\nPrevents accidental deletion of this container.\\nYou must disable protection before removing.\\n\\n(App default: ${var_protection:-no})\" 14 62; then\n        _protect_ct=\"yes\"\n      else\n        if [ $? -eq 1 ]; then\n          _protect_ct=\"no\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 26: Device Node Creation (mknod)\n    # ═══════════════════════════════════════════════════════════════════════════\n    26)\n      local mknod_default_flag=\"--defaultno\"\n      [[ \"$_enable_mknod\" == \"1\" ]] && mknod_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"DEVICE NODE CREATION\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        $mknod_default_flag \\\n        --yesno \"\\nAllow device node creation (mknod)?\\n\\nRequired for: Creating device files inside container.\\nExperimental feature (requires kernel 5.3+).\\n\\n(App default: ${var_mknod:-0})\" 14 62; then\n        _enable_mknod=\"1\"\n      else\n        if [ $? -eq 1 ]; then\n          _enable_mknod=\"0\"\n        else\n          ((STEP--))\n          continue\n        fi\n      fi\n      ((STEP++))\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 27: Mount Filesystems\n    # ═══════════════════════════════════════════════════════════════════════════\n    27)\n      local mount_hint=\"\"\n      [[ -n \"$_mount_fs\" ]] && mount_hint=\"$_mount_fs\" || mount_hint=\"(none)\"\n\n      if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"MOUNT FILESYSTEMS\" \\\n        --ok-button \"Next\" --cancel-button \"Back\" \\\n        --inputbox \"\\nAllow specific filesystem mounts.\\n\\nComma-separated list: nfs, cifs, fuse, ext4, etc.\\nLeave empty for defaults (none).\\n\\nCurrent: $mount_hint\" 14 62 \"$_mount_fs\" \\\n        3>&1 1>&2 2>&3); then\n        # Normalize: strip spaces and trailing/leading commas\n        result=\"${result// /}\"\n        result=\"${result%%,}\"\n        result=\"${result##,}\"\n        _mount_fs=\"$result\"\n        ((STEP++))\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 28: Optional host-side post-install hook (path on the Proxmox HOST)\n    # ═══════════════════════════════════════════════════════════════════════════\n    28)\n      local _hook_prompt=\"Optional: absolute path to a *.sh file ON THE PROXMOX HOST.\n\nIt runs as root on the HOST (NOT in the LXC) after the container\nis fully provisioned and started.\n\nAvailable env vars: APP, NSAPP, CTID, IP, HN, STORAGE, BRG.\n\nLeave empty to skip.\"\n      while true; do\n        if result=$(whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n          --title \"POST-INSTALL HOOK (HOST)\" \\\n          --ok-button \"Next\" --cancel-button \"Back\" \\\n          --inputbox \"$_hook_prompt\" 16 70 \"${_post_install}\" \\\n          3>&1 1>&2 2>&3); then\n          # Normalize: strip surrounding whitespace\n          result=\"$(printf '%s' \"$result\" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\"\n          if [[ -z \"$result\" ]]; then\n            _post_install=\"\"\n            ((STEP++))\n            break\n          fi\n          # Reject obvious shell-meta sneaking through\n          if [[ \"$result\" == *';'* || \"$result\" == *'$('* || \"$result\" == *'`'* || \"$result\" == *'&&'* || \"$result\" == *'||'* ]]; then\n            whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID PATH\" \\\n              --msgbox \"Path contains shell metacharacters. Please provide a plain absolute file path.\" 10 70\n            continue\n          fi\n          if [[ \"$result\" != /* ]]; then\n            whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID PATH\" \\\n              --msgbox \"Path must be absolute (start with /).\\n\\nGot: $result\" 10 70\n            continue\n          fi\n          if [[ ! -f \"$result\" ]]; then\n            if ! whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"FILE NOT FOUND\" \\\n              --yesno \"File does not exist on host:\\n\\n$result\\n\\nKeep this path anyway?\" 12 70; then\n              continue\n            fi\n          fi\n          _post_install=\"$result\"\n          ((STEP++))\n          break\n        else\n          ((STEP--))\n          break\n        fi\n      done\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 29: Verbose Mode & Confirmation\n    # ═══════════════════════════════════════════════════════════════════════════\n    29)\n      local verbose_default_flag=\"--defaultno\"\n      [[ \"$_verbose\" == \"yes\" ]] && verbose_default_flag=\"\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"VERBOSE MODE\" \\\n        $verbose_default_flag \\\n        --yesno \"\\nEnable Verbose Mode?\\n\\nShows detailed output during installation.\" 12 58; then\n        _verbose=\"yes\"\n      else\n        _verbose=\"no\"\n      fi\n      # Build summary\n      local ct_type_desc=\"Unprivileged\"\n      [[ \"$_ct_type\" == \"0\" ]] && ct_type_desc=\"Privileged\"\n\n      local nesting_desc=\"Disabled\"\n      [[ \"$_enable_nesting\" == \"1\" ]] && nesting_desc=\"Enabled\"\n\n      local keyctl_desc=\"Disabled\"\n      [[ \"$_enable_keyctl\" == \"1\" ]] && keyctl_desc=\"Enabled\"\n\n      local protect_desc=\"No\"\n      [[ \"$_protect_ct\" == \"yes\" || \"$_protect_ct\" == \"1\" ]] && protect_desc=\"Yes\"\n\n      local tz_display=\"${_ct_timezone:-Host TZ}\"\n      local apt_display=\"${_apt_cacher:-no}\"\n      [[ \"$_apt_cacher\" == \"yes\" && -n \"$_apt_cacher_ip\" ]] && apt_display=\"$_apt_cacher_ip\"\n\n      local post_install_display=\"${_post_install:-(none)}\"\n      local post_install_warn=\"\"\n      [[ -n \"$_post_install\" ]] && post_install_warn=\"\n  ⚠ Hook runs as root on Proxmox HOST (not in LXC)\"\n\n      local summary=\"Container Type: $ct_type_desc\nContainer ID: $_ct_id\nHostname: $_hostname\n\nResources:\n  Disk: ${_disk_size} GB\n  CPU: $_core_count cores\n  RAM: $_ram_size MiB\n\nNetwork:\n  Bridge: $_bridge\n  IPv4: $_net\n  IPv6: $_ipv6_method\n\nFeatures:\n  FUSE: $_enable_fuse | TUN: $_enable_tun\n  Nesting: $nesting_desc | Keyctl: $keyctl_desc\n  Mknod: $([ \"$_enable_mknod\" == \"1\" ] && echo Enabled || echo Disabled) | Mount FS: ${_mount_fs:-(none)}\n  GPU: $_enable_gpu | Protection: $protect_desc\n\nAdvanced:\n  Timezone: $tz_display\n  APT Cacher: $apt_display\n  Verbose: $_verbose\n  Post-Install Script: ${post_install_display}${post_install_warn}\"\n\n      if whiptail --backtitle \"Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]\" \\\n        --title \"CONFIRM SETTINGS\" \\\n        --ok-button \"Create LXC\" --cancel-button \"Back\" \\\n        --yesno \"$summary\\n\\nCreate ${APP} LXC with these settings?\" 32 62; then\n        ((STEP++))\n      else\n        ((STEP--))\n      fi\n      ;;\n    esac\n  done\n\n  # ═══════════════════════════════════════════════════════════════════════════\n  # Apply all collected values to global variables\n  # ═══════════════════════════════════════════════════════════════════════════\n  CT_TYPE=\"$_ct_type\"\n  PW=\"$_pw\"\n  CT_ID=\"$_ct_id\"\n  HN=\"$_hostname\"\n  DISK_SIZE=\"$_disk_size\"\n  CORE_COUNT=\"$_core_count\"\n  RAM_SIZE=\"$_ram_size\"\n  BRG=\"$_bridge\"\n  NET=\"$_net\"\n  GATE=\"$_gate\"\n  IPV6_METHOD=\"$_ipv6_method\"\n  IPV6_ADDR=\"$_ipv6_addr\"\n  IPV6_GATE=\"$_ipv6_gate\"\n  TAGS=\"$_tags\"\n  ENABLE_FUSE=\"$_enable_fuse\"\n  ENABLE_TUN=\"$_enable_tun\"\n  ENABLE_GPU=\"$_enable_gpu\"\n  ENABLE_NESTING=\"$_enable_nesting\"\n  ENABLE_KEYCTL=\"$_enable_keyctl\"\n  ENABLE_MKNOD=\"$_enable_mknod\"\n  ALLOW_MOUNT_FS=\"$_mount_fs\"\n  PROTECT_CT=\"$_protect_ct\"\n  CT_TIMEZONE=\"$_ct_timezone\"\n  APT_CACHER=\"$_apt_cacher\"\n  APT_CACHER_IP=\"$_apt_cacher_ip\"\n  VERBOSE=\"$_verbose\"\n  var_post_install=\"$_post_install\"\n\n  # Update var_* based on user choice (for functions that check these)\n  var_gpu=\"$_enable_gpu\"\n  var_fuse=\"$_enable_fuse\"\n  var_tun=\"$_enable_tun\"\n  var_nesting=\"$_enable_nesting\"\n  var_keyctl=\"$_enable_keyctl\"\n  var_mknod=\"$_enable_mknod\"\n  var_mount_fs=\"$_mount_fs\"\n  var_protection=\"$_protect_ct\"\n  var_timezone=\"$_ct_timezone\"\n  var_apt_cacher=\"$_apt_cacher\"\n  var_apt_cacher_ip=\"$_apt_cacher_ip\"\n\n  # Format optional values\n  [[ -n \"$_mtu\" ]] && MTU=\",mtu=$_mtu\" || MTU=\"\"\n  [[ -n \"$_sd\" ]] && SD=\"-searchdomain=$_sd\" || SD=\"\"\n  [[ -n \"$_ns\" ]] && NS=\"-nameserver=$_ns\" || NS=\"\"\n  [[ -n \"$_mac\" ]] && MAC=\",hwaddr=$_mac\" || MAC=\"\"\n  [[ -n \"$_vlan\" ]] && VLAN=\",tag=$_vlan\" || VLAN=\"\"\n\n  export SSH_KEYS_FILE\n\n  # Exit alternate screen buffer before showing summary (so output remains visible)\n  tput rmcup 2>/dev/null || true\n  trap - RETURN\n\n  # Display final summary\n  echo -e \"\\n${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}\"\n  echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$([ \"$CT_TYPE\" == \"1\" ] && echo \"Unprivileged\" || echo \"Privileged\")${CL}\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n  echo -e \"${NETWORK}${BOLD}${DGN}IPv4: ${BGN}$NET${CL}\"\n  echo -e \"${NETWORK}${BOLD}${DGN}IPv6: ${BGN}$IPV6_METHOD${CL}\"\n  echo -e \"${FUSE}${BOLD}${DGN}FUSE Support: ${BGN}${ENABLE_FUSE:-no}${CL}\"\n  [[ \"${ENABLE_TUN:-no}\" == \"yes\" ]] && echo -e \"${NETWORK}${BOLD}${DGN}TUN/TAP Support: ${BGN}$ENABLE_TUN${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Nesting: ${BGN}$([ \"${ENABLE_NESTING:-1}\" == \"1\" ] && echo \"Enabled\" || echo \"Disabled\")${CL}\"\n  [[ \"${ENABLE_KEYCTL:-0}\" == \"1\" ]] && echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Keyctl: ${BGN}Enabled${CL}\"\n  echo -e \"${GPU}${BOLD}${DGN}GPU Passthrough: ${BGN}${ENABLE_GPU:-no}${CL}\"\n  [[ \"${ENABLE_MKNOD:-0}\" == \"1\" ]] && echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Mknod: ${BGN}Enabled${CL}\"\n  [[ -n \"${ALLOW_MOUNT_FS:-}\" ]] && echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Mount FS: ${BGN}${ALLOW_MOUNT_FS}${CL}\"\n  [[ \"${PROTECT_CT:-no}\" == \"yes\" || \"${PROTECT_CT:-no}\" == \"1\" ]] && echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Protection: ${BGN}Enabled${CL}\"\n  [[ -n \"${CT_TIMEZONE:-}\" ]] && echo -e \"${INFO}${BOLD}${DGN}Timezone: ${BGN}$CT_TIMEZONE${CL}\"\n  [[ \"$APT_CACHER\" == \"yes\" ]] && echo -e \"${INFO}${BOLD}${DGN}APT Cacher: ${BGN}$APT_CACHER_IP${CL}\"\n  echo -e \"${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}\"\n  echo -e \"${CREATING}${BOLD}${RD}Creating an LXC of ${APP} using the above advanced settings${CL}\"\n\n  # Log settings to file\n  log_section \"CONTAINER SETTINGS (ADVANCED) - ${APP}\"\n  log_msg \"Application: ${APP}\"\n  log_msg \"PVE Version: ${PVEVERSION} (Kernel: ${KERNEL_VERSION})\"\n  log_msg \"Operating System: $var_os ($var_version)\"\n  log_msg \"Container Type: $([ \"$CT_TYPE\" == \"1\" ] && echo \"Unprivileged\" || echo \"Privileged\")\"\n  log_msg \"Container ID: $CT_ID\"\n  log_msg \"Hostname: $HN\"\n  log_msg \"Disk Size: ${DISK_SIZE} GB\"\n  log_msg \"CPU Cores: $CORE_COUNT\"\n  log_msg \"RAM Size: ${RAM_SIZE} MiB\"\n  log_msg \"Bridge: $BRG\"\n  log_msg \"IPv4: $NET\"\n  log_msg \"IPv6: $IPV6_METHOD\"\n  log_msg \"FUSE Support: ${ENABLE_FUSE:-no}\"\n  log_msg \"Nesting: $([ \"${ENABLE_NESTING:-1}\" == \"1\" ] && echo \"Enabled\" || echo \"Disabled\")\"\n  log_msg \"Mknod: $([ \"${ENABLE_MKNOD:-0}\" == \"1\" ] && echo \"Enabled\" || echo \"Disabled\")\"\n  [[ -n \"${ALLOW_MOUNT_FS:-}\" ]] && log_msg \"Mount FS: ${ALLOW_MOUNT_FS}\"\n  log_msg \"GPU Passthrough: ${ENABLE_GPU:-no}\"\n  log_msg \"Verbose Mode: $VERBOSE\"\n  log_msg \"Session ID: ${SESSION_ID}\"\n}\n\n# ==============================================================================\n# SECTION 7: USER INTERFACE & DIAGNOSTICS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# diagnostics_check()\n#\n# - Ensures diagnostics config file exists at /usr/local/community-scripts/diagnostics\n# - Asks user whether to send anonymous diagnostic data (first run only)\n# - Saves DIAGNOSTICS=yes/no in the config file\n# - Reads current diagnostics setting from existing file\n# - Sets global DIAGNOSTICS variable for API telemetry opt-in/out\n# ------------------------------------------------------------------------------\ndiagnostics_check() {\n  local config_dir=\"/usr/local/community-scripts\"\n  local config_file=\"${config_dir}/diagnostics\"\n\n  mkdir -p \"$config_dir\"\n\n  if [[ -f \"$config_file\" ]]; then\n    DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' \"$config_file\") || true\n    DIAGNOSTICS=\"${DIAGNOSTICS:-no}\"\n    return\n  fi\n\n  local result\n  result=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n    --title \"TELEMETRY & DIAGNOSTICS\" \\\n    --ok-button \"Confirm\" --cancel-button \"Exit\" \\\n    --radiolist \"\\nHelp improve Community-Scripts by sharing anonymous data.\\n\\nWhat we collect:\\n  - Container resources (CPU, RAM, disk), OS & PVE version\\n  - Application name, install method and status\\n\\nWhat we DON'T collect:\\n  - No IP addresses, hostnames, or personal data\\n\\nYou can change this anytime in the Settings menu.\\nPrivacy: https://github.com/community-scripts/telemetry-service/blob/main/docs/PRIVACY.md\\n\\nUse SPACE to select, ENTER to confirm.\" 22 76 2 \\\n    \"yes\" \"Yes, share anonymous data\" OFF \\\n    \"no\" \"No, opt out\" OFF \\\n    3>&1 1>&2 2>&3) || result=\"no\"\n\n  DIAGNOSTICS=\"${result:-no}\"\n\n  cat <<EOF >\"$config_file\"\nDIAGNOSTICS=${DIAGNOSTICS}\n\n# Community-Scripts Telemetry Configuration\n# https://telemetry.community-scripts.org\n#\n# This file stores your telemetry preference.\n# Set DIAGNOSTICS=yes to share anonymous installation data.\n# Set DIAGNOSTICS=no to disable telemetry.\n#\n# You can also change this via the Settings menu during installation.\n#\n# Data collected (when enabled):\n#   disk_size, core_count, ram_size, os_type, os_version,\n#   nsapp, method, pve_version, status, exit_code\n#\n# No personal data (IPs, hostnames, passwords) is ever collected.\n# Privacy: https://github.com/community-scripts/telemetry-service/blob/main/docs/PRIVACY.md\nEOF\n}\n\ndiagnostics_menu() {\n  local current=\"${DIAGNOSTICS:-no}\"\n  local status_text=\"DISABLED\"\n  [[ \"$current\" == \"yes\" ]] && status_text=\"ENABLED\"\n\n  local dialog_text=(\n    \"Telemetry is currently: ${status_text}\\n\\n\"\n    \"Anonymous data helps us improve scripts and track issues.\\n\"\n    \"No personal data is ever collected.\\n\\n\"\n    \"More info: https://telemetry.community-scripts.org\\n\\n\"\n    \"Do you want to ${current:+change this setting}?\"\n  )\n\n  if [[ \"$current\" == \"yes\" ]]; then\n    if whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"TELEMETRY SETTINGS\" \\\n      --yesno \"${dialog_text[*]}\" 14 64 \\\n      --yes-button \"Disable\" --no-button \"Keep enabled\"; then\n      DIAGNOSTICS=\"no\"\n      sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics\n      whiptail --msgbox \"Telemetry disabled.\\n\\nNote: Existing containers keep their current setting.\\nNew containers will inherit this choice.\" 10 58\n    fi\n  else\n    if whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"TELEMETRY SETTINGS\" \\\n      --yesno \"${dialog_text[*]}\" 14 64 \\\n      --yes-button \"Enable\" --no-button \"Keep disabled\"; then\n      DIAGNOSTICS=\"yes\"\n      sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics\n      whiptail --msgbox \"Telemetry enabled.\\n\\nNote: Existing containers keep their current setting.\\nNew containers will inherit this choice.\" 10 58\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# echo_default()\n#\n# - Prints summary of default values (ID, OS, type, disk, RAM, CPU, etc.)\n# - Uses icons and formatting for readability\n# - Convert CT_TYPE to description\n# - Also logs settings to log file for debugging\n# ------------------------------------------------------------------------------\necho_default() {\n  CT_TYPE_DESC=\"Unprivileged\"\n  if [ \"$CT_TYPE\" -eq 0 ]; then\n    CT_TYPE_DESC=\"Privileged\"\n  fi\n  echo -e \"${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}\"\n  if [[ -n \"${var_gpu:-}\" && \"${var_gpu}\" == \"yes\" ]]; then\n    echo -e \"${GPU}${BOLD}${DGN}GPU Passthrough: ${BGN}Enabled${CL}\"\n  fi\n  if [ \"$VERBOSE\" == \"yes\" ]; then\n    echo -e \"${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}\"\n  fi\n  echo -e \"${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}\"\n  echo -e \"  \"\n\n  # Log settings to file\n  log_section \"CONTAINER SETTINGS - ${APP}\"\n  log_msg \"Application: ${APP}\"\n  log_msg \"PVE Version: ${PVEVERSION} (Kernel: ${KERNEL_VERSION})\"\n  log_msg \"Container ID: ${CT_ID}\"\n  log_msg \"Operating System: $var_os ($var_version)\"\n  log_msg \"Container Type: $CT_TYPE_DESC\"\n  log_msg \"Disk Size: ${DISK_SIZE} GB\"\n  log_msg \"CPU Cores: ${CORE_COUNT}\"\n  log_msg \"RAM Size: ${RAM_SIZE} MiB\"\n  [[ -n \"${var_gpu:-}\" && \"${var_gpu}\" == \"yes\" ]] && log_msg \"GPU Passthrough: Enabled\"\n  [[ \"$VERBOSE\" == \"yes\" ]] && log_msg \"Verbose Mode: Enabled\"\n  log_msg \"Session ID: ${SESSION_ID}\"\n}\n\n# ------------------------------------------------------------------------------\n# install_script()\n#\n# - Main entrypoint for installation mode\n# - Runs safety checks (pve_check, root_check, maxkeys_check, diagnostics_check)\n# - Builds interactive menu (Default, Verbose, Advanced, My Defaults, App Defaults, Diagnostics, Storage, Exit)\n# - Applies chosen settings and triggers container build\n# ------------------------------------------------------------------------------\ninstall_script() {\n  pve_check\n  shell_check\n  root_check\n  arch_check\n  ssh_check\n  maxkeys_check\n  diagnostics_check\n\n  if systemctl is-active -q ping-instances.service; then\n    systemctl -q stop ping-instances.service\n  fi\n\n  NEXTID=$(pvesh get /cluster/nextid)\n\n  # Get timezone using timedatectl (Debian 13+ compatible)\n  # Fallback to /etc/timezone for older systems\n  if command -v timedatectl >/dev/null 2>&1; then\n    timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo \"UTC\")\n  elif [ -f /etc/timezone ]; then\n    timezone=$(cat /etc/timezone)\n  else\n    timezone=\"UTC\"\n  fi\n  [[ \"${timezone:-}\" == Etc/* ]] && timezone=\"host\" # pct doesn't accept Etc/* zones\n\n  # Show APP Header\n  header_info\n\n  # --- Support CLI argument as direct preset (default, advanced, …) ---\n  CHOICE=\"${mode:-${1:-}}\"\n\n  # If no CLI argument → show whiptail menu\n  # Build menu dynamically based on available options\n  local appdefaults_option=\"\"\n  local settings_option=\"\"\n  local menu_items=(\n    \"1\" \"Default Install\"\n    \"2\" \"Advanced Install\"\n    \"3\" \"User Defaults\"\n  )\n\n  if [ -f \"$(get_app_defaults_path)\" ]; then\n    appdefaults_option=\"4\"\n    menu_items+=(\"4\" \"App Defaults for ${APP}\")\n    settings_option=\"5\"\n    menu_items+=(\"5\" \"Settings\")\n  else\n    settings_option=\"4\"\n    menu_items+=(\"4\" \"Settings\")\n  fi\n\n  APPDEFAULTS_OPTION=\"$appdefaults_option\"\n  SETTINGS_OPTION=\"$settings_option\"\n\n  # Main menu loop - allows returning from Settings\n  while true; do\n    if [ -z \"$CHOICE\" ]; then\n      TMP_CHOICE=$(whiptail \\\n        --backtitle \"Proxmox VE Helper Scripts\" \\\n        --title \"Community-Scripts Options\" \\\n        --ok-button \"Select\" --cancel-button \"Exit Script\" \\\n        --notags \\\n        --menu \"\\nChoose an option:\\n Use TAB or Arrow keys to navigate, ENTER to select.\\n\" \\\n        20 60 9 \\\n        \"${menu_items[@]}\" \\\n        --default-item \"1\" \\\n        3>&1 1>&2 2>&3) || exit_script\n      CHOICE=\"$TMP_CHOICE\"\n    fi\n\n    # --- Main case ---\n    local defaults_target=\"\"\n    local run_maybe_offer=\"no\"\n    case \"$CHOICE\" in\n    1 | default | DEFAULT)\n      header_info\n      echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}\"\n      VERBOSE=\"no\"\n      METHOD=\"default\"\n      base_settings \"$VERBOSE\"\n      echo_default\n      defaults_target=\"$(ensure_global_default_vars_file)\"\n      break\n      ;;\n    2 | advanced | ADVANCED)\n      header_info\n      echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Install on node $PVEHOST_NAME${CL}\"\n      METHOD=\"advanced\"\n      base_settings\n      advanced_settings\n      defaults_target=\"$(ensure_global_default_vars_file)\"\n      run_maybe_offer=\"yes\"\n      break\n      ;;\n    3 | mydefaults | MYDEFAULTS | userdefaults | USERDEFAULTS)\n      default_var_settings || {\n        msg_error \"Failed to apply default.vars\"\n        exit 110\n      }\n      defaults_target=\"/usr/local/community-scripts/default.vars\"\n      break\n      ;;\n    \"$APPDEFAULTS_OPTION\" | appdefaults | APPDEFAULTS)\n      if [ -f \"$(get_app_defaults_path)\" ]; then\n        header_info\n        echo -e \"${DEFAULT}${BOLD}${BL}Using App Defaults for ${APP} on node $PVEHOST_NAME${CL}\"\n        METHOD=\"appdefaults\"\n        load_vars_file \"$(get_app_defaults_path)\" \"yes\" # Force override script defaults\n        base_settings\n        echo_default\n        defaults_target=\"$(get_app_defaults_path)\"\n        break\n      else\n        msg_error \"No App Defaults available for ${APP}\"\n        exit 111\n      fi\n      ;;\n    \"$SETTINGS_OPTION\" | settings | SETTINGS)\n      settings_menu\n      # After settings menu, show main menu again\n      header_info\n      CHOICE=\"\"\n      ;;\n    generated | GENERATED)\n      header_info\n      echo -e \"${DEFAULT}${BOLD}${BL}Using Generated Settings on node $PVEHOST_NAME${CL}\"\n      VERBOSE=\"no\"\n      METHOD=\"generated\"\n      base_settings \"$VERBOSE\"\n      echo_default\n      break\n      ;;\n    *)\n      msg_error \"Invalid option: $CHOICE\"\n      exit 112\n      ;;\n    esac\n  done\n\n  if [[ -n \"$defaults_target\" ]]; then\n    ensure_storage_selection_for_vars_file \"$defaults_target\"\n  fi\n\n  if [[ \"$run_maybe_offer\" == \"yes\" ]]; then\n    maybe_offer_save_app_defaults\n  fi\n}\n\nedit_default_storage() {\n  local vf=\"/usr/local/community-scripts/default.vars\"\n\n  # Ensure file exists\n  if [[ ! -f \"$vf\" ]]; then\n    mkdir -p \"$(dirname \"$vf\")\"\n    touch \"$vf\"\n  fi\n\n  # Let ensure_storage_selection_for_vars_file handle everything\n  ensure_storage_selection_for_vars_file \"$vf\"\n}\n\nsettings_menu() {\n  while true; do\n    local settings_items=(\n      \"1\" \"Manage API-Diagnostic Setting\"\n      \"2\" \"Edit Default.vars\"\n    )\n    if [ -f \"$(get_app_defaults_path)\" ]; then\n      settings_items+=(\"3\" \"Edit App.vars for ${APP}\")\n      settings_items+=(\"4\" \"Back to Main Menu\")\n    else\n      settings_items+=(\"3\" \"Back to Main Menu\")\n    fi\n\n    local choice\n    choice=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"Community-Scripts SETTINGS Menu\" \\\n      --ok-button \"Select\" --cancel-button \"Exit Script\" \\\n      --menu \"\\n\\nChoose a settings option:\\n\\nUse Arrow keys to navigate, ENTER to select, TAB for buttons.\" 20 60 9 \\\n      \"${settings_items[@]}\" \\\n      3>&1 1>&2 2>&3) || exit_script\n\n    case \"$choice\" in\n    1) diagnostics_menu ;;\n    2) ${EDITOR:-nano} /usr/local/community-scripts/default.vars ;;\n    3)\n      if [ -f \"$(get_app_defaults_path)\" ]; then\n        ${EDITOR:-nano} \"$(get_app_defaults_path)\"\n      else\n        # Back was selected (no app.vars available)\n        return\n      fi\n      ;;\n    4)\n      # Back to main menu\n      return\n      ;;\n    esac\n  done\n}\n\n# ------------------------------------------------------------------------------\n# check_container_resources()\n#\n# - Compares host RAM/CPU with required values\n# - Warns if under-provisioned and asks user to continue or abort\n# ------------------------------------------------------------------------------\ncheck_container_resources() {\n  current_ram=$(free -m | awk 'NR==2{print $2}')\n  current_cpu=$(nproc)\n\n  if [[ \"$current_ram\" -lt \"$var_ram\" ]] || [[ \"$current_cpu\" -lt \"$var_cpu\" ]]; then\n    msg_warn \"Under-provisioned: Required ${var_cpu} CPU/${var_ram}MB RAM, Current ${current_cpu} CPU/${current_ram}MB RAM\"\n    echo -e \"${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\\n\"\n    if is_unattended; then\n      msg_error \"Aborted: under-provisioned LXC in unattended mode (${current_cpu} CPU/${current_ram}MB RAM < ${var_cpu} CPU/${var_ram}MB RAM)\"\n      exit 113\n    fi\n    echo -ne \"${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? <yes/No>  \"\n    read -r prompt </dev/tty\n    if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then\n      msg_error \"Aborted: under-provisioned LXC (${current_cpu} CPU/${current_ram}MB RAM < ${var_cpu} CPU/${var_ram}MB RAM)\"\n      exit 113\n    fi\n  else\n    echo -e \"\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# check_container_storage()\n#\n# - Checks /boot partition usage\n# - Warns if usage >80% and asks user confirmation before proceeding\n# ------------------------------------------------------------------------------\ncheck_container_storage() {\n  total_size=$(df /boot --output=size | tail -n 1)\n  local used_size=$(df /boot --output=used | tail -n 1)\n  usage=$((100 * used_size / total_size))\n  if ((usage > 80)); then\n    msg_warn \"Storage is dangerously low (${usage}% used on /boot)\"\n    if is_unattended; then\n      msg_error \"Aborted: storage too low in unattended mode (${usage}% used on /boot)\"\n      exit 114\n    fi\n    echo -ne \"Continue anyway? <y/N>  \"\n    read -r prompt </dev/tty\n    if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then\n      msg_error \"Aborted: storage too low (${usage}% used)\"\n      exit 114\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# ssh_extract_keys_from_file()\n#\n# - Extracts valid SSH public keys from given file\n# - Supports RSA, Ed25519, ECDSA and filters out comments/invalid lines\n# ------------------------------------------------------------------------------\nssh_extract_keys_from_file() {\n  local f=\"$1\"\n  [[ -r \"$f\" ]] || return 0\n  tr -d '\\r' <\"$f\" | awk '\n    /^[[:space:]]*#/ {next}\n    /^[[:space:]]*$/ {next}\n    # bare format: type base64 [comment]\n    /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/ {print; next}\n    # with options: find from first key-type onward\n    {\n      match($0, /(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/)\n      if (RSTART>0) { print substr($0, RSTART) }\n    }\n  '\n}\n\n# ------------------------------------------------------------------------------\n# ssh_build_choices_from_files()\n#\n# - Builds interactive whiptail checklist of available SSH keys\n# - Generates fingerprint, type and comment for each key\n# ------------------------------------------------------------------------------\nssh_build_choices_from_files() {\n  local -a files=(\"$@\")\n  CHOICES=()\n  COUNT=0\n  MAPFILE=\"$(mktemp)\"\n  local id key typ fp cmt base ln=0\n\n  for f in \"${files[@]}\"; do\n    [[ -f \"$f\" && -r \"$f\" ]] || continue\n    base=\"$(basename -- \"$f\")\"\n    case \"$base\" in\n    known_hosts | known_hosts.* | config) continue ;;\n    id_*) [[ \"$f\" != *.pub ]] && continue ;;\n    esac\n\n    # map every key in file\n    while IFS= read -r key; do\n      [[ -n \"$key\" ]] || continue\n\n      typ=\"\"\n      fp=\"\"\n      cmt=\"\"\n      # Only the pure key part (without options) is already included in ‘key’.\n      read -r _typ _b64 _cmt <<<\"$key\"\n      typ=\"${_typ:-key}\"\n      cmt=\"${_cmt:-}\"\n      # Fingerprint via ssh-keygen (if available)\n      if command -v ssh-keygen >/dev/null 2>&1; then\n        fp=\"$(printf '%s\\n' \"$key\" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')\"\n      fi\n      # Label shorten\n      [[ ${#cmt} -gt 40 ]] && cmt=\"${cmt:0:37}...\"\n\n      ln=$((ln + 1))\n      COUNT=$((COUNT + 1))\n      id=\"K${COUNT}\"\n      echo \"${id}|${key}\" >>\"$MAPFILE\"\n      CHOICES+=(\"$id\" \"[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}\" \"OFF\")\n    done < <(ssh_extract_keys_from_file \"$f\")\n  done\n}\n\n# ------------------------------------------------------------------------------\n# ssh_discover_default_files()\n#\n# - Scans standard paths for SSH keys\n# - Includes ~/.ssh/*.pub, /etc/ssh/authorized_keys, etc.\n# ------------------------------------------------------------------------------\nssh_discover_default_files() {\n  local -a cand=()\n  shopt -s nullglob\n  cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)\n  cand+=(/root/.ssh/*.pub)\n  cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)\n  shopt -u nullglob\n  printf '%s\\0' \"${cand[@]}\"\n}\n\nconfigure_ssh_settings() {\n  local step_info=\"${1:-}\"\n  local backtitle=\"Proxmox VE Helper Scripts\"\n  [[ -n \"$step_info\" ]] && backtitle=\"Proxmox VE Helper Scripts [${step_info}]\"\n\n  SSH_KEYS_FILE=\"$(mktemp)\"\n  : >\"$SSH_KEYS_FILE\"\n\n  IFS=$'\\0' read -r -d '' -a _def_files < <(ssh_discover_default_files && printf '\\0')\n  ssh_build_choices_from_files \"${_def_files[@]}\"\n  local default_key_count=\"$COUNT\"\n\n  local ssh_key_mode\n  if [[ \"$default_key_count\" -gt 0 ]]; then\n    ssh_key_mode=$(whiptail --backtitle \"$backtitle\" --title \"SSH KEY SOURCE\" --menu \\\n      \"Provision SSH keys for root:\" 14 72 4 \\\n      \"found\" \"Select from detected keys (${default_key_count})\" \\\n      \"manual\" \"Paste a single public key\" \\\n      \"folder\" \"Scan another folder (path or glob)\" \\\n      \"none\" \"No keys\" 3>&1 1>&2 2>&3) || exit_script\n  else\n    ssh_key_mode=$(whiptail --backtitle \"$backtitle\" --title \"SSH KEY SOURCE\" --menu \\\n      \"No host keys detected; choose manual/none:\" 12 72 2 \\\n      \"manual\" \"Paste a single public key\" \\\n      \"none\" \"No keys\" 3>&1 1>&2 2>&3) || exit_script\n  fi\n\n  case \"$ssh_key_mode\" in\n  found)\n    local selection\n    selection=$(whiptail --backtitle \"$backtitle\" --title \"SELECT HOST KEYS\" \\\n      --checklist \"Select one or more keys to import:\" 20 140 10 \"${CHOICES[@]}\" 3>&1 1>&2 2>&3) || exit_script\n    for tag in $selection; do\n      tag=\"${tag%\\\"}\"\n      tag=\"${tag#\\\"}\"\n      local line\n      line=$(grep -E \"^${tag}\\|\" \"$MAPFILE\" | head -n1 | cut -d'|' -f2- || true)\n      [[ -n \"$line\" ]] && printf '%s\\n' \"$line\" >>\"$SSH_KEYS_FILE\"\n    done\n    ;;\n  manual)\n    SSH_AUTHORIZED_KEY=\"$(whiptail --backtitle \"$backtitle\" \\\n      --inputbox \"Paste one SSH public key line (ssh-ed25519/ssh-rsa/...)\" 10 72 --title \"SSH Public Key\" 3>&1 1>&2 2>&3)\"\n    [[ -n \"$SSH_AUTHORIZED_KEY\" ]] && printf '%s\\n' \"$SSH_AUTHORIZED_KEY\" >>\"$SSH_KEYS_FILE\"\n    ;;\n  folder)\n    local glob_path\n    glob_path=$(whiptail --backtitle \"$backtitle\" \\\n      --inputbox \"Enter a folder or glob to scan (e.g. /root/.ssh/*.pub)\" 10 72 --title \"Scan Folder/Glob\" 3>&1 1>&2 2>&3)\n    if [[ -n \"$glob_path\" ]]; then\n      shopt -s nullglob\n      read -r -a _scan_files <<<\"$glob_path\"\n      shopt -u nullglob\n      if [[ \"${#_scan_files[@]}\" -gt 0 ]]; then\n        ssh_build_choices_from_files \"${_scan_files[@]}\"\n        if [[ \"$COUNT\" -gt 0 ]]; then\n          local folder_selection\n          folder_selection=$(whiptail --backtitle \"$backtitle\" --title \"SELECT FOLDER KEYS\" \\\n            --checklist \"Select key(s) to import:\" 20 78 10 \"${CHOICES[@]}\" 3>&1 1>&2 2>&3) || exit_script\n          for tag in $folder_selection; do\n            tag=\"${tag%\\\"}\"\n            tag=\"${tag#\\\"}\"\n            local line\n            line=$(grep -E \"^${tag}\\|\" \"$MAPFILE\" | head -n1 | cut -d'|' -f2- || true)\n            [[ -n \"$line\" ]] && printf '%s\\n' \"$line\" >>\"$SSH_KEYS_FILE\"\n          done\n        else\n          whiptail --backtitle \"$backtitle\" --msgbox \"No keys found in: $glob_path\" 8 60\n        fi\n      else\n        whiptail --backtitle \"$backtitle\" --msgbox \"Path/glob returned no files.\" 8 60\n      fi\n    fi\n    ;;\n  none)\n    :\n    ;;\n  esac\n\n  if [[ -s \"$SSH_KEYS_FILE\" ]]; then\n    sort -u -o \"$SSH_KEYS_FILE\" \"$SSH_KEYS_FILE\"\n    printf '\\n' >>\"$SSH_KEYS_FILE\"\n  fi\n\n  # Always show SSH access dialog - user should be able to enable SSH even without keys\n  if (whiptail --backtitle \"$backtitle\" --defaultno --title \"SSH ACCESS\" --yesno \"Enable root SSH access?\" 10 58); then\n    SSH=\"yes\"\n  else\n    SSH=\"no\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# msg_menu()\n#\n# - Displays a numbered menu for update_script() functions\n# - In silent mode (PHS_SILENT=1): auto-selects the default option\n# - In interactive mode: shows menu via read with 10s timeout + default fallback\n# - Usage: CHOICE=$(msg_menu \"Title\" \"tag1\" \"Description 1\" \"tag2\" \"Desc 2\" ...)\n# - The first item is always the default\n# - Returns the selected tag to stdout\n# - If no valid selection or timeout, returns the default (first) tag\n# ------------------------------------------------------------------------------\nmsg_menu() {\n  local title=\"$1\"\n  shift\n\n  # Parse items into parallel arrays: tags[] and descriptions[]\n  local -a tags=()\n  local -a descs=()\n  while [[ $# -ge 2 ]]; do\n    tags+=(\"$1\")\n    descs+=(\"$2\")\n    shift 2\n  done\n\n  local default_tag=\"${tags[0]}\"\n  local count=${#tags[@]}\n\n  # Silent mode: return default immediately\n  if [[ -n \"${PHS_SILENT+x}\" ]] && [[ \"${PHS_SILENT}\" == \"1\" ]]; then\n    echo \"$default_tag\"\n    return 0\n  fi\n\n  # Display menu to /dev/tty so it doesn't get captured by command substitution\n  {\n    echo \"\"\n    msg_custom \"📋\" \"${BL}\" \"${title}\"\n    echo \"\"\n    for i in \"${!tags[@]}\"; do\n      local marker=\"  \"\n      [[ $i -eq 0 ]] && marker=\"* \"\n      printf \"${TAB3}${marker}%s) %s\\n\" \"${tags[$i]}\" \"${descs[$i]}\"\n    done\n    echo \"\"\n  } >/dev/tty\n\n  local selection=\"\"\n  read -r -t 10 -p \"${TAB3}Select [default=${default_tag}, timeout 10s]: \" selection </dev/tty >/dev/tty || true\n\n  # Validate selection\n  if [[ -n \"$selection\" ]]; then\n    for tag in \"${tags[@]}\"; do\n      if [[ \"$selection\" == \"$tag\" ]]; then\n        echo \"$selection\"\n        return 0\n      fi\n    done\n    msg_warn \"Invalid selection '${selection}' - using default: ${default_tag}\"\n  fi\n\n  echo \"$default_tag\"\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# run_addon_updates()\n#\n# - Scans /usr/local/bin/update_* for addon update scripts installed alongside\n#   the main application (e.g. by tools/addon/*.sh)\n# - For each found addon, prompts the user (60s timeout, default no) whether\n#   it should be updated as well\n# - Skipped entirely when PHS_SILENT=1 to keep unattended updates predictable\n# ------------------------------------------------------------------------------\nrun_addon_updates() {\n  shopt -s nullglob\n  local addons=(/usr/local/bin/update_*)\n  shopt -u nullglob\n\n  ((${#addons[@]} == 0)) && return 0\n\n  if [[ \"${PHS_SILENT:-0}\" == \"1\" ]]; then\n    msg_info \"Detected ${#addons[@]} addon update script(s) - skipping (PHS_SILENT)\"\n    return 0\n  fi\n\n  echo\n  echo -e \"${INFO}${YW} Detected installed addon update script(s):${CL}\"\n  local a name\n  for a in \"${addons[@]}\"; do\n    echo -e \"${TAB}- ${a##*/update_}\"\n  done\n  echo\n\n  local ans\n  for a in \"${addons[@]}\"; do\n    name=\"${a##*/update_}\"\n    printf 'Do you also want to update addon \"%s\"? (y/N) [60s]: ' \"$name\"\n    ans=\"\"\n    if read -r -t 60 ans; then :; else echo; fi\n    case \"${ans,,}\" in\n    y | yes)\n      bash \"$a\" || msg_warn \"Addon update for $name failed (rc=$?)\"\n      ;;\n    *)\n      msg_info \"Skipped addon: $name\"\n      ;;\n    esac\n  done\n}\n\n# ------------------------------------------------------------------------------\n# start()\n#\n# - Entry point of script\n# - On Proxmox host: calls install_script\n# - In silent mode: runs update_script with automatic cleanup\n# - Otherwise: shows update/setting menu and runs update_script with cleanup\n# ------------------------------------------------------------------------------\nstart() {\n  source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func)\n  if command -v pveversion >/dev/null 2>&1; then\n    install_script || return 0\n    return 0\n  elif [ ! -z ${PHS_SILENT+x} ] && [[ \"${PHS_SILENT}\" == \"1\" ]]; then\n    VERBOSE=\"no\"\n    set_std_mode\n    ensure_profile_loaded\n    get_lxc_ip\n    update_script\n    run_addon_updates\n    update_motd_ip\n    cleanup_lxc\n  else\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"${APP} LXC Update/Setting\" --menu \\\n      \"Support/Update functions for ${APP} LXC. Choose an option:\" \\\n      12 60 3 \\\n      \"1\" \"YES (Silent Mode)\" \\\n      \"2\" \"YES (Verbose Mode)\" \\\n      \"3\" \"NO (Cancel Update)\" --nocancel --default-item \"1\" 3>&1 1>&2 2>&3)\n\n    case \"$CHOICE\" in\n    1)\n      VERBOSE=\"no\"\n      set_std_mode\n      ;;\n    2)\n      VERBOSE=\"yes\"\n      set_std_mode\n      ;;\n    3)\n      clear\n      exit_script\n      exit 0\n      ;;\n    esac\n    ensure_profile_loaded\n    get_lxc_ip\n    update_script\n    run_addon_updates\n    update_motd_ip\n    cleanup_lxc\n  fi\n}\n\n# ==============================================================================\n# SECTION 8: CONTAINER CREATION & DEPLOYMENT\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# build_container()\n#\n# - Main function for creating and configuring LXC container\n# - Builds network configuration string (IP, gateway, VLAN, MTU, MAC, IPv6)\n# - Creates container via pct create with all specified settings\n# - Applies features: FUSE, TUN, keyctl, VAAPI passthrough\n# - Starts container and waits for network connectivity\n# - Installs base packages (curl, sudo, etc.)\n# - Injects SSH keys if configured\n# - Executes <app>-install.sh inside container\n# - Posts installation telemetry to API if diagnostics enabled\n# ------------------------------------------------------------------------------\nbuild_container() {\n  #  if [ \"$VERBOSE\" == \"yes\" ]; then set -x; fi\n\n  NET_STRING=\"-net0 name=eth0,bridge=${BRG:-vmbr0}\"\n\n  # MAC\n  if [[ -n \"$MAC\" ]]; then\n    case \"$MAC\" in\n    ,hwaddr=*) NET_STRING+=\"$MAC\" ;;\n    *) NET_STRING+=\",hwaddr=$MAC\" ;;\n    esac\n  fi\n\n  # IP (always required, default dhcp)\n  NET_STRING+=\",ip=${NET:-dhcp}\"\n\n  # Gateway\n  if [[ -n \"$GATE\" ]]; then\n    case \"$GATE\" in\n    ,gw=) ;;\n    ,gw=*) NET_STRING+=\"$GATE\" ;;\n    *) NET_STRING+=\",gw=$GATE\" ;;\n    esac\n  fi\n\n  # VLAN\n  if [[ -n \"$VLAN\" ]]; then\n    case \"$VLAN\" in\n    ,tag=*) NET_STRING+=\"$VLAN\" ;;\n    *) NET_STRING+=\",tag=$VLAN\" ;;\n    esac\n  fi\n\n  # MTU\n  if [[ -n \"$MTU\" ]]; then\n    case \"$MTU\" in\n    ,mtu=*) NET_STRING+=\"$MTU\" ;;\n    *) NET_STRING+=\",mtu=$MTU\" ;;\n    esac\n  fi\n\n  # IPv6 Handling\n  case \"$IPV6_METHOD\" in\n  auto) NET_STRING=\"$NET_STRING,ip6=auto\" ;;\n  dhcp) NET_STRING=\"$NET_STRING,ip6=dhcp\" ;;\n  static)\n    if [[ -n \"$IPV6_ADDR\" ]]; then\n      NET_STRING=\"$NET_STRING,ip6=$IPV6_ADDR\"\n      [ -n \"$IPV6_GATE\" ] && NET_STRING=\"$NET_STRING,gw6=$IPV6_GATE\"\n    fi\n    ;;\n  none) ;;\n  esac\n\n  # Build FEATURES string based on container type and user choices\n  FEATURES=\"\"\n\n  # Nesting support (user configurable, default enabled)\n  if [ \"${ENABLE_NESTING:-1}\" == \"1\" ]; then\n    FEATURES=\"nesting=1\"\n  fi\n\n  # Keyctl for unprivileged containers (needed for Docker)\n  if [ \"$CT_TYPE\" == \"1\" ]; then\n    [ -n \"$FEATURES\" ] && FEATURES=\"$FEATURES,\"\n    FEATURES=\"${FEATURES}keyctl=1\"\n  fi\n\n  if [ \"$ENABLE_FUSE\" == \"yes\" ]; then\n    [ -n \"$FEATURES\" ] && FEATURES=\"$FEATURES,\"\n    FEATURES=\"${FEATURES}fuse=1\"\n  fi\n\n  # Mknod support (user configurable via advanced settings)\n  if [ \"${ENABLE_MKNOD:-0}\" == \"1\" ]; then\n    [ -n \"$FEATURES\" ] && FEATURES=\"$FEATURES,\"\n    FEATURES=\"${FEATURES}mknod=1\"\n  fi\n\n  # Mount filesystem types (user configurable via advanced settings)\n  if [ -n \"${ALLOW_MOUNT_FS:-}\" ]; then\n    # Sanitize: strip spaces, trailing/leading commas, then convert commas to semicolons\n    local _mount_clean=\"${ALLOW_MOUNT_FS// /}\"\n    _mount_clean=\"${_mount_clean%%,}\"\n    _mount_clean=\"${_mount_clean##,}\"\n    _mount_clean=\"${_mount_clean%%;}\"\n    _mount_clean=\"${_mount_clean//,/;}\"\n    if [ -n \"$_mount_clean\" ]; then\n      [ -n \"$FEATURES\" ] && FEATURES=\"$FEATURES,\"\n      FEATURES=\"${FEATURES}mount=${_mount_clean}\"\n    fi\n  fi\n\n  # Build PCT_OPTIONS as string for export\n  local _func_url\n  if [ \"$var_os\" == \"alpine\" ]; then\n    _func_url=\"https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/alpine-install.func\"\n  else\n    _func_url=\"https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/install.func\"\n  fi\n  export FUNCTIONS_FILE_PATH=\"$(curl -fsSL \"$_func_url\")\"\n  if [[ -z \"$FUNCTIONS_FILE_PATH\" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then\n    msg_error \"Failed to download install functions from: $_func_url\"\n    exit 115\n  fi\n\n  # Core exports for install.func\n  export DIAGNOSTICS=\"$DIAGNOSTICS\"\n  export RANDOM_UUID=\"$RANDOM_UUID\"\n  export EXECUTION_ID=\"$EXECUTION_ID\"\n  export SESSION_ID=\"$SESSION_ID\"\n  export CACHER=\"$APT_CACHER\"\n  export CACHER_IP=\"$APT_CACHER_IP\"\n  export tz=\"$timezone\"\n  export APPLICATION=\"$APP\"\n  export app=\"$NSAPP\"\n  export PASSWORD=\"$PW\"\n  export VERBOSE=\"$VERBOSE\"\n  export SSH_ROOT=\"${SSH}\"\n  export SSH_AUTHORIZED_KEY\n  export CTID=\"$CT_ID\"\n  export CTTYPE=\"$CT_TYPE\"\n  export ENABLE_FUSE=\"$ENABLE_FUSE\"\n  export ENABLE_TUN=\"$ENABLE_TUN\"\n  export PCT_OSTYPE=\"$var_os\"\n  export PCT_OSVERSION=\"$var_version\"\n  export PCT_DISK_SIZE=\"$DISK_SIZE\"\n  export IPV6_METHOD=\"$IPV6_METHOD\"\n  export ENABLE_GPU=\"$ENABLE_GPU\"\n\n  # DEV_MODE exports (optional, for debugging)\n  export BUILD_LOG=\"$BUILD_LOG\"\n  export INSTALL_LOG=\"/root/.install-${SESSION_ID}.log\"\n\n  # Keep host-side logging on BUILD_LOG (not exported — invisible to container)\n  # Without this, get_active_logfile() would return INSTALL_LOG (a container path)\n  # and all host msg_info/msg_ok/msg_error would write to /root/.install-SESSION.log\n  # on the HOST instead of BUILD_LOG, causing incomplete telemetry logs.\n  _HOST_LOGFILE=\"$BUILD_LOG\"\n\n  export dev_mode=\"${dev_mode:-}\"\n  export DEV_MODE_MOTD=\"${DEV_MODE_MOTD:-false}\"\n  export DEV_MODE_KEEP=\"${DEV_MODE_KEEP:-false}\"\n  export DEV_MODE_TRACE=\"${DEV_MODE_TRACE:-false}\"\n  export DEV_MODE_PAUSE=\"${DEV_MODE_PAUSE:-false}\"\n  export DEV_MODE_BREAKPOINT=\"${DEV_MODE_BREAKPOINT:-false}\"\n  export DEV_MODE_LOGS=\"${DEV_MODE_LOGS:-false}\"\n  export DEV_MODE_DRYRUN=\"${DEV_MODE_DRYRUN:-false}\"\n\n  # Build PCT_OPTIONS as multi-line string\n  PCT_OPTIONS_STRING=\"  -hostname $HN\"\n\n  # Only add -tags if TAGS is not empty\n  if [ -n \"$TAGS\" ]; then\n    PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  -tags $TAGS\"\n  fi\n\n  # Only add -features if FEATURES is not empty\n  if [ -n \"$FEATURES\" ]; then\n    PCT_OPTIONS_STRING=\"  -features $FEATURES\n$PCT_OPTIONS_STRING\"\n  fi\n\n  # Add searchdomain if specified\n  if [ -n \"$SD\" ]; then\n    PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  $SD\"\n  fi\n\n  # Add nameserver if specified\n  if [ -n \"$NS\" ]; then\n    PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  $NS\"\n  fi\n\n  # Network configuration\n  PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  $NET_STRING\n  -onboot 1\n  -cores $CORE_COUNT\n  -memory $RAM_SIZE\n  -unprivileged $CT_TYPE\"\n\n  # Protection flag (if var_protection was set)\n  if [ \"${PROTECT_CT:-}\" == \"1\" ] || [ \"${PROTECT_CT:-}\" == \"yes\" ]; then\n    PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  -protection 1\"\n  fi\n\n  # Timezone (map Etc/* to \"host\" as pct doesn't accept them)\n  if [ -n \"${CT_TIMEZONE:-}\" ]; then\n    local _pct_timezone=\"$CT_TIMEZONE\"\n    [[ \"$_pct_timezone\" == Etc/* ]] && _pct_timezone=\"host\"\n    PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  -timezone $_pct_timezone\"\n  fi\n\n  # Password (already formatted)\n  if [ -n \"$PW\" ]; then\n    PCT_OPTIONS_STRING=\"$PCT_OPTIONS_STRING\n  $PW\"\n  fi\n\n  # Export as string (this works, unlike arrays!)\n  export PCT_OPTIONS=\"$PCT_OPTIONS_STRING\"\n  export TEMPLATE_STORAGE=\"${var_template_storage:-}\"\n  export CONTAINER_STORAGE=\"${var_container_storage:-}\"\n\n  # Validate storage space only if CONTAINER_STORAGE is already set\n  # (Storage selection happens in create_lxc_container for some modes)\n  if [[ -n \"$CONTAINER_STORAGE\" ]]; then\n    msg_info \"Validating storage space\"\n    if ! validate_storage_space \"$CONTAINER_STORAGE\" \"$DISK_SIZE\" \"no\"; then\n      local free_space\n      free_space=$(pvesm status 2>/dev/null | awk -v s=\"$CONTAINER_STORAGE\" '$1 == s { print $6 }')\n      local free_fmt\n      free_fmt=$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f \"$free_space\" 2>/dev/null || echo \"${free_space}KB\")\n      msg_error \"Not enough space on '$CONTAINER_STORAGE'. Required: ${DISK_SIZE}GB, Available: ${free_fmt}\"\n      exit 214\n    fi\n    msg_ok \"Storage space validated\"\n  fi\n\n  create_lxc_container || exit $?\n\n  # Transition to 'configuring' — container created, now setting up OS/userland\n  post_progress_to_api \"configuring\"\n\n  LXC_CONFIG=\"/etc/pve/lxc/${CTID}.conf\"\n\n  # ============================================================================\n  # GPU/USB PASSTHROUGH CONFIGURATION\n  # ============================================================================\n\n  # Check if GPU passthrough is enabled\n  # Returns true only if var_gpu is explicitly set to \"yes\"\n  # Can be set via:\n  #   - Environment variable: var_gpu=yes bash -c \"...\"\n  #   - CT script default: var_gpu=\"${var_gpu:-no}\"\n  #   - Advanced settings wizard\n  #   - App defaults file: /usr/local/community-scripts/defaults/<app>.vars\n  is_gpu_app() {\n    [[ \"${var_gpu:-no}\" == \"yes\" ]] && return 0\n    return 1\n  }\n\n  # Detect all available GPU devices\n  detect_gpu_devices() {\n    INTEL_DEVICES=()\n    AMD_DEVICES=()\n    NVIDIA_DEVICES=()\n\n    # Store PCI info to avoid multiple calls\n    # grep returns exit 1 when no match — use || true to prevent ERR trap\n    local pci_vga_info\n    pci_vga_info=$(lspci -nn 2>/dev/null | grep -E \"VGA|Display|3D\" || true)\n\n    # No GPU-related PCI devices at all? Skip silently.\n    if [[ -z \"$pci_vga_info\" ]]; then\n      msg_debug \"No VGA/Display/3D PCI devices found\"\n      return 0\n    fi\n\n    # Check for Intel GPU - look for Intel vendor ID [8086]\n    if grep -q \"\\[8086:\" <<<\"$pci_vga_info\"; then\n      msg_custom \"🎮\" \"${BL}\" \"Detected Intel GPU\"\n      if [[ -d /dev/dri ]]; then\n        for d in /dev/dri/renderD* /dev/dri/card*; do\n          [[ -e \"$d\" ]] && INTEL_DEVICES+=(\"$d\")\n        done\n      fi\n    fi\n\n    # Check for AMD GPU - look for AMD vendor IDs [1002] (AMD/ATI) or [1022] (AMD)\n    if grep -qE \"\\[1002:|\\[1022:\" <<<\"$pci_vga_info\"; then\n      msg_custom \"🎮\" \"${RD}\" \"Detected AMD GPU\"\n      if [[ -d /dev/dri ]]; then\n        # Only add if not already claimed by Intel\n        if [[ ${#INTEL_DEVICES[@]} -eq 0 ]]; then\n          for d in /dev/dri/renderD* /dev/dri/card*; do\n            [[ -e \"$d\" ]] && AMD_DEVICES+=(\"$d\")\n          done\n        fi\n      fi\n    fi\n\n    # Check for NVIDIA GPU - look for NVIDIA vendor ID [10de]\n    if grep -q \"\\[10de:\" <<<\"$pci_vga_info\"; then\n      msg_custom \"🎮\" \"${GN}\" \"Detected NVIDIA GPU\"\n\n      # Simple passthrough - just bind /dev/nvidia* devices if they exist\n      # Only include character devices (-c), skip directories like /dev/nvidia-caps\n      for d in /dev/nvidia*; do\n        [[ -c \"$d\" ]] && NVIDIA_DEVICES+=(\"$d\")\n      done\n      # Also check for devices inside /dev/nvidia-caps/ directory\n      if [[ -d /dev/nvidia-caps ]]; then\n        for d in /dev/nvidia-caps/*; do\n          [[ -c \"$d\" ]] && NVIDIA_DEVICES+=(\"$d\")\n        done\n      fi\n\n      if [[ ${#NVIDIA_DEVICES[@]} -gt 0 ]]; then\n        msg_custom \"🎮\" \"${GN}\" \"Found ${#NVIDIA_DEVICES[@]} NVIDIA device(s) for passthrough\"\n      else\n        msg_warn \"NVIDIA GPU detected via PCI but no /dev/nvidia* devices found\"\n        msg_custom \"ℹ️\" \"${YW}\" \"Skipping NVIDIA passthrough (host drivers may not be loaded)\"\n      fi\n    fi\n\n    # Debug output\n    msg_debug \"Intel devices: ${INTEL_DEVICES[*]}\"\n    msg_debug \"AMD devices: ${AMD_DEVICES[*]}\"\n    msg_debug \"NVIDIA devices: ${NVIDIA_DEVICES[*]}\"\n  }\n\n  # Configure USB passthrough for privileged containers\n  configure_usb_passthrough() {\n    if [[ \"$CT_TYPE\" != \"0\" ]]; then\n      return 0\n    fi\n\n    msg_info \"Configuring automatic USB passthrough (privileged container)\"\n    cat <<EOF >>\"$LXC_CONFIG\"\n# Automatic USB passthrough (privileged container)\nlxc.cgroup2.devices.allow: a\nlxc.cap.drop:\nlxc.cgroup2.devices.allow: c 188:* rwm\nlxc.cgroup2.devices.allow: c 189:* rwm\nlxc.mount.entry: /dev/serial/by-id  dev/serial/by-id  none bind,optional,create=dir\nlxc.mount.entry: /dev/ttyUSB0       dev/ttyUSB0       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyUSB1       dev/ttyUSB1       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyACM0       dev/ttyACM0       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyACM1       dev/ttyACM1       none bind,optional,create=file\nEOF\n    msg_ok \"USB passthrough configured\"\n  }\n\n  # Configure GPU passthrough\n  configure_gpu_passthrough() {\n    # Skip if:\n    # GPU passthrough is enabled when var_gpu=\"yes\":\n    # - Set via environment variable: var_gpu=yes bash -c \"...\"\n    # - Set in CT script: var_gpu=\"${var_gpu:-no}\"\n    # - Enabled in advanced_settings wizard\n    # - Configured in app defaults file\n    if ! is_gpu_app \"$APP\"; then\n      return 0\n    fi\n\n    detect_gpu_devices\n\n    # Count available GPU types\n    local gpu_count=0\n    local available_gpus=()\n\n    if [[ ${#INTEL_DEVICES[@]} -gt 0 ]]; then\n      available_gpus+=(\"INTEL\")\n      gpu_count=$((gpu_count + 1))\n    fi\n\n    if [[ ${#AMD_DEVICES[@]} -gt 0 ]]; then\n      available_gpus+=(\"AMD\")\n      gpu_count=$((gpu_count + 1))\n    fi\n\n    if [[ ${#NVIDIA_DEVICES[@]} -gt 0 ]]; then\n      available_gpus+=(\"NVIDIA\")\n      gpu_count=$((gpu_count + 1))\n    fi\n\n    if [[ $gpu_count -eq 0 ]]; then\n      msg_custom \"ℹ️\" \"${YW}\" \"No GPU devices found for passthrough\"\n      return 0\n    fi\n\n    local selected_gpu=\"\"\n\n    if [[ $gpu_count -eq 1 ]]; then\n      # Automatic selection for single GPU\n      selected_gpu=\"${available_gpus[0]}\"\n      msg_ok \"Automatically configuring ${selected_gpu} GPU passthrough\"\n    else\n      # Multiple GPUs - ask user\n      echo -e \"\\n${INFO} Multiple GPU types detected:\"\n      for gpu in \"${available_gpus[@]}\"; do\n        echo \"  - $gpu\"\n      done\n      read -rp \"Which GPU type to passthrough? (${available_gpus[*]}): \" selected_gpu </dev/tty\n      selected_gpu=\"${selected_gpu^^}\"\n\n      # Validate selection\n      local valid=0\n      for gpu in \"${available_gpus[@]}\"; do\n        [[ \"$selected_gpu\" == \"$gpu\" ]] && valid=1\n      done\n\n      if [[ $valid -eq 0 ]]; then\n        msg_warn \"Invalid selection. Skipping GPU passthrough.\"\n        return 0\n      fi\n    fi\n\n    # Apply passthrough configuration based on selection\n    local dev_idx=0\n\n    case \"$selected_gpu\" in\n    INTEL | AMD)\n      local devices=()\n      [[ \"$selected_gpu\" == \"INTEL\" ]] && devices=(\"${INTEL_DEVICES[@]}\")\n      [[ \"$selected_gpu\" == \"AMD\" ]] && devices=(\"${AMD_DEVICES[@]}\")\n\n      # Use pct set to add devices with proper dev0/dev1 format\n      # GIDs will be detected and set after container starts\n      local dev_index=0\n      for dev in \"${devices[@]}\"; do\n        # Add to config using pct set (will be visible in GUI)\n        echo \"dev${dev_index}: ${dev},gid=44\" >>\"$LXC_CONFIG\"\n        dev_index=$((dev_index + 1))\n      done\n\n      export GPU_TYPE=\"$selected_gpu\"\n      msg_ok \"${selected_gpu} GPU passthrough configured (${#devices[@]} devices)\"\n      ;;\n\n    NVIDIA)\n      if [[ ${#NVIDIA_DEVICES[@]} -eq 0 ]]; then\n        msg_warn \"No NVIDIA devices available for passthrough\"\n        return 0\n      fi\n\n      # Use pct set for NVIDIA devices\n      local dev_index=0\n      for dev in \"${NVIDIA_DEVICES[@]}\"; do\n        echo \"dev${dev_index}: ${dev},gid=44\" >>\"$LXC_CONFIG\"\n        dev_index=$((dev_index + 1))\n      done\n\n      export GPU_TYPE=\"NVIDIA\"\n      msg_ok \"NVIDIA GPU passthrough configured (${#NVIDIA_DEVICES[@]} devices) - install drivers in container if needed\"\n      ;;\n    esac\n  }\n\n  # Additional device passthrough\n  configure_additional_devices() {\n    # TUN device passthrough\n    if [ \"$ENABLE_TUN\" == \"yes\" ]; then\n      cat <<EOF >>\"$LXC_CONFIG\"\nlxc.cgroup2.devices.allow: c 10:200 rwm\nlxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file\nEOF\n    fi\n\n    # Coral TPU passthrough\n    if [[ -e /dev/apex_0 ]]; then\n      msg_custom \"🔌\" \"${BL}\" \"Detected Coral TPU - configuring passthrough\"\n      echo \"lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file\" >>\"$LXC_CONFIG\"\n    fi\n  }\n\n  # Execute pre-start configurations\n  configure_usb_passthrough\n  configure_gpu_passthrough\n  configure_additional_devices\n\n  # Increase disk size for AMD ROCm runtime (~4GB extra needed)\n  if [[ \"${GPU_TYPE:-}\" == \"AMD\" ]]; then\n    local rocm_extra=4\n    local new_disk_size=$((PCT_DISK_SIZE + rocm_extra))\n    if pct resize \"$CTID\" rootfs \"${new_disk_size}G\" >/dev/null 2>&1; then\n      msg_ok \"Disk resized ${PCT_DISK_SIZE}GB → ${new_disk_size}GB for ROCm\"\n    else\n      msg_warn \"Failed to resize disk for ROCm — installation may fail if space is insufficient\"\n    fi\n  fi\n\n  # ============================================================================\n  # START CONTAINER AND INSTALL USERLAND\n  # ============================================================================\n\n  msg_info \"Starting LXC Container\"\n  pct start \"$CTID\"\n\n  # Wait for container to be running\n  for i in {1..10}; do\n    if pct status \"$CTID\" | grep -q \"status: running\"; then\n      msg_ok \"Started LXC Container\"\n      break\n    fi\n    sleep 1\n    if [ \"$i\" -eq 10 ]; then\n      local ct_status\n      ct_status=$(pct status \"$CTID\" 2>/dev/null || echo \"unknown\")\n      msg_error \"LXC Container did not reach running state (status: ${ct_status})\"\n      exit 117\n    fi\n  done\n\n  # Wait for network (skip for Alpine initially)\n  if [ \"$var_os\" != \"alpine\" ]; then\n    msg_info \"Waiting for network in LXC container\"\n\n    # Wait for IP assignment (IPv4 or IPv6)\n    local ip_in_lxc=\"\"\n    for i in {1..60}; do\n      # Try IPv4 first\n      ip_in_lxc=$(pct exec \"$CTID\" -- ip -4 addr show dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)\n      # Fallback to IPv6 if IPv4 not available\n      if [ -z \"$ip_in_lxc\" ]; then\n        ip_in_lxc=$(pct exec \"$CTID\" -- ip -6 addr show dev eth0 scope global 2>/dev/null | awk '/inet6 / {print $2}' | cut -d/ -f1 | head -n1)\n      fi\n      [ -n \"$ip_in_lxc\" ] && break\n      # Progressive backoff: 1s for first 20, 2s for next 20, 3s for last 20\n      if [ \"$i\" -le 20 ]; then\n        sleep 1\n      elif [ \"$i\" -le 40 ]; then\n        sleep 2\n      else\n        sleep 3\n      fi\n    done\n\n    if [ -z \"$ip_in_lxc\" ]; then\n      msg_error \"No IP assigned to CT $CTID after 60 attempts\"\n      msg_custom \"🔧\" \"${YW}\" \"Troubleshooting:\"\n      echo \"  • Verify bridge ${BRG} exists and has connectivity\"\n      echo \"  • Check if DHCP server is reachable (if using DHCP)\"\n      echo \"  • Verify static IP configuration (if using static IP)\"\n      echo \"  • Check Proxmox firewall rules\"\n      echo \"  • If using Tailscale: Disable MagicDNS temporarily\"\n      exit 118\n    fi\n\n    # Verify basic connectivity (ping test)\n    local ping_success=false\n    for retry in {1..3}; do\n      if pct exec \"$CTID\" -- ping -c 1 -W 2 1.1.1.1 &>/dev/null ||\n        pct exec \"$CTID\" -- ping -c 1 -W 2 8.8.8.8 &>/dev/null ||\n        pct exec \"$CTID\" -- ping6 -c 1 -W 2 2606:4700:4700::1111 &>/dev/null; then\n        ping_success=true\n        break\n      fi\n      sleep 2\n    done\n\n    if [ \"$ping_success\" = false ]; then\n      msg_warn \"Network configured (IP: $ip_in_lxc) but connectivity test failed - installation will continue\"\n    else\n      msg_ok \"Network in LXC is reachable (ping)\"\n    fi\n  fi\n  # Function to get correct GID inside container\n  get_container_gid() {\n    local group=\"$1\"\n    local gid=$(pct exec \"$CTID\" -- getent group \"$group\" 2>/dev/null | cut -d: -f3)\n    echo \"${gid:-44}\" # Default to 44 if not found\n  }\n\n  fix_gpu_gids\n\n  # Fix Debian 13 LXC template bug where / is owned by nobody:nogroup\n  # This must be done from the host as unprivileged containers cannot chown /\n  local rootfs\n  rootfs=$(pct config \"$CTID\" | grep -E '^rootfs:' | sed 's/rootfs: //' | cut -d',' -f1 || true)\n  if [[ -n \"$rootfs\" ]]; then\n    local mount_point=\"/var/lib/lxc/${CTID}/rootfs\"\n    if [[ -d \"$mount_point\" ]] && [[ \"$(stat -c '%U' \"$mount_point\")\" != \"root\" ]]; then\n      chown root:root \"$mount_point\" 2>/dev/null || true\n    fi\n  fi\n\n  # Continue with standard container setup\n  msg_info \"Customizing LXC Container\"\n\n  # # Install GPU userland if configured\n  # if [[ \"${ENABLE_VAAPI:-0}\" == \"1\" ]]; then\n  #   install_gpu_userland \"VAAPI\"\n  # fi\n\n  # if [[ \"${ENABLE_NVIDIA:-0}\" == \"1\" ]]; then\n  #   install_gpu_userland \"NVIDIA\"\n  # fi\n\n  # Disable error trap for entire customization & install phase.\n  # All errors are handled explicitly — recovery menu shown on failure.\n  # Without this, customization errors (e.g. container stopped during base package\n  # install) would trigger error_handler() with a simple \"Remove broken container?\"\n  # prompt instead of the full recovery menu with retry/repair options.\n  set +Eeuo pipefail\n  trap - ERR\n\n  local install_exit_code=0\n\n  # Continue with standard container setup\n  if [ \"$var_os\" == \"alpine\" ]; then\n    sleep 3\n    pct exec \"$CTID\" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories\nhttps://dl-cdn.alpinelinux.org/alpine/latest-stable/main\nhttps://dl-cdn.alpinelinux.org/alpine/latest-stable/community\nEOF'\n    pct exec \"$CTID\" -- ash -c \"apk add bash newt curl openssh nano mc ncurses jq\" >>\"$BUILD_LOG\" 2>&1 || {\n      msg_warn \"apk install failed (dl-cdn.alpinelinux.org), trying alternate mirrors...\"\n      local alpine_exit=0\n      pct exec \"$CTID\" -- ash -c '\n        ALPINE_MIRRORS=\"mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org\"\n        for m in $(printf \"%s\\n\" $ALPINE_MIRRORS | shuf); do\n          if wget -q --spider --timeout=2 \"http://$m/alpine/latest-stable/main/\" 2>/dev/null; then\n            echo \"  Attempting mirror: $m\"\n            cat <<EOF >/etc/apk/repositories\nhttp://$m/alpine/latest-stable/main\nhttp://$m/alpine/latest-stable/community\nEOF\n            if apk update >/dev/null 2>&1 && apk add bash newt curl openssh nano mc ncurses jq >/dev/null 2>&1; then\n              echo \"  CDN set to $m: tests passed\"\n              exit 0\n            else\n              echo \"  Mirror $m failed\"\n            fi\n          fi\n        done\n        exit 2\n      ' && alpine_exit=0 || alpine_exit=$?\n      if [[ $alpine_exit -ne 0 ]]; then\n        msg_error \"Failed to install base packages in Alpine container\"\n        install_exit_code=1\n      fi\n    }\n  else\n    sleep 3\n    LANG=${LANG:-en_US.UTF-8}\n    local LANG_ESC=\"${LANG//./\\\\.}\"\n    LANG_ESC=\"${LANG_ESC//|/\\\\|}\"\n    pct exec \"$CTID\" -- bash -c \"sed -i \\\"/$LANG_ESC/ s/^# //\\\" /etc/locale.gen\"\n    pct exec \"$CTID\" -- bash -c \"locale_line=\\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \\$1}' | head -n 1) && \\\n    echo LANG=\\$locale_line >/etc/default/locale && \\\n    locale-gen >/dev/null && \\\n    export LANG=\\$locale_line\"\n\n    if [[ -z \"${tz:-}\" ]]; then\n      tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo \"UTC\")\n    fi\n    [[ \"${tz:-}\" == Etc/* ]] && tz=\"UTC\" # Normalize Etc/* to UTC for container setup\n\n    if pct exec \"$CTID\" -- test -e \"/usr/share/zoneinfo/$tz\"; then\n      # Set timezone using symlink (Debian 13+ compatible)\n      # Create /etc/timezone for backwards compatibility with older scripts\n      pct exec \"$CTID\" -- bash -c \"tz='$tz'; ln -sf \\\"/usr/share/zoneinfo/\\$tz\\\" /etc/localtime && echo \\\"\\$tz\\\" >/etc/timezone || true\"\n    else\n      msg_warn \"Skipping timezone setup – zone '$tz' not found in container\"\n    fi\n\n    # Detect broken DNS resolver (e.g. Tailscale MagicDNS) and inject public DNS\n    if ! pct exec \"$CTID\" -- bash -c \"getent hosts deb.debian.org >/dev/null 2>&1 && getent hosts archive.ubuntu.com >/dev/null 2>&1\"; then\n      msg_warn \"APT repository DNS resolution failed in container, injecting public DNS servers\"\n      pct exec \"$CTID\" -- bash -c \"echo -e 'nameserver 8.8.8.8\\nnameserver 1.1.1.1' >/etc/resolv.conf\"\n    fi\n\n    pct exec \"$CTID\" -- bash -c \"apt-get update 2>&1 && apt-get install -y sudo curl mc gnupg2 jq 2>&1\" >>\"$BUILD_LOG\" 2>&1 || {\n      local failed_mirror\n      failed_mirror=$(pct exec \"$CTID\" -- bash -c \"grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null\" 2>/dev/null || echo \"unknown\")\n      msg_warn \"apt-get update failed (${failed_mirror}), trying alternate mirrors...\"\n      local mirror_exit=0\n      pct exec \"$CTID\" -- bash -c '\n        APT_BASE=\"sudo curl mc gnupg2 jq\"\n        DISTRO=$(. /etc/os-release 2>/dev/null && echo \"$ID\" || echo \"debian\")\n\n        if [ \"$DISTRO\" = \"ubuntu\" ]; then\n          EU_MIRRORS=\"de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de\"\n          US_MIRRORS=\"us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu\"\n          AP_MIRRORS=\"au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au\"\n        else\n          EU_MIRRORS=\"ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net\"\n          US_MIRRORS=\"ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com\"\n          AP_MIRRORS=\"ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in\"\n        fi\n\n        TZ=$(cat /etc/timezone 2>/dev/null || echo \"UTC\")\n        case \"$TZ\" in\n          Europe/*|Arctic/*) REGIONAL=\"$EU_MIRRORS\"; OTHERS=\"$US_MIRRORS $AP_MIRRORS\" ;;\n          America/*) REGIONAL=\"$US_MIRRORS\"; OTHERS=\"$EU_MIRRORS $AP_MIRRORS\" ;;\n          Asia/*|Australia/*|Pacific/*) REGIONAL=\"$AP_MIRRORS\"; OTHERS=\"$EU_MIRRORS $US_MIRRORS\" ;;\n          *) REGIONAL=\"\"; OTHERS=\"$EU_MIRRORS $US_MIRRORS $AP_MIRRORS\" ;;\n        esac\n\n        echo \"Acquire::By-Hash \\\"no\\\";\" >/etc/apt/apt.conf.d/99no-by-hash\n\n        try_mirrors() {\n          for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do\n            [ -f \"$src\" ] && sed -i \"s|URIs: http[s]*://[^/]*/|URIs: http://${1}/|g; s|deb http[s]*://[^/]*/|deb http://${1}/|g\" \"$src\"\n          done\n          rm -rf /var/lib/apt/lists/*\n          APT_OUT=$(apt-get update 2>&1)\n          APT_RC=$?\n          if echo \"$APT_OUT\" | grep -qi \"hashsum\\|hash sum\"; then\n            echo \"  Mirror $1 failed (hash mismatch)\"\n            return 1\n          elif echo \"$APT_OUT\" | grep -qi \"SSL\\|certificate\"; then\n            echo \"  Mirror $1 failed (SSL/certificate error)\"\n            return 1\n          elif [ $APT_RC -ne 0 ]; then\n            echo \"  Mirror $1 failed (apt-get update error)\"\n            return 1\n          elif apt-get install -y $APT_BASE >/dev/null 2>&1; then\n            echo \"  CDN set to $1: tests passed\"\n            return 0\n          else\n            echo \"  Mirror $1 failed (package install error)\"\n            return 1\n          fi\n        }\n\n        scan_reachable() {\n          local result=\"\"\n          for m in $1; do\n            if timeout 2 bash -c \"echo >/dev/tcp/$m/80\" 2>/dev/null; then\n              result=\"$result $m\"\n            fi\n          done\n          echo \"$result\" | xargs\n        }\n\n        # Phase 1: Scan global mirrors first (independent of local CDN issues)\n        OTHERS_OK=$(scan_reachable \"$OTHERS\")\n        OTHERS_PICK=$(printf \"%s\\n\" $OTHERS_OK | shuf | head -3 | xargs)\n\n        for mirror in $OTHERS_PICK; do\n          echo \"  Attempting mirror: $mirror\"\n          try_mirrors \"$mirror\" && exit 0\n        done\n\n        # Phase 2: Try primary mirror\n        if [ \"$DISTRO\" = \"ubuntu\" ]; then\n          PRIMARY=\"archive.ubuntu.com\"\n        else\n          PRIMARY=\"ftp.debian.org\"\n        fi\n        if timeout 2 bash -c \"echo >/dev/tcp/$PRIMARY/80\" 2>/dev/null; then\n          echo \"  Attempting mirror: $PRIMARY\"\n          try_mirrors \"$PRIMARY\" && exit 0\n        fi\n\n        # Phase 3: Fall back to regional mirrors\n        REGIONAL_OK=$(scan_reachable \"$REGIONAL\")\n        REGIONAL_PICK=$(printf \"%s\\n\" $REGIONAL_OK | shuf | head -3 | xargs)\n\n        for mirror in $REGIONAL_PICK; do\n          echo \"  Attempting mirror: $mirror\"\n          try_mirrors \"$mirror\" && exit 0\n        done\n\n        exit 2\n      ' && mirror_exit=0 || mirror_exit=$?\n      if [[ $mirror_exit -eq 2 ]]; then\n        msg_warn \"Multiple mirrors failed (possible CDN synchronization issue).\"\n        if [[ \"$var_os\" == \"ubuntu\" ]]; then\n          msg_warn \"Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors\"\n        else\n          msg_warn \"Find Debian mirrors at: https://www.debian.org/mirror/list\"\n        fi\n        local custom_mirror=\"\"\n        while true; do\n          read -rp \"  Enter a mirror hostname (or 'skip' to abort): \" custom_mirror </dev/tty\n          [[ -z \"$custom_mirror\" ]] && continue\n          [[ \"$custom_mirror\" == \"skip\" ]] && break\n          [[ ! \"$custom_mirror\" =~ ^[a-zA-Z0-9._-]+$ ]] && {\n            msg_warn \"Invalid hostname format.\"\n            continue\n          }\n          pct exec \"$CTID\" -- bash -c \"\n            for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do\n              [ -f \\\"\\$src\\\" ] && sed -i \\\"s|URIs: http[s]*://[^/]*/|URIs: http://${custom_mirror}/|g; s|deb http[s]*://[^/]*/|deb http://${custom_mirror}/|g\\\" \\\"\\$src\\\"\n            done\n            rm -rf /var/lib/apt/lists/*\n            apt-get update >/dev/null 2>&1 && apt-get install -y sudo curl mc gnupg2 jq >/dev/null 2>&1\n          \" && break\n          msg_warn \"Mirror '${custom_mirror}' also failed. Try another or type 'skip'.\"\n        done\n        if [[ \"$custom_mirror\" == \"skip\" ]]; then\n          msg_error \"apt-get base packages installation failed\"\n          install_exit_code=1\n        fi\n      elif [[ $mirror_exit -ne 0 ]]; then\n        msg_error \"apt-get base packages installation failed\"\n        install_exit_code=1\n      fi\n    }\n  fi\n\n  # Only continue with installation if customization succeeded\n  if [[ $install_exit_code -eq 0 ]]; then\n    msg_ok \"Customized LXC Container\"\n\n    # Optional DNS override for retry scenarios (inside LXC, never on host)\n    if [[ \"${DNS_RETRY_OVERRIDE:-false}\" == \"true\" ]]; then\n      msg_info \"Applying DNS retry override in LXC (8.8.8.8, 1.1.1.1)\"\n      pct exec \"$CTID\" -- bash -c \"printf 'nameserver 8.8.8.8\\nnameserver 1.1.1.1\\n' >/etc/resolv.conf\" >/dev/null 2>&1 || true\n      msg_ok \"DNS override applied in LXC\"\n    fi\n\n    # Install SSH keys\n    install_ssh_keys_into_ct\n\n    # Start timer for duration tracking\n    start_install_timer\n\n    # Run application installer\n    # Error handling already disabled above (before customization phase)\n\n    # Signal handlers use this flag to stop the container on abort (SIGHUP/SIGINT/SIGTERM)\n    # Without this, SSH disconnects leave the container running as an orphan process\n    # that sends \"configuring\" status AFTER the host already reported \"failed\"\n    export CONTAINER_INSTALLING=true\n\n    lxc-attach -n \"$CTID\" -- bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/install/${var_install}.sh)\"\n    local lxc_exit=$?\n\n    unset CONTAINER_INSTALLING\n\n    # Keep error handling DISABLED during failure detection and recovery\n    # Re-enabling it here would cause any pct exec/pull failure to trigger\n    # error_handler() on the host, bypassing the recovery menu entirely\n\n    # Check for error flag file in container (more reliable than lxc-attach exit code)\n    if [[ -n \"${SESSION_ID:-}\" ]]; then\n      local error_flag=\"/root/.install-${SESSION_ID}.failed\"\n      if pct exec \"$CTID\" -- test -f \"$error_flag\" 2>/dev/null; then\n        install_exit_code=$(pct exec \"$CTID\" -- cat \"$error_flag\" 2>/dev/null || echo \"1\")\n        pct exec \"$CTID\" -- rm -f \"$error_flag\" 2>/dev/null || true\n      fi\n    fi\n\n    # Fallback to lxc-attach exit code if no flag file\n    if [[ $install_exit_code -eq 0 && ${lxc_exit:-0} -ne 0 ]]; then\n      install_exit_code=${lxc_exit:-0}\n    fi\n  fi # end: if [[ $install_exit_code -eq 0 ]] (customization succeeded)\n\n  # Installation or customization failed?\n  if [[ $install_exit_code -ne 0 ]]; then\n    # Prevent job-control signals from suspending the script during recovery.\n    # In non-interactive shells (bash -c), background processes (spinner) can\n    # trigger terminal-related signals that stop the entire process group.\n    # TSTP = Ctrl+Z, TTIN = bg read from tty, TTOU = bg write to tty (tostop)\n    trap '' TSTP TTIN TTOU\n\n    msg_error \"Installation failed in container ${CTID} (exit code: ${install_exit_code})\"\n\n    # Copy install log from container BEFORE API call so get_error_text() can read it\n    local build_log_copied=false\n    local install_log_copied=false\n    local combined_log=\"/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log\"\n\n    if [[ -n \"$CTID\" && -n \"${SESSION_ID:-}\" ]]; then\n      # Create combined log with header\n      {\n        echo \"================================================================================\"\n        echo \"COMBINED INSTALLATION LOG - ${APP:-LXC}\"\n        echo \"Container ID: ${CTID}\"\n        echo \"Session ID: ${SESSION_ID}\"\n        echo \"Timestamp: $(date '+%Y-%m-%d %H:%M:%S')\"\n        echo \"================================================================================\"\n        echo \"\"\n      } >\"$combined_log\"\n\n      # Append BUILD_LOG (host-side creation log) if it exists\n      if [[ -f \"${BUILD_LOG}\" ]]; then\n        {\n          echo \"================================================================================\"\n          echo \"PHASE 1: CONTAINER CREATION (Host)\"\n          echo \"================================================================================\"\n          cat \"${BUILD_LOG}\"\n          echo \"\"\n        } >>\"$combined_log\"\n        build_log_copied=true\n      fi\n\n      # Copy and append INSTALL_LOG from container\n      local temp_install_log=\"/tmp/.install-temp-${SESSION_ID}.log\"\n      if pct pull \"$CTID\" \"/root/.install-${SESSION_ID}.log\" \"$temp_install_log\" 2>/dev/null; then\n        {\n          echo \"================================================================================\"\n          echo \"PHASE 2: APPLICATION INSTALLATION (Container)\"\n          echo \"================================================================================\"\n          cat \"$temp_install_log\"\n          echo \"\"\n        } >>\"$combined_log\"\n        rm -f \"$temp_install_log\"\n        install_log_copied=true\n        # Point INSTALL_LOG to combined log so get_full_log() finds it\n        INSTALL_LOG=\"$combined_log\"\n      fi\n    fi\n\n    # Defense-in-depth: Ensure error handling stays disabled during recovery.\n    # Some functions (e.g. silent/$STD) unconditionally re-enable set -Eeuo pipefail\n    # and trap 'error_handler' ERR. If any code path above called such a function,\n    # the grep/sed pipelines below would trigger error_handler on non-match (exit 1).\n    set +Eeuo pipefail\n    trap - ERR\n\n    # --- Exit code 1 subclassification: analyze logs BEFORE telemetry call ---\n    # Exit code 1 is generic (\"General error\"). Analyze logs to determine the\n    # real error category so telemetry gets a useful classification instead of \"shell\".\n    local is_oom=false\n    local is_network_issue=false\n    local is_apt_issue=false\n    local is_cmd_not_found=false\n    local is_disk_full=false\n\n    if [[ $install_exit_code -eq 1 && -f \"$combined_log\" ]]; then\n      if grep -qiE 'E: Unable to|E: Package|E: Failed to fetch|dpkg.*error|broken packages|unmet dependencies|dpkg --configure -a' \"$combined_log\"; then\n        is_apt_issue=true\n      fi\n      if grep -qiE 'Cannot allocate memory|Out of memory|oom-killer|Killed process|JavaScript heap' \"$combined_log\"; then\n        is_oom=true\n      fi\n      if grep -qiE 'Could not resolve|DNS|Connection refused|Network is unreachable|No route to host|Temporary failure resolving|Failed to fetch' \"$combined_log\"; then\n        is_network_issue=true\n      fi\n      if grep -qiE ': command not found|No such file or directory.*/s?bin/' \"$combined_log\"; then\n        is_cmd_not_found=true\n      fi\n      if grep -qiE 'ENOSPC|no space left on device|Disk quota exceeded|errno -28' \"$combined_log\"; then\n        is_disk_full=true\n      fi\n    fi\n\n    # Set override for categorize_error() so telemetry gets the real category\n    if [[ \"$is_apt_issue\" == true ]]; then\n      export ERROR_CATEGORY_OVERRIDE=\"dependency\"\n    elif [[ \"$is_oom\" == true ]]; then\n      export ERROR_CATEGORY_OVERRIDE=\"resource\"\n    elif [[ \"$is_network_issue\" == true ]]; then\n      export ERROR_CATEGORY_OVERRIDE=\"network\"\n    elif [[ \"$is_disk_full\" == true ]]; then\n      export ERROR_CATEGORY_OVERRIDE=\"storage\"\n    elif [[ \"$is_cmd_not_found\" == true ]]; then\n      export ERROR_CATEGORY_OVERRIDE=\"dependency\"\n    fi\n\n    # Report failure to telemetry API (now with log available on host)\n    # NOTE: Do NOT use msg_info/spinner here — the background spinner process\n    # causes SIGTSTP in non-interactive shells (bash -c \"$(curl ...)\"), which\n    # stops the entire process group and prevents the recovery dialog from appearing.\n    $STD echo -e \"${TAB}⏳ Reporting failure to telemetry...\"\n    post_update_to_api \"failed\" \"$install_exit_code\"\n    $STD echo -e \"${TAB}${CM:-✔} Failure reported\"\n\n    # Show combined log location\n    if [[ -n \"$CTID\" && -n \"${SESSION_ID:-}\" ]]; then\n      msg_custom \"📋\" \"${YW}\" \"Installation log: ${combined_log}\"\n    fi\n\n    # Dev mode: Keep container or open breakpoint shell\n    if [[ \"${DEV_MODE_KEEP:-false}\" == \"true\" ]]; then\n      msg_dev \"Keep mode active - container ${CTID} preserved\"\n      return 0\n    elif [[ \"${DEV_MODE_BREAKPOINT:-false}\" == \"true\" ]]; then\n      msg_dev \"Breakpoint mode - opening shell in container ${CTID}\"\n      echo -e \"${YW}Type 'exit' to return to host${CL}\"\n      pct enter \"$CTID\"\n      echo \"\"\n      echo -en \"${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}\"\n      if read -r response </dev/tty && [[ \"$response\" =~ ^[Yy]$ ]]; then\n        pct stop \"$CTID\" &>/dev/null || true\n        pct destroy \"$CTID\" &>/dev/null || true\n        msg_ok \"Container ${CTID} removed\"\n      else\n        msg_dev \"Container ${CTID} kept for debugging\"\n      fi\n      exit $install_exit_code\n    fi\n\n    # Prompt user for cleanup with 60s timeout\n    echo \"\"\n\n    # Extend error detection for non-exit-1 codes (exit 1 was already analyzed above)\n    # The is_* flags were set above for exit code 1 log analysis; here we add\n    # exit-code-specific detections for other codes.\n    local error_explanation=\"\"\n    if declare -f explain_exit_code >/dev/null 2>&1; then\n      error_explanation=\"$(explain_exit_code \"$install_exit_code\")\"\n    fi\n\n    # OOM detection: exit codes 134 (SIGABRT/heap), 137 (SIGKILL/OOM), 243 (Node.js heap)\n    if [[ $install_exit_code -eq 134 || $install_exit_code -eq 137 || $install_exit_code -eq 243 ]]; then\n      is_oom=true\n    fi\n\n    # APT/DPKG detection: exit codes 100-102 (APT), 255 (DPKG with log evidence)\n    case \"$install_exit_code\" in\n    100 | 101 | 102) is_apt_issue=true ;;\n    255)\n      if [[ -f \"$combined_log\" ]] && grep -qiE 'dpkg|apt-get|apt\\.conf|broken packages|unmet dependencies|E: Sub-process|E: Failed' \"$combined_log\"; then\n        is_apt_issue=true\n      fi\n      ;;\n    esac\n\n    # Disk full / ENOSPC detection: errno -28 (ENOSPC), exit 228 (custom handler), exit 23 (curl write error)\n    if [[ $install_exit_code -eq 228 || $install_exit_code -eq 23 ]]; then\n      is_disk_full=true\n    fi\n    if [[ -f \"$combined_log\" ]] && grep -qiE 'ENOSPC|no space left on device|No space left on device|Disk quota exceeded|errno -28' \"$combined_log\"; then\n      is_disk_full=true\n    fi\n\n    # Command not found detection\n    if [[ $install_exit_code -eq 127 ]]; then\n      is_cmd_not_found=true\n    fi\n\n    # Network-related detection (curl/apt/git fetch failures and transient network issues)\n    case \"$install_exit_code\" in\n    6 | 7 | 22 | 28 | 35 | 52 | 56 | 57 | 75 | 78) is_network_issue=true ;;\n    100)\n      # APT can fail due to network (Failed to fetch)\n      if [[ -f \"$combined_log\" ]] && grep -qiE 'Failed to fetch|Could not resolve|Connection failed|Network is unreachable|Temporary failure resolving' \"$combined_log\"; then\n        is_network_issue=true\n      fi\n      ;;\n    128)\n      if [[ -f \"$combined_log\" ]] && grep -qiE 'RPC failed|early EOF|fetch-pack|HTTP/2 stream|Could not resolve host|Temporary failure resolving|Failed to fetch|Connection reset|Network is unreachable' \"$combined_log\"; then\n        is_network_issue=true\n      fi\n      ;;\n    esac\n\n    # Show error explanation if available\n    if [[ -n \"$error_explanation\" ]]; then\n      echo -e \"${TAB}${RD}Error: ${error_explanation}${CL}\"\n      echo \"\"\n    fi\n\n    # Show specific hints for known error types\n    if [[ $install_exit_code -eq 10 ]]; then\n      echo -e \"${TAB}${INFO} This error usually means the container needs ${GN}privileged${CL} mode or Docker/nesting support.\"\n      echo -e \"${TAB}${INFO} Recreate with: Advanced Install → Container Type: ${GN}Privileged${CL}\"\n      echo \"\"\n    fi\n\n    if [[ $install_exit_code -eq 125 || $install_exit_code -eq 126 ]]; then\n      echo -e \"${TAB}${INFO} The command exists but cannot be executed. This may be a ${GN}permission${CL} issue.\"\n      echo -e \"${TAB}${INFO} If using Docker, ensure the container is ${GN}privileged${CL} or has correct permissions.\"\n      echo \"\"\n    fi\n\n    if [[ \"$is_disk_full\" == true ]]; then\n      echo -e \"${TAB}${INFO} The container ran out of disk space during installation (${GN}ENOSPC${CL}).\"\n      echo -e \"${TAB}${INFO} Current disk size: ${GN}${DISK_SIZE} GB${CL}. A rebuild with doubled disk may resolve this.\"\n      echo \"\"\n    fi\n\n    if [[ \"$is_cmd_not_found\" == true ]]; then\n      local missing_cmd=\"\"\n      if [[ -f \"$combined_log\" ]]; then\n        missing_cmd=$(grep -oiE '[a-zA-Z0-9_.-]+: command not found' \"$combined_log\" 2>/dev/null | tail -1 | sed 's/: command not found//') || true\n      fi\n      if [[ -n \"$missing_cmd\" ]]; then\n        echo -e \"${TAB}${INFO} Missing command: ${GN}${missing_cmd}${CL}\"\n      fi\n      echo \"\"\n    fi\n\n    # Build recovery menu based on error type\n    echo -e \"${YW}What would you like to do?${CL}\"\n    echo \"\"\n    echo -e \"  ${GN}1)${CL} Remove container and exit\"\n    echo -e \"  ${GN}2)${CL} Keep container for debugging\"\n    echo -e \"  ${GN}3)${CL} Retry with verbose mode (full rebuild)\"\n\n    local next_option=4\n    local APT_OPTION=\"\" OOM_OPTION=\"\" DNS_OPTION=\"\" DISK_OPTION=\"\"\n\n    if [[ \"$is_apt_issue\" == true ]]; then\n      if [[ \"$var_os\" == \"alpine\" ]]; then\n        echo -e \"  ${GN}${next_option})${CL} Repair APK state and re-run install (in-place)\"\n      else\n        echo -e \"  ${GN}${next_option})${CL} Repair APT/DPKG state and re-run install (in-place)\"\n      fi\n      APT_OPTION=$next_option\n      next_option=$((next_option + 1))\n    fi\n\n    if [[ \"$is_oom\" == true ]]; then\n      local recovery_attempt=\"${RECOVERY_ATTEMPT:-0}\"\n      if [[ $recovery_attempt -lt 2 ]]; then\n        local new_ram=$((RAM_SIZE * 2))\n        local new_cpu=$((CORE_COUNT * 2))\n        echo -e \"  ${GN}${next_option})${CL} Retry with more resources (RAM: ${RAM_SIZE}→${new_ram} MiB, CPU: ${CORE_COUNT}→${new_cpu} cores)\"\n        OOM_OPTION=$next_option\n        next_option=$((next_option + 1))\n      else\n        echo -e \"  ${DGN}-)${CL} ${DGN}OOM retry exhausted (already retried ${recovery_attempt}x)${CL}\"\n      fi\n    fi\n\n    if [[ \"$is_disk_full\" == true ]]; then\n      local disk_recovery_attempt=\"${DISK_RECOVERY_ATTEMPT:-0}\"\n      if [[ $disk_recovery_attempt -lt 2 ]]; then\n        local new_disk=$((DISK_SIZE * 2))\n        echo -e \"  ${GN}${next_option})${CL} Retry with more disk space (Disk: ${DISK_SIZE}→${new_disk} GB)\"\n        DISK_OPTION=$next_option\n        next_option=$((next_option + 1))\n      else\n        echo -e \"  ${DGN}-)${CL} ${DGN}Disk resize retry exhausted (already retried ${disk_recovery_attempt}x)${CL}\"\n      fi\n    fi\n\n    if [[ \"$is_network_issue\" == true ]]; then\n      echo -e \"  ${GN}${next_option})${CL} Retry with DNS override in LXC (8.8.8.8 / 1.1.1.1)\"\n      DNS_OPTION=$next_option\n      next_option=$((next_option + 1))\n    fi\n\n    local max_option=$((next_option - 1))\n\n    echo \"\"\n    echo -en \"${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}\"\n\n    local response=\"\"\n    if read -t 60 -r response; then\n      case \"${response:-1}\" in\n      1)\n        # Remove container\n        echo -e \"\\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}\"\n        pct stop \"$CTID\" &>/dev/null || true\n        pct destroy \"$CTID\" &>/dev/null || true\n        echo -e \"${BFR}${CM}${GN}Container ${CTID} removed${CL}\"\n        ;;\n      2)\n        echo -e \"\\n${TAB}${YW}Container ${CTID} kept for debugging${CL}\"\n        # Dev mode: Setup MOTD/SSH for debugging access to broken container\n        if [[ \"${DEV_MODE_MOTD:-false}\" == \"true\" ]]; then\n          echo -e \"${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}\"\n          if pct exec \"$CTID\" -- bash -c \"\n              source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/install.func)\n              declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true\n            \" >/dev/null 2>&1; then\n            local ct_ip=$(pct exec \"$CTID\" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)\n            echo -e \"${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}\"\n          fi\n        fi\n        exit $install_exit_code\n        ;;\n      3)\n        # Retry with verbose mode (full rebuild)\n        echo -e \"\\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild...${CL}\"\n        pct stop \"$CTID\" &>/dev/null || true\n        pct destroy \"$CTID\" &>/dev/null || true\n        echo -e \"${BFR}${CM}${GN}Container ${CTID} removed${CL}\"\n        echo \"\"\n        # Get new container ID\n        local old_ctid=\"$CTID\"\n        export CTID=$(get_valid_container_id \"$CTID\")\n        export VERBOSE=\"yes\"\n        export var_verbose=\"yes\"\n\n        # Show rebuild summary\n        echo -e \"${YW}Rebuilding with preserved settings:${CL}\"\n        echo -e \"  Container ID: ${old_ctid} → ${CTID}\"\n        echo -e \"  RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores | Disk: ${DISK_SIZE} GB\"\n        echo -e \"  Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}\"\n        echo -e \"  Verbose: ${GN}enabled${CL}\"\n        echo \"\"\n        msg_info \"Restarting installation...\"\n        # Re-run build_container\n        build_container\n        return $?\n        ;;\n      *)\n        # Handle dynamic smart recovery options via named option variables\n        local handled=false\n\n        if [[ -n \"${APT_OPTION}\" && \"${response}\" == \"${APT_OPTION}\" ]]; then\n          # Package manager in-place repair: fix broken state and re-run install script\n          handled=true\n          if [[ \"$var_os\" == \"alpine\" ]]; then\n            echo -e \"\\n${TAB}${HOLD}${YW}Repairing APK state in container ${CTID}...${CL}\"\n            pct exec \"$CTID\" -- ash -c \"\n              apk fix 2>/dev/null || true\n              apk cache clean 2>/dev/null || true\n              apk update 2>/dev/null || true\n            \" >/dev/null 2>&1 || true\n            echo -e \"${BFR}${CM}${GN}APK state repaired in container ${CTID}${CL}\"\n          else\n            echo -e \"\\n${TAB}${HOLD}${YW}Repairing APT/DPKG state in container ${CTID}...${CL}\"\n            pct exec \"$CTID\" -- bash -c \"\n              DEBIAN_FRONTEND=noninteractive dpkg --configure -a 2>/dev/null || true\n              apt-get -f install -y 2>/dev/null || true\n              apt-get clean 2>/dev/null\n              apt-get update 2>/dev/null || true\n            \" >/dev/null 2>&1 || true\n            echo -e \"${BFR}${CM}${GN}APT/DPKG state repaired in container ${CTID}${CL}\"\n          fi\n          echo \"\"\n          export VERBOSE=\"yes\"\n          export var_verbose=\"yes\"\n\n          echo -e \"${YW}Re-running installation in existing container ${CTID}:${CL}\"\n          echo -e \"  RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores | Disk: ${DISK_SIZE} GB\"\n          echo -e \"  Verbose: ${GN}enabled${CL}\"\n          echo \"\"\n          msg_info \"Re-running installation script...\"\n\n          # Re-run install script in existing container (don't destroy/recreate)\n          set +Eeuo pipefail\n          trap - ERR\n          lxc-attach -n \"$CTID\" -- bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/install/${var_install}.sh)\"\n          local apt_retry_exit=$?\n          set -Eeuo pipefail\n          trap 'error_handler' ERR\n\n          # Check for error flag from retry\n          local apt_retry_code=0\n          if [[ -n \"${SESSION_ID:-}\" ]]; then\n            local retry_error_flag=\"/root/.install-${SESSION_ID}.failed\"\n            if pct exec \"$CTID\" -- test -f \"$retry_error_flag\" 2>/dev/null; then\n              apt_retry_code=$(pct exec \"$CTID\" -- cat \"$retry_error_flag\" 2>/dev/null || echo \"1\")\n              pct exec \"$CTID\" -- rm -f \"$retry_error_flag\" 2>/dev/null || true\n            fi\n          fi\n\n          if [[ $apt_retry_code -eq 0 && $apt_retry_exit -ne 0 ]]; then\n            apt_retry_code=$apt_retry_exit\n          fi\n\n          if [[ $apt_retry_code -eq 0 ]]; then\n            msg_ok \"Installation completed successfully after APT repair!\"\n            INSTALL_COMPLETE=true\n            post_update_to_api \"done\" \"0\" \"force\"\n            return 0\n          else\n            msg_error \"Installation still failed after APT repair (exit code: ${apt_retry_code})\"\n            install_exit_code=$apt_retry_code\n          fi\n        fi\n\n        if [[ -n \"${OOM_OPTION}\" && \"${response}\" == \"${OOM_OPTION}\" ]]; then\n          # Retry with doubled resources\n          handled=true\n          echo -e \"\\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more resources...${CL}\"\n          pct stop \"$CTID\" &>/dev/null || true\n          pct destroy \"$CTID\" &>/dev/null || true\n          echo -e \"${BFR}${CM}${GN}Container ${CTID} removed${CL}\"\n          echo \"\"\n          local old_ctid=\"$CTID\"\n          local old_ram=\"$RAM_SIZE\"\n          local old_cpu=\"$CORE_COUNT\"\n          export CTID=$(get_valid_container_id \"$CTID\")\n          export RAM_SIZE=$((RAM_SIZE * 2))\n          export CORE_COUNT=$((CORE_COUNT * 2))\n          export var_ram=\"$RAM_SIZE\"\n          export var_cpu=\"$CORE_COUNT\"\n          export VERBOSE=\"yes\"\n          export var_verbose=\"yes\"\n          export RECOVERY_ATTEMPT=$((${RECOVERY_ATTEMPT:-0} + 1))\n\n          echo -e \"${YW}Rebuilding with increased resources (attempt ${RECOVERY_ATTEMPT}/2):${CL}\"\n          echo -e \"  Container ID: ${old_ctid} → ${CTID}\"\n          echo -e \"  RAM: ${old_ram} → ${GN}${RAM_SIZE}${CL} MiB (x2)\"\n          echo -e \"  CPU: ${old_cpu} → ${GN}${CORE_COUNT}${CL} cores (x2)\"\n          echo -e \"  Disk: ${DISK_SIZE} GB | Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}\"\n          echo -e \"  Verbose: ${GN}enabled${CL}\"\n          echo \"\"\n          msg_info \"Restarting installation...\"\n          build_container\n          return $?\n        fi\n\n        if [[ -n \"${DISK_OPTION}\" && \"${response}\" == \"${DISK_OPTION}\" ]]; then\n          # Retry with doubled disk size\n          handled=true\n          echo -e \"\\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more disk space...${CL}\"\n          pct stop \"$CTID\" &>/dev/null || true\n          pct destroy \"$CTID\" &>/dev/null || true\n          echo -e \"${BFR}${CM}${GN}Container ${CTID} removed${CL}\"\n          echo \"\"\n          local old_ctid=\"$CTID\"\n          local old_disk=\"$DISK_SIZE\"\n          export CTID=$(get_valid_container_id \"$CTID\")\n          export DISK_SIZE=$((DISK_SIZE * 2))\n          export var_disk=\"$DISK_SIZE\"\n          export VERBOSE=\"yes\"\n          export var_verbose=\"yes\"\n          export DISK_RECOVERY_ATTEMPT=$((${DISK_RECOVERY_ATTEMPT:-0} + 1))\n\n          echo -e \"${YW}Rebuilding with increased disk space (attempt ${DISK_RECOVERY_ATTEMPT}/2):${CL}\"\n          echo -e \"  Container ID: ${old_ctid} → ${CTID}\"\n          echo -e \"  Disk: ${old_disk} → ${GN}${DISK_SIZE}${CL} GB (x2)\"\n          echo -e \"  RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores\"\n          echo -e \"  Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}\"\n          echo -e \"  Verbose: ${GN}enabled${CL}\"\n          echo \"\"\n          msg_info \"Restarting installation...\"\n          build_container\n          return $?\n        fi\n\n        if [[ -n \"${DNS_OPTION}\" && \"${response}\" == \"${DNS_OPTION}\" ]]; then\n          # Retry with DNS override in LXC\n          handled=true\n          echo -e \"\\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with DNS override...${CL}\"\n          pct stop \"$CTID\" &>/dev/null || true\n          pct destroy \"$CTID\" &>/dev/null || true\n          echo -e \"${BFR}${CM}${GN}Container ${CTID} removed${CL}\"\n          echo \"\"\n          local old_ctid=\"$CTID\"\n          export CTID=$(get_valid_container_id \"$CTID\")\n          export DNS_RETRY_OVERRIDE=\"true\"\n          export VERBOSE=\"yes\"\n          export var_verbose=\"yes\"\n\n          echo -e \"${YW}Rebuilding with DNS override in LXC:${CL}\"\n          echo -e \"  Container ID: ${old_ctid} → ${CTID}\"\n          echo -e \"  DNS: ${GN}8.8.8.8, 1.1.1.1${CL} (inside LXC only)\"\n          echo -e \"  Verbose: ${GN}enabled${CL}\"\n          echo \"\"\n          msg_info \"Restarting installation...\"\n          build_container\n          return $?\n        fi\n\n        if [[ \"$handled\" == false ]]; then\n          echo -e \"\\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}\"\n          exit $install_exit_code\n        fi\n        ;;\n      esac\n    else\n      # Timeout - auto-remove\n      echo \"\"\n      msg_info \"No response - removing container ${CTID}\"\n      pct stop \"$CTID\" &>/dev/null || true\n      pct destroy \"$CTID\" &>/dev/null || true\n      msg_ok \"Container ${CTID} removed\"\n    fi\n\n    # Force one final status update attempt after cleanup\n    # This ensures status is updated even if the first attempt failed (e.g., HTTP 400)\n    $STD echo -e \"${TAB}⏳ Finalizing telemetry report...\"\n    post_update_to_api \"failed\" \"$install_exit_code\" \"force\"\n    $STD echo -e \"${TAB}${CM:-✔} Telemetry finalized\"\n\n    # Restore default job-control signal handling before exit\n    trap - TSTP TTIN TTOU\n    exit $install_exit_code\n  fi\n\n  # Re-enable error handling after successful install or recovery menu completion\n  set -Eeuo pipefail\n  trap 'error_handler' ERR\n}\n\ndestroy_lxc() {\n  if [[ -z \"$CT_ID\" ]]; then\n    msg_error \"No CT_ID found. Nothing to remove.\"\n    return 65\n  fi\n\n  # Abort on Ctrl-C / Ctrl-D / ESC\n  trap 'echo; msg_error \"Aborted by user (SIGINT/SIGQUIT)\"; return 130' INT QUIT\n\n  local prompt\n  if ! read -rp \"Remove this Container? <y/N> \" prompt </dev/tty; then\n    # read returns non-zero on Ctrl-D/ESC\n    msg_error \"Aborted input (Ctrl-D/ESC)\"\n    return 130\n  fi\n\n  case \"${prompt,,}\" in\n  y | yes)\n    if pct stop \"$CT_ID\" &>/dev/null && pct destroy \"$CT_ID\" &>/dev/null; then\n      msg_ok \"Removed Container $CT_ID\"\n    else\n      msg_error \"Failed to remove Container $CT_ID\"\n      return 1\n    fi\n    ;;\n  \"\" | n | no)\n    msg_custom \"ℹ️\" \"${BL}\" \"Container was not removed.\"\n    ;;\n  *)\n    msg_warn \"Invalid response. Container was not removed.\"\n    ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# Storage discovery / selection helpers\n# ------------------------------------------------------------------------------\nresolve_storage_preselect() {\n  local class=\"$1\" preselect=\"$2\" required_content=\"\"\n  case \"$class\" in\n  template) required_content=\"vztmpl\" ;;\n  container) required_content=\"rootdir\" ;;\n  *) return 65 ;;\n  esac\n  [[ -z \"$preselect\" ]] && return 1\n  if ! pvesm status -content \"$required_content\" | awk 'NR>1{print $1}' | grep -qx -- \"$preselect\"; then\n    msg_warn \"Preselected storage '${preselect}' does not support content '${required_content}' (or not found)\"\n    return 238\n  fi\n\n  local line total used free\n  line=\"$(pvesm status | awk -v s=\"$preselect\" 'NR>1 && $1==s {print $0}')\"\n  if [[ -z \"$line\" ]]; then\n    STORAGE_INFO=\"n/a\"\n  else\n    total=\"$(awk '{print $4}' <<<\"$line\")\"\n    used=\"$(awk '{print $5}' <<<\"$line\")\"\n    free=\"$(awk '{print $6}' <<<\"$line\")\"\n    local total_h used_h free_h\n    if command -v numfmt >/dev/null 2>&1; then\n      total_h=\"$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f \"$total\" 2>/dev/null || echo \"$total\")\"\n      used_h=\"$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f \"$used\" 2>/dev/null || echo \"$used\")\"\n      free_h=\"$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f \"$free\" 2>/dev/null || echo \"$free\")\"\n      STORAGE_INFO=\"Free: ${free_h}  Used: ${used_h}\"\n    else\n      STORAGE_INFO=\"Free: ${free}  Used: ${used}\"\n    fi\n  fi\n  STORAGE_RESULT=\"$preselect\"\n  return 0\n}\n\nfix_gpu_gids() {\n  if [[ -z \"${GPU_TYPE:-}\" ]]; then\n    return 0\n  fi\n\n  msg_info \"Detecting and setting correct GPU group IDs\"\n\n  # Get actual GIDs from container\n  local video_gid=$(pct exec \"$CTID\" -- sh -c \"getent group video 2>/dev/null | cut -d: -f3\")\n  local render_gid=$(pct exec \"$CTID\" -- sh -c \"getent group render 2>/dev/null | cut -d: -f3\")\n\n  # Create groups if they don't exist\n  if [[ -z \"$video_gid\" ]]; then\n    pct exec \"$CTID\" -- sh -c \"groupadd -r video 2>/dev/null || true\" >/dev/null 2>&1\n    video_gid=$(pct exec \"$CTID\" -- sh -c \"getent group video 2>/dev/null | cut -d: -f3\")\n    [[ -z \"$video_gid\" ]] && video_gid=\"44\"\n  fi\n\n  if [[ -z \"$render_gid\" ]]; then\n    pct exec \"$CTID\" -- sh -c \"groupadd -r render 2>/dev/null || true\" >/dev/null 2>&1\n    render_gid=$(pct exec \"$CTID\" -- sh -c \"getent group render 2>/dev/null | cut -d: -f3\")\n    [[ -z \"$render_gid\" ]] && render_gid=\"104\"\n  fi\n\n  # Stop container to update config\n  pct stop \"$CTID\" >/dev/null 2>&1\n  sleep 1\n\n  # Validate GIDs are numeric before sed\n  [[ \"$render_gid\" =~ ^[0-9]+$ ]] || render_gid=\"104\"\n  [[ \"$video_gid\" =~ ^[0-9]+$ ]] || video_gid=\"44\"\n\n  # Update dev entries with correct GIDs\n  sed -i.bak -E \"s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\\1,gid=${render_gid}|g\" \"$LXC_CONFIG\"\n  sed -i -E \"s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\\1,gid=${video_gid}|g\" \"$LXC_CONFIG\"\n\n  # Restart container\n  pct start \"$CTID\" >/dev/null 2>&1\n  sleep 2\n\n  msg_ok \"GPU passthrough configured (video:${video_gid}, render:${render_gid})\"\n\n  # For privileged containers: also fix permissions inside container\n  if [[ \"$CT_TYPE\" == \"0\" ]]; then\n    pct exec \"$CTID\" -- sh -c \"\n      if [ -d /dev/dri ]; then\n        for dev in /dev/dri/*; do\n          if [ -e \\\"\\$dev\\\" ]; then\n            case \\\"\\$dev\\\" in\n              *renderD*) chgrp ${render_gid} \\\"\\$dev\\\" 2>/dev/null || true ;;\n              *)         chgrp ${video_gid} \\\"\\$dev\\\" 2>/dev/null || true ;;\n            esac\n            chmod 660 \\\"\\$dev\\\" 2>/dev/null || true\n          fi\n        done\n      fi\n    \" >/dev/null 2>&1\n  fi\n}\n\ncheck_storage_support() {\n  local CONTENT=\"$1\" VALID=0\n  while IFS= read -r line; do\n    local STORAGE_NAME\n    STORAGE_NAME=$(awk '{print $1}' <<<\"$line\")\n    [[ -n \"$STORAGE_NAME\" ]] && VALID=1\n  done < <(pvesm status -content \"$CONTENT\" 2>/dev/null | awk 'NR>1')\n  [[ $VALID -eq 1 ]]\n}\n\nselect_storage() {\n  local CLASS=$1 CONTENT CONTENT_LABEL\n  case $CLASS in\n  container)\n    CONTENT='rootdir'\n    CONTENT_LABEL='Container'\n    ;;\n  template)\n    CONTENT='vztmpl'\n    CONTENT_LABEL='Container template'\n    ;;\n  iso)\n    CONTENT='iso'\n    CONTENT_LABEL='ISO image'\n    ;;\n  images)\n    CONTENT='images'\n    CONTENT_LABEL='VM Disk image'\n    ;;\n  backup)\n    CONTENT='backup'\n    CONTENT_LABEL='Backup'\n    ;;\n  snippets)\n    CONTENT='snippets'\n    CONTENT_LABEL='Snippets'\n    ;;\n  *)\n    msg_error \"Invalid storage class '$CLASS'\"\n    return 65\n    ;;\n  esac\n\n  declare -A STORAGE_MAP\n  local -a MENU=()\n  local COL_WIDTH=0\n\n  while read -r TAG TYPE _ TOTAL USED FREE _; do\n    [[ -n \"$TAG\" && -n \"$TYPE\" ]] || continue\n    local DISPLAY=\"${TAG} (${TYPE})\"\n    local USED_FMT=$(numfmt --to=iec --from-unit=1024 --format %.1f <<<\"$USED\")\n    local FREE_FMT=$(numfmt --to=iec --from-unit=1024 --format %.1f <<<\"$FREE\")\n    local INFO=\"Free: ${FREE_FMT}B  Used: ${USED_FMT}B\"\n    STORAGE_MAP[\"$DISPLAY\"]=\"$TAG\"\n    MENU+=(\"$DISPLAY\" \"$INFO\" \"OFF\")\n    ((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}\n  done < <(pvesm status -content \"$CONTENT\" | awk 'NR>1')\n\n  if [[ ${#MENU[@]} -eq 0 ]]; then\n    msg_error \"No storage found for content type '$CONTENT'.\"\n    return 2\n  fi\n\n  if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then\n    STORAGE_RESULT=\"${STORAGE_MAP[${MENU[0]}]}\"\n    STORAGE_INFO=\"${MENU[1]}\"\n    return 0\n  fi\n\n  local WIDTH=$((COL_WIDTH + 42))\n  while true; do\n    local DISPLAY_SELECTED\n    DISPLAY_SELECTED=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"Storage Pools\" \\\n      --radiolist \"Which storage pool for ${CONTENT_LABEL,,}?\\n(Spacebar to select)\" \\\n      16 \"$WIDTH\" 6 \"${MENU[@]}\" 3>&1 1>&2 2>&3) || { exit_script; }\n\n    DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<\"$DISPLAY_SELECTED\")\n    if [[ -z \"$DISPLAY_SELECTED\" || -z \"${STORAGE_MAP[$DISPLAY_SELECTED]+_}\" ]]; then\n      whiptail --msgbox \"No valid storage selected. Please try again.\" 8 58\n      continue\n    fi\n    STORAGE_RESULT=\"${STORAGE_MAP[$DISPLAY_SELECTED]}\"\n    for ((i = 0; i < ${#MENU[@]}; i += 3)); do\n      if [[ \"${MENU[$i]}\" == \"$DISPLAY_SELECTED\" ]]; then\n        STORAGE_INFO=\"${MENU[$i + 1]}\"\n        break\n      fi\n    done\n\n    # Validate storage space for container storage\n    if [[ \"$CLASS\" == \"container\" && -n \"${DISK_SIZE:-}\" ]]; then\n      validate_storage_space \"$STORAGE_RESULT\" \"$DISK_SIZE\" \"yes\"\n      # Continue even if validation fails - user was warned\n    fi\n\n    return 0\n  done\n}\n\n# ------------------------------------------------------------------------------\n# validate_storage_space()\n#\n# - Validates if storage has enough free space for container\n# - Takes storage name and required size in GB\n# - Returns 0 if enough space, 1 if not enough, 2 if storage unavailable\n# - Can optionally show whiptail warning\n# - Handles all storage types: dir, lvm, lvmthin, zfs, nfs, cifs, etc.\n# ------------------------------------------------------------------------------\nvalidate_storage_space() {\n  local storage=\"$1\"\n  local required_gb=\"${2:-8}\"\n  local show_dialog=\"${3:-no}\"\n\n  # Get full storage line from pvesm status\n  local storage_line\n  storage_line=$(pvesm status 2>/dev/null | awk -v s=\"$storage\" '$1 == s {print $0}')\n\n  # Check if storage exists and is active\n  if [[ -z \"$storage_line\" ]]; then\n    [[ \"$show_dialog\" == \"yes\" ]] && whiptail --msgbox \"⚠️  Warning: Storage '$storage' not found!\\n\\nThe storage may be unavailable or disabled.\" 10 60\n    return 236\n  fi\n\n  # Check storage status (column 3)\n  local status\n  status=$(awk '{print $3}' <<<\"$storage_line\")\n  if [[ \"$status\" == \"disabled\" ]]; then\n    [[ \"$show_dialog\" == \"yes\" ]] && whiptail --msgbox \"⚠️  Warning: Storage '$storage' is disabled!\\n\\nPlease enable the storage first.\" 10 60\n    return 236\n  fi\n\n  # Get storage type and free space (column 6)\n  local storage_type storage_free\n  storage_type=$(awk '{print $2}' <<<\"$storage_line\")\n  storage_free=$(awk '{print $6}' <<<\"$storage_line\")\n\n  # Some storage types (like PBS, iSCSI) don't report size info\n  # In these cases, skip space validation\n  if [[ -z \"$storage_free\" || \"$storage_free\" == \"0\" ]]; then\n    # Silent pass for storages without size info\n    return 0\n  fi\n\n  local required_kb=$((required_gb * 1024 * 1024))\n  local free_gb_fmt\n  free_gb_fmt=$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f \"$storage_free\" 2>/dev/null || echo \"${storage_free}KB\")\n\n  if [[ \"$storage_free\" -lt \"$required_kb\" ]]; then\n    if [[ \"$show_dialog\" == \"yes\" ]]; then\n      whiptail --msgbox \"⚠️  Warning: Storage '$storage' may not have enough space!\\n\\nStorage Type: ${storage_type}\\nRequired: ${required_gb}GB\\nAvailable: ${free_gb_fmt}\\n\\nYou can continue, but creation might fail.\" 14 70\n    fi\n    return 236\n  fi\n\n  return 0\n}\n\n# ==============================================================================\n# SECTION 8: CONTAINER CREATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# create_lxc_container()\n#\n# - Main function for creating LXC containers\n# - Handles all phases: validation, template discovery, container creation,\n#   network config, storage, etc.\n# - Extensive error checking with detailed exit codes\n# ------------------------------------------------------------------------------\ncreate_lxc_container() {\n  # ------------------------------------------------------------------------------\n  # Optional verbose mode (debug tracing)\n  # ------------------------------------------------------------------------------\n  if [[ \"${CREATE_LXC_VERBOSE:-no}\" == \"yes\" ]]; then set -x; fi\n\n  # ------------------------------------------------------------------------------\n  # Helpers (dynamic versioning / template parsing)\n  # ------------------------------------------------------------------------------\n  pkg_ver() { dpkg-query -W -f='${Version}\\n' \"$1\" 2>/dev/null || echo \"\"; }\n  pkg_cand() { apt-cache policy \"$1\" 2>/dev/null | awk '/Candidate:/ {print $2}'; }\n\n  ver_ge() { dpkg --compare-versions \"$1\" ge \"$2\"; }\n  ver_gt() { dpkg --compare-versions \"$1\" gt \"$2\"; }\n  ver_lt() { dpkg --compare-versions \"$1\" lt \"$2\"; }\n\n  # Extract Debian OS minor from template name: debian-13-standard_13.1-1_amd64.tar.zst => \"13.1\"\n  parse_template_osver() { sed -n 's/.*_\\([0-9][0-9]*\\(\\.[0-9]\\+\\)\\?\\)-.*/\\1/p' <<<\"$1\"; }\n\n  # Switch to the previous OS major version template and retry pct create\n  # Determines the fallback version automatically based on available templates.\n  # Returns: 0 = success, 1 = failed\n  fallback_to_previous_os_version() {\n    local old_template=\"$TEMPLATE\"\n    local os_type=\"${PCT_OSTYPE:-}\"\n    local current_ver=\"${PCT_OSVERSION:-}\"\n\n    # Determine template search pattern based on OS type\n    local tpl_pattern=\"\"\n    case \"$os_type\" in\n    debian | ubuntu) tpl_pattern=\"-standard_\" ;;\n    alpine | fedora | rocky | centos) tpl_pattern=\"-default_\" ;;\n    *) tpl_pattern=\"\" ;;\n    esac\n\n    msg_info \"Searching for an older $os_type template (current: $os_type $current_ver)\"\n\n    # Collect all available versions for this OS type (local + online)\n    local -a all_versions=()\n\n    # Local templates\n    mapfile -t _local_vers < <(\n      pveam list \"$TEMPLATE_STORAGE\" 2>/dev/null |\n        awk -v os=\"$os_type\" -v pat=\"$tpl_pattern\" '$1 ~ (\"^\"os\"|/\"os) && $1 ~ pat {print $1}' |\n        sed 's|.*/||' |\n        sed -E \"s/^${os_type}-([0-9]+(\\.[0-9]+)?).*/\\1/\" |\n        sort -u -V\n    )\n    all_versions+=(\"${_local_vers[@]}\")\n\n    # Online templates (only if needed)\n    if command -v timeout &>/dev/null; then\n      timeout 30 pveam update >/dev/null 2>&1 || true\n    else\n      pveam update >/dev/null 2>&1 || true\n    fi\n    mapfile -t _online_vers < <(\n      pveam available -section system 2>/dev/null |\n        awk '{print $2}' |\n        grep -E \"^${os_type}-[0-9]\" |\n        { [[ -n \"$tpl_pattern\" ]] && grep \"$tpl_pattern\" || cat; } |\n        sed -E \"s/^${os_type}-([0-9]+(\\.[0-9]+)?).*/\\1/\" |\n        sort -u -V 2>/dev/null || true\n    )\n    all_versions+=(\"${_online_vers[@]}\")\n\n    # Deduplicate and sort, find the highest version below current\n    local fallback_ver=\"\"\n    fallback_ver=$(printf '%s\\n' \"${all_versions[@]}\" | sort -u -V | awk -v cur=\"$current_ver\" '{\n      # Compare major versions: extract major part\n      split($0, a, \".\")\n      split(cur, b, \".\")\n      if (a[1]+0 < b[1]+0) ver=$0\n    } END { if (ver) print ver }')\n\n    if [[ -z \"$fallback_ver\" ]]; then\n      msg_error \"No older $os_type template version found.\"\n      return 1\n    fi\n\n    msg_ok \"Fallback version: $os_type $fallback_ver\"\n\n    # Find the actual template file for this version\n    local fallback_search=\"${os_type}-${fallback_ver}\"\n    local fallback_template=\"\"\n\n    # Check local first\n    mapfile -t _fb_local < <(\n      pveam list \"$TEMPLATE_STORAGE\" 2>/dev/null |\n        awk -v search=\"$fallback_search\" -v pat=\"$tpl_pattern\" '$1 ~ search && $1 ~ pat {print $1}' |\n        sed 's|.*/||' | sort -t - -k 2 -V\n    )\n    if [[ ${#_fb_local[@]} -gt 0 ]]; then\n      fallback_template=\"${_fb_local[-1]}\"\n    else\n      # Check online\n      mapfile -t _fb_online < <(\n        pveam available -section system 2>/dev/null |\n          awk '{print $2}' |\n          grep -E \"^${fallback_search}.*${tpl_pattern}\" |\n          sort -t - -k 2 -V 2>/dev/null || true\n      )\n      [[ ${#_fb_online[@]} -gt 0 ]] && fallback_template=\"${_fb_online[-1]}\"\n    fi\n\n    if [[ -z \"$fallback_template\" ]]; then\n      msg_error \"No template found for $os_type $fallback_ver.\"\n      return 1\n    fi\n\n    msg_ok \"Found template: $fallback_template\"\n\n    # Download if needed\n    local fallback_path\n    fallback_path=\"$(pvesm path \"$TEMPLATE_STORAGE:vztmpl/$fallback_template\" 2>/dev/null || true)\"\n    [[ -z \"$fallback_path\" ]] && fallback_path=\"/var/lib/vz/template/cache/$fallback_template\"\n\n    if [[ ! -f \"$fallback_path\" ]]; then\n      msg_info \"Downloading $os_type $fallback_ver template\"\n      if ! pveam download \"$TEMPLATE_STORAGE\" \"$fallback_template\" >>\"${BUILD_LOG:-/dev/null}\" 2>&1; then\n        msg_error \"Failed to download $os_type $fallback_ver template.\"\n        return 1\n      fi\n      msg_ok \"Template downloaded\"\n    fi\n\n    # Update variables\n    TEMPLATE=\"$fallback_template\"\n    TEMPLATE_PATH=\"$fallback_path\"\n    PCT_OSVERSION=\"$fallback_ver\"\n    export PCT_OSVERSION\n\n    # Retry pct create\n    msg_info \"Retrying container creation with $os_type $fallback_ver\"\n    if pct create \"$CTID\" \"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}\" $PCT_OPTIONS >>\"$LOGFILE\" 2>&1; then\n      msg_ok \"Container created successfully with $os_type $fallback_ver (fallback from $old_template).\"\n      return 0\n    else\n      msg_error \"Container creation with $os_type $fallback_ver also failed. See $LOGFILE\"\n      return 1\n    fi\n  }\n\n  # Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create\n  # Returns:\n  #   0 = no upgrade needed / container created after upgrade or explicit fallback\n  #   1 = upgraded (and if do_retry=yes and retry succeeded, creation done)\n  #   2 = user chose ignore\n  #   3 = upgrade attempted but failed OR retry failed\n  #   4 = user cancelled\n  offer_lxc_stack_upgrade_and_maybe_retry() {\n    local do_retry=\"${1:-no}\" # yes|no\n    local _pvec_i _pvec_c _lxcp_i _lxcp_c need=0\n    local _ans\n\n    has_previous_os_version_template() {\n      local os_type=\"${PCT_OSTYPE:-}\"\n      local current_ver=\"${PCT_OSVERSION:-}\"\n      local tpl_pattern=\"${TEMPLATE_PATTERN:-${TEMPLATE:-}}\"\n      local -a all_versions=()\n\n      [[ -n \"$os_type\" && -n \"$current_ver\" ]] || return 1\n\n      mapfile -t _local_vers < <(\n        pveam list \"$TEMPLATE_STORAGE\" 2>/dev/null |\n          awk '{print $1}' |\n          sed 's|.*/||' |\n          grep -E \"^${os_type}-[0-9]\" |\n          { [[ -n \"$tpl_pattern\" ]] && grep \"$tpl_pattern\" || cat; } |\n          sed -E \"s/^${os_type}-([0-9]+(\\.[0-9]+)?).*/\\1/\" |\n          sort -u -V\n      )\n      all_versions+=(\"${_local_vers[@]}\")\n\n      if command -v timeout &>/dev/null; then\n        timeout 30 pveam update >/dev/null 2>&1 || true\n      else\n        pveam update >/dev/null 2>&1 || true\n      fi\n      mapfile -t _online_vers < <(\n        pveam available -section system 2>/dev/null |\n          awk '{print $2}' |\n          grep -E \"^${os_type}-[0-9]\" |\n          { [[ -n \"$tpl_pattern\" ]] && grep \"$tpl_pattern\" || cat; } |\n          sed -E \"s/^${os_type}-([0-9]+(\\.[0-9]+)?).*/\\1/\" |\n          sort -u -V 2>/dev/null || true\n      )\n      all_versions+=(\"${_online_vers[@]}\")\n\n      printf '%s\\n' \"${all_versions[@]}\" | sort -u -V | awk -v cur=\"$current_ver\" '\n        {\n          split($0, a, \".\")\n          split(cur, b, \".\")\n          if (a[1]+0 < b[1]+0) found=1\n        }\n        END { exit found ? 0 : 1 }\n      '\n    }\n\n    _pvec_i=\"$(pkg_ver pve-container)\"\n    _lxcp_i=\"$(pkg_ver lxc-pve)\"\n    _pvec_c=\"$(pkg_cand pve-container)\"\n    _lxcp_c=\"$(pkg_cand lxc-pve)\"\n\n    if [[ -n \"$_pvec_c\" && \"$_pvec_c\" != \"none\" ]]; then\n      ver_gt \"$_pvec_c\" \"${_pvec_i:-0}\" && need=1\n    fi\n    if [[ -n \"$_lxcp_c\" && \"$_lxcp_c\" != \"none\" ]]; then\n      ver_gt \"$_lxcp_c\" \"${_lxcp_i:-0}\" && need=1\n    fi\n    if [[ $need -eq 0 ]]; then\n      msg_debug \"No newer candidate for pve-container/lxc-pve (installed=$_pvec_i/$_lxcp_i, cand=$_pvec_c/$_lxcp_c)\"\n      return 0\n    fi\n\n    msg_warn \"An update for the Proxmox LXC stack is available\"\n    echo \"  pve-container: installed=${_pvec_i:-n/a}  candidate=${_pvec_c:-n/a}\"\n    echo \"  lxc-pve     : installed=${_lxcp_i:-n/a}  candidate=${_lxcp_c:-n/a}\"\n    echo \"  note        : option 1 runs host apt update + apt upgrade\"\n    echo\n\n    # Offer older OS version fallback when template version might be too new for LXC stack\n    local _has_fallback_option=false\n    if [[ \"$do_retry\" == \"yes\" ]] && has_previous_os_version_template; then\n      _has_fallback_option=true\n      echo \"  [1] Run host upgrade now (recommended). WARNING: this runs apt upgrade and updates all Packages on your host!\"\n      echo \"  [2] Use an older ${PCT_OSTYPE} template instead (may not work with all scripts)\"\n      echo \"  [3] Ignore\"\n      echo \"  [4] Cancel\"\n      echo\n      read -rp \"Select option [1/2/3/4]: \" _ans </dev/tty\n    else\n      echo \"  [1] Run host upgrade now (recommended). WARNING: this runs apt upgrade and updates all Packages on your host!\"\n      echo \"  [2] Ignore\"\n      echo \"  [3] Cancel\"\n      echo\n      read -rp \"Select option [1/2/3]: \" _ans </dev/tty\n    fi\n\n    if [[ \"$_has_fallback_option\" == true ]]; then\n      case \"$_ans\" in\n      1)\n        _ans=\"y\"\n        ;;\n      2)\n        if fallback_to_previous_os_version; then\n          return 0\n        else\n          return 3\n        fi\n        ;;\n      3)\n        return 2\n        ;;\n      4)\n        return 4\n        ;;\n      *)\n        return 4\n        ;;\n      esac\n    else\n      case \"$_ans\" in\n      1)\n        _ans=\"y\"\n        ;;\n      2)\n        return 2\n        ;;\n      *)\n        return 4\n        ;;\n      esac\n    fi\n\n    case \"${_ans,,}\" in\n    y | yes)\n      msg_info \"Running host upgrade for LXC stack compatibility\"\n      apt_update_safe\n      if $STD apt-get upgrade -y; then\n        msg_ok \"LXC stack upgraded.\"\n        # Verify pct binary still works after upgrade (partial upgrades can break Perl modules)\n        if ! pct list &>/dev/null; then\n          msg_error \"LXC stack upgrade caused PVE tool breakage (likely Perl module incompatibility).\"\n          msg_custom \"⚠️\" \"${YW}\" \"A partial package upgrade has left the PVE stack in an inconsistent state.\"\n          msg_custom \"🔧\" \"${YW}\" \"Please run the following on the Proxmox host, then retry:\"\n          echo -e \"${TAB}  apt update && apt upgrade -y\"\n          echo -e \"${TAB}  reboot\"\n          return 3\n        fi\n        if [[ \"$do_retry\" == \"yes\" ]]; then\n          msg_info \"Retrying container creation after upgrade\"\n          if pct create \"$CTID\" \"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}\" $PCT_OPTIONS >>\"$LOGFILE\" 2>&1; then\n            msg_ok \"Container created successfully after upgrade.\"\n            return 0\n          else\n            msg_error \"pct create still failed after upgrade. See $LOGFILE\"\n            return 3\n          fi\n        fi\n        return 1\n      else\n        msg_error \"Upgrade failed. Please check APT output.\"\n        return 3\n      fi\n      ;;\n    *) return 2 ;;\n    esac\n  }\n\n  resolve_template_storage_path() {\n    local storage=\"$1\"\n    local name=\"$2\"\n    local path=\"\"\n\n    path=$(pvesm path \"${storage}:vztmpl/${name}\" 2>/dev/null || true)\n    if [[ -z \"$path\" ]]; then\n      local storage_base\n      storage_base=$(awk -v s=\"$storage\" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)\n      if [[ -n \"$storage_base\" ]]; then\n        path=\"${storage_base%/}/template/cache/$name\"\n      fi\n    fi\n\n    if [[ -z \"$path\" ]]; then\n      path=\"/var/lib/vz/template/cache/$name\"\n    fi\n\n    echo \"$path\"\n  }\n\n  arm64_template_variant() {\n    local os=\"$1\"\n    local version=\"$2\"\n\n    case \"$os\" in\n    debian)\n      case \"$version\" in\n      11 | 11.*) echo \"bullseye\" ;;\n      12 | 12.*) echo \"bookworm\" ;;\n      13 | 13.*) echo \"trixie\" ;;\n      *) echo \"trixie\" ;;\n      esac\n      ;;\n    alpine)\n       echo \"3.22\"\n      ;;\n    ubuntu)\n      case \"$version\" in\n      20.04* | focal) echo \"focal\" ;;\n      24.04* | noble) echo \"noble\" ;;\n      24.10* | oracular) echo \"oracular\" ;;\n      *) echo \"jammy\" ;;\n      esac\n      ;;\n    *)\n      echo \"\"\n      return 1\n      ;;\n    esac\n  }\n\n  download_arm64_template() {\n    local os=\"$1\"\n    local variant=\"$2\"\n    local dest=\"$3\"\n    local dest_dir\n\n    dest_dir=$(dirname \"$dest\")\n    mkdir -p \"$dest_dir\" || {\n      msg_error \"Unable to create template directory '$dest_dir'.\"\n      exit 207\n    }\n\n    if [[ \"$os\" == \"debian\" ]]; then\n      local api_url=\"https://api.github.com/repos/asylumexp/debian-ifupdown2-lxc/releases/latest\"\n      local release_json download_url\n      release_json=$(curl -fsSL \"$api_url\") || {\n        msg_error \"Unable to query Debian template release metadata.\"\n        exit 207\n      }\n      download_url=$(grep -Eo \"https://[^\\\"]*debian-${variant}-arm64-rootfs\\.tar\\.xz\" <<<\"$release_json\" | head -n1)\n      if [[ -z \"$download_url\" ]]; then\n        msg_error \"Could not find Debian ${variant} ARM64 template download URL.\"\n        exit 207\n      fi\n      msg_info \"Downloading Debian ${variant} ARM64 template\"\n      if ! wget -q \"$download_url\" -O \"$dest\"; then\n        msg_error \"A problem occurred while downloading the Debian ARM64 template.\"\n        exit 208\n      fi\n    else\n      local template_url=\"https://jenkins.linuxcontainers.org/job/image-${os}/architecture=arm64,release=${variant},variant=default/lastStableBuild/artifact/rootfs.tar.xz\"\n      msg_info \"Downloading ${os^} ${variant} ARM64 template\"\n      if ! wget -q \"$template_url\" -O \"$dest\"; then\n        msg_error \"A problem occurred while downloading the ${os} ARM64 template.\"\n        exit 208\n      fi\n    fi\n\n    msg_ok \"Downloaded LXC Template\"\n  }\n\n  # ------------------------------------------------------------------------------\n  # Required input variables\n  # ------------------------------------------------------------------------------\n  [[ \"${CTID:-}\" ]] || {\n    msg_error \"You need to set 'CTID' variable.\"\n    exit 203\n  }\n  [[ \"${PCT_OSTYPE:-}\" ]] || {\n    msg_error \"You need to set 'PCT_OSTYPE' variable.\"\n    exit 204\n  }\n\n  msg_debug \"CTID=$CTID\"\n  msg_debug \"PCT_OSTYPE=$PCT_OSTYPE\"\n  msg_debug \"PCT_OSVERSION=${PCT_OSVERSION:-default}\"\n\n  # ID checks\n  [[ \"$CTID\" -ge 100 ]] || {\n    msg_error \"ID cannot be less than 100.\"\n    exit 205\n  }\n  if qm status \"$CTID\" &>/dev/null || pct status \"$CTID\" &>/dev/null; then\n    msg_warn \"Container/VM ID $CTID is already in use (detected late). Reassigning...\"\n    CTID=$(get_valid_container_id \"$((CTID + 1))\")\n    export CTID\n    msg_ok \"Reassigned to container ID $CTID\"\n  fi\n\n  # Report installation start to API early - captures failures in storage/template/create\n  post_to_api\n\n  # Transition to 'validation' — Proxmox-internal checks (storage, template, cluster)\n  post_progress_to_api \"validation\"\n\n  # Storage capability check\n  check_storage_support \"rootdir\" || {\n    msg_error \"No valid storage found for 'rootdir' [Container]\"\n    exit 119\n  }\n  check_storage_support \"vztmpl\" || {\n    msg_error \"No valid storage found for 'vztmpl' [Template]\"\n    exit 120\n  }\n\n  # Template storage selection\n  if resolve_storage_preselect template \"${TEMPLATE_STORAGE:-}\"; then\n    TEMPLATE_STORAGE=\"$STORAGE_RESULT\"\n    TEMPLATE_STORAGE_INFO=\"$STORAGE_INFO\"\n    msg_ok \"Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]\"\n  else\n    while true; do\n      if [[ -z \"${var_template_storage:-}\" ]]; then\n        if select_storage template; then\n          TEMPLATE_STORAGE=\"$STORAGE_RESULT\"\n          TEMPLATE_STORAGE_INFO=\"$STORAGE_INFO\"\n          msg_ok \"Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]\"\n          break\n        fi\n      fi\n    done\n  fi\n\n  # Container storage selection\n  if resolve_storage_preselect container \"${CONTAINER_STORAGE:-}\"; then\n    CONTAINER_STORAGE=\"$STORAGE_RESULT\"\n    CONTAINER_STORAGE_INFO=\"$STORAGE_INFO\"\n    msg_ok \"Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]\"\n  else\n    if [[ -z \"${var_container_storage:-}\" ]]; then\n      if select_storage container; then\n        CONTAINER_STORAGE=\"$STORAGE_RESULT\"\n        CONTAINER_STORAGE_INFO=\"$STORAGE_INFO\"\n        msg_ok \"Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]\"\n      fi\n    fi\n  fi\n\n  msg_info \"Validating storage '$CONTAINER_STORAGE'\"\n  # Check if storage.cfg is accessible (pmxcfs must be mounted)\n  if [[ ! -f /etc/pve/storage.cfg ]]; then\n    if ! mountpoint -q /etc/pve 2>/dev/null; then\n      msg_error \"Proxmox cluster filesystem (pmxcfs) is not mounted at /etc/pve.\"\n      msg_custom \"🔧\" \"${YW}\" \"Try: systemctl restart pve-cluster\"\n    else\n      msg_error \"/etc/pve/storage.cfg does not exist.\"\n      msg_custom \"🔧\" \"${YW}\" \"Check Proxmox cluster filesystem integrity: pvecm status\"\n    fi\n    exit 213\n  fi\n\n  STORAGE_TYPE=$(grep -E \"^[^:]+:[[:space:]]*$CONTAINER_STORAGE[[:space:]]*$\" /etc/pve/storage.cfg | cut -d: -f1 | head -1 || true)\n\n  # Fallback: use pvesm status to determine storage type\n  if [[ -z \"$STORAGE_TYPE\" ]]; then\n    STORAGE_TYPE=$(pvesm status -storage \"$CONTAINER_STORAGE\" 2>/dev/null | awk 'NR>1{print $2}')\n  fi\n\n  if [[ -z \"$STORAGE_TYPE\" ]]; then\n    msg_error \"Storage '$CONTAINER_STORAGE' not found in /etc/pve/storage.cfg\"\n    msg_custom \"📋\" \"${YW}\" \"Available storages: $(pvesm status 2>/dev/null | awk 'NR>1{printf \"%s (%s) \", $1, $2}' || echo 'n/a')\"\n    if [[ -r /etc/pve/storage.cfg ]]; then\n      msg_custom \"📋\" \"${YW}\" \"Storage definitions found in config:\"\n      grep -E '^[a-z]+:' /etc/pve/storage.cfg 2>/dev/null | while IFS= read -r _line; do\n        echo \"${TAB}  $_line\"\n      done\n    fi\n    msg_custom \"📖\" \"${YW}\" \"See https://pve.proxmox.com/wiki/Storage for storage configuration details.\"\n    exit 213\n  fi\n\n  case \"$STORAGE_TYPE\" in\n  iscsidirect)\n    msg_error \"Storage '$CONTAINER_STORAGE' uses iSCSI-direct which does not support container rootfs.\"\n    exit 212\n    ;;\n  iscsi | zfs)\n    msg_error \"Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) does not support container rootdir content.\"\n    exit 213\n    ;;\n  cephfs)\n    msg_error \"Storage '$CONTAINER_STORAGE' uses CephFS which is not supported for LXC rootfs.\"\n    exit 219\n    ;;\n  pbs)\n    msg_error \"Storage '$CONTAINER_STORAGE' is a Proxmox Backup Server — cannot be used for containers.\"\n    exit 224\n    ;;\n  linstor | rbd | nfs | cifs)\n    if ! pvesm status -storage \"$CONTAINER_STORAGE\" &>/dev/null; then\n      msg_error \"Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) is not accessible or inactive.\"\n      exit 217\n    fi\n    ;;\n  esac\n\n  if ! pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx \"$CONTAINER_STORAGE\"; then\n    msg_error \"Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) does not support 'rootdir' content.\"\n    msg_custom \"💡\" \"${YW}\" \"Enable 'Disk image' (rootdir) for storage '${CONTAINER_STORAGE}' in:\"\n    msg_custom \"   \" \"${YW}\" \"Datacenter → Storage → ${CONTAINER_STORAGE} → Edit → Content\"\n    msg_custom \"📖\" \"${YW}\" \"See: https://pve.proxmox.com/wiki/Storage\"\n    msg_custom \"🔗\" \"${YW}\" \"Help: https://github.com/community-scripts/ProxmoxVE/discussions\"\n    exit 213\n  fi\n  msg_ok \"Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated\"\n\n  msg_info \"Validating template storage '$TEMPLATE_STORAGE'\"\n  TEMPLATE_TYPE=$(grep -E \"^[^:]+: $TEMPLATE_STORAGE$\" /etc/pve/storage.cfg | cut -d: -f1 || true)\n\n  if ! pvesm status -content vztmpl 2>/dev/null | awk 'NR>1{print $1}' | grep -qx \"$TEMPLATE_STORAGE\"; then\n    msg_warn \"Template storage '$TEMPLATE_STORAGE' may not support 'vztmpl'\"\n  fi\n  msg_ok \"Template storage '$TEMPLATE_STORAGE' validated\"\n\n  # Cluster quorum (if cluster)\n  if [[ -f /etc/pve/corosync.conf ]]; then\n    msg_info \"Checking cluster quorum\"\n    if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then\n      msg_error \"Cluster is not quorate. Start all nodes or configure quorum device (QDevice).\"\n      exit 210\n    fi\n    msg_ok \"Cluster is quorate\"\n  fi\n\n  # ------------------------------------------------------------------------------\n  # Template discovery & validation (ARM64 custom templates)\n  # ------------------------------------------------------------------------------\n  msg_info \"Preparing template\"\n\n  CUSTOM_TEMPLATE_VARIANT=$(arm64_template_variant \"$PCT_OSTYPE\" \"${PCT_OSVERSION:-}\")\n  if [[ -z \"$CUSTOM_TEMPLATE_VARIANT\" ]]; then\n    msg_error \"No template mapping for ${PCT_OSTYPE} ${PCT_OSVERSION:-latest}\"\n    exit 207\n  fi\n\n  TEMPLATE=\"${PCT_OSTYPE}-${CUSTOM_TEMPLATE_VARIANT}-rootfs.tar.xz\"\n  TEMPLATE_PATH=\"$(resolve_template_storage_path \"$TEMPLATE_STORAGE\" \"$TEMPLATE\")\"\n  TEMPLATE_SOURCE=\"custom-arm64\"\n\n  if [[ ! -f \"$TEMPLATE_PATH\" ]]; then\n    download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n  else\n    msg_ok \"Template ${BL}$TEMPLATE${CL} found locally.\"\n  fi\n\n  if [[ \"$(stat -c%s \"$TEMPLATE_PATH\")\" -lt 1000000 ]]; then\n    msg_warn \"Template file too small – re-downloading.\"\n    rm -f \"$TEMPLATE_PATH\"\n    download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n  fi\n\n  if ! tar -tf \"$TEMPLATE_PATH\" &>/dev/null; then\n    msg_warn \"Template appears corrupted – re-downloading.\"\n    rm -f \"$TEMPLATE_PATH\"\n    download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n  fi\n\n  msg_ok \"Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]\"\n  msg_debug \"Resolved TEMPLATE_PATH=$TEMPLATE_PATH\"\n\n  # ------------------------------------------------------------------------------\n  # Dynamic preflight for Debian 13.x: offer upgrade if available (no hard mins)\n  # ------------------------------------------------------------------------------\n  if [[ \"$PCT_OSTYPE\" == \"debian\" ]]; then\n    OSVER=\"$(parse_template_osver \"$TEMPLATE\")\"\n    if [[ -n \"$OSVER\" ]]; then\n      offer_lxc_stack_upgrade_and_maybe_retry \"no\" || true\n    fi\n  fi\n\n  # ------------------------------------------------------------------------------\n  # Create LXC Container\n  # ------------------------------------------------------------------------------\n  msg_info \"Creating LXC container\"\n\n  # Ensure subuid/subgid entries exist\n  grep -q \"root:100000:65536\" /etc/subuid || echo \"root:100000:65536\" >>/etc/subuid\n  grep -q \"root:100000:65536\" /etc/subgid || echo \"root:100000:65536\" >>/etc/subgid\n\n  # PCT_OPTIONS is now a string (exported from build_container)\n  # Add rootfs if not already specified\n  if [[ ! \"$PCT_OPTIONS\" =~ \"-rootfs\" ]]; then\n    PCT_OPTIONS=\"$PCT_OPTIONS\n  -rootfs $CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}\"\n  fi\n\n  # Lock by template file (avoid concurrent template downloads/validation)\n  lockfile=\"/tmp/template.${TEMPLATE}.lock\"\n\n  # Cleanup stale lock files (older than 1 hour - likely from crashed processes)\n  if [[ -f \"$lockfile\" ]]; then\n    local lock_age=$(($(date +%s) - $(stat -c %Y \"$lockfile\" 2>/dev/null || echo 0)))\n    if [[ $lock_age -gt 3600 ]]; then\n      msg_warn \"Removing stale template lock file (age: ${lock_age}s)\"\n      rm -f \"$lockfile\"\n    fi\n  fi\n\n  exec 9>\"$lockfile\" || {\n    msg_error \"Failed to create lock file '$lockfile'.\"\n    exit 200\n  }\n\n  # Retry logic for template lock (another container creation may be running)\n  local lock_attempts=0\n  local max_lock_attempts=10\n  local lock_wait_time=30\n\n  while ! flock -w \"$lock_wait_time\" 9; do\n    lock_attempts=$((lock_attempts + 1))\n    if [[ $lock_attempts -ge $max_lock_attempts ]]; then\n      msg_error \"Timeout while waiting for template lock after ${max_lock_attempts} attempts.\"\n      msg_custom \"💡\" \"${YW}\" \"Another container creation may be stuck. Check running processes or remove: $lockfile\"\n      exit 211\n    fi\n    msg_custom \"⏳\" \"${YW}\" \"Another container is being created with this template. Waiting... (attempt ${lock_attempts}/${max_lock_attempts})\"\n  done\n\n  LOGFILE=\"/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log\"\n\n  # Helper: append pct_create log to BUILD_LOG before exit so combined log has full context\n  _flush_pct_log() {\n    if [[ -s \"${LOGFILE:-}\" && -n \"${BUILD_LOG:-}\" ]]; then\n      {\n        echo \"\"\n        echo \"--- pct create output (${LOGFILE}) ---\"\n        cat \"$LOGFILE\"\n        echo \"--- end pct create output ---\"\n      } >>\"$BUILD_LOG\" 2>/dev/null || true\n    fi\n  }\n\n  # Validate template before pct create (while holding lock)\n  if [[ ! -s \"$TEMPLATE_PATH\" || \"$(stat -c%s \"$TEMPLATE_PATH\" 2>/dev/null || echo 0)\" -lt 1000000 ]]; then\n    msg_info \"Template file missing or too small – downloading\"\n    rm -f \"$TEMPLATE_PATH\"\n    download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n    msg_ok \"Template downloaded\"\n  elif ! tar -tf \"$TEMPLATE_PATH\" &>/dev/null; then\n    msg_info \"Template appears corrupted – re-downloading\"\n    rm -f \"$TEMPLATE_PATH\"\n    download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n    msg_ok \"Template re-downloaded\"\n  fi\n\n  # Release lock after template validation - pct create has its own internal locking\n  exec 9>&-\n\n  msg_debug \"pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS\"\n  msg_debug \"Logfile: $LOGFILE\"\n\n  # First attempt (PCT_OPTIONS is a multi-line string, use it directly)\n  # Disable globbing: unquoted $PCT_OPTIONS needs word-splitting but must not glob-expand\n  # (e.g. passwords containing * or ? would match filenames otherwise)\n  set -f\n  if ! pct create \"$CTID\" \"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}\" $PCT_OPTIONS >\"$LOGFILE\" 2>&1; then\n    msg_debug \"Container creation failed on ${TEMPLATE_STORAGE}. Checking error...\"\n\n    # Check for Perl module breakage (partial PVE upgrade)\n    if grep -qiE 'Compilation failed|Bareword.*not allowed' \"$LOGFILE\"; then\n      msg_error \"Container creation failed due to broken Perl modules on the PVE host.\"\n      msg_custom \"⚠️\" \"${YW}\" \"This usually happens after a partial PVE package upgrade.\"\n      msg_custom \"🔧\" \"${YW}\" \"Please run the following on the Proxmox host, then retry:\"\n      echo -e \"${TAB}  apt update && apt dist-upgrade -y\"\n      echo -e \"${TAB}  reboot\"\n      _flush_pct_log\n      exit 232\n    fi\n\n    # Check if CTID collision (race condition: ID claimed between validation and creation)\n    if grep -qiE 'already exists|already in use' \"$LOGFILE\"; then\n      local old_ctid=\"$CTID\"\n      CTID=$(get_valid_container_id \"$((CTID + 1))\")\n      export CTID\n      msg_warn \"Container ID $old_ctid was claimed by another process. Retrying with ID $CTID\"\n      LOGFILE=\"/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log\"\n      if pct create \"$CTID\" \"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}\" $PCT_OPTIONS >\"$LOGFILE\" 2>&1; then\n        msg_ok \"Container successfully created with new ID $CTID\"\n      else\n        msg_error \"Container creation failed even with new ID $CTID. See $LOGFILE\"\n        _flush_pct_log\n        exit 209\n      fi\n    else\n      # Not a CTID collision - check if template issue and retry with fresh download\n      if grep -qiE 'unable to open|corrupt|invalid' \"$LOGFILE\"; then\n        msg_info \"Template may be corrupted – re-downloading\"\n        rm -f \"$TEMPLATE_PATH\"\n        download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n      elif ! tar -tf \"$TEMPLATE_PATH\" &>/dev/null; then\n        msg_warn \"Template appears corrupted – re-downloading.\"\n        rm -f \"$TEMPLATE_PATH\"\n        download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$TEMPLATE_PATH\"\n      fi\n\n      # Retry after repair\n      if ! pct create \"$CTID\" \"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}\" $PCT_OPTIONS >>\"$LOGFILE\" 2>&1; then\n        # Fallback to local storage if not already on local\n        if [[ \"$TEMPLATE_STORAGE\" != \"local\" ]]; then\n          msg_info \"Retrying container creation with fallback to local storage\"\n          LOCAL_TEMPLATE_PATH=\"/var/lib/vz/template/cache/$TEMPLATE\"\n          if [[ ! -f \"$LOCAL_TEMPLATE_PATH\" ]]; then\n            msg_info \"Downloading template to local...\"\n            download_arm64_template \"$PCT_OSTYPE\" \"$CUSTOM_TEMPLATE_VARIANT\" \"$LOCAL_TEMPLATE_PATH\"\n          fi\n          if ! pct create \"$CTID\" \"local:vztmpl/${TEMPLATE}\" $PCT_OPTIONS >>\"$LOGFILE\" 2>&1; then\n            # Local fallback also failed - check for LXC stack version issue\n            if grep -qiE 'unsupported .* version' \"$LOGFILE\"; then\n              msg_warn \"pct reported 'unsupported version' – LXC stack might be too old for this template\"\n              offer_lxc_stack_upgrade_and_maybe_retry \"yes\"\n              rc=$?\n              case $rc in\n              0) : ;;\n              2)\n                msg_error \"LXC stack upgrade ignored. Please inspect: $LOGFILE\"\n                _flush_pct_log\n                exit 231\n                ;;\n              3)\n                msg_error \"LXC stack upgrade failed. Please inspect: $LOGFILE\"\n                _flush_pct_log\n                exit 231\n                ;;\n              4)\n                msg_error \"Cancelled by user.\"\n                _flush_pct_log\n                exit 231\n                ;;\n              esac\n            else\n              msg_error \"Container creation failed. See $LOGFILE\"\n              if whiptail --yesno \"pct create failed.\\nDo you want to enable verbose debug mode and view detailed logs?\" 12 70; then\n                set -x\n                pct create \"$CTID\" \"local:vztmpl/${TEMPLATE}\" $PCT_OPTIONS 2>&1 | tee -a \"$LOGFILE\"\n                set +x\n              fi\n              _flush_pct_log\n              exit 209\n            fi\n          else\n            msg_ok \"Container successfully created using local fallback.\"\n          fi\n        else\n          # Already on local storage and still failed - check LXC stack version\n          if grep -qiE 'unsupported .* version' \"$LOGFILE\"; then\n            msg_warn \"pct reported 'unsupported version' – LXC stack might be too old for this template\"\n            offer_lxc_stack_upgrade_and_maybe_retry \"yes\"\n            rc=$?\n            case $rc in\n            0) : ;; # success - container created, continue\n            2)\n              msg_error \"LXC stack upgrade ignored. Please inspect: $LOGFILE\"\n              _flush_pct_log\n              exit 231\n              ;;\n            3)\n              msg_error \"LXC stack upgrade failed. Please inspect: $LOGFILE\"\n              _flush_pct_log\n              exit 231\n              ;;\n            4)\n              msg_error \"Cancelled by user.\"\n              _flush_pct_log\n              exit 231\n              ;;\n            esac\n          else\n            msg_error \"Container creation failed. See $LOGFILE\"\n            if whiptail --yesno \"pct create failed.\\nDo you want to enable verbose debug mode and view detailed logs?\" 12 70; then\n              set -x\n              pct create \"$CTID\" \"local:vztmpl/${TEMPLATE}\" $PCT_OPTIONS 2>&1 | tee -a \"$LOGFILE\"\n              set +x\n            fi\n            _flush_pct_log\n            exit 209\n          fi\n        fi\n      else\n        msg_ok \"Container successfully created after template repair.\"\n      fi\n    fi # close CTID collision else-branch\n  fi\n  set +f # re-enable globbing after pct create block\n\n  # Verify container exists (allow up to 10s for pmxcfs sync in clusters)\n  local _pct_visible=false\n  for _pct_check in {1..10}; do\n    if pct list | awk '{print $1}' | grep -qx \"$CTID\"; then\n      _pct_visible=true\n      break\n    fi\n    sleep 1\n  done\n  if [[ \"$_pct_visible\" != true ]]; then\n    msg_error \"Container ID $CTID not listed in 'pct list' after 10s. See $LOGFILE\"\n    msg_custom \"🔧\" \"${YW}\" \"This can happen in clusters with pmxcfs sync delays.\"\n    _flush_pct_log\n    exit 215\n  fi\n\n  # Verify config rootfs\n  grep -q '^rootfs:' \"/etc/pve/lxc/$CTID.conf\" || {\n    msg_error \"RootFS entry missing in container config. See $LOGFILE\"\n    _flush_pct_log\n    exit 216\n  }\n\n  msg_ok \"LXC Container ${BL}$CTID${CL} ${GN}was successfully created.\"\n\n  # Append pct create log to BUILD_LOG for combined log visibility\n  if [[ -s \"$LOGFILE\" && -n \"${BUILD_LOG:-}\" ]]; then\n    {\n      echo \"\"\n      echo \"--- pct create output ---\"\n      cat \"$LOGFILE\"\n      echo \"--- end pct create output ---\"\n    } >>\"$BUILD_LOG\" 2>/dev/null || true\n  fi\n}\n\n# ==============================================================================\n# SECTION 9: POST-INSTALLATION & FINALIZATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# description()\n#\n# - Sets container description with formatted HTML content\n# - Includes:\n#   * Community-Scripts logo\n#   * Application name\n#   * Links to GitHub, Discussions, Issues\n#   * Ko-fi donation badge\n# - Restarts ping-instances.service if present (monitoring)\n# - Posts final \"done\" status to API telemetry\n# ------------------------------------------------------------------------------\ndescription() {\n  IP=$(pct exec \"$CTID\" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n  local script_slug script_url donate_url\n\n  script_slug=\"${SCRIPT_SLUG:-${NSAPP}}\"\n  script_slug=\"$(echo \"$script_slug\" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')\"\n  script_url=\"https://community-scripts.org/scripts/${script_slug}\"\n  donate_url=\"https://community-scripts.org/donate\"\n\n  # Generate LXC Description\n  DESCRIPTION=$(\n    cat <<EOF\n<div align='center'>\n  <a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='${donate_url}' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/❤️-Sponsoring%20%26%20Donations-FF5E5B' alt='Sponsoring and donations' />\n    </a>\n  </p>\n\n  <p style='margin: 12px 0;'>\n    <a href='${script_url}' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/📦-Open%20Script%20Page-00617f' alt='Open script page' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n  )\n  pct set \"$CTID\" -description \"$DESCRIPTION\"\n\n  if [[ -f /etc/systemd/system/ping-instances.service ]]; then\n    systemctl start ping-instances.service\n  fi\n\n  # Optional host-side post-install hook\n  # Path comes from var_post_install (default.vars / app.vars / advanced settings).\n  # Runs ON THE PROXMOX HOST after the container is up and configured.\n  # Exposed env vars: APP, NSAPP, CTID, IP, HN, STORAGE, BRG.\n  # Output (stdout/stderr) is captured to /var/log/community-scripts/post-install-<CTID>.log\n  if [[ -n \"${var_post_install:-}\" ]]; then\n    local _hook_log_dir=\"/var/log/community-scripts\"\n    local _hook_log=\"${_hook_log_dir}/post-install-${CTID}.log\"\n    mkdir -p \"$_hook_log_dir\" 2>/dev/null || true\n\n    if [[ ! -f \"${var_post_install}\" ]]; then\n      msg_error \"Post-install hook not found on host: ${var_post_install}\"\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n        --title \"POST-INSTALL HOOK FAILED\" \\\n        --msgbox \"The configured post-install hook was not found on the Proxmox host:\\n\\n${var_post_install}\\n\\nThe LXC was created successfully, but the hook did NOT run.\" 14 72 || true\n    else\n      msg_info \"Running post-install hook: ${var_post_install}\"\n      local _hook_rc=0\n      APP=\"$APP\" NSAPP=\"${NSAPP:-}\" CTID=\"$CTID\" IP=\"$IP\" HN=\"${HN:-}\" \\\n        STORAGE=\"${STORAGE:-}\" BRG=\"${BRG:-}\" \\\n        bash \"${var_post_install}\" >\"${_hook_log}\" 2>&1 || _hook_rc=$?\n      if [[ $_hook_rc -eq 0 ]]; then\n        msg_ok \"Post-install hook completed (log: ${_hook_log})\"\n      else\n        msg_error \"Post-install hook failed (rc=${_hook_rc}) – see ${_hook_log}\"\n        local _hook_tail=\"\"\n        _hook_tail=\"$(tail -n 15 \"${_hook_log}\" 2>/dev/null || true)\"\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n          --title \"POST-INSTALL HOOK FAILED\" \\\n          --msgbox \"Hook exited with code ${_hook_rc}.\\n\\nScript: ${var_post_install}\\nLog:    ${_hook_log}\\n\\n--- Last log lines ---\\n${_hook_tail}\\n\\nThe LXC itself was created successfully.\" 22 78 || true\n      fi\n    fi\n  fi\n\n  INSTALL_COMPLETE=true\n  post_update_to_api \"done\" \"none\"\n}\n\n# ==============================================================================\n# SECTION 10: ERROR HANDLING & EXIT TRAPS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# ensure_log_on_host()\n#\n# - Ensures INSTALL_LOG points to a readable file on the host\n# - If INSTALL_LOG points to a container path (e.g. /root/.install-*),\n#   tries to pull it from the container and create a combined log\n# - This allows get_error_text() to find actual error output for telemetry\n# - Uses timeout on pct pull to prevent hangs on dead/unresponsive containers\n# ------------------------------------------------------------------------------\nensure_log_on_host() {\n  # Already readable on host? Nothing to do.\n  [[ -n \"${INSTALL_LOG:-}\" && -s \"${INSTALL_LOG}\" ]] && return 0\n\n  # Try pulling from container and creating combined log\n  if [[ -n \"${CTID:-}\" && -n \"${SESSION_ID:-}\" ]] && command -v pct &>/dev/null; then\n    local combined_log=\"/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log\"\n    if [[ ! -s \"$combined_log\" ]]; then\n      # Create combined log\n      {\n        echo \"================================================================================\"\n        echo \"COMBINED INSTALLATION LOG - ${APP:-LXC}\"\n        echo \"Container ID: ${CTID}\"\n        echo \"Session ID: ${SESSION_ID}\"\n        echo \"Timestamp: $(date '+%Y-%m-%d %H:%M:%S')\"\n        echo \"================================================================================\"\n        echo \"\"\n      } >\"$combined_log\" 2>/dev/null || return 0\n      # Append BUILD_LOG if it exists\n      if [[ -f \"${BUILD_LOG:-}\" ]]; then\n        {\n          echo \"================================================================================\"\n          echo \"PHASE 1: CONTAINER CREATION (Host)\"\n          echo \"================================================================================\"\n          cat \"${BUILD_LOG}\"\n          echo \"\"\n        } >>\"$combined_log\"\n      fi\n      # Pull INSTALL_LOG from container (with timeout to prevent hangs on dead containers)\n      local temp_log=\"/tmp/.install-temp-${SESSION_ID}.log\"\n      if timeout 8 pct pull \"$CTID\" \"/root/.install-${SESSION_ID}.log\" \"$temp_log\" 2>/dev/null; then\n        {\n          echo \"================================================================================\"\n          echo \"PHASE 2: APPLICATION INSTALLATION (Container)\"\n          echo \"================================================================================\"\n          cat \"$temp_log\"\n          echo \"\"\n        } >>\"$combined_log\"\n        rm -f \"$temp_log\"\n      fi\n    fi\n    if [[ -s \"$combined_log\" ]]; then\n      INSTALL_LOG=\"$combined_log\"\n    fi\n  fi\n}\n\n# ==============================================================================\n# TRAP MANAGEMENT\n# ==============================================================================\n# All traps (ERR, EXIT, INT, TERM, HUP) are set by catch_errors() in\n# error_handler.func — called at the top of this file after sourcing.\n#\n# Do NOT set duplicate traps here. The handlers in error_handler.func\n# (on_exit, on_interrupt, on_terminate, on_hangup, error_handler) already:\n#   - Send telemetry via post_update_to_api / _send_abort_telemetry\n#   - Stop orphaned containers via _stop_container_if_installing\n#   - Collect logs via ensure_log_on_host\n#   - Clean up lock files and spinner processes\n#\n# Previously, inline traps here overwrote catch_errors() traps, causing:\n#   - error_handler() never fired (no error output, no cleanup dialog)\n#   - on_hangup() never fired (SSH disconnect → stuck records)\n#   - Duplicated logic in two places (hard to debug)\n# ==============================================================================\n"
  },
  {
    "path": "misc/cloud-init.func",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: community-scripts ORG\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/branch/main/LICENSE\n# Revision: 1\n\n# ==============================================================================\n# CLOUD-INIT.FUNC - VM CLOUD-INIT CONFIGURATION LIBRARY\n# ==============================================================================\n#\n# Universal helper library for Cloud-Init configuration in Proxmox VMs.\n# Provides functions for:\n#\n#   - Native Proxmox Cloud-Init setup (user, password, network, SSH keys)\n#   - Interactive configuration dialogs (whiptail)\n#   - IP address retrieval via qemu-guest-agent\n#   - Cloud-Init status monitoring and waiting\n#\n# Usage:\n#   source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/cloud-init.func)\n#   setup_cloud_init \"$VMID\" \"$STORAGE\" \"$HN\" \"yes\"\n#\n# Compatible with: Debian, Ubuntu, and all Cloud-Init enabled distributions\n# ==============================================================================\n\n# ==============================================================================\n# SECTION 1: CONFIGURATION DEFAULTS\n# ==============================================================================\n# These can be overridden before sourcing this library\n\n# Disable 'unbound variable' errors for this library (restored at end)\n_OLD_SET_STATE=$(set +o | grep -E 'set -(e|u|o)')\nset +u\n\nCLOUDINIT_DEFAULT_USER=\"${CLOUDINIT_DEFAULT_USER:-root}\"\nCLOUDINIT_DNS_SERVERS=\"${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}\"\nCLOUDINIT_SEARCH_DOMAIN=\"${CLOUDINIT_SEARCH_DOMAIN:-local}\"\nCLOUDINIT_SSH_KEYS=\"${CLOUDINIT_SSH_KEYS:-}\" # Empty by default - user must explicitly provide keys\n\n# ==============================================================================\n# SECTION 2: SSH KEY DISCOVERY AND SELECTION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# _ci_ssh_extract_keys_from_file - Extracts valid SSH public keys from a file\n# ------------------------------------------------------------------------------\nfunction _ci_ssh_extract_keys_from_file() {\n  local file=\"$1\"\n  [[ -f \"$file\" && -r \"$file\" ]] || return 0\n  grep -E '^(ssh-(rsa|ed25519|dss|ecdsa)|ecdsa-sha2-)' \"$file\" 2>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# _ci_ssh_discover_files - Scans standard paths for SSH keys\n# ------------------------------------------------------------------------------\nfunction _ci_ssh_discover_files() {\n  local -a cand=()\n  shopt -s nullglob\n  cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)\n  cand+=(/root/.ssh/*.pub)\n  cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)\n  shopt -u nullglob\n  printf '%s\\0' \"${cand[@]}\"\n}\n\n# ------------------------------------------------------------------------------\n# _ci_ssh_build_choices - Builds whiptail checklist from SSH key files\n#\n# Sets: CI_SSH_CHOICES (array), CI_SSH_COUNT (int), CI_SSH_MAPFILE (path)\n# ------------------------------------------------------------------------------\nfunction _ci_ssh_build_choices() {\n  local -a files=(\"$@\")\n  CI_SSH_CHOICES=()\n  CI_SSH_COUNT=0\n  CI_SSH_MAPFILE=\"$(mktemp)\"\n  local id key typ fp cmt base\n\n  for f in \"${files[@]}\"; do\n    [[ -f \"$f\" && -r \"$f\" ]] || continue\n    base=\"$(basename -- \"$f\")\"\n    # Skip known_hosts and private keys\n    case \"$base\" in\n    known_hosts | known_hosts.* | config) continue ;;\n    id_*) [[ \"$f\" != *.pub ]] && continue ;;\n    esac\n\n    while IFS= read -r key; do\n      [[ -n \"$key\" ]] || continue\n\n      typ=\"\"\n      fp=\"\"\n      cmt=\"\"\n      read -r _typ _b64 _cmt <<<\"$key\"\n      typ=\"${_typ:-key}\"\n      cmt=\"${_cmt:-}\"\n\n      # Get fingerprint via ssh-keygen if available\n      if command -v ssh-keygen >/dev/null 2>&1; then\n        fp=\"$(printf '%s\\n' \"$key\" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')\"\n      fi\n\n      # Shorten long comments\n      [[ ${#cmt} -gt 40 ]] && cmt=\"${cmt:0:37}...\"\n\n      CI_SSH_COUNT=$((CI_SSH_COUNT + 1))\n      id=\"K${CI_SSH_COUNT}\"\n      echo \"${id}|${key}\" >>\"$CI_SSH_MAPFILE\"\n      CI_SSH_CHOICES+=(\"$id\" \"[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}\" \"OFF\")\n    done < <(_ci_ssh_extract_keys_from_file \"$f\")\n  done\n}\n\n# ------------------------------------------------------------------------------\n# configure_cloudinit_ssh_keys - Interactive SSH key selection for Cloud-Init\n#\n# Usage: configure_cloudinit_ssh_keys\n# Sets: CLOUDINIT_SSH_KEYS (path to temporary file with selected keys)\n# ------------------------------------------------------------------------------\nfunction configure_cloudinit_ssh_keys() {\n  local backtitle=\"Proxmox VE Helper Scripts\"\n  local ssh_key_mode\n\n  # Create temp file for selected keys\n  CLOUDINIT_SSH_KEYS_TEMP=\"$(mktemp)\"\n  : >\"$CLOUDINIT_SSH_KEYS_TEMP\"\n\n  # Discover keys and build choices\n  IFS=$'\\0' read -r -d '' -a _def_files < <(_ci_ssh_discover_files && printf '\\0')\n  _ci_ssh_build_choices \"${_def_files[@]}\"\n  local default_key_count=\"$CI_SSH_COUNT\"\n\n  if [[ \"$default_key_count\" -gt 0 ]]; then\n    ssh_key_mode=$(whiptail --backtitle \"$backtitle\" --title \"SSH KEY SOURCE\" --menu \\\n      \"Provision SSH keys for Cloud-Init VM:\" 14 72 4 \\\n      \"found\" \"Select from detected keys (${default_key_count})\" \\\n      \"manual\" \"Paste a single public key\" \\\n      \"folder\" \"Scan another folder (path or glob)\" \\\n      \"none\" \"No SSH keys (password auth only)\" 3>&1 1>&2 2>&3) || return 1\n  else\n    ssh_key_mode=$(whiptail --backtitle \"$backtitle\" --title \"SSH KEY SOURCE\" --menu \\\n      \"No host keys detected. Choose:\" 12 72 3 \\\n      \"manual\" \"Paste a single public key\" \\\n      \"folder\" \"Scan another folder (path or glob)\" \\\n      \"none\" \"No SSH keys (password auth only)\" 3>&1 1>&2 2>&3) || return 1\n  fi\n\n  case \"$ssh_key_mode\" in\n  found)\n    # Show checklist with individual keys\n    local selection\n    selection=$(whiptail --backtitle \"$backtitle\" --title \"SELECT SSH KEYS\" \\\n      --checklist \"Select one or more keys to import:\" 20 140 10 \"${CI_SSH_CHOICES[@]}\" 3>&1 1>&2 2>&3) || return 1\n\n    for tag in $selection; do\n      tag=\"${tag%\\\"}\"\n      tag=\"${tag#\\\"}\"\n      local line\n      line=$(grep -E \"^${tag}\\|\" \"$CI_SSH_MAPFILE\" | head -n1 | cut -d'|' -f2-)\n      [[ -n \"$line\" ]] && printf '%s\\n' \"$line\" >>\"$CLOUDINIT_SSH_KEYS_TEMP\"\n    done\n    local imported\n    imported=$(wc -l <\"$CLOUDINIT_SSH_KEYS_TEMP\")\n    echo -e \"${ROOTSSH:-  🔑  }${BOLD}${DGN}SSH Keys: ${BGN}${imported} key(s) selected${CL}\"\n    ;;\n  manual)\n    local pubkey\n    pubkey=$(whiptail --backtitle \"$backtitle\" --title \"PASTE SSH PUBLIC KEY\" \\\n      --inputbox \"Paste your SSH public key (ssh-rsa, ssh-ed25519, etc.):\" 10 76 3>&1 1>&2 2>&3) || return 1\n    if [[ -n \"$pubkey\" ]]; then\n      echo \"$pubkey\" >\"$CLOUDINIT_SSH_KEYS_TEMP\"\n      echo -e \"${ROOTSSH:-  🔑  }${BOLD}${DGN}SSH Keys: ${BGN}1 key added manually${CL}\"\n    else\n      echo -e \"${ROOTSSH:-  🔑  }${BOLD}${DGN}SSH Keys: ${BGN}none (empty input)${CL}\"\n      CLOUDINIT_SSH_KEYS=\"\"\n      rm -f \"$CLOUDINIT_SSH_KEYS_TEMP\" \"$CI_SSH_MAPFILE\" 2>/dev/null\n      return 0\n    fi\n    ;;\n  folder)\n    local glob_path\n    glob_path=$(whiptail --backtitle \"$backtitle\" --title \"SCAN FOLDER/GLOB\" \\\n      --inputbox \"Enter a folder or glob to scan (e.g. /root/.ssh/*.pub):\" 10 72 3>&1 1>&2 2>&3) || return 1\n    if [[ -n \"$glob_path\" ]]; then\n      shopt -s nullglob\n      local -a _scan_files=($glob_path)\n      shopt -u nullglob\n      if [[ \"${#_scan_files[@]}\" -gt 0 ]]; then\n        _ci_ssh_build_choices \"${_scan_files[@]}\"\n        if [[ \"$CI_SSH_COUNT\" -gt 0 ]]; then\n          local folder_selection\n          folder_selection=$(whiptail --backtitle \"$backtitle\" --title \"SELECT FOLDER KEYS\" \\\n            --checklist \"Select key(s) to import:\" 20 140 10 \"${CI_SSH_CHOICES[@]}\" 3>&1 1>&2 2>&3) || return 1\n          for tag in $folder_selection; do\n            tag=\"${tag%\\\"}\"\n            tag=\"${tag#\\\"}\"\n            local line\n            line=$(grep -E \"^${tag}\\|\" \"$CI_SSH_MAPFILE\" | head -n1 | cut -d'|' -f2-)\n            [[ -n \"$line\" ]] && printf '%s\\n' \"$line\" >>\"$CLOUDINIT_SSH_KEYS_TEMP\"\n          done\n          local imported\n          imported=$(wc -l <\"$CLOUDINIT_SSH_KEYS_TEMP\")\n          echo -e \"${ROOTSSH:-  🔑  }${BOLD}${DGN}SSH Keys: ${BGN}${imported} key(s) from folder${CL}\"\n        else\n          whiptail --backtitle \"$backtitle\" --msgbox \"No keys found in: $glob_path\" 8 60\n        fi\n      else\n        whiptail --backtitle \"$backtitle\" --msgbox \"Path/glob returned no files.\" 8 60\n      fi\n    fi\n    ;;\n  none | *)\n    echo -e \"${ROOTSSH:-  🔑  }${BOLD}${DGN}SSH Keys: ${BGN}none (password auth only)${CL}\"\n    CLOUDINIT_SSH_KEYS=\"\"\n    rm -f \"$CLOUDINIT_SSH_KEYS_TEMP\" \"$CI_SSH_MAPFILE\" 2>/dev/null\n    return 0\n    ;;\n  esac\n\n  # Cleanup mapfile\n  rm -f \"$CI_SSH_MAPFILE\" 2>/dev/null\n\n  # Set the variable for setup_cloud_init to use\n  if [[ -s \"$CLOUDINIT_SSH_KEYS_TEMP\" ]]; then\n    CLOUDINIT_SSH_KEYS=\"$CLOUDINIT_SSH_KEYS_TEMP\"\n  else\n    CLOUDINIT_SSH_KEYS=\"\"\n    rm -f \"$CLOUDINIT_SSH_KEYS_TEMP\"\n  fi\n\n  return 0\n}\n\n# ==============================================================================\n# SECTION 3: HELPER FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# _ci_msg - Internal message helper with fallback\n# ------------------------------------------------------------------------------\nfunction _ci_msg_info() { msg_info \"$1\" 2>/dev/null || echo \"[INFO] $1\"; }\nfunction _ci_msg_ok() { msg_ok \"$1\" 2>/dev/null || echo \"[OK] $1\"; }\nfunction _ci_msg_warn() { msg_warn \"$1\" 2>/dev/null || echo \"[WARN] $1\"; }\nfunction _ci_msg_error() { msg_error \"$1\" 2>/dev/null || echo \"[ERROR] $1\"; }\n\n# ------------------------------------------------------------------------------\n# validate_ip_cidr - Validate IP address in CIDR format\n# Usage: validate_ip_cidr \"192.168.1.100/24\" && echo \"Valid\"\n# Returns: 0 if valid, 1 if invalid\n# ------------------------------------------------------------------------------\nfunction validate_ip_cidr() {\n  local ip_cidr=\"$1\"\n  # Match: 0-255.0-255.0-255.0-255/0-32\n  if [[ \"$ip_cidr\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then\n    # Validate each octet is 0-255\n    local ip=\"${ip_cidr%/*}\"\n    IFS='.' read -ra octets <<<\"$ip\"\n    for octet in \"${octets[@]}\"; do\n      ((octet > 255)) && return 1\n    done\n    return 0\n  fi\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# validate_ip - Validate plain IP address (no CIDR)\n# Usage: validate_ip \"192.168.1.1\" && echo \"Valid\"\n# ------------------------------------------------------------------------------\nfunction validate_ip() {\n  local ip=\"$1\"\n  if [[ \"$ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n    IFS='.' read -ra octets <<<\"$ip\"\n    for octet in \"${octets[@]}\"; do\n      ((octet > 255)) && return 1\n    done\n    return 0\n  fi\n  return 1\n}\n\n# ==============================================================================\n# SECTION 3: MAIN CLOUD-INIT FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# setup_cloud_init - Configures Proxmox Native Cloud-Init\n# ------------------------------------------------------------------------------\n# Parameters:\n#   $1 - VMID (required)\n#   $2 - Storage name (required)\n#   $3 - Hostname (optional, default: vm-<vmid>)\n#   $4 - Enable Cloud-Init (yes/no, default: no)\n#   $5 - User (optional, default: root)\n#   $6 - Network mode (dhcp/static, default: dhcp)\n#   $7 - Static IP (optional, format: 192.168.1.100/24)\n#   $8 - Gateway (optional)\n#   $9 - Nameservers (optional, default: 1.1.1.1 8.8.8.8)\n#\n# Returns: 0 on success, 1 on failure\n# Exports: CLOUDINIT_USER, CLOUDINIT_PASSWORD, CLOUDINIT_CRED_FILE\n# ==============================================================================\nfunction setup_cloud_init() {\n  local vmid=\"$1\"\n  local storage=\"$2\"\n  local hostname=\"${3:-vm-${vmid}}\"\n  local enable=\"${4:-no}\"\n  local ciuser=\"${5:-$CLOUDINIT_DEFAULT_USER}\"\n  local network_mode=\"${6:-dhcp}\"\n  local static_ip=\"${7:-}\"\n  local gateway=\"${8:-}\"\n  local nameservers=\"${9:-$CLOUDINIT_DNS_SERVERS}\"\n\n  # Skip if not enabled\n  if [ \"$enable\" != \"yes\" ]; then\n    return 0\n  fi\n\n  # Validate static IP if provided\n  if [ \"$network_mode\" = \"static\" ]; then\n    if [ -n \"$static_ip\" ] && ! validate_ip_cidr \"$static_ip\"; then\n      _ci_msg_error \"Invalid static IP format: $static_ip (expected: x.x.x.x/xx)\"\n      return 65\n    fi\n    if [ -n \"$gateway\" ] && ! validate_ip \"$gateway\"; then\n      _ci_msg_error \"Invalid gateway IP format: $gateway\"\n      return 65\n    fi\n  fi\n\n  _ci_msg_info \"Configuring Cloud-Init\"\n\n  # Create Cloud-Init drive (try ide2 first, then scsi1 as fallback)\n  if ! qm set \"$vmid\" --ide2 \"${storage}:cloudinit\" >/dev/null 2>&1; then\n    qm set \"$vmid\" --scsi1 \"${storage}:cloudinit\" >/dev/null 2>&1\n  fi\n\n  # Set user\n  qm set \"$vmid\" --ciuser \"$ciuser\" >/dev/null\n\n  # Generate and set secure random password\n  local cipassword=$(openssl rand -base64 16)\n  qm set \"$vmid\" --cipassword \"$cipassword\" >/dev/null\n\n  # Add SSH keys only if explicitly provided (not auto-imported from host)\n  if [ -n \"${CLOUDINIT_SSH_KEYS:-}\" ] && [ -f \"$CLOUDINIT_SSH_KEYS\" ]; then\n    qm set \"$vmid\" --sshkeys \"$CLOUDINIT_SSH_KEYS\" >/dev/null 2>&1 || true\n    _ci_msg_info \"SSH keys imported from: $CLOUDINIT_SSH_KEYS\"\n  fi\n\n  # Configure network\n  if [ \"$network_mode\" = \"static\" ] && [ -n \"$static_ip\" ] && [ -n \"$gateway\" ]; then\n    qm set \"$vmid\" --ipconfig0 \"ip=${static_ip},gw=${gateway}\" >/dev/null\n  else\n    qm set \"$vmid\" --ipconfig0 \"ip=dhcp\" >/dev/null\n  fi\n\n  # Set DNS servers\n  qm set \"$vmid\" --nameserver \"$nameservers\" >/dev/null\n\n  # Set search domain\n  qm set \"$vmid\" --searchdomain \"$CLOUDINIT_SEARCH_DOMAIN\" >/dev/null\n\n  # Enable package upgrades on first boot (if supported by Proxmox version)\n  qm set \"$vmid\" --ciupgrade 1 >/dev/null 2>&1 || true\n\n  # Save credentials to file (with restrictive permissions)\n  local cred_file=\"/tmp/${hostname}-${vmid}-cloud-init-credentials.txt\"\n  umask 077\n  cat >\"$cred_file\" <<EOF\n╔══════════════════════════════════════════════════════════════════╗\n║  ⚠️  SECURITY WARNING: DELETE THIS FILE AFTER NOTING CREDENTIALS  ║\n╚══════════════════════════════════════════════════════════════════╝\n\nCloud-Init Credentials\n────────────────────────────────────────\nVM ID:    ${vmid}\nHostname: ${hostname}\nCreated:  $(date)\n\nUsername: ${ciuser}\nPassword: ${cipassword}\n\nNetwork:  ${network_mode}$([ \"$network_mode\" = \"static\" ] && echo \" (IP: ${static_ip}, GW: ${gateway})\" || echo \" (DHCP)\")\nDNS:      ${nameservers}\n\n────────────────────────────────────────\nSSH Access (if keys configured):\n  ssh ${ciuser}@<vm-ip>\n\nProxmox UI Configuration:\n  VM ${vmid} > Cloud-Init > Edit\n  - User, Password, SSH Keys\n  - Network (IP Config)\n  - DNS, Search Domain\n\n────────────────────────────────────────\n🗑️  To delete this file:\n  rm -f ${cred_file}\n────────────────────────────────────────\nEOF\n  chmod 600 \"$cred_file\"\n\n  _ci_msg_ok \"Cloud-Init configured (User: ${ciuser})\"\n\n  # Export for use in calling script (DO NOT display password here - will be shown in summary)\n  export CLOUDINIT_USER=\"$ciuser\"\n  export CLOUDINIT_PASSWORD=\"$cipassword\"\n  export CLOUDINIT_CRED_FILE=\"$cred_file\"\n\n  return 0\n}\n\n# ==============================================================================\n# SECTION 4: INTERACTIVE CONFIGURATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# configure_cloud_init_interactive - Whiptail dialog for Cloud-Init setup\n# ------------------------------------------------------------------------------\n# Prompts user for Cloud-Init configuration choices\n# Returns configuration via exported variables:\n#   - CLOUDINIT_ENABLE (yes/no)\n#   - CLOUDINIT_USER\n#   - CLOUDINIT_NETWORK_MODE (dhcp/static)\n#   - CLOUDINIT_IP (if static)\n#   - CLOUDINIT_GW (if static)\n#   - CLOUDINIT_DNS\n# ------------------------------------------------------------------------------\nfunction configure_cloud_init_interactive() {\n  local default_user=\"${1:-root}\"\n\n  # Check if whiptail is available\n  if ! command -v whiptail >/dev/null 2>&1; then\n    echo \"Warning: whiptail not available, skipping interactive configuration\"\n    export CLOUDINIT_ENABLE=\"no\"\n    return 127\n  fi\n\n  # Ask if user wants to enable Cloud-Init\n  if ! (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CLOUD-INIT\" \\\n    --yesno \"Enable Cloud-Init for VM configuration?\\n\\nCloud-Init allows automatic configuration of:\\n• User accounts and passwords\\n• SSH keys\\n• Network settings (DHCP/Static)\\n• DNS configuration\\n\\nYou can also configure these settings later in Proxmox UI.\" 16 68); then\n    export CLOUDINIT_ENABLE=\"no\"\n    return 0\n  fi\n\n  export CLOUDINIT_ENABLE=\"yes\"\n\n  # Username\n  if CLOUDINIT_USER=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \\\n    \"Cloud-Init Username\" 8 58 \"$default_user\" --title \"USERNAME\" 3>&1 1>&2 2>&3); then\n    export CLOUDINIT_USER=\"${CLOUDINIT_USER:-$default_user}\"\n  else\n    export CLOUDINIT_USER=\"$default_user\"\n  fi\n\n  # Network configuration\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"NETWORK MODE\" \\\n    --yesno \"Use DHCP for network configuration?\\n\\nSelect 'No' for static IP configuration.\" 10 58); then\n    export CLOUDINIT_NETWORK_MODE=\"dhcp\"\n  else\n    export CLOUDINIT_NETWORK_MODE=\"static\"\n\n    # Static IP with validation\n    while true; do\n      if CLOUDINIT_IP=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \\\n        \"Static IP Address (CIDR format)\\nExample: 192.168.1.100/24\" 9 58 \"\" --title \"IP ADDRESS\" 3>&1 1>&2 2>&3); then\n        if validate_ip_cidr \"$CLOUDINIT_IP\"; then\n          export CLOUDINIT_IP\n          break\n        else\n          whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID IP\" \\\n            --msgbox \"Invalid IP format: $CLOUDINIT_IP\\n\\nPlease use CIDR format: x.x.x.x/xx\\nExample: 192.168.1.100/24\" 10 50\n        fi\n      else\n        _ci_msg_warn \"Static IP required, falling back to DHCP\"\n        export CLOUDINIT_NETWORK_MODE=\"dhcp\"\n        break\n      fi\n    done\n\n    # Gateway with validation\n    if [ \"$CLOUDINIT_NETWORK_MODE\" = \"static\" ]; then\n      while true; do\n        if CLOUDINIT_GW=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \\\n          \"Gateway IP Address\\nExample: 192.168.1.1\" 8 58 \"\" --title \"GATEWAY\" 3>&1 1>&2 2>&3); then\n          if validate_ip \"$CLOUDINIT_GW\"; then\n            export CLOUDINIT_GW\n            break\n          else\n            whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID GATEWAY\" \\\n              --msgbox \"Invalid gateway format: $CLOUDINIT_GW\\n\\nPlease use format: x.x.x.x\\nExample: 192.168.1.1\" 10 50\n          fi\n        else\n          _ci_msg_warn \"Gateway required, falling back to DHCP\"\n          export CLOUDINIT_NETWORK_MODE=\"dhcp\"\n          break\n        fi\n      done\n    fi\n  fi\n\n  # DNS Servers\n  if CLOUDINIT_DNS=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \\\n    \"DNS Servers (space-separated)\" 8 58 \"1.1.1.1 8.8.8.8\" --title \"DNS SERVERS\" 3>&1 1>&2 2>&3); then\n    export CLOUDINIT_DNS=\"${CLOUDINIT_DNS:-1.1.1.1 8.8.8.8}\"\n  else\n    export CLOUDINIT_DNS=\"1.1.1.1 8.8.8.8\"\n  fi\n\n  return 0\n}\n\n# ==============================================================================\n# SECTION 5: UTILITY FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# display_cloud_init_info - Show Cloud-Init summary after setup\n# ------------------------------------------------------------------------------\nfunction display_cloud_init_info() {\n  local vmid=\"$1\"\n  local hostname=\"${2:-}\"\n\n  if [ -n \"$CLOUDINIT_CRED_FILE\" ] && [ -f \"$CLOUDINIT_CRED_FILE\" ]; then\n    if [ -n \"${INFO:-}\" ]; then\n      echo -e \"\\n${INFO}${BOLD:-}${GN:-} Cloud-Init Configuration:${CL:-}\"\n      echo -e \"${TAB:-  }${DGN:-}User: ${BGN:-}${CLOUDINIT_USER:-root}${CL:-}\"\n      echo -e \"${TAB:-  }${DGN:-}Password: ${BGN:-}${CLOUDINIT_PASSWORD}${CL:-}\"\n      echo -e \"${TAB:-  }${DGN:-}Credentials: ${BL:-}${CLOUDINIT_CRED_FILE}${CL:-}\"\n      echo -e \"${TAB:-  }${RD:-}⚠️  Delete credentials file after noting password!${CL:-}\"\n      echo -e \"${TAB:-  }${YW:-}💡 Configure in Proxmox UI: VM ${vmid} > Cloud-Init${CL:-}\"\n    else\n      echo \"\"\n      echo \"[INFO] Cloud-Init Configuration:\"\n      echo \"  User: ${CLOUDINIT_USER:-root}\"\n      echo \"  Password: ${CLOUDINIT_PASSWORD}\"\n      echo \"  Credentials: ${CLOUDINIT_CRED_FILE}\"\n      echo \"  ⚠️  Delete credentials file after noting password!\"\n      echo \"  Configure in Proxmox UI: VM ${vmid} > Cloud-Init\"\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# cleanup_cloud_init_credentials - Remove credentials file\n# ------------------------------------------------------------------------------\n# Usage: cleanup_cloud_init_credentials\n# Call this after user has noted/saved the credentials\n# ------------------------------------------------------------------------------\nfunction cleanup_cloud_init_credentials() {\n  if [ -n \"$CLOUDINIT_CRED_FILE\" ] && [ -f \"$CLOUDINIT_CRED_FILE\" ]; then\n    rm -f \"$CLOUDINIT_CRED_FILE\"\n    _ci_msg_ok \"Credentials file removed: $CLOUDINIT_CRED_FILE\"\n    unset CLOUDINIT_CRED_FILE\n    return 0\n  fi\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# has_cloud_init - Check if VM has Cloud-Init configured\n# ------------------------------------------------------------------------------\nfunction has_cloud_init() {\n  local vmid=\"$1\"\n  qm config \"$vmid\" 2>/dev/null | grep -qE \"(ide2|scsi1):.*cloudinit\"\n}\n\n# ------------------------------------------------------------------------------\n# regenerate_cloud_init - Regenerate Cloud-Init configuration\n# ------------------------------------------------------------------------------\nfunction regenerate_cloud_init() {\n  local vmid=\"$1\"\n\n  if has_cloud_init \"$vmid\"; then\n    _ci_msg_info \"Regenerating Cloud-Init configuration\"\n    qm cloudinit update \"$vmid\" >/dev/null 2>&1 || true\n    _ci_msg_ok \"Cloud-Init configuration regenerated\"\n    return 0\n  else\n    _ci_msg_warn \"VM $vmid does not have Cloud-Init configured\"\n    return 1\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# get_vm_ip - Get VM IP address via qemu-guest-agent\n# ------------------------------------------------------------------------------\nfunction get_vm_ip() {\n  local vmid=\"$1\"\n  local timeout=\"${2:-30}\"\n\n  local elapsed=0\n  while [ $elapsed -lt $timeout ]; do\n    local vm_ip=$(qm guest cmd \"$vmid\" network-get-interfaces 2>/dev/null |\n      jq -r '.[] | select(.name != \"lo\") | .\"ip-addresses\"[]? | select(.\"ip-address-type\" == \"ipv4\") | .\"ip-address\"' 2>/dev/null | head -1)\n\n    if [ -n \"$vm_ip\" ]; then\n      echo \"$vm_ip\"\n      return 0\n    fi\n\n    sleep 2\n    elapsed=$((elapsed + 2))\n  done\n\n  return 7\n}\n\n# ------------------------------------------------------------------------------\n# wait_for_cloud_init - Wait for Cloud-Init to complete (requires SSH access)\n# ------------------------------------------------------------------------------\nfunction wait_for_cloud_init() {\n  local vmid=\"$1\"\n  local timeout=\"${2:-300}\"\n  local vm_ip=\"${3:-}\"\n\n  # Get IP if not provided\n  if [ -z \"$vm_ip\" ]; then\n    vm_ip=$(get_vm_ip \"$vmid\" 60)\n  fi\n\n  if [ -z \"$vm_ip\" ]; then\n    _ci_msg_warn \"Unable to determine VM IP address\"\n    return 7\n  fi\n\n  _ci_msg_info \"Waiting for Cloud-Init to complete on ${vm_ip}\"\n\n  local elapsed=0\n  while [ $elapsed -lt $timeout ]; do\n    if timeout 10 ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\\n      \"${CLOUDINIT_USER:-root}@${vm_ip}\" \"cloud-init status --wait\" 2>/dev/null; then\n      _ci_msg_ok \"Cloud-Init completed successfully\"\n      return 0\n    fi\n    sleep 10\n    elapsed=$((elapsed + 10))\n  done\n\n  _ci_msg_warn \"Cloud-Init did not complete within ${timeout}s\"\n  return 150\n}\n\n# ==============================================================================\n# SECTION 6: EXPORTS\n# ==============================================================================\n# Export all functions for use in other scripts\n\nexport -f setup_cloud_init 2>/dev/null || true\nexport -f configure_cloud_init_interactive 2>/dev/null || true\nexport -f display_cloud_init_info 2>/dev/null || true\nexport -f cleanup_cloud_init_credentials 2>/dev/null || true\nexport -f has_cloud_init 2>/dev/null || true\nexport -f regenerate_cloud_init 2>/dev/null || true\nexport -f get_vm_ip 2>/dev/null || true\nexport -f wait_for_cloud_init 2>/dev/null || true\nexport -f validate_ip_cidr 2>/dev/null || true\nexport -f validate_ip 2>/dev/null || true\n\n# Restore previous shell options if they were saved\nif [ -n \"${_OLD_SET_STATE:-}\" ]; then\n  eval \"$_OLD_SET_STATE\"\nfi\n\n# ==============================================================================\n# SECTION 7: EXAMPLES & DOCUMENTATION\n# ==============================================================================\n: <<'EXAMPLES'\n\n# Example 1: Simple DHCP setup (most common)\nsetup_cloud_init \"$VMID\" \"$STORAGE\" \"$HN\" \"yes\"\n\n# Example 2: Static IP setup\nsetup_cloud_init \"$VMID\" \"$STORAGE\" \"myserver\" \"yes\" \"root\" \"static\" \"192.168.1.100/24\" \"192.168.1.1\"\n\n# Example 3: Interactive configuration in advanced_settings()\nconfigure_cloud_init_interactive \"admin\"\nif [ \"$CLOUDINIT_ENABLE\" = \"yes\" ]; then\n  setup_cloud_init \"$VMID\" \"$STORAGE\" \"$HN\" \"yes\" \"$CLOUDINIT_USER\" \\\n    \"$CLOUDINIT_NETWORK_MODE\" \"$CLOUDINIT_IP\" \"$CLOUDINIT_GW\" \"$CLOUDINIT_DNS\"\nfi\n\n# Example 4: Display info after VM creation\ndisplay_cloud_init_info \"$VMID\" \"$HN\"\n\n# Example 5: Check if VM has Cloud-Init\nif has_cloud_init \"$VMID\"; then\n    echo \"Cloud-Init is configured\"\nfi\n\n# Example 6: Wait for Cloud-Init to complete after VM start\nif [ \"$START_VM\" = \"yes\" ]; then\n    qm start \"$VMID\"\n    sleep 30\n    wait_for_cloud_init \"$VMID\" 300\nfi\n\n# Example 7: Cleanup credentials file after user has noted password\ndisplay_cloud_init_info \"$VMID\" \"$HN\"\nread -p \"Have you saved the credentials? (y/N): \" -r\n[[ $REPLY =~ ^[Yy]$ ]] && cleanup_cloud_init_credentials\n\n# Example 8: Validate IP before using\nif validate_ip_cidr \"192.168.1.100/24\"; then\n  echo \"Valid IP/CIDR\"\nfi\n\nEXAMPLES\n"
  },
  {
    "path": "misc/core.func",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# License: MIT | https://raw.githubusercontent.com/asylumexp/Proxmox/main/LICENSE\n\n# ==============================================================================\n# CORE FUNCTIONS - LXC CONTAINER UTILITIES\n# ==============================================================================\n#\n# This file provides core utility functions for LXC container management\n# including colors, formatting, validation checks, message output, and\n# execution helpers used throughout the Community-Scripts ecosystem.\n#\n# Usage:\n#   source <(curl -fsSL https://git.community-scripts.org/.../core.func)\n#   load_functions\n#\n# ==============================================================================\n\n[[ -n \"${_CORE_FUNC_LOADED:-}\" ]] && return\n_CORE_FUNC_LOADED=1\n\n# ==============================================================================\n# SECTION 1: INITIALIZATION & SETUP\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# load_functions()\n#\n# - Initializes all core utility groups (colors, formatting, icons, defaults)\n# - Ensures functions are loaded only once via __FUNCTIONS_LOADED flag\n# - Must be called at start of any script using these utilities\n# ------------------------------------------------------------------------------\nload_functions() {\n  [[ -n \"${__FUNCTIONS_LOADED:-}\" ]] && return\n  __FUNCTIONS_LOADED=1\n  color\n  formatting\n  icons\n  default_vars\n  set_std_mode\n}\n\n# ------------------------------------------------------------------------------\n# color()\n#\n# - Sets ANSI color codes for styled terminal output\n# - Variables: YW (yellow), YWB (yellow bright), BL (blue), RD (red)\n#             GN (green), DGN (dark green), BGN (background green), CL (clear)\n# ------------------------------------------------------------------------------\ncolor() {\n  YW=$(echo \"\\033[33m\")\n  YWB=$'\\e[93m'\n  BL=$(echo \"\\033[36m\")\n  RD=$(echo \"\\033[01;31m\")\n  BGN=$(echo \"\\033[4;92m\")\n  GN=$(echo \"\\033[1;92m\")\n  DGN=$(echo \"\\033[32m\")\n  CL=$(echo \"\\033[m\")\n}\n\n# ------------------------------------------------------------------------------\n# color_spinner()\n#\n# - Sets ANSI color codes specifically for spinner animation\n# - Variables: CS_YW (spinner yellow), CS_YWB (spinner yellow bright),\n#             CS_CL (spinner clear)\n# - Used by spinner() function to avoid color conflicts\n# ------------------------------------------------------------------------------\ncolor_spinner() {\n  CS_YW=$'\\033[33m'\n  CS_YWB=$'\\033[93m'\n  CS_CL=$'\\033[m'\n}\n\n# ------------------------------------------------------------------------------\n# formatting()\n#\n# - Defines formatting helpers for terminal output\n# - BFR: Backspace and clear line sequence\n# - BOLD: Bold text escape code\n# - TAB/TAB3: Indentation spacing\n# ------------------------------------------------------------------------------\nformatting() {\n  BFR=\"\\\\r\\\\033[K\"\n  BOLD=$(echo \"\\033[1m\")\n  HOLD=\" \"\n  TAB=\"  \"\n  TAB3=\"      \"\n}\n\n# ------------------------------------------------------------------------------\n# icons()\n#\n# - Sets symbolic emoji icons used throughout user feedback\n# - Provides consistent visual indicators for success, error, info, etc.\n# - Icons: CM (checkmark), CROSS (error), INFO (info), HOURGLASS (wait), etc.\n# ------------------------------------------------------------------------------\nicons() {\n  CM=\"${TAB}✔️${TAB}\"\n  CROSS=\"${TAB}✖️${TAB}\"\n  DNSOK=\"✔️ \"\n  DNSFAIL=\"${TAB}✖️${TAB}\"\n  INFO=\"${TAB}💡${TAB}${CL}\"\n  OS=\"${TAB}🖥️${TAB}${CL}\"\n  OSVERSION=\"${TAB}🌟${TAB}${CL}\"\n  CONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\n  DISKSIZE=\"${TAB}💾${TAB}${CL}\"\n  CPUCORE=\"${TAB}🧠${TAB}${CL}\"\n  RAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\n  SEARCH=\"${TAB}🔍${TAB}${CL}\"\n  VERBOSE_CROPPED=\"🔍${TAB}\"\n  VERIFYPW=\"${TAB}🔐${TAB}${CL}\"\n  CONTAINERID=\"${TAB}🆔${TAB}${CL}\"\n  HOSTNAME=\"${TAB}🏠${TAB}${CL}\"\n  BRIDGE=\"${TAB}🌉${TAB}${CL}\"\n  NETWORK=\"${TAB}📡${TAB}${CL}\"\n  GATEWAY=\"${TAB}🌐${TAB}${CL}\"\n  ICON_DISABLEIPV6=\"${TAB}🚫${TAB}${CL}\"\n  DEFAULT=\"${TAB}⚙️${TAB}${CL}\"\n  MACADDRESS=\"${TAB}🔗${TAB}${CL}\"\n  VLANTAG=\"${TAB}🏷️${TAB}${CL}\"\n  ROOTSSH=\"${TAB}🔑${TAB}${CL}\"\n  CREATING=\"${TAB}🚀${TAB}${CL}\"\n  ADVANCED=\"${TAB}🧩${TAB}${CL}\"\n  FUSE=\"${TAB}🗂️${TAB}${CL}\"\n  GPU=\"${TAB}🎮${TAB}${CL}\"\n  HOURGLASS=\"${TAB}⏳${TAB}\"\n}\n\n# ------------------------------------------------------------------------------\n# ensure_profile_loaded()\n#\n# - Sources /etc/profile.d/*.sh scripts if not already loaded\n# - Fixes PATH issues when running via pct enter/exec (non-login shells)\n# - Safe to call multiple times (uses guard variable)\n# - Should be called in update_script() or any script running inside LXC\n# ------------------------------------------------------------------------------\nensure_profile_loaded() {\n  # Skip if already loaded or running on Proxmox host\n  [[ -n \"${_PROFILE_LOADED:-}\" ]] && return\n  command -v pveversion &>/dev/null && return\n\n  # Source all profile.d scripts to ensure PATH is complete\n  if [[ -d /etc/profile.d ]]; then\n    for script in /etc/profile.d/*.sh; do\n      [[ -r \"$script\" ]] && source \"$script\" || true\n    done\n  fi\n\n  # Also ensure /usr/local/bin is in PATH (common install location)\n  if [[ \":$PATH:\" != *\":/usr/local/bin:\"* ]]; then\n    export PATH=\"/usr/local/bin:$PATH\"\n  fi\n\n  export _PROFILE_LOADED=1\n}\n\n# ------------------------------------------------------------------------------\n# default_vars()\n#\n# - Sets default retry and wait variables used for system actions\n# - RETRY_NUM: Maximum number of retry attempts (default: 10)\n# - RETRY_EVERY: Seconds to wait between retries (default: 3)\n# - i: Counter variable initialized to RETRY_NUM\n# ------------------------------------------------------------------------------\ndefault_vars() {\n  RETRY_NUM=10\n  RETRY_EVERY=3\n  i=$RETRY_NUM\n}\n\n# ------------------------------------------------------------------------------\n# set_std_mode()\n#\n# - Sets default verbose mode for script and OS execution\n# - If VERBOSE=yes: STD=\"\" (show all output)\n# - If VERBOSE=no: STD=\"silent\" (suppress output via silent() wrapper)\n# - If DEV_MODE_TRACE=true: Enables bash tracing (set -x)\n# ------------------------------------------------------------------------------\nset_std_mode() {\n  if [ \"${VERBOSE:-no}\" = \"yes\" ]; then\n    STD=\"\"\n  else\n    STD=\"silent\"\n  fi\n\n  # Enable bash tracing if trace mode active\n  if [[ \"${DEV_MODE_TRACE:-false}\" == \"true\" ]]; then\n    set -x\n    export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# parse_dev_mode()\n#\n# - Parses comma-separated dev_mode variable (e.g., \"motd,keep,trace\")\n# - Sets global flags for each mode:\n#   * DEV_MODE_MOTD: Setup SSH/MOTD before installation\n#   * DEV_MODE_KEEP: Never delete container on failure\n#   * DEV_MODE_TRACE: Enable bash set -x tracing\n#   * DEV_MODE_PAUSE: Pause after each msg_info step\n#   * DEV_MODE_BREAKPOINT: Open shell on error instead of cleanup\n#   * DEV_MODE_LOGS: Persist all logs to /var/log/community-scripts/\n#   * DEV_MODE_DRYRUN: Show commands without executing\n# - Call this early in script execution\n# ------------------------------------------------------------------------------\nparse_dev_mode() {\n  local mode\n  # Initialize all flags to false\n  export DEV_MODE_MOTD=false\n  export DEV_MODE_KEEP=false\n  export DEV_MODE_TRACE=false\n  export DEV_MODE_PAUSE=false\n  export DEV_MODE_BREAKPOINT=false\n  export DEV_MODE_LOGS=false\n  export DEV_MODE_DRYRUN=false\n\n  # Parse comma-separated modes\n  if [[ -n \"${dev_mode:-}\" ]]; then\n    IFS=',' read -ra MODES <<<\"$dev_mode\"\n    for mode in \"${MODES[@]}\"; do\n      mode=\"$(echo \"$mode\" | xargs)\" # Trim whitespace\n      case \"$mode\" in\n      motd) export DEV_MODE_MOTD=true ;;\n      keep) export DEV_MODE_KEEP=true ;;\n      trace) export DEV_MODE_TRACE=true ;;\n      pause) export DEV_MODE_PAUSE=true ;;\n      breakpoint) export DEV_MODE_BREAKPOINT=true ;;\n      logs) export DEV_MODE_LOGS=true ;;\n      dryrun) export DEV_MODE_DRYRUN=true ;;\n      *)\n        if declare -f msg_warn >/dev/null 2>&1; then\n          msg_warn \"Unknown dev_mode: '$mode' (ignored)\"\n        else\n          echo \"[WARN] Unknown dev_mode: '$mode' (ignored)\" >&2\n        fi\n        ;;\n      esac\n    done\n\n    # Show active dev modes\n    local active_modes=()\n    [[ $DEV_MODE_MOTD == true ]] && active_modes+=(\"motd\")\n    [[ $DEV_MODE_KEEP == true ]] && active_modes+=(\"keep\")\n    [[ $DEV_MODE_TRACE == true ]] && active_modes+=(\"trace\")\n    [[ $DEV_MODE_PAUSE == true ]] && active_modes+=(\"pause\")\n    [[ $DEV_MODE_BREAKPOINT == true ]] && active_modes+=(\"breakpoint\")\n    [[ $DEV_MODE_LOGS == true ]] && active_modes+=(\"logs\")\n    [[ $DEV_MODE_DRYRUN == true ]] && active_modes+=(\"dryrun\")\n\n    if [[ ${#active_modes[@]} -gt 0 ]]; then\n      if declare -f msg_custom >/dev/null 2>&1; then\n        msg_custom \"🔧\" \"${YWB}\" \"Dev modes active: ${active_modes[*]}\"\n      else\n        echo \"[DEV] Active modes: ${active_modes[*]}\" >&2\n      fi\n    fi\n  fi\n}\n\n# ==============================================================================\n# SECTION 2: VALIDATION CHECKS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# shell_check()\n#\n# - Verifies that the script is running under Bash shell\n# - Exits with error message if different shell is detected\n# - Required because scripts use Bash-specific features\n# ------------------------------------------------------------------------------\nshell_check() {\n  if [[ \"$(ps -p $$ -o comm=)\" != \"bash\" ]]; then\n    clear\n    msg_error \"Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit 103\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# root_check()\n#\n# - Verifies script is running with root privileges\n# - Detects if executed via sudo (which can cause issues)\n# - Exits with error if not running as root directly\n# ------------------------------------------------------------------------------\nroot_check() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit 104\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# pve_check()\n#\n# - Validates Proxmox VE version compatibility\n# - Supported: PVE 8.0-8.9 and PVE 9.0-9.1\n# - Exits with error message if unsupported version detected\n# ------------------------------------------------------------------------------\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0–9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not yet supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.9 or 9.0 – 9.1\"\n  exit 105\n}\n\n# ------------------------------------------------------------------------------\n# arch_check()\n#\n# - Validates system architecture is amd64/x86_64\n# - Exits with error message for unsupported architectures (e.g., ARM/PiMox)\n# - Provides link to ARM64-compatible scripts\n# ------------------------------------------------------------------------------\narch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    msg_error \"This script will not work with AMD64.\"\n    msg_warn \"Visit https://github.com/asylumexp/Proxmox for ARM64 support.\"\n    sleep 2\n    exit 106\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# ssh_check()\n#\n# - Detects if script is running over SSH connection\n# - Warns user for external SSH connections (recommends Proxmox shell)\n# - Skips warning for local/same-subnet connections\n# - Does not abort execution, only warns\n# ------------------------------------------------------------------------------\nssh_check() {\n  if [ -n \"$SSH_CLIENT\" ]; then\n    local client_ip=$(awk '{print $1}' <<<\"$SSH_CLIENT\")\n    local host_ip=$(hostname -I | awk '{print $1}')\n\n    # Check if connection is local (Proxmox WebUI or same machine)\n    # - localhost (127.0.0.1, ::1)\n    # - same IP as host\n    # - local network range (10.x, 172.16-31.x, 192.168.x)\n    if [[ \"$client_ip\" == \"127.0.0.1\" || \"$client_ip\" == \"::1\" || \"$client_ip\" == \"$host_ip\" ]]; then\n      return\n    fi\n\n    # Check if client is in same local network (optional, safer approach)\n    local host_subnet=$(echo \"$host_ip\" | cut -d. -f1-3)\n    local client_subnet=$(echo \"$client_ip\" | cut -d. -f1-3)\n    if [[ \"$host_subnet\" == \"$client_subnet\" ]]; then\n      return\n    fi\n\n    # Only warn for truly external connections\n    msg_warn \"Running via external SSH (client: $client_ip).\"\n    msg_warn \"For better stability, consider using the Proxmox Shell (Console) instead.\"\n  fi\n}\n\n# ==============================================================================\n# SECTION 3: EXECUTION HELPERS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# get_active_logfile()\n#\n# - Returns the appropriate log file based on execution context\n# - _HOST_LOGFILE: Override for host context (keeps host logging on BUILD_LOG\n#   even after INSTALL_LOG is exported for the container)\n# - INSTALL_LOG: Container operations (application installation)\n# - BUILD_LOG: Host operations (container creation)\n# - Fallback to BUILD_LOG if neither is set\n# ------------------------------------------------------------------------------\nget_active_logfile() {\n  # Host override: _HOST_LOGFILE is set (not exported) in build.func to keep\n  # host-side logging in BUILD_LOG after INSTALL_LOG is exported for the container.\n  # Without this, all host msg_info/msg_ok/msg_error would write to\n  # /root/.install-SESSION.log (a container path) instead of BUILD_LOG.\n  if [[ -n \"${_HOST_LOGFILE:-}\" ]]; then\n    echo \"$_HOST_LOGFILE\"\n  elif [[ -n \"${INSTALL_LOG:-}\" ]]; then\n    echo \"$INSTALL_LOG\"\n  elif [[ -n \"${BUILD_LOG:-}\" ]]; then\n    echo \"$BUILD_LOG\"\n  else\n    # Fallback for legacy scripts\n    echo \"/tmp/build-$(date +%Y%m%d_%H%M%S).log\"\n  fi\n}\n\n# Legacy compatibility: SILENT_LOGFILE points to active log\nSILENT_LOGFILE=\"$(get_active_logfile)\"\n\n# ------------------------------------------------------------------------------\n# strip_ansi()\n#\n# - Removes ANSI escape sequences from input text\n# - Used to clean colored output for log files\n# - Handles both piped input and arguments\n# ------------------------------------------------------------------------------\nstrip_ansi() {\n  if [[ $# -gt 0 ]]; then\n    echo -e \"$*\" | sed 's/\\x1b\\[[0-9;]*m//g; s/\\x1b\\[[0-9;]*[a-zA-Z]//g'\n  else\n    sed 's/\\x1b\\[[0-9;]*m//g; s/\\x1b\\[[0-9;]*[a-zA-Z]//g'\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# log_msg()\n#\n# - Writes message to active log file without ANSI codes\n# - Adds timestamp prefix for log correlation\n# - Creates log file if it doesn't exist\n# - Arguments: message text (can include ANSI codes, will be stripped)\n# ------------------------------------------------------------------------------\nlog_msg() {\n  local msg=\"$*\"\n  local logfile\n  logfile=\"$(get_active_logfile)\"\n\n  [[ -z \"$msg\" ]] && return\n  [[ -z \"$logfile\" ]] && return\n\n  # Ensure log directory exists\n  mkdir -p \"$(dirname \"$logfile\")\" 2>/dev/null || true\n\n  # Strip ANSI codes and write with timestamp\n  local clean_msg\n  clean_msg=$(strip_ansi \"$msg\")\n  echo \"[$(date '+%Y-%m-%d %H:%M:%S')] $clean_msg\" >>\"$logfile\"\n}\n\n# ------------------------------------------------------------------------------\n# log_section()\n#\n# - Writes a section header to the log file\n# - Used for separating different phases of installation\n# - Arguments: section name\n# ------------------------------------------------------------------------------\nlog_section() {\n  local section=\"$1\"\n  local logfile\n  logfile=\"$(get_active_logfile)\"\n\n  [[ -z \"$logfile\" ]] && return\n  mkdir -p \"$(dirname \"$logfile\")\" 2>/dev/null || true\n\n  {\n    echo \"\"\n    echo \"================================================================================\"\n    echo \"[$(date '+%Y-%m-%d %H:%M:%S')] $section\"\n    echo \"================================================================================\"\n  } >>\"$logfile\"\n}\n\n# ------------------------------------------------------------------------------\n# silent()\n#\n# - Executes command with output redirected to active log file\n# - On error: displays last 20 lines of log and exits with original exit code\n# - Temporarily disables error trap to capture exit code correctly\n# - Saves and restores previous error handling state (so callers that\n#   intentionally disabled error handling aren't silently re-enabled)\n# - Sources explain_exit_code() for detailed error messages\n# ------------------------------------------------------------------------------\nsilent() {\n  local cmd=\"$*\"\n  local caller_line=\"${BASH_LINENO[0]:-unknown}\"\n  local logfile=\"$(get_active_logfile)\"\n\n  # Dryrun mode: Show command without executing\n  if [[ \"${DEV_MODE_DRYRUN:-false}\" == \"true\" ]]; then\n    if declare -f msg_custom >/dev/null 2>&1; then\n      msg_custom \"🔍\" \"${BL}\" \"[DRYRUN] $cmd\"\n    else\n      echo \"[DRYRUN] $cmd\" >&2\n    fi\n    return 0\n  fi\n\n  # Save current error handling state before disabling\n  # This prevents re-enabling error handling when the caller intentionally\n  # disabled it (e.g. build_container recovery section)\n  local _restore_errexit=false\n  [[ \"$-\" == *e* ]] && _restore_errexit=true\n\n  set +Eeuo pipefail\n  trap - ERR\n\n  \"$@\" >>\"$logfile\" 2>&1\n  local rc=$?\n\n  # Restore error handling ONLY if it was active before this call\n  if $_restore_errexit; then\n    set -Eeuo pipefail\n    trap 'error_handler' ERR\n  fi\n\n  if [[ $rc -ne 0 ]]; then\n    # Source explain_exit_code if needed\n    if ! declare -f explain_exit_code >/dev/null 2>&1; then\n      if ! source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func); then\n        explain_exit_code() { echo \"unknown (error_handler.func download failed)\"; }\n      fi\n    fi\n\n    # Return instead of exit so that callers can use `$STD cmd || true`\n    # or `if $STD cmd; then ...` to handle errors gracefully.\n    # When no || / if is used, set -e + ERR trap will still catch it\n    # and error_handler() will display the error and exit.\n    #\n    # Set flag so error_handler knows to show log tail from silent's logfile\n    export _SILENT_FAILED_RC=\"$rc\"\n    export _SILENT_FAILED_CMD=\"$cmd\"\n    export _SILENT_FAILED_LINE=\"$caller_line\"\n    export _SILENT_FAILED_LOG=\"$logfile\"\n\n    return \"$rc\"\n  fi\n\n  # Clear stale flags on success (prevents false positives if a previous\n  # $STD cmd || true failed and a later non-silent command triggers error_handler)\n  unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE _SILENT_FAILED_LOG 2>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# apt_update_safe()\n#\n# - Runs apt-get update with graceful error handling\n# - On failure: shows warning with common causes instead of aborting\n# - Logs full output to active log file\n# - Returns 0 even on failure so the caller can continue\n# - Typical cause: enterprise repos returning 401 Unauthorized\n#\n# Usage:\n#   apt_update_safe       # Warn on failure, continue without aborting\n# ------------------------------------------------------------------------------\napt_update_safe() {\n  local logfile\n  logfile=\"$(get_active_logfile)\"\n\n  local _restore_errexit=false\n  [[ \"$-\" == *e* ]] && _restore_errexit=true\n\n  set +Eeuo pipefail\n  trap - ERR\n\n  apt-get update >>\"$logfile\" 2>&1\n  local rc=$?\n\n  if $_restore_errexit; then\n    set -Eeuo pipefail\n    trap 'error_handler' ERR\n  fi\n\n  if [[ $rc -ne 0 ]]; then\n    msg_warn \"apt-get update exited with code ${rc} — some repositories may have failed.\"\n\n    # Check log for common 401/403 enterprise repo issues\n    if grep -qiE '401\\s*Unauthorized|403\\s*Forbidden|enterprise\\.proxmox\\.com' \"$logfile\" 2>/dev/null; then\n      echo -e \"${TAB}${INFO} ${YWB}Hint: Proxmox enterprise repository returned an auth error.${CL}\"\n      echo -e \"${TAB}       If you don't have a subscription, you can disable the enterprise\"\n      echo -e \"${TAB}       repo and use the no-subscription repo instead.\"\n    fi\n\n    echo -e \"${TAB}${INFO} ${YWB}Continuing despite partial update failure — packages may still be installable.${CL}\"\n    echo \"\"\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# spinner()\n#\n# - Displays animated spinner with rotating characters (⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)\n# - Shows SPINNER_MSG alongside animation\n# - Runs in infinite loop until killed by stop_spinner()\n# - Uses color_spinner() colors for output\n# ------------------------------------------------------------------------------\nspinner() {\n  local chars=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)\n  local msg=\"${SPINNER_MSG:-Processing...}\"\n  local i=0\n  while true; do\n    local index=$((i++ % ${#chars[@]}))\n    printf \"\\r\\033[2K%s %b\" \"${CS_YWB}${chars[$index]}${CS_CL}\" \"${CS_YWB}${msg}${CS_CL}\"\n    sleep 0.1\n  done\n}\n\n# ------------------------------------------------------------------------------\n# clear_line()\n#\n# - Clears current terminal line using tput or ANSI escape codes\n# - Moves cursor to beginning of line (carriage return)\n# - Erases from cursor to end of line\n# - Fallback to ANSI codes if tput not available\n# ------------------------------------------------------------------------------\nclear_line() {\n  tput cr 2>/dev/null || echo -en \"\\r\"\n  tput el 2>/dev/null || echo -en \"\\033[K\"\n}\n\n# ------------------------------------------------------------------------------\n# stop_spinner()\n#\n# - Stops running spinner process by PID\n# - Reads PID from SPINNER_PID variable or /tmp/.spinner.pid file\n# - Attempts graceful kill, then forced kill if needed\n# - Cleans up temp file and resets terminal state\n# - Unsets SPINNER_PID and SPINNER_MSG variables\n# ------------------------------------------------------------------------------\nstop_spinner() {\n  local pid=\"${SPINNER_PID:-}\"\n  [[ -z \"$pid\" && -f /tmp/.spinner.pid ]] && pid=$(</tmp/.spinner.pid)\n\n  if [[ -n \"$pid\" && \"$pid\" =~ ^[0-9]+$ ]]; then\n    if kill \"$pid\" 2>/dev/null; then\n      sleep 0.05\n      kill -9 \"$pid\" 2>/dev/null || true\n      wait \"$pid\" 2>/dev/null || true\n    fi\n    rm -f /tmp/.spinner.pid\n  fi\n\n  unset SPINNER_PID SPINNER_MSG\n  stty sane 2>/dev/null || true\n  stty -tostop 2>/dev/null || true\n}\n\n# ==============================================================================\n# SECTION 4: MESSAGE OUTPUT\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# msg_info()\n#\n# - Displays informational message with spinner animation\n# - Shows each unique message only once (tracked via MSG_INFO_SHOWN)\n# - In verbose/Alpine mode: shows hourglass icon instead of spinner\n# - Stops any existing spinner before starting new one\n# - Backgrounds spinner process and stores PID for later cleanup\n# ------------------------------------------------------------------------------\nmsg_info() {\n  local msg=\"$1\"\n  [[ -z \"$msg\" ]] && return\n\n  if ! declare -p MSG_INFO_SHOWN &>/dev/null || ! declare -A MSG_INFO_SHOWN &>/dev/null; then\n    declare -gA MSG_INFO_SHOWN=()\n  fi\n  [[ -n \"${MSG_INFO_SHOWN[\"$msg\"]+x}\" ]] && return\n  MSG_INFO_SHOWN[\"$msg\"]=1\n\n  # Log to file\n  log_msg \"[INFO] $msg\"\n\n  stop_spinner\n  SPINNER_MSG=\"$msg\"\n\n  if is_verbose_mode || is_alpine; then\n    local HOURGLASS=\"${TAB}⏳${TAB}\"\n    printf \"\\r\\e[2K%s %b\" \"$HOURGLASS\" \"${YW}${msg}${CL}\" >&2\n\n    # Pause mode: Wait for Enter after each step\n    if [[ \"${DEV_MODE_PAUSE:-false}\" == \"true\" ]]; then\n      echo -en \"\\n${YWB}[PAUSE]${CL} Press Enter to continue...\" >&2\n      read -r\n    fi\n    return\n  fi\n\n  color_spinner\n  spinner &\n  SPINNER_PID=$!\n  echo \"$SPINNER_PID\" >/tmp/.spinner.pid\n  disown \"$SPINNER_PID\" 2>/dev/null || true\n\n  # Pause mode: Stop spinner and wait\n  if [[ \"${DEV_MODE_PAUSE:-false}\" == \"true\" ]]; then\n    stop_spinner\n    echo -en \"\\n${YWB}[PAUSE]${CL} Press Enter to continue...\" >&2\n    read -r\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# msg_ok()\n#\n# - Displays success message with checkmark icon\n# - Stops spinner and clears line before output\n# - Removes message from MSG_INFO_SHOWN to allow re-display\n# - Uses green color for success indication\n# ------------------------------------------------------------------------------\nmsg_ok() {\n  local msg=\"$1\"\n  [[ -z \"$msg\" ]] && return\n  stop_spinner\n  clear_line\n  echo -e \"$CM ${GN}${msg}${CL}\"\n  log_msg \"[OK] $msg\"\n  local sanitized_msg\n  sanitized_msg=$(printf '%s' \"$msg\" | sed 's/\\x1b\\[[0-9;]*m//g; s/[^a-zA-Z0-9_]/_/g')\n  unset 'MSG_INFO_SHOWN['\"$sanitized_msg\"']' 2>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# msg_error()\n#\n# - Displays error message with cross/X icon\n# - Stops spinner before output\n# - Uses red color for error indication\n# - Outputs to stderr\n# ------------------------------------------------------------------------------\nmsg_error() {\n  stop_spinner\n  local msg=\"$1\"\n  echo -e \"${BFR:-}${CROSS:-✖️} ${RD}${msg}${CL}\" >&2\n  log_msg \"[ERROR] $msg\"\n}\n\n# ------------------------------------------------------------------------------\n# msg_warn()\n#\n# - Displays warning message with info/lightbulb icon\n# - Stops spinner before output\n# - Uses bright yellow color for warning indication\n# - Outputs to stderr\n# ------------------------------------------------------------------------------\nmsg_warn() {\n  stop_spinner\n  local msg=\"$1\"\n  echo -e \"${BFR:-}${INFO:-ℹ️} ${YWB}${msg}${CL}\" >&2\n  log_msg \"[WARN] $msg\"\n}\n\n# ------------------------------------------------------------------------------\n# msg_custom()\n#\n# - Displays custom message with user-defined symbol and color\n# - Arguments: symbol, color code, message text\n# - Stops spinner before output\n# - Useful for specialized status messages\n# ------------------------------------------------------------------------------\nmsg_custom() {\n  local symbol=\"${1:-\"[*]\"}\"\n  local color=\"${2:-\"\\e[36m\"}\"\n  local msg=\"${3:-}\"\n  [[ -z \"$msg\" ]] && return\n  stop_spinner\n  echo -e \"${BFR:-} ${symbol} ${color}${msg}${CL:-\\e[0m}\"\n  log_msg \"$msg\"\n}\n\n# ------------------------------------------------------------------------------\n# msg_debug()\n#\n# - Displays debug message with timestamp when var_full_verbose=1\n# - Automatically enables var_verbose if not already set\n# - Shows date/time prefix for log correlation\n# - Uses bright yellow color for debug output\n# ------------------------------------------------------------------------------\nmsg_debug() {\n  if [[ \"${var_full_verbose:-0}\" == \"1\" ]]; then\n    [[ \"${var_verbose:-0}\" != \"1\" ]] && var_verbose=1\n    echo -e \"${YWB}[$(date '+%F %T')] [DEBUG]${CL} $*\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# msg_dev()\n#\n# - Display development mode messages with 🔧 icon\n# - Only shown when dev_mode is active\n# - Useful for debugging and development-specific output\n# - Format: [DEV] message with distinct formatting\n# - Usage: msg_dev \"Container ready for debugging\"\n# ------------------------------------------------------------------------------\nmsg_dev() {\n  if [[ -n \"${dev_mode:-}\" ]]; then\n    echo -e \"${SEARCH}${BOLD}${DGN}🔧 [DEV]${CL} $*\"\n  fi\n}\n#\n# - Displays error message and immediately terminates script\n# - Sends SIGINT to current process to trigger error handler\n# - Use for unrecoverable errors that require immediate exit\n# ------------------------------------------------------------------------------\nfatal() {\n  msg_error \"$1\"\n  kill -INT $$\n}\n\n# ==============================================================================\n# SECTION 5: UTILITY FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# exit_script()\n#\n# - Called when user cancels an action\n# - Clears screen and displays exit message\n# - Exits with default exit code\n# ------------------------------------------------------------------------------\nexit_script() {\n  clear\n  msg_error \"User exited script\"\n  exit 0\n}\n\n# ------------------------------------------------------------------------------\n# get_header()\n#\n# - Downloads and caches application header ASCII art\n# - Falls back to local cache if already downloaded\n# - Determines app type (ct/vm) from APP_TYPE variable\n# - Returns header content or empty string on failure\n# ------------------------------------------------------------------------------\nget_header() {\n  local app_name=$(echo \"${APP,,}\" | tr -d ' ')\n  local app_type=${APP_TYPE:-ct} # Default to 'ct' if not set\n  local header_dir=\"${app_type}\"\n  [[ \"$app_type\" == \"addon\" ]] && header_dir=\"tools\"\n  local header_url=\"https://raw.githubusercontent.com/asylumexp/Proxmox/main/${header_dir}/headers/${app_name}\"\n  local local_header_path=\"/usr/local/community-scripts/headers/${app_type}/${app_name}\"\n\n  mkdir -p \"$(dirname \"$local_header_path\")\"\n\n  if [ ! -s \"$local_header_path\" ]; then\n    if ! curl -fsSL \"$header_url\" -o \"$local_header_path\"; then\n      msg_warn \"Failed to download header: $header_url\"\n      return 250\n    fi\n  fi\n\n  cat \"$local_header_path\" 2>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# header_info()\n#\n# - Displays application header ASCII art at top of screen\n# - Clears screen before displaying header\n# - Detects terminal width for formatting\n# - Returns silently if header not available\n# ------------------------------------------------------------------------------\nheader_info() {\n  # Guard against printing the header twice in the same session (e.g. when\n  # the ct script calls header_info at global scope AND again inside\n  # update_script()).\n  [[ \"${_HEADER_SHOWN:-0}\" == \"1\" ]] && return 0\n  _HEADER_SHOWN=1\n\n  local app_name=$(echo \"${APP,,}\" | tr -d ' ')\n  local header_content\n\n  header_content=$(get_header \"$app_name\") || header_content=\"\"\n\n  clear\n  local term_width\n  term_width=$(tput cols 2>/dev/null || echo 120)\n\n  if [ -n \"$header_content\" ]; then\n    echo \"$header_content\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# ensure_tput()\n#\n# - Ensures tput command is available for terminal control\n# - Installs ncurses-bin on Debian/Ubuntu or ncurses on Alpine\n# - Required for clear_line() and terminal width detection\n# ------------------------------------------------------------------------------\nensure_tput() {\n  if ! command -v tput >/dev/null 2>&1; then\n    if grep -qi 'alpine' /etc/os-release; then\n      apk add --no-cache ncurses >/dev/null 2>&1 || msg_warn \"Failed to install ncurses (tput may be unavailable)\"\n    elif command -v apt-get >/dev/null 2>&1; then\n      apt-get update -qq >/dev/null\n      apt-get install -y -qq ncurses-bin >/dev/null 2>&1 || msg_warn \"Failed to install ncurses-bin (tput may be unavailable)\"\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# is_alpine()\n#\n# - Detects if running on Alpine Linux\n# - Checks var_os, PCT_OSTYPE, or /etc/os-release\n# - Returns 0 if Alpine, 1 otherwise\n# - Used to adjust behavior for Alpine-specific commands\n# ------------------------------------------------------------------------------\nis_alpine() {\n  local os_id=\"${var_os:-${PCT_OSTYPE:-}}\"\n\n  if [[ -z \"$os_id\" && -f /etc/os-release ]]; then\n    os_id=\"$(\n      . /etc/os-release 2>/dev/null\n      echo \"${ID:-}\"\n    )\"\n  fi\n\n  [[ \"$os_id\" == \"alpine\" ]]\n}\n\n# ------------------------------------------------------------------------------\n# is_verbose_mode()\n#\n# - Determines if script should run in verbose mode\n# - Checks VERBOSE and var_verbose variables\n# - Used by msg_info() to decide between spinner and static output\n# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info()\n#   to allow spinner output to pass through pipes (e.g. lxc-attach | tee)\n# ------------------------------------------------------------------------------\nis_verbose_mode() {\n  local verbose=\"${VERBOSE:-${var_verbose:-no}}\"\n  [[ \"$verbose\" != \"no\" ]]\n}\n\n# ------------------------------------------------------------------------------\n# is_unattended()\n#\n# - Detects if script is running in unattended/non-interactive mode\n# - Checks MODE variable first (primary method)\n# - Falls back to legacy flags (PHS_SILENT, var_unattended)\n# - Returns 0 (true) if unattended, 1 (false) otherwise\n# - Used by prompt functions to auto-apply defaults\n#\n# Modes that are unattended:\n#   - default (1)      : Use script defaults, no prompts\n#   - mydefaults (3)   : Use user's default.vars, no prompts\n#   - appdefaults (4)  : Use app-specific defaults, no prompts\n#\n# Modes that are interactive:\n#   - advanced (2)     : Full wizard with all options\n#\n# Note: Even in advanced mode, install scripts run unattended because\n#       all values are already collected during the wizard phase.\n# ------------------------------------------------------------------------------\nis_unattended() {\n  # Primary: Check MODE variable (case-insensitive)\n  local mode=\"${MODE:-${mode:-}}\"\n  mode=\"${mode,,}\" # lowercase\n\n  case \"$mode\" in\n  default | 1)\n    return 0\n    ;;\n  mydefaults | userdefaults | 3)\n    return 0\n    ;;\n  appdefaults | 4)\n    return 0\n    ;;\n  advanced | 2)\n    # Advanced mode is interactive ONLY during wizard\n    # Inside container (install scripts), it should be unattended\n    # Check if we're inside a container (no pveversion command)\n    if ! command -v pveversion &>/dev/null; then\n      # We're inside the container - all values already collected\n      return 0\n    fi\n    # On host during wizard - interactive\n    return 1\n    ;;\n  esac\n\n  # Legacy fallbacks for compatibility\n  [[ \"${PHS_SILENT:-0}\" == \"1\" ]] && return 0\n  [[ \"${var_unattended:-}\" =~ ^(yes|true|1)$ ]] && return 0\n  [[ \"${UNATTENDED:-}\" =~ ^(yes|true|1)$ ]] && return 0\n\n  # No TTY available = unattended\n  [[ ! -t 0 ]] && return 0\n\n  # Default: interactive\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# show_missing_values_warning()\n#\n# - Displays a summary of required values that used fallback defaults\n# - Should be called at the end of install scripts\n# - Only shows warning if MISSING_REQUIRED_VALUES array has entries\n# - Provides clear guidance on what needs manual configuration\n#\n# Global:\n#   MISSING_REQUIRED_VALUES - Array of variable names that need configuration\n#\n# Example:\n#   # At end of install script:\n#   show_missing_values_warning\n# ------------------------------------------------------------------------------\nshow_missing_values_warning() {\n  if [[ ${#MISSING_REQUIRED_VALUES[@]} -gt 0 ]]; then\n    echo \"\"\n    echo -e \"${YW}╔════════════════════════════════════════════════════════════╗${CL}\"\n    echo -e \"${YW}║  ⚠️  MANUAL CONFIGURATION REQUIRED                          ║${CL}\"\n    echo -e \"${YW}╠════════════════════════════════════════════════════════════╣${CL}\"\n    echo -e \"${YW}║  The following values were not provided and need to be     ║${CL}\"\n    echo -e \"${YW}║  configured manually for the service to work properly:     ║${CL}\"\n    echo -e \"${YW}╟────────────────────────────────────────────────────────────╢${CL}\"\n    for val in \"${MISSING_REQUIRED_VALUES[@]}\"; do\n      printf \"${YW}║${CL}  • %-56s ${YW}║${CL}\\n\" \"$val\"\n    done\n    echo -e \"${YW}╟────────────────────────────────────────────────────────────╢${CL}\"\n    echo -e \"${YW}║  Check the service configuration files or environment      ║${CL}\"\n    echo -e \"${YW}║  variables and update the placeholder values.              ║${CL}\"\n    echo -e \"${YW}╚════════════════════════════════════════════════════════════╝${CL}\"\n    echo \"\"\n    return 1\n  fi\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# prompt_confirm()\n#\n# - Prompts user for yes/no confirmation with timeout and unattended support\n# - In unattended mode: immediately returns default value\n# - In interactive mode: waits for user input with configurable timeout\n# - After timeout: auto-applies default value\n#\n# Arguments:\n#   $1 - Prompt message (required)\n#   $2 - Default value: \"y\" or \"n\" (optional, default: \"n\")\n#   $3 - Timeout in seconds (optional, default: 60)\n#\n# Returns:\n#   0 - User confirmed (yes)\n#   1 - User declined (no) or timeout with default \"n\"\n#\n# Example:\n#   if prompt_confirm \"Proceed with installation?\" \"y\" 30; then\n#     echo \"Installing...\"\n#   fi\n#\n#   # Unattended: prompt_confirm will use default without waiting\n#   var_unattended=yes\n#   prompt_confirm \"Delete files?\" \"n\" && echo \"Deleting\" || echo \"Skipped\"\n# ------------------------------------------------------------------------------\nprompt_confirm() {\n  local message=\"${1:-Confirm?}\"\n  local default=\"${2:-n}\"\n  local timeout=\"${3:-60}\"\n  local response\n\n  # Normalize default to lowercase\n  default=\"${default,,}\"\n  [[ \"$default\" != \"y\" ]] && default=\"n\"\n\n  # Build prompt hint\n  local hint\n  if [[ \"$default\" == \"y\" ]]; then\n    hint=\"[Y/n]\"\n  else\n    hint=\"[y/N]\"\n  fi\n\n  # Unattended mode: apply default immediately\n  if is_unattended; then\n    if [[ \"$default\" == \"y\" ]]; then\n      return 0\n    else\n      return 1\n    fi\n  fi\n\n  # Check if running in a TTY\n  if [[ ! -t 0 ]]; then\n    # Not a TTY, use default\n    if [[ \"$default\" == \"y\" ]]; then\n      return 0\n    else\n      return 1\n    fi\n  fi\n\n  # Interactive prompt with timeout\n  echo -en \"${YW}${message} ${hint} (auto-${default} in ${timeout}s): ${CL}\"\n\n  if read -t \"$timeout\" -r response; then\n    # User provided input\n    response=\"${response,,}\" # lowercase\n    case \"$response\" in\n    y | yes)\n      return 0\n      ;;\n    n | no)\n      return 1\n      ;;\n    \"\")\n      # Empty response, use default\n      if [[ \"$default\" == \"y\" ]]; then\n        return 0\n      else\n        return 1\n      fi\n      ;;\n    *)\n      # Invalid input, use default\n      echo -e \"${YW}Invalid response, using default: ${default}${CL}\"\n      if [[ \"$default\" == \"y\" ]]; then\n        return 0\n      else\n        return 1\n      fi\n      ;;\n    esac\n  else\n    # Timeout occurred\n    echo \"\" # Newline after timeout\n    echo -e \"${YW}Timeout - auto-selecting: ${default}${CL}\"\n    if [[ \"$default\" == \"y\" ]]; then\n      return 0\n    else\n      return 1\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# prompt_input()\n#\n# - Prompts user for text input with timeout and unattended support\n# - In unattended mode: immediately returns default value\n# - In interactive mode: waits for user input with configurable timeout\n# - After timeout: auto-applies default value\n#\n# Arguments:\n#   $1 - Prompt message (required)\n#   $2 - Default value (optional, default: \"\")\n#   $3 - Timeout in seconds (optional, default: 60)\n#\n# Output:\n#   Prints the user input or default value to stdout\n#\n# Example:\n#   username=$(prompt_input \"Enter username:\" \"admin\" 30)\n#   echo \"Using username: $username\"\n#\n#   # With validation\n#   while true; do\n#     port=$(prompt_input \"Enter port:\" \"8080\" 30)\n#     [[ \"$port\" =~ ^[0-9]+$ ]] && break\n#     echo \"Invalid port number\"\n#   done\n# ------------------------------------------------------------------------------\nprompt_input() {\n  local message=\"${1:-Enter value:}\"\n  local default=\"${2:-}\"\n  local timeout=\"${3:-60}\"\n  local response\n\n  # Build display default hint\n  local hint=\"\"\n  [[ -n \"$default\" ]] && hint=\" (default: ${default})\"\n\n  # Unattended mode: return default immediately\n  if is_unattended; then\n    echo \"$default\"\n    return 0\n  fi\n\n  # Check if running in a TTY\n  if [[ ! -t 0 ]]; then\n    # Not a TTY, use default\n    echo \"$default\"\n    return 0\n  fi\n\n  # Interactive prompt with timeout\n  echo -en \"${YW}${message}${hint} (auto-default in ${timeout}s): ${CL}\" >&2\n\n  if read -t \"$timeout\" -r response; then\n    # User provided input (or pressed Enter for empty)\n    if [[ -n \"$response\" ]]; then\n      echo \"$response\"\n    else\n      echo \"$default\"\n    fi\n  else\n    # Timeout occurred\n    echo \"\" >&2 # Newline after timeout\n    echo -e \"${YW}Timeout - using default: ${default}${CL}\" >&2\n    echo \"$default\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# prompt_input_required()\n#\n# - Prompts user for REQUIRED text input with fallback support\n# - In unattended mode: Uses fallback value if no env var set (with warning)\n# - In interactive mode: loops until user provides non-empty input\n# - Tracks missing required values for end-of-script summary\n#\n# Arguments:\n#   $1 - Prompt message (required)\n#   $2 - Fallback/example value for unattended mode (optional)\n#   $3 - Timeout in seconds (optional, default: 120)\n#   $4 - Environment variable name hint for error messages (optional)\n#\n# Output:\n#   Prints the user input or fallback value to stdout\n#\n# Returns:\n#   0 - Success (value provided or fallback used)\n#   1 - Failed (interactive timeout without input)\n#\n# Global:\n#   MISSING_REQUIRED_VALUES - Array tracking fields that used fallbacks\n#\n# Example:\n#   # With fallback - script continues even in unattended mode\n#   token=$(prompt_input_required \"Enter API Token:\" \"YOUR_TOKEN_HERE\" 60 \"var_api_token\")\n#\n#   # Check at end of script if any values need manual configuration\n#   if [[ ${#MISSING_REQUIRED_VALUES[@]} -gt 0 ]]; then\n#     msg_warn \"Please configure: ${MISSING_REQUIRED_VALUES[*]}\"\n#   fi\n# ------------------------------------------------------------------------------\n# Global array to track missing required values\ndeclare -g -a MISSING_REQUIRED_VALUES=()\n\nprompt_input_required() {\n  local message=\"${1:-Enter required value:}\"\n  local fallback=\"${2:-CHANGE_ME}\"\n  local timeout=\"${3:-120}\"\n  local env_var_hint=\"${4:-}\"\n  local response=\"\"\n\n  # Check if value is already set via environment variable (if hint provided)\n  if [[ -n \"$env_var_hint\" ]]; then\n    local env_value=\"${!env_var_hint:-}\"\n    if [[ -n \"$env_value\" ]]; then\n      echo \"$env_value\"\n      return 0\n    fi\n  fi\n\n  # Unattended mode: use fallback with warning\n  if is_unattended; then\n    if [[ -n \"$env_var_hint\" ]]; then\n      echo -e \"${YW}⚠ Required value '${env_var_hint}' not set - using fallback: ${fallback}${CL}\" >&2\n      MISSING_REQUIRED_VALUES+=(\"$env_var_hint\")\n    else\n      echo -e \"${YW}⚠ Required value not provided - using fallback: ${fallback}${CL}\" >&2\n      MISSING_REQUIRED_VALUES+=(\"(unnamed)\")\n    fi\n    echo \"$fallback\"\n    return 0\n  fi\n\n  # Check if running in a TTY\n  if [[ ! -t 0 ]]; then\n    echo -e \"${YW}⚠ Not interactive - using fallback: ${fallback}${CL}\" >&2\n    MISSING_REQUIRED_VALUES+=(\"${env_var_hint:-unnamed}\")\n    echo \"$fallback\"\n    return 0\n  fi\n\n  # Interactive prompt - loop until non-empty input or use fallback on timeout\n  local attempts=0\n  while [[ -z \"$response\" ]]; do\n    attempts=$((attempts + 1))\n\n    if [[ $attempts -gt 3 ]]; then\n      echo -e \"${YW}Too many empty inputs - using fallback: ${fallback}${CL}\" >&2\n      MISSING_REQUIRED_VALUES+=(\"${env_var_hint:-manual_input}\")\n      echo \"$fallback\"\n      return 0\n    fi\n\n    echo -en \"${YW}${message} (required, timeout ${timeout}s): ${CL}\" >&2\n\n    if read -t \"$timeout\" -r response; then\n      if [[ -z \"$response\" ]]; then\n        echo -e \"${YW}This field is required. Please enter a value. (attempt ${attempts}/3)${CL}\" >&2\n      fi\n    else\n      # Timeout occurred - use fallback\n      echo \"\" >&2\n      echo -e \"${YW}Timeout - using fallback value: ${fallback}${CL}\" >&2\n      MISSING_REQUIRED_VALUES+=(\"${env_var_hint:-timeout}\")\n      echo \"$fallback\"\n      return 0\n    fi\n  done\n\n  echo \"$response\"\n}\n\n# ------------------------------------------------------------------------------\n# prompt_select()\n#\n# - Prompts user to select from a list of options with timeout support\n# - In unattended mode: immediately returns default selection\n# - In interactive mode: displays numbered menu and waits for choice\n# - After timeout: auto-applies default selection\n#\n# Arguments:\n#   $1 - Prompt message (required)\n#   $2 - Default option number, 1-based (optional, default: 1)\n#   $3 - Timeout in seconds (optional, default: 60)\n#   $4+ - Options to display (required, at least 2)\n#\n# Output:\n#   Prints the selected option value to stdout\n#\n# Returns:\n#   0 - Success\n#   1 - No options provided or invalid state\n#\n# Example:\n#   choice=$(prompt_select \"Select database:\" 1 30 \"PostgreSQL\" \"MySQL\" \"SQLite\")\n#   echo \"Selected: $choice\"\n#\n#   # With array\n#   options=(\"Option A\" \"Option B\" \"Option C\")\n#   selected=$(prompt_select \"Choose:\" 2 60 \"${options[@]}\")\n# ------------------------------------------------------------------------------\nprompt_select() {\n  local message=\"${1:-Select option:}\"\n  local default=\"${2:-1}\"\n  local timeout=\"${3:-60}\"\n  shift 3\n\n  local options=(\"$@\")\n  local num_options=${#options[@]}\n\n  # Validate options\n  if [[ $num_options -eq 0 ]]; then\n    msg_warn \"prompt_select called with no options\"\n    echo \"\" >&2\n    return 65\n  fi\n\n  # Validate default\n  if [[ ! \"$default\" =~ ^[0-9]+$ ]] || [[ \"$default\" -lt 1 ]] || [[ \"$default\" -gt \"$num_options\" ]]; then\n    default=1\n  fi\n\n  # Unattended mode: return default immediately\n  if is_unattended; then\n    echo \"${options[$((default - 1))]}\"\n    return 0\n  fi\n\n  # Check if running in a TTY\n  if [[ ! -t 0 ]]; then\n    echo \"${options[$((default - 1))]}\"\n    return 0\n  fi\n\n  # Display menu\n  echo -e \"${YW}${message}${CL}\" >&2\n  local i\n  for i in \"${!options[@]}\"; do\n    local num=$((i + 1))\n    if [[ $num -eq $default ]]; then\n      echo -e \"  ${GN}${num})${CL} ${options[$i]} ${YW}(default)${CL}\" >&2\n    else\n      echo -e \"  ${GN}${num})${CL} ${options[$i]}\" >&2\n    fi\n  done\n\n  # Interactive prompt with timeout\n  echo -en \"${YW}Select [1-${num_options}] (auto-select ${default} in ${timeout}s): ${CL}\" >&2\n\n  local response\n  if read -t \"$timeout\" -r response; then\n    if [[ -z \"$response\" ]]; then\n      # Empty response, use default\n      echo \"${options[$((default - 1))]}\"\n    elif [[ \"$response\" =~ ^[0-9]+$ ]] && [[ \"$response\" -ge 1 ]] && [[ \"$response\" -le \"$num_options\" ]]; then\n      # Valid selection\n      echo \"${options[$((response - 1))]}\"\n    else\n      # Invalid input, use default\n      echo -e \"${YW}Invalid selection, using default: ${options[$((default - 1))]}${CL}\" >&2\n      echo \"${options[$((default - 1))]}\"\n    fi\n  else\n    # Timeout occurred\n    echo \"\" >&2 # Newline after timeout\n    echo -e \"${YW}Timeout - auto-selecting: ${options[$((default - 1))]}${CL}\" >&2\n    echo \"${options[$((default - 1))]}\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# prompt_password()\n#\n# - Prompts user for password input with hidden characters\n# - In unattended mode: returns default or generates random password\n# - Supports auto-generation of secure passwords\n# - After timeout: generates random password if allowed\n#\n# Arguments:\n#   $1 - Prompt message (required)\n#   $2 - Default value or \"generate\" for auto-generation (optional)\n#   $3 - Timeout in seconds (optional, default: 60)\n#   $4 - Minimum length for validation (optional, default: 0 = no minimum)\n#\n# Output:\n#   Prints the password to stdout\n#\n# Example:\n#   password=$(prompt_password \"Enter password:\" \"generate\" 30 8)\n#   echo \"Password set\"\n#\n#   # Require user input (no default)\n#   db_pass=$(prompt_password \"Database password:\" \"\" 60 12)\n# ------------------------------------------------------------------------------\nprompt_password() {\n  local message=\"${1:-Enter password:}\"\n  local default=\"${2:-}\"\n  local timeout=\"${3:-60}\"\n  local min_length=\"${4:-0}\"\n  local response\n\n  # Generate random password if requested\n  local generated=\"\"\n  if [[ \"$default\" == \"generate\" ]]; then\n    generated=$(openssl rand -base64 16 2>/dev/null | tr -dc 'a-zA-Z0-9' | head -c 16)\n    [[ -z \"$generated\" ]] && generated=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16)\n    default=\"$generated\"\n  fi\n\n  # Unattended mode: return default immediately\n  if is_unattended; then\n    echo \"$default\"\n    return 0\n  fi\n\n  # Check if running in a TTY\n  if [[ ! -t 0 ]]; then\n    echo \"$default\"\n    return 0\n  fi\n\n  # Build hint\n  local hint=\"\"\n  if [[ -n \"$generated\" ]]; then\n    hint=\" (Enter for auto-generated)\"\n  elif [[ -n \"$default\" ]]; then\n    hint=\" (Enter for default)\"\n  fi\n  [[ \"$min_length\" -gt 0 ]] && hint=\"${hint} [min ${min_length} chars]\"\n\n  # Interactive prompt with timeout (silent input)\n  echo -en \"${YW}${message}${hint} (timeout ${timeout}s): ${CL}\" >&2\n\n  if read -t \"$timeout\" -rs response; then\n    echo \"\" >&2 # Newline after hidden input\n    if [[ -n \"$response\" ]]; then\n      # Validate minimum length\n      if [[ \"$min_length\" -gt 0 ]] && [[ ${#response} -lt \"$min_length\" ]]; then\n        echo -e \"${YW}Password too short (min ${min_length}), using default${CL}\" >&2\n        echo \"$default\"\n      else\n        echo \"$response\"\n      fi\n    else\n      echo \"$default\"\n    fi\n  else\n    # Timeout occurred\n    echo \"\" >&2 # Newline after timeout\n    echo -e \"${YW}Timeout - using generated password${CL}\" >&2\n    echo \"$default\"\n  fi\n}\n\n# ==============================================================================\n# SECTION 6: CLEANUP & MAINTENANCE\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# cleanup_lxc()\n#\n# - Cleans package manager and language caches (safe for installs AND updates)\n# - Supports Alpine (apk), Debian/Ubuntu (apt), Python, Node.js, Go, Rust, Ruby, PHP\n# - Uses fallback error handling to prevent cleanup failures from breaking installs\n# ------------------------------------------------------------------------------\ncleanup_lxc() {\n  msg_info \"Cleaning up\"\n\n  if is_alpine; then\n    $STD apk cache clean || true\n    rm -rf /var/cache/apk/*\n  else\n    $STD apt -y autoremove 2>/dev/null || msg_warn \"apt autoremove failed (non-critical)\"\n    $STD apt -y autoclean 2>/dev/null || msg_warn \"apt autoclean failed (non-critical)\"\n    $STD apt -y clean 2>/dev/null || msg_warn \"apt clean failed (non-critical)\"\n  fi\n\n  find /tmp /var/tmp -type f -name 'tmp*' -delete 2>/dev/null || true\n  find /tmp /var/tmp -type f -name 'tempfile*' -delete 2>/dev/null || true\n\n  # Python\n  if command -v pip &>/dev/null; then\n    rm -rf /root/.cache/pip 2>/dev/null || true\n  fi\n  if command -v uv &>/dev/null; then\n    rm -rf /root/.cache/uv 2>/dev/null || true\n  fi\n\n  # Node.js\n  if command -v npm &>/dev/null; then\n    rm -rf /root/.npm/_cacache /root/.npm/_logs 2>/dev/null || true\n  fi\n  if command -v yarn &>/dev/null; then\n    rm -rf /root/.cache/yarn /root/.yarn/cache 2>/dev/null || true\n  fi\n  if command -v pnpm &>/dev/null; then\n    pnpm store prune &>/dev/null || true\n  fi\n\n  # Go (only build cache, not modules)\n  if command -v go &>/dev/null; then\n    $STD go clean -cache 2>/dev/null || true\n  fi\n\n  # Rust (only registry cache, not build artifacts)\n  if command -v cargo &>/dev/null; then\n    rm -rf /root/.cargo/registry/cache /root/.cargo/.package-cache 2>/dev/null || true\n  fi\n\n  # Ruby\n  if command -v gem &>/dev/null; then\n    rm -rf /root/.gem/cache 2>/dev/null || true\n  fi\n\n  # PHP\n  if command -v composer &>/dev/null; then\n    rm -rf /root/.composer/cache 2>/dev/null || true\n  fi\n\n  msg_ok \"Cleaned\"\n\n  # Send progress ping if available (defined in install.func)\n  if declare -f post_progress_to_api &>/dev/null; then\n    post_progress_to_api\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# check_or_create_swap()\n#\n# - Checks if swap is active on system\n# - Offers to create swap file if none exists\n# - Prompts user for swap size in MB\n# - Creates /swapfile with specified size\n# - Activates swap immediately\n# - Returns 0 if swap active or successfully created, 1 if declined/failed\n# ------------------------------------------------------------------------------\ncheck_or_create_swap() {\n  msg_info \"Checking for active swap\"\n\n  if swapon --noheadings --show | grep -q 'swap'; then\n    msg_ok \"Swap is active\"\n    return 0\n  fi\n\n  msg_error \"No active swap detected\"\n\n  if ! prompt_confirm \"Do you want to create a swap file?\" \"n\" 60; then\n    msg_info \"Skipping swap file creation\"\n    return 1\n  fi\n\n  local swap_size_mb\n  swap_size_mb=$(prompt_input \"Enter swap size in MB (e.g., 2048 for 2GB):\" \"2048\" 60)\n  if ! [[ \"$swap_size_mb\" =~ ^[0-9]+$ ]]; then\n    msg_error \"Invalid swap size: '${swap_size_mb}' (must be a number in MB)\"\n    return 65\n  fi\n\n  local swap_file=\"/swapfile\"\n\n  msg_info \"Creating ${swap_size_mb}MB swap file at $swap_file\"\n  if ! dd if=/dev/zero of=\"$swap_file\" bs=1M count=\"$swap_size_mb\" status=progress; then\n    msg_error \"Failed to allocate swap file (dd failed)\"\n    return 150\n  fi\n  if ! chmod 600 \"$swap_file\"; then\n    msg_error \"Failed to set permissions on $swap_file\"\n    return 150\n  fi\n  if ! mkswap \"$swap_file\"; then\n    msg_error \"Failed to format swap file (mkswap failed)\"\n    return 150\n  fi\n  if ! swapon \"$swap_file\"; then\n    msg_error \"Failed to activate swap (swapon failed)\"\n    return 150\n  fi\n  msg_ok \"Swap file created and activated successfully\"\n}\n\n# ------------------------------------------------------------------------------\n# Loads LOCAL_IP from persistent store or detects if missing.\n#\n# Description:\n#   - Loads from /run/local-ip.env or performs runtime lookup\n# ------------------------------------------------------------------------------\n\nfunction get_lxc_ip() {\n  local IP_FILE=\"/run/local-ip.env\"\n  if [[ -f \"$IP_FILE\" ]]; then\n    # shellcheck disable=SC1090\n    source \"$IP_FILE\"\n  fi\n\n  if [[ -z \"${LOCAL_IP:-}\" ]]; then\n    get_current_ip() {\n      local ip\n\n      # Try direct interface lookup for eth0 FIRST (most reliable for LXC) - IPv4\n      ip=$(ip -4 addr show eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)\n      if [[ -n \"$ip\" && \"$ip\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n        echo \"$ip\"\n        return 0\n      fi\n\n      # Fallback: Try hostname -I (returns IPv4 first if available)\n      if command -v hostname >/dev/null 2>&1; then\n        ip=$(hostname -I 2>/dev/null | awk '{print $1}')\n        if [[ -n \"$ip\" && \"$ip\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n          echo \"$ip\"\n          return 0\n        fi\n      fi\n\n      # Try routing table with IPv4 targets\n      local ipv4_targets=(\"8.8.8.8\" \"1.1.1.1\" \"default\")\n      for target in \"${ipv4_targets[@]}\"; do\n        if [[ \"$target\" == \"default\" ]]; then\n          ip=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i==\"src\") print $(i+1)}')\n        else\n          ip=$(ip route get \"$target\" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i==\"src\") print $(i+1)}')\n        fi\n        if [[ -n \"$ip\" ]]; then\n          echo \"$ip\"\n          return 0\n        fi\n      done\n\n      # IPv6 fallback: Try direct interface lookup for eth0\n      ip=$(ip -6 addr show eth0 scope global 2>/dev/null | awk '/inet6 / {print $2}' | cut -d/ -f1 | head -n1)\n      if [[ -n \"$ip\" && \"$ip\" =~ : ]]; then\n        echo \"$ip\"\n        return 0\n      fi\n\n      # IPv6 fallback: Try hostname -I for IPv6\n      if command -v hostname >/dev/null 2>&1; then\n        ip=$(hostname -I 2>/dev/null | tr ' ' '\\n' | grep -E ':' | head -n1)\n        if [[ -n \"$ip\" && \"$ip\" =~ : ]]; then\n          echo \"$ip\"\n          return 0\n        fi\n      fi\n\n      # IPv6 fallback: Use routing table with IPv6 targets\n      local ipv6_targets=(\"2001:4860:4860::8888\" \"2606:4700:4700::1111\")\n      for target in \"${ipv6_targets[@]}\"; do\n        ip=$(ip -6 route get \"$target\" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i==\"src\") print $(i+1)}')\n        if [[ -n \"$ip\" && \"$ip\" =~ : ]]; then\n          echo \"$ip\"\n          return 0\n        fi\n      done\n\n      return 6\n    }\n\n    LOCAL_IP=\"$(get_current_ip || true)\"\n    if [[ -z \"$LOCAL_IP\" ]]; then\n      msg_error \"Could not determine LOCAL_IP (checked: eth0, hostname -I, ip route, IPv6 targets)\"\n      return 6\n    fi\n  fi\n\n  export LOCAL_IP\n}\n\n# ==============================================================================\n# SIGNAL TRAPS\n# ==============================================================================\n\ntrap 'stop_spinner' EXIT INT TERM\n"
  },
  {
    "path": "misc/error_handler.func",
    "content": "#!/usr/bin/env bash\n# ------------------------------------------------------------------------------\n# ERROR HANDLER - ERROR & SIGNAL MANAGEMENT\n# ------------------------------------------------------------------------------\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# ------------------------------------------------------------------------------\n#\n# Provides comprehensive error handling and signal management for all scripts.\n# Includes:\n#   - Exit code explanations (shell, package managers, databases, custom codes)\n#   - Error handler with detailed logging\n#   - Signal handlers (EXIT, INT, TERM)\n#   - Initialization function for trap setup\n#\n# Usage:\n#   source <(curl -fsSL .../error_handler.func)\n#   catch_errors\n#\n# ------------------------------------------------------------------------------\n\n# ==============================================================================\n# SECTION 1: EXIT CODE EXPLANATIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# explain_exit_code()\n#\n# - Canonical version is defined in api.func (sourced before this file)\n# - This section only provides a fallback if api.func was not loaded\n# - See api.func SECTION 1 for the authoritative exit code mappings\n# ------------------------------------------------------------------------------\nif ! declare -f explain_exit_code &>/dev/null; then\n  explain_exit_code() {\n    local code=\"$1\"\n    case \"$code\" in\n    1) echo \"General error / Operation not permitted\" ;;\n    2) echo \"Misuse of shell builtins (e.g. syntax error)\" ;;\n    3) echo \"General syntax or argument error\" ;;\n    10) echo \"Docker / privileged mode required (unsupported environment)\" ;;\n    4) echo \"curl: Feature not supported or protocol error\" ;;\n    5) echo \"curl: Could not resolve proxy\" ;;\n    6) echo \"curl: DNS resolution failed (could not resolve host)\" ;;\n    7) echo \"curl: Failed to connect (network unreachable / host down)\" ;;\n    8) echo \"curl: Server reply error (FTP/SFTP or apk untrusted key)\" ;;\n    16) echo \"curl: HTTP/2 framing layer error\" ;;\n    18) echo \"curl: Partial file (transfer not completed)\" ;;\n    22) echo \"curl: HTTP error returned (404, 429, 500+)\" ;;\n    23) echo \"curl: Write error (disk full or permissions)\" ;;\n    24) echo \"curl: Write to local file failed\" ;;\n    25) echo \"curl: Upload failed\" ;;\n    26) echo \"curl: Read error on local file (I/O)\" ;;\n    27) echo \"curl: Out of memory (memory allocation failed)\" ;;\n    28) echo \"curl: Operation timeout (network slow or server not responding)\" ;;\n    30) echo \"curl: FTP port command failed\" ;;\n    32) echo \"curl: FTP SIZE command failed\" ;;\n    33) echo \"curl: HTTP range error\" ;;\n    34) echo \"curl: HTTP post error\" ;;\n    35) echo \"curl: SSL/TLS handshake failed (certificate error)\" ;;\n    36) echo \"curl: FTP bad download resume\" ;;\n    39) echo \"curl: LDAP search failed\" ;;\n    44) echo \"curl: Internal error (bad function call order)\" ;;\n    45) echo \"curl: Interface error (failed to bind to specified interface)\" ;;\n    46) echo \"curl: Bad password entered\" ;;\n    47) echo \"curl: Too many redirects\" ;;\n    48) echo \"curl: Unknown command line option specified\" ;;\n    51) echo \"curl: SSL peer certificate or SSH host key verification failed\" ;;\n    52) echo \"curl: Empty reply from server (got nothing)\" ;;\n    55) echo \"curl: Failed sending network data\" ;;\n    56) echo \"curl: Receive error (connection reset by peer)\" ;;\n    57) echo \"curl: Unrecoverable poll/select error (system I/O failure)\" ;;\n    59) echo \"curl: Couldn't use specified SSL cipher\" ;;\n    61) echo \"curl: Bad/unrecognized transfer encoding\" ;;\n    63) echo \"curl: Maximum file size exceeded\" ;;\n    75) echo \"Temporary failure (retry later)\" ;;\n    78) echo \"curl: Remote file not found (404 on FTP/file)\" ;;\n    79) echo \"curl: SSH session error (key exchange/auth failed)\" ;;\n    92) echo \"curl: HTTP/2 stream error (protocol violation)\" ;;\n    95) echo \"curl: HTTP/3 layer error\" ;;\n    64) echo \"Usage error (wrong arguments)\" ;;\n    65) echo \"Data format error (bad input data)\" ;;\n    66) echo \"Input file not found (cannot open input)\" ;;\n    67) echo \"User not found (addressee unknown)\" ;;\n    68) echo \"Host not found (hostname unknown)\" ;;\n    69) echo \"Service unavailable\" ;;\n    70) echo \"Internal software error\" ;;\n    71) echo \"System error (OS-level failure)\" ;;\n    72) echo \"Critical OS file missing\" ;;\n    73) echo \"Cannot create output file\" ;;\n    74) echo \"I/O error\" ;;\n    76) echo \"Remote protocol error\" ;;\n    77) echo \"Permission denied\" ;;\n    100) echo \"APT: Package manager error (broken packages / dependency problems)\" ;;\n    101) echo \"APT: Configuration error (bad sources.list, malformed config)\" ;;\n    102) echo \"APT: Lock held by another process (dpkg/apt still running)\" ;;\n\n    # --- Script Validation & Setup (103-123) ---\n    103) echo \"Validation: Shell is not Bash\" ;;\n    104) echo \"Validation: Not running as root (or invoked via sudo)\" ;;\n    105) echo \"Validation: Proxmox VE version not supported\" ;;\n    106) echo \"Validation: Architecture not supported (ARM / PiMox)\" ;;\n    107) echo \"Validation: Kernel key parameters unreadable\" ;;\n    108) echo \"Validation: Kernel key limits exceeded\" ;;\n    109) echo \"Proxmox: No available container ID after max attempts\" ;;\n    110) echo \"Proxmox: Failed to apply default.vars\" ;;\n    111) echo \"Proxmox: App defaults file not available\" ;;\n    112) echo \"Proxmox: Invalid install menu option\" ;;\n    113) echo \"LXC: Under-provisioned — user aborted update\" ;;\n    114) echo \"LXC: Storage too low — user aborted update\" ;;\n    115) echo \"Download: install.func download failed or incomplete\" ;;\n    116) echo \"Proxmox: Default bridge vmbr0 not found\" ;;\n    117) echo \"LXC: Container did not reach running state\" ;;\n    118) echo \"LXC: No IP assigned to container after timeout\" ;;\n    119) echo \"Proxmox: No valid storage for rootdir content\" ;;\n    120) echo \"Proxmox: No valid storage for vztmpl content\" ;;\n    121) echo \"LXC: Container network not ready (no IP after retries)\" ;;\n    122) echo \"LXC: No internet connectivity — user declined to continue\" ;;\n    123) echo \"LXC: Local IP detection failed\" ;;\n    124) echo \"Command timed out (timeout command)\" ;;\n    125) echo \"Command failed to start (Docker daemon or execution error)\" ;;\n    126) echo \"Command invoked cannot execute (permission problem?)\" ;;\n    127) echo \"Command not found\" ;;\n    128) echo \"Invalid argument to exit\" ;;\n    129) echo \"Killed by SIGHUP (terminal closed / hangup)\" ;;\n    130) echo \"Aborted by user (SIGINT)\" ;;\n    131) echo \"Killed by SIGQUIT (core dumped)\" ;;\n    132) echo \"Killed by SIGILL (illegal CPU instruction)\" ;;\n    134) echo \"Process aborted (SIGABRT - possibly Node.js heap overflow)\" ;;\n    137) echo \"Killed (SIGKILL / Out of memory?)\" ;;\n    139) echo \"Segmentation fault (core dumped)\" ;;\n    141) echo \"Broken pipe (SIGPIPE - output closed prematurely)\" ;;\n    143) echo \"Terminated (SIGTERM)\" ;;\n    144) echo \"Killed by signal 16 (SIGUSR1 / SIGSTKFLT)\" ;;\n    146) echo \"Killed by signal 18 (SIGTSTP)\" ;;\n    150) echo \"Systemd: Service failed to start\" ;;\n    151) echo \"Systemd: Service unit not found\" ;;\n    152) echo \"Permission denied (EACCES)\" ;;\n    153) echo \"Build/compile failed (make/gcc/cmake)\" ;;\n    154) echo \"Node.js: Native addon build failed (node-gyp)\" ;;\n    160) echo \"Python: Virtualenv / uv environment missing or broken\" ;;\n    161) echo \"Python: Dependency resolution failed\" ;;\n    162) echo \"Python: Installation aborted (permissions or EXTERNALLY-MANAGED)\" ;;\n    170) echo \"PostgreSQL: Connection failed (server not running / wrong socket)\" ;;\n    171) echo \"PostgreSQL: Authentication failed (bad user/password)\" ;;\n    172) echo \"PostgreSQL: Database does not exist\" ;;\n    173) echo \"PostgreSQL: Fatal error in query / syntax\" ;;\n    180) echo \"MySQL/MariaDB: Connection failed (server not running / wrong socket)\" ;;\n    181) echo \"MySQL/MariaDB: Authentication failed (bad user/password)\" ;;\n    182) echo \"MySQL/MariaDB: Database does not exist\" ;;\n    183) echo \"MySQL/MariaDB: Fatal error in query / syntax\" ;;\n    190) echo \"MongoDB: Connection failed (server not running)\" ;;\n    191) echo \"MongoDB: Authentication failed (bad user/password)\" ;;\n    192) echo \"MongoDB: Database not found\" ;;\n    193) echo \"MongoDB: Fatal query error\" ;;\n    200) echo \"Proxmox: Failed to create lock file\" ;;\n    203) echo \"Proxmox: Missing CTID variable\" ;;\n    204) echo \"Proxmox: Missing PCT_OSTYPE variable\" ;;\n    205) echo \"Proxmox: Invalid CTID (<100)\" ;;\n    206) echo \"Proxmox: CTID already in use\" ;;\n    207) echo \"Proxmox: Password contains unescaped special characters\" ;;\n    208) echo \"Proxmox: Invalid configuration (DNS/MAC/Network format)\" ;;\n    209) echo \"Proxmox: Container creation failed\" ;;\n    210) echo \"Proxmox: Cluster not quorate\" ;;\n    211) echo \"Proxmox: Timeout waiting for template lock\" ;;\n    212) echo \"Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)\" ;;\n    213) echo \"Proxmox: Storage type does not support 'rootdir' content\" ;;\n    214) echo \"Proxmox: Not enough storage space\" ;;\n    215) echo \"Proxmox: Container created but not listed (ghost state)\" ;;\n    216) echo \"Proxmox: RootFS entry missing in config\" ;;\n    217) echo \"Proxmox: Storage not accessible\" ;;\n    218) echo \"Proxmox: Template file corrupted or incomplete\" ;;\n    219) echo \"Proxmox: CephFS does not support containers - use RBD\" ;;\n    220) echo \"Proxmox: Unable to resolve template path\" ;;\n    221) echo \"Proxmox: Template file not readable\" ;;\n    222) echo \"Proxmox: Template download failed\" ;;\n    223) echo \"Proxmox: Template not available after download\" ;;\n    224) echo \"Proxmox: PBS storage is for backups only\" ;;\n    225) echo \"Proxmox: No template available for OS/Version\" ;;\n    231) echo \"Proxmox: LXC stack upgrade failed\" ;;\n\n    # --- Tools & Addon Scripts (232-238) ---\n    232) echo \"Tools: Wrong execution environment (run on PVE host, not inside LXC)\" ;;\n    233) echo \"Tools: Application not installed (update prerequisite missing)\" ;;\n    234) echo \"Tools: No LXC containers found or available\" ;;\n    235) echo \"Tools: Backup or restore operation failed\" ;;\n    236) echo \"Tools: Required hardware not detected\" ;;\n    237) echo \"Tools: Dependency package installation failed\" ;;\n    238) echo \"Tools: OS or distribution not supported for this addon\" ;;\n\n    239) echo \"npm/Node.js: Unexpected runtime error or dependency failure\" ;;\n    243) echo \"Node.js: Out of memory (JavaScript heap out of memory)\" ;;\n    245) echo \"Node.js: Invalid command-line option\" ;;\n    246) echo \"Node.js: Internal JavaScript Parse Error\" ;;\n    247) echo \"Node.js: Fatal internal error\" ;;\n    248) echo \"Node.js: Invalid C++ addon / N-API failure\" ;;\n    249) echo \"npm/pnpm/yarn: Unknown fatal error\" ;;\n\n    # --- Application Install/Update Errors (250-254) ---\n    250) echo \"App: Download failed or version not determined\" ;;\n    251) echo \"App: File extraction failed (corrupt or incomplete archive)\" ;;\n    252) echo \"App: Required file or resource not found\" ;;\n    253) echo \"App: Data migration required — update aborted\" ;;\n    254) echo \"App: User declined prompt or input timed out\" ;;\n\n    255) echo \"DPKG: Fatal internal error\" ;;\n    *) echo \"Unknown error\" ;;\n    esac\n  }\nfi\n\n# ==============================================================================\n# SECTION 2: ERROR HANDLERS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# error_handler()\n#\n# - Main error handler triggered by ERR trap\n# - Arguments: exit_code, command, line_number\n# - Behavior:\n#   * Returns silently if exit_code is 0 (success)\n#   * Sources explain_exit_code() for detailed error description\n#   * Displays error message with:\n#     - Line number where error occurred\n#     - Exit code with explanation\n#     - Command that failed\n#   * Shows last 20 lines of SILENT_LOGFILE if available\n#   * Copies log to container /root for later inspection\n#   * Exits with original exit code\n# ------------------------------------------------------------------------------\nerror_handler() {\n  local exit_code=${1:-$?}\n  local command=${2:-${BASH_COMMAND:-unknown}}\n  local line_number=${BASH_LINENO[0]:-unknown}\n\n  command=\"${command//\\$STD/}\"\n\n  # If error originated from silent(), use its captured metadata\n  # This provides the actual command and line number instead of \"silent ...\"\n  if [[ -n \"${_SILENT_FAILED_RC:-}\" ]]; then\n    exit_code=\"$_SILENT_FAILED_RC\"\n    command=\"$_SILENT_FAILED_CMD\"\n    line_number=\"$_SILENT_FAILED_LINE\"\n    # Clear flags to prevent stale data on subsequent errors\n    unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE\n  fi\n\n  if [[ \"$exit_code\" -eq 0 ]]; then\n    return 0\n  fi\n\n  # Stop spinner and restore cursor FIRST — before any output\n  # This prevents spinner text overlapping with error messages\n  if declare -f stop_spinner >/dev/null 2>&1; then\n    stop_spinner 2>/dev/null || true\n  fi\n  printf \"\\e[?25h\"\n\n  local explanation\n  explanation=\"$(explain_exit_code \"$exit_code\")\"\n\n  # ALWAYS report failure to API immediately - don't wait for container checks\n  # This ensures we capture failures that occur before/after container exists\n  if declare -f post_update_to_api &>/dev/null; then\n    post_update_to_api \"failed\" \"$exit_code\" 2>/dev/null || true\n  else\n    # Container context: post_update_to_api not available (api.func not sourced)\n    # Send status directly via curl so container failures are never lost\n    _send_abort_telemetry \"$exit_code\" 2>/dev/null || true\n  fi\n\n  # Use msg_error if available, fallback to echo\n  if declare -f msg_error >/dev/null 2>&1; then\n    msg_error \"in line ${line_number}: exit code ${exit_code} (${explanation}): while executing command ${command}\"\n  else\n    echo -e \"\\n${RD}[ERROR]${CL} in line ${RD}${line_number}${CL}: exit code ${RD}${exit_code}${CL} (${explanation}): while executing command ${YWB}${command}${CL}\\n\"\n  fi\n\n  if [[ -n \"${DEBUG_LOGFILE:-}\" ]]; then\n    {\n      echo \"------ ERROR ------\"\n      echo \"Timestamp : $(date '+%Y-%m-%d %H:%M:%S')\"\n      echo \"Exit Code : $exit_code ($explanation)\"\n      echo \"Line      : $line_number\"\n      echo \"Command   : $command\"\n      echo \"-------------------\"\n    } >>\"$DEBUG_LOGFILE\"\n  fi\n\n  # Get active log file (BUILD_LOG or INSTALL_LOG)\n  # Prefer silent()'s logfile when available (contains the actual command output)\n  local active_log=\"\"\n  if [[ -n \"${_SILENT_FAILED_LOG:-}\" && -s \"${_SILENT_FAILED_LOG}\" ]]; then\n    active_log=\"$_SILENT_FAILED_LOG\"\n    unset _SILENT_FAILED_LOG\n  elif declare -f get_active_logfile >/dev/null 2>&1; then\n    active_log=\"$(get_active_logfile)\"\n  elif [[ -n \"${SILENT_LOGFILE:-}\" ]]; then\n    active_log=\"$SILENT_LOGFILE\"\n  fi\n\n  # If active_log points to a container-internal path that doesn't exist on host,\n  # fall back to BUILD_LOG (host-side log)\n  if [[ -n \"$active_log\" && ! -s \"$active_log\" && -n \"${BUILD_LOG:-}\" && -s \"${BUILD_LOG}\" ]]; then\n    active_log=\"$BUILD_LOG\"\n  fi\n\n  # Show last log lines if available\n  if [[ -n \"$active_log\" && -s \"$active_log\" ]]; then\n    echo -e \"\\n${TAB}--- Last 20 lines of log ---\"\n    tail -n 20 \"$active_log\"\n    echo -e \"${TAB}-----------------------------------\\n\"\n  fi\n\n  # Detect probable Node.js heap OOM and print actionable guidance.\n  # This avoids generic SIGABRT/SIGKILL confusion for frontend build failures.\n  local node_oom_detected=\"false\"\n  local node_build_context=\"false\"\n  if [[ \"$command\" =~ (npm|pnpm|yarn|node|vite|turbo) ]]; then\n    node_build_context=\"true\"\n  fi\n  if [[ \"$exit_code\" == \"243\" ]]; then\n    node_oom_detected=\"true\"\n  elif [[ -n \"$active_log\" && -s \"$active_log\" ]]; then\n    if tail -n 200 \"$active_log\" 2>/dev/null | grep -Eqi 'Reached heap limit|JavaScript heap out of memory|Allocation failed - JavaScript heap out of memory|FATAL ERROR: Reached heap limit'; then\n      node_oom_detected=\"true\"\n    fi\n  fi\n\n  if [[ \"$node_oom_detected\" == \"true\" ]] || { [[ \"$node_build_context\" == \"true\" ]] && [[ \"$exit_code\" =~ ^(134|137)$ ]]; }; then\n    local heap_hint_mb=\"\"\n\n    # If explicitly configured, prefer the current value for troubleshooting output.\n    if [[ -n \"${NODE_OPTIONS:-}\" ]] && [[ \"${NODE_OPTIONS}\" =~ max-old-space-size=([0-9]+) ]]; then\n      heap_hint_mb=\"${BASH_REMATCH[1]}\"\n    elif [[ -n \"${var_ram:-}\" ]] && [[ \"${var_ram}\" =~ ^[0-9]+$ ]]; then\n      heap_hint_mb=$((var_ram * 75 / 100))\n    else\n      local mem_kb=\"\"\n      mem_kb=$(awk '/^MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null || echo \"\")\n      if [[ \"$mem_kb\" =~ ^[0-9]+$ ]]; then\n        local mem_mb=$((mem_kb / 1024))\n        heap_hint_mb=$((mem_mb * 75 / 100))\n      fi\n    fi\n\n    if [[ -z \"$heap_hint_mb\" ]] || ((heap_hint_mb < 1024)); then\n      heap_hint_mb=1024\n    elif ((heap_hint_mb > 12288)); then\n      heap_hint_mb=12288\n    fi\n\n    if declare -f msg_warn >/dev/null 2>&1; then\n      msg_warn \"Possible Node.js heap OOM. Try: export NODE_OPTIONS=\\\"--max-old-space-size=${heap_hint_mb}\\\" and rerun the build.\"\n    else\n      echo -e \"${YW}Possible Node.js heap OOM. Try: export NODE_OPTIONS=\\\"--max-old-space-size=${heap_hint_mb}\\\" and rerun the build.${CL}\"\n    fi\n  fi\n\n  # Detect context: Container (INSTALL_LOG set + inside container /root) vs Host\n  if [[ -n \"${INSTALL_LOG:-}\" && -f \"${INSTALL_LOG:-}\" && -d /root ]]; then\n    # CONTAINER CONTEXT: Copy log and create flag file for host\n    local container_log=\"/root/.install-${SESSION_ID:-error}.log\"\n    cp \"${INSTALL_LOG}\" \"$container_log\" 2>/dev/null || true\n\n    # Create error flag file with exit code for host detection\n    echo \"$exit_code\" >\"/root/.install-${SESSION_ID:-error}.failed\" 2>/dev/null || true\n    # Log path is shown by host as combined log - no need to show container path\n  else\n    # HOST CONTEXT: Show local log path and offer container cleanup\n    if [[ -n \"$active_log\" && -s \"$active_log\" ]]; then\n      if declare -f msg_custom >/dev/null 2>&1; then\n        msg_custom \"📋\" \"${YW}\" \"Full log: ${active_log}\"\n      else\n        echo -e \"${YW}Full log:${CL} ${BL}${active_log}${CL}\"\n      fi\n    fi\n\n    # Offer to remove container if it exists (build errors after container creation)\n    if [[ -n \"${CTID:-}\" ]] && command -v pct &>/dev/null && pct status \"$CTID\" &>/dev/null; then\n      echo \"\"\n      if declare -f msg_custom >/dev/null 2>&1; then\n        echo -en \"${TAB}❓${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}\"\n      else\n        echo -en \"${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}\"\n      fi\n\n      # Read user response\n      local response=\"\"\n      if read -t 60 -r response; then\n        if [[ -z \"$response\" || \"$response\" =~ ^[Yy]$ ]]; then\n          echo \"\"\n          if declare -f msg_info >/dev/null 2>&1; then\n            msg_info \"Removing container ${CTID}\"\n          else\n            echo -e \"${YW}Removing container ${CTID}${CL}\"\n          fi\n          pct stop \"$CTID\" &>/dev/null || true\n          pct destroy \"$CTID\" &>/dev/null || true\n          if declare -f msg_ok >/dev/null 2>&1; then\n            msg_ok \"Container ${CTID} removed\"\n          else\n            echo -e \"${GN}✔${CL} Container ${CTID} removed\"\n          fi\n        elif [[ \"$response\" =~ ^[Nn]$ ]]; then\n          echo \"\"\n          if declare -f msg_warn >/dev/null 2>&1; then\n            msg_warn \"Container ${CTID} kept for debugging\"\n          else\n            echo -e \"${YW}Container ${CTID} kept for debugging${CL}\"\n          fi\n        fi\n      else\n        # Timeout - auto-remove\n        echo \"\"\n        if declare -f msg_info >/dev/null 2>&1; then\n          msg_info \"No response - removing container ${CTID}\"\n        else\n          echo -e \"${YW}No response - removing container ${CTID}${CL}\"\n        fi\n        pct stop \"$CTID\" &>/dev/null || true\n        pct destroy \"$CTID\" &>/dev/null || true\n        if declare -f msg_ok >/dev/null 2>&1; then\n          msg_ok \"Container ${CTID} removed\"\n        else\n          echo -e \"${GN}✔${CL} Container ${CTID} removed\"\n        fi\n      fi\n\n      # Force one final status update attempt after cleanup\n      # This ensures status is updated even if the first attempt failed (e.g., HTTP 400)\n      if declare -f post_update_to_api &>/dev/null; then\n        post_update_to_api \"failed\" \"$exit_code\" \"force\"\n      fi\n    fi\n  fi\n\n  exit \"$exit_code\"\n}\n\n# ==============================================================================\n# SECTION 3: TELEMETRY & CLEANUP HELPERS FOR SIGNAL HANDLERS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# _send_abort_telemetry()\n#\n# - Sends failure/abort status to telemetry API\n# - Works in BOTH host context (post_update_to_api available) and\n#   container context (only curl available, api.func not sourced)\n# - Container context is critical: without this, container-side failures\n#   and signal exits are never reported, leaving records stuck in\n#   \"installing\" or \"configuring\" forever\n# - Arguments: $1 = exit_code\n# ------------------------------------------------------------------------------\n_send_abort_telemetry() {\n  local exit_code=\"${1:-1}\"\n  # Try full API function first (host context - api.func sourced)\n  if declare -f post_update_to_api &>/dev/null; then\n    post_update_to_api \"failed\" \"$exit_code\" 2>/dev/null || true\n    return\n  fi\n  # Fallback: direct curl (container context - api.func NOT sourced)\n  # This is the ONLY way containers can report failures to telemetry\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  # Collect last 200 log lines for error diagnosis (best-effort)\n  # Container context has no get_full_log(), so we gather as much as possible\n  local error_text=\"\"\n  local logfile=\"\"\n  if [[ -n \"${INSTALL_LOG:-}\" && -s \"${INSTALL_LOG}\" ]]; then\n    logfile=\"${INSTALL_LOG}\"\n  elif [[ -n \"${SILENT_LOGFILE:-}\" && -s \"${SILENT_LOGFILE}\" ]]; then\n    logfile=\"${SILENT_LOGFILE}\"\n  fi\n\n  if [[ -n \"$logfile\" ]]; then\n    error_text=$(tail -n 200 \"$logfile\" 2>/dev/null | sed 's/\\x1b\\[[0-9;]*[a-zA-Z]//g; s/\\\\/\\\\\\\\/g; s/\"/\\\\\"/g; s/\\r//g' | tr '\\n' '|' | sed 's/|$//' | head -c 16384 | tr -d '\\000-\\010\\013\\014\\016-\\037\\177') || true\n  fi\n\n  # Prepend exit code explanation header (like build_error_string does on host)\n  local explanation=\"\"\n  if declare -f explain_exit_code &>/dev/null; then\n    explanation=$(explain_exit_code \"$exit_code\" 2>/dev/null) || true\n  fi\n  if [[ -n \"$explanation\" && -n \"$error_text\" ]]; then\n    error_text=\"exit_code=${exit_code} | ${explanation}|---|${error_text}\"\n  elif [[ -n \"$explanation\" && -z \"$error_text\" ]]; then\n    error_text=\"exit_code=${exit_code} | ${explanation}\"\n  fi\n\n  # Calculate duration if start time is available\n  local duration=\"\"\n  if [[ -n \"${DIAGNOSTICS_START_TIME:-}\" ]]; then\n    duration=$(($(date +%s) - DIAGNOSTICS_START_TIME))\n  fi\n\n  # Categorize error if function is available (may not be in minimal container context)\n  local error_category=\"\"\n  if declare -f categorize_error &>/dev/null; then\n    error_category=$(categorize_error \"$exit_code\" 2>/dev/null) || true\n  fi\n\n  # Build JSON payload with error context\n  local payload\n  payload=\"{\\\"random_id\\\":\\\"${RANDOM_UUID}\\\",\\\"execution_id\\\":\\\"${EXECUTION_ID:-${RANDOM_UUID}}\\\",\\\"type\\\":\\\"${TELEMETRY_TYPE:-lxc}\\\",\\\"nsapp\\\":\\\"${NSAPP:-${app:-unknown}}\\\",\\\"status\\\":\\\"failed\\\",\\\"exit_code\\\":${exit_code}\"\n  [[ -n \"$error_text\" ]] && payload=\"${payload},\\\"error\\\":\\\"${error_text}\\\"\"\n  [[ -n \"$error_category\" ]] && payload=\"${payload},\\\"error_category\\\":\\\"${error_category}\\\"\"\n  [[ -n \"$duration\" ]] && payload=\"${payload},\\\"duration\\\":${duration}\"\n  payload=\"${payload}}\"\n\n  local api_url=\"${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}\"\n\n  # 2 attempts (retry once on failure) — original had no retry\n  local attempt\n  for attempt in 1 2; do\n    if curl -fsS -m 5 -X POST \"$api_url\" \\\n      -H \"Content-Type: application/json\" \\\n      -d \"$payload\" &>/dev/null; then\n      return 0\n    fi\n    [[ $attempt -eq 1 ]] && sleep 1\n  done\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# _stop_container_if_installing()\n#\n# - Stops the LXC container if we're in the install phase\n# - Prevents orphaned container processes when the host exits due to a signal\n#   (SSH disconnect, Ctrl+C, SIGTERM) — without this, the container keeps\n#   running and may send \"configuring\" status AFTER the host already sent\n#   \"failed\", leaving records permanently stuck in \"configuring\"\n# - Only acts when:\n#   * CONTAINER_INSTALLING flag is set (during lxc-attach in build_container)\n#   * CTID is set (container was created)\n#   * pct command is available (we're on the Proxmox host, not inside a container)\n# - Does NOT destroy the container — just stops it for potential debugging\n# ------------------------------------------------------------------------------\n_stop_container_if_installing() {\n  [[ \"${CONTAINER_INSTALLING:-}\" == \"true\" ]] || return 0\n  [[ -n \"${CTID:-}\" ]] || return 0\n  command -v pct &>/dev/null || return 0\n  pct stop \"$CTID\" 2>/dev/null || true\n}\n\n# ==============================================================================\n# SECTION 4: SIGNAL HANDLERS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# on_exit()\n#\n# - EXIT trap handler — runs on EVERY script termination\n# - Catches orphaned \"installing\"/\"configuring\" records:\n#   * If post_to_api sent \"installing\" but post_update_to_api never ran\n#   * Reports final status to prevent records stuck forever\n# - Best-effort log collection for failed installs\n# - Stops orphaned container processes on failure\n# - Cleans up lock files\n# ------------------------------------------------------------------------------\non_exit() {\n  local exit_code=$?\n\n  # Report orphaned telemetry records\n  # Two scenarios handled:\n  # 1. POST_TO_API_DONE=true but POST_UPDATE_DONE=false: Record was created but\n  #    never got a final status update → send abort/done now.\n  # 2. POST_TO_API_DONE=false but DIAGNOSTICS=yes: Initial post failed (server\n  #    unreachable/timeout), but the server has fallback create-on-update logic,\n  #    so a status update can still create the record. Worth one last try.\n  if [[ \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ \"${POST_TO_API_DONE:-}\" == \"true\" || \"${DIAGNOSTICS:-no}\" == \"yes\" ]]; then\n      if [[ $exit_code -ne 0 ]]; then\n        _send_abort_telemetry \"$exit_code\"\n      elif [[ \"${INSTALL_COMPLETE:-}\" == \"true\" ]] && declare -f post_update_to_api >/dev/null 2>&1; then\n        # Only report success if the install was explicitly marked complete.\n        # Without this guard, early bailouts (e.g. user cancelled) with exit 0\n        # would be falsely reported as successful installations.\n        post_update_to_api \"done\" \"0\" 2>/dev/null || true\n      fi\n    fi\n  fi\n\n  # Best-effort log collection on failure (non-critical, telemetry already sent)\n  if [[ $exit_code -ne 0 ]] && declare -f ensure_log_on_host >/dev/null 2>&1; then\n    ensure_log_on_host 2>/dev/null || true\n  fi\n\n  # Stop orphaned container if we're in the install phase and exiting with error\n  if [[ $exit_code -ne 0 ]]; then\n    _stop_container_if_installing\n  fi\n\n  [[ -n \"${lockfile:-}\" && -e \"$lockfile\" ]] && rm -f \"$lockfile\"\n  exit \"$exit_code\"\n}\n\n# ------------------------------------------------------------------------------\n# on_interrupt()\n#\n# - SIGINT (Ctrl+C) trap handler\n# - Reports status FIRST (time-critical: container may be dying)\n# - Stops orphaned container to prevent \"configuring\" ghost records\n# - Exits with code 130 (128 + SIGINT=2)\n# ------------------------------------------------------------------------------\non_interrupt() {\n  # Stop spinner and restore cursor before any output\n  if declare -f stop_spinner >/dev/null 2>&1; then\n    stop_spinner 2>/dev/null || true\n  fi\n  printf \"\\e[?25h\" 2>/dev/null || true\n\n  _send_abort_telemetry \"130\"\n  _stop_container_if_installing\n  if declare -f msg_error >/dev/null 2>&1; then\n    msg_error \"Interrupted by user (SIGINT)\" 2>/dev/null || true\n  else\n    echo -e \"\\n${RD}Interrupted by user (SIGINT)${CL}\" 2>/dev/null || true\n  fi\n  exit 130\n}\n\n# ------------------------------------------------------------------------------\n# on_terminate()\n#\n# - SIGTERM trap handler\n# - Reports status FIRST (time-critical: process being killed)\n# - Stops orphaned container to prevent \"configuring\" ghost records\n# - Exits with code 143 (128 + SIGTERM=15)\n# ------------------------------------------------------------------------------\non_terminate() {\n  # Stop spinner and restore cursor before any output\n  if declare -f stop_spinner >/dev/null 2>&1; then\n    stop_spinner 2>/dev/null || true\n  fi\n  printf \"\\e[?25h\" 2>/dev/null || true\n\n  _send_abort_telemetry \"143\"\n  _stop_container_if_installing\n  if declare -f msg_error >/dev/null 2>&1; then\n    msg_error \"Terminated by signal (SIGTERM)\" 2>/dev/null || true\n  else\n    echo -e \"\\n${RD}Terminated by signal (SIGTERM)${CL}\" 2>/dev/null || true\n  fi\n  exit 143\n}\n\n# ------------------------------------------------------------------------------\n# on_hangup()\n#\n# - SIGHUP trap handler (SSH disconnect, terminal closed)\n# - CRITICAL: This was previously MISSING from catch_errors(), causing\n#   container processes to become orphans on SSH disconnect — the #1 cause\n#   of records stuck in \"installing\" and \"configuring\" states\n# - Reports status via direct curl (terminal is already closed, no output)\n# - Stops orphaned container to prevent ghost records\n# - Exits with code 129 (128 + SIGHUP=1)\n# ------------------------------------------------------------------------------\non_hangup() {\n  # Stop spinner (no cursor restore needed — terminal is already gone)\n  if declare -f stop_spinner >/dev/null 2>&1; then\n    stop_spinner 2>/dev/null || true\n  fi\n\n  _send_abort_telemetry \"129\"\n  _stop_container_if_installing\n  exit 129\n}\n\n# ==============================================================================\n# SECTION 5: INITIALIZATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# catch_errors()\n#\n# - Initializes error handling and signal traps\n# - Enables strict error handling:\n#   * set -Ee: Exit on error, inherit ERR trap in functions\n#   * set -o pipefail: Pipeline fails if any command fails\n#   * set -u: (optional) Exit on undefined variable (if STRICT_UNSET=1)\n# - Sets up traps:\n#   * ERR  → error_handler (script errors)\n#   * EXIT → on_exit      (any termination — cleanup + orphan detection)\n#   * INT  → on_interrupt  (Ctrl+C)\n#   * TERM → on_terminate  (kill / systemd stop)\n#   * HUP  → on_hangup     (SSH disconnect / terminal closed)\n# - Call this function early in every script\n# ------------------------------------------------------------------------------\ncatch_errors() {\n  set -Ee -o pipefail\n  if [ \"${STRICT_UNSET:-0}\" = \"1\" ]; then\n    set -u\n  fi\n\n  trap 'error_handler' ERR\n  trap on_exit EXIT\n  trap on_interrupt INT\n  trap on_terminate TERM\n  trap on_hangup HUP\n}\n"
  },
  {
    "path": "misc/install.func",
    "content": "# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster)\n# Co-Author: MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\n# ==============================================================================\n# INSTALL.FUNC - CONTAINER INSTALLATION & SETUP\n# ==============================================================================\n#\n# This file provides installation functions executed inside LXC containers\n# after creation. Handles:\n#\n#   - Network connectivity verification (IPv4/IPv6)\n#   - OS updates and package installation\n#   - DNS resolution checks\n#   - MOTD and SSH configuration\n#   - Container customization and auto-login\n#\n# Usage:\n#   - Sourced by <app>-install.sh scripts\n#   - Executes via pct exec inside container\n#   - Requires internet connectivity\n#\n# ==============================================================================\n\n# ==============================================================================\n# SECTION 1: INITIALIZATION\n# ==============================================================================\n\nif ! command -v curl >/dev/null 2>&1; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt update >/dev/null 2>&1\n  apt install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\nload_functions\ncatch_errors\n\n# Persist diagnostics setting inside container (exported from build.func)\n# so addon scripts running later can find the user's choice\nif [[ ! -f /usr/local/community-scripts/diagnostics ]]; then\n  mkdir -p /usr/local/community-scripts\n  echo \"DIAGNOSTICS=${DIAGNOSTICS:-no}\" >/usr/local/community-scripts/diagnostics\nfi\n\n# Get LXC IP address (must be called INSIDE container, after network is up)\nget_lxc_ip\n\n# ------------------------------------------------------------------------------\n# post_progress_to_api()\n#\n# - Lightweight progress ping from inside the container\n# - Updates the existing telemetry record status\n# - Arguments:\n#   * $1: status (optional, default: \"configuring\")\n# - Signals that the installation is actively progressing (not stuck)\n# - Fire-and-forget: never blocks or fails the script\n# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set\n# ------------------------------------------------------------------------------\npost_progress_to_api() {\n  command -v curl &>/dev/null || return 0\n  [[ \"${DIAGNOSTICS:-no}\" == \"no\" ]] && return 0\n  [[ -z \"${RANDOM_UUID:-}\" ]] && return 0\n\n  local progress_status=\"${1:-configuring}\"\n\n  curl -fsS -m 5 -X POST \"https://telemetry.community-scripts.org/telemetry\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"{\\\"random_id\\\":\\\"${RANDOM_UUID}\\\",\\\"execution_id\\\":\\\"${EXECUTION_ID:-${RANDOM_UUID}}\\\",\\\"type\\\":\\\"lxc\\\",\\\"nsapp\\\":\\\"${app:-unknown}\\\",\\\"status\\\":\\\"${progress_status}\\\"}\" &>/dev/null || true\n}\n\n# ==============================================================================\n# SECTION 2: NETWORK & CONNECTIVITY\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# verb_ip6()\n#\n# - Configures IPv6 based on DISABLEIPV6 variable\n# - If DISABLEIPV6=yes: disables IPv6 via sysctl\n# - Sets verbose mode via set_std_mode()\n# ------------------------------------------------------------------------------\nverb_ip6() {\n  set_std_mode # Set STD mode based on VERBOSE\n\n  if [ \"${IPV6_METHOD:-}\" = \"disable\" ]; then\n    msg_info \"Disabling IPv6 (this may affect some services)\"\n    mkdir -p /etc/sysctl.d\n    $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <<EOF\n# Disable IPv6 (set by community-scripts)\nnet.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1\nnet.ipv6.conf.lo.disable_ipv6 = 1\nEOF\n    $STD sysctl -p /etc/sysctl.d/99-disable-ipv6.conf\n    msg_ok \"Disabled IPv6\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# setting_up_container()\n#\n# - Verifies network connectivity via hostname -I\n# - Retries up to RETRY_NUM times with RETRY_EVERY seconds delay\n# - Removes Python EXTERNALLY-MANAGED restrictions\n# - Disables systemd-networkd-wait-online.service for faster boot\n# - Exits with error if network unavailable after retries\n# ------------------------------------------------------------------------------\nsetting_up_container() {\n  msg_info \"Setting up Container OS\"\n\n  # Fix Debian 13 LXC template bug where / is owned by nobody\n  # Only attempt in privileged containers (unprivileged cannot chown /)\n  if [[ \"$(stat -c '%U' /)\" != \"root\" ]]; then\n    (chown root:root / 2>/dev/null) || true\n  fi\n\n  for ((i = RETRY_NUM; i > 0; i--)); do\n    if [ \"$(hostname -I)\" != \"\" ]; then\n      break\n    fi\n    echo 1>&2 -en \"${CROSS}${RD} No Network! \"\n    sleep $RETRY_EVERY\n  done\n  if [ \"$(hostname -I)\" = \"\" ]; then\n    echo 1>&2 -e \"\\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}\"\n    echo -e \"${NETWORK}Check Network Settings\"\n    exit 121\n  fi\n  rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\n  systemctl disable -q --now systemd-networkd-wait-online.service\n  msg_ok \"Set up Container OS\"\n  #msg_custom \"${CM}\" \"${GN}\" \"Network Connected: ${BL}$(hostname -I)\"\n  msg_ok \"Network Connected: ${BL}$(hostname -I)\"\n  post_progress_to_api\n}\n\n# ------------------------------------------------------------------------------\n# network_check()\n#\n# - Comprehensive network connectivity check for IPv4 and IPv6\n# - Tests connectivity to multiple DNS servers:\n#   * IPv4: 1.1.1.1 (Cloudflare), 8.8.8.8 (Google), 9.9.9.9 (Quad9)\n#   * IPv6: 2606:4700:4700::1111, 2001:4860:4860::8888, 2620:fe::fe\n# - Verifies DNS resolution for GitHub and Community-Scripts domains\n# - Prompts user to continue if no internet detected\n# - Uses fatal() on DNS resolution failure for critical hosts\n# ------------------------------------------------------------------------------\nnetwork_check() {\n  set +e\n  trap - ERR\n  ipv4_connected=false\n  ipv6_connected=false\n  sleep 1\n\n  # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers.\n  if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then\n    msg_ok \"IPv4 Internet Connected\"\n    ipv4_connected=true\n  else\n    msg_error \"IPv4 Internet Not Connected\"\n  fi\n\n  # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers.\n  if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then\n    msg_ok \"IPv6 Internet Connected\"\n    ipv6_connected=true\n  else\n    msg_error \"IPv6 Internet Not Connected\"\n  fi\n\n  # If both IPv4 and IPv6 checks fail, prompt the user\n  if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then\n    read -r -p \"No Internet detected, would you like to continue anyway? <y/N> \" prompt\n    if [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n      echo -e \"${INFO}${RD}Expect Issues Without Internet${CL}\"\n    else\n      echo -e \"${NETWORK}Check Network Settings\"\n      exit 122\n    fi\n  fi\n\n  # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6)\n  GIT_HOSTS=(\"github.com\" \"raw.githubusercontent.com\" \"api.github.com\" \"git.community-scripts.org\")\n  GIT_STATUS=\"Git DNS:\"\n  DNS_FAILED=false\n\n  for HOST in \"${GIT_HOSTS[@]}\"; do\n    RESOLVEDIP=$(getent hosts \"$HOST\" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1)\n    if [[ -z \"$RESOLVEDIP\" ]]; then\n      GIT_STATUS+=\"$HOST:($DNSFAIL)\"\n      DNS_FAILED=true\n    else\n      GIT_STATUS+=\" $HOST:($DNSOK)\"\n    fi\n  done\n\n  if [[ \"$DNS_FAILED\" == true ]]; then\n    fatal \"$GIT_STATUS\"\n  else\n    msg_ok \"$GIT_STATUS\"\n  fi\n\n  set -e\n  trap 'error_handler' ERR\n}\n\n# ==============================================================================\n# SECTION 3: OS UPDATE & PACKAGE MANAGEMENT\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# apt_update_safe()\n#\n# - Runs apt-get update with CDN mirror fallback\n# - On failure, detects distro (Debian/Ubuntu) and tries alternate mirrors\n# - Three-phase approach: global mirrors → primary mirror → regional mirrors\n# - Falls back to manual user prompt if all auto mirrors fail\n# - Detects hash mismatch, SSL errors, and generic apt failures\n# ------------------------------------------------------------------------------\napt_update_safe() {\n  if $STD apt-get update; then\n    return 0\n  fi\n\n  local failed_mirror\n  failed_mirror=$(grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null || echo \"unknown\")\n  msg_warn \"apt-get update failed (${failed_mirror}), trying alternate mirrors...\"\n\n  local distro\n  distro=$(. /etc/os-release 2>/dev/null && echo \"$ID\" || echo \"debian\")\n\n  local eu_mirrors us_mirrors ap_mirrors\n  if [[ \"$distro\" == \"ubuntu\" ]]; then\n    eu_mirrors=\"de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de\"\n    us_mirrors=\"us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu\"\n    ap_mirrors=\"au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au\"\n  else\n    eu_mirrors=\"ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net\"\n    us_mirrors=\"ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com\"\n    ap_mirrors=\"ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in\"\n  fi\n\n  local tz regional others\n  tz=$(cat /etc/timezone 2>/dev/null || echo \"UTC\")\n  case \"$tz\" in\n  Europe/* | Arctic/*)\n    regional=\"$eu_mirrors\"\n    others=\"$us_mirrors $ap_mirrors\"\n    ;;\n  America/*)\n    regional=\"$us_mirrors\"\n    others=\"$eu_mirrors $ap_mirrors\"\n    ;;\n  Asia/* | Australia/* | Pacific/*)\n    regional=\"$ap_mirrors\"\n    others=\"$eu_mirrors $us_mirrors\"\n    ;;\n  *)\n    regional=\"\"\n    others=\"$eu_mirrors $us_mirrors $ap_mirrors\"\n    ;;\n  esac\n\n  echo 'Acquire::By-Hash \"no\";' >/etc/apt/apt.conf.d/99no-by-hash\n\n  _try_apt_mirror() {\n    local m=$1\n    for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do\n      [[ -f \"$src\" ]] && sed -i \"s|URIs: http[s]*://[^/]*/|URIs: http://${m}/|g; s|deb http[s]*://[^/]*/|deb http://${m}/|g\" \"$src\"\n    done\n    rm -rf /var/lib/apt/lists/*\n    local out\n    out=$(apt-get update 2>&1)\n    if echo \"$out\" | grep -qi \"hashsum\\|hash sum\"; then\n      msg_warn \"Mirror ${m} failed (hash mismatch)\"\n      return 1\n    elif echo \"$out\" | grep -qi \"SSL\\|certificate\"; then\n      msg_warn \"Mirror ${m} failed (SSL/certificate error)\"\n      return 1\n    elif echo \"$out\" | grep -q \"^E:\"; then\n      msg_warn \"Mirror ${m} failed (apt-get update error)\"\n      return 1\n    else\n      msg_ok \"CDN set to ${m}: tests passed\"\n      return 0\n    fi\n  }\n\n  _scan_reachable() {\n    local result=\"\"\n    for m in $1; do\n      if timeout 2 bash -c \"echo >/dev/tcp/$m/80\" 2>/dev/null; then\n        result=\"$result $m\"\n      fi\n    done\n    echo \"$result\" | xargs\n  }\n\n  local apt_ok=false\n\n  # Phase 1: Scan global mirrors first (independent of local CDN issues)\n  local others_ok\n  others_ok=$(_scan_reachable \"$others\")\n  local others_pick\n  others_pick=$(printf '%s\\n' $others_ok | shuf | head -3 | xargs)\n\n  for mirror in $others_pick; do\n    msg_custom \"${INFO}\" \"${YW}\" \"Attempting mirror: ${mirror}\"\n    if _try_apt_mirror \"$mirror\"; then\n      apt_ok=true\n      break\n    fi\n  done\n\n  # Phase 2: Try primary mirror\n  if [[ \"$apt_ok\" != true ]]; then\n    local primary\n    if [[ \"$distro\" == \"ubuntu\" ]]; then\n      primary=\"archive.ubuntu.com\"\n    else\n      primary=\"ftp.debian.org\"\n    fi\n    if timeout 2 bash -c \"echo >/dev/tcp/$primary/80\" 2>/dev/null; then\n      msg_custom \"${INFO}\" \"${YW}\" \"Attempting mirror: ${primary}\"\n      if _try_apt_mirror \"$primary\"; then\n        apt_ok=true\n      fi\n    fi\n  fi\n\n  # Phase 3: Fall back to regional mirrors\n  if [[ \"$apt_ok\" != true ]]; then\n    local regional_ok\n    regional_ok=$(_scan_reachable \"$regional\")\n    local regional_pick\n    regional_pick=$(printf '%s\\n' $regional_ok | shuf | head -3 | xargs)\n\n    for mirror in $regional_pick; do\n      msg_custom \"${INFO}\" \"${YW}\" \"Attempting mirror: ${mirror}\"\n      if _try_apt_mirror \"$mirror\"; then\n        apt_ok=true\n        break\n      fi\n    done\n  fi\n\n  # Phase 4: All auto mirrors failed, prompt user\n  if [[ \"$apt_ok\" != true ]]; then\n    msg_warn \"Multiple mirrors failed (possible CDN synchronization issue).\"\n    if [[ \"$distro\" == \"ubuntu\" ]]; then\n      msg_warn \"Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors\"\n    else\n      msg_warn \"Find Debian mirrors at: https://www.debian.org/mirror/list\"\n    fi\n    local custom_mirror\n    while true; do\n      read -rp \"  Enter a mirror hostname (or 'skip' to abort): \" custom_mirror </dev/tty\n      [[ -z \"$custom_mirror\" ]] && continue\n      [[ \"$custom_mirror\" == \"skip\" ]] && break\n      [[ ! \"$custom_mirror\" =~ ^[a-zA-Z0-9._-]+$ ]] && {\n        msg_warn \"Invalid hostname format.\"\n        continue\n      }\n      if _try_apt_mirror \"$custom_mirror\"; then\n        apt_ok=true\n        break\n      fi\n      msg_warn \"Mirror '${custom_mirror}' also failed. Try another or type 'skip'.\"\n    done\n  fi\n\n  if [[ \"$apt_ok\" != true ]]; then\n    msg_error \"All mirrors failed. Check network or try again later.\"\n    return 1\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# update_os()\n#\n# - Updates container OS via apt-get update and dist-upgrade\n# - Configures APT cacher proxy if CACHER=yes (accelerates package downloads)\n# - Removes Python EXTERNALLY-MANAGED restrictions for pip\n# - Sources tools.func for additional setup functions after update\n# - Uses $STD wrapper to suppress output unless VERBOSE=yes\n# ------------------------------------------------------------------------------\nupdate_os() {\n  msg_info \"Updating Container OS\"\n  if [[ \"$CACHER\" == \"yes\" ]]; then\n    echo 'Acquire::http::Proxy-Auto-Detect \"/usr/local/bin/apt-proxy-detect.sh\";' >/etc/apt/apt.conf.d/00aptproxy\n    local _proxy_raw=\"${CACHER_IP}\"\n    local _proxy_host _proxy_port _proxy_url\n    # Parse host and port from URL or plain IP/hostname\n    _proxy_host=$(echo \"$_proxy_raw\" | sed -e 's|https\\?://||' -e 's|/.*||' | cut -d: -f1)\n    _proxy_port=$(echo \"$_proxy_raw\" | sed -e 's|https\\?://||' -e 's|/.*||' | cut -s -d: -f2)\n    if [[ \"$_proxy_raw\" =~ ^https?:// ]]; then\n      # Full URL provided — use as-is for proxy output, extract port for nc check\n      _proxy_url=\"$_proxy_raw\"\n      _proxy_port=\"${_proxy_port:-80}\"\n    else\n      # Legacy: plain IP or hostname — default to http + port 3142\n      _proxy_port=\"${_proxy_port:-3142}\"\n      _proxy_url=\"http://${_proxy_raw}:${_proxy_port}\"\n    fi\n    cat <<EOF >/usr/local/bin/apt-proxy-detect.sh\n#!/bin/bash\nif nc -w1 -z \"${_proxy_host}\" ${_proxy_port}; then\n  echo -n \"${_proxy_url}\"\nelse\n  echo -n \"DIRECT\"\nfi\nEOF\n    chmod +x /usr/local/bin/apt-proxy-detect.sh\n  fi\n  apt_update_safe\n  $STD apt-get -o Dpkg::Options::=\"--force-confold\" -y dist-upgrade\n\t$STD apt-get install -y sudo curl mc gnupg2 openssh-server wget gcc\n  rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\n  msg_ok \"Updated Container OS\"\n  post_progress_to_api\n\n  local tools_content\n  tools_content=$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func) || {\n    msg_error \"Failed to download tools.func\"\n    exit 115\n  }\n  source /dev/stdin <<<\"$tools_content\"\n  if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then\n    msg_error \"tools.func loaded but incomplete — missing expected functions\"\n    exit 115\n  fi\n}\n\n# ==============================================================================\n# SECTION 4: MOTD & SSH CONFIGURATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# motd_ssh()\n#\n# - Configures Message of the Day (MOTD) with container information\n# - Creates /etc/profile.d/00_lxc-details.sh with:\n#   * Application name\n#   * Warning banner (DEV repository)\n#   * OS name and version\n#   * Hostname and IP address\n#   * GitHub repository link\n# - Disables executable flag on /etc/update-motd.d/* scripts\n# - Enables root SSH access if SSH_ROOT=yes\n# - Configures TERM environment variable for better terminal support\n# ------------------------------------------------------------------------------\nmotd_ssh() {\n  # Set terminal to 256-color mode\n  grep -qxF \"export TERM='xterm-256color'\" /root/.bashrc || echo \"export TERM='xterm-256color'\" >>/root/.bashrc\n\n  PROFILE_FILE=\"/etc/profile.d/00_lxc-details.sh\"\n  echo \"echo -e \\\"\\\"\" >\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${BOLD}${APPLICATION} LXC Container${CL}\"\\\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts & pimox-scripts ${YW}| GitHub: ${GN}https://github.com/asylumexp/ProxmoxVE${CL}\\\"\" >>\"$PROFILE_FILE\"\n  echo \"echo \\\"\\\"\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${OS}${YW} OS: ${GN}\\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\\\"') - Version: \\$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\\\"')${CL}\\\"\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\\$(hostname)${CL}\\\"\" >>\"$PROFILE_FILE\"\n  echo -e \"echo -e \\\"${TAB}${INFO}${YW} IP Address: ${GN}\\$(hostname -I | awk '{print \\$1}')${CL}\\\"\" >>\"$PROFILE_FILE\"\n\n  # Disable default MOTD scripts\n  chmod -x /etc/update-motd.d/*\n\n  if [[ \"${SSH_ROOT}\" == \"yes\" ]]; then\n    sed -i \"s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g\" /etc/ssh/sshd_config\n    systemctl restart sshd\n  fi\n  post_progress_to_api\n}\n\n# ==============================================================================\n# SECTION 5: CONTAINER CUSTOMIZATION\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# customize()\n#\n# - Customizes container for passwordless root login if PASSWORD is empty\n# - Configures getty for auto-login via /etc/systemd/system/container-getty@1.service.d/override.conf\n# - Creates /usr/bin/update script for easy application updates\n# - Injects SSH authorized keys if SSH_AUTHORIZED_KEY variable is set\n# - Sets proper permissions on SSH directories and key files\n# ------------------------------------------------------------------------------\ncustomize() {\n  if [[ \"$PASSWORD\" == \"\" ]]; then\n    msg_info \"Customizing Container\"\n    GETTY_OVERRIDE=\"/etc/systemd/system/container-getty@1.service.d/override.conf\"\n    mkdir -p \"$(dirname \"$GETTY_OVERRIDE\")\"\n    cat <<EOF >\"$GETTY_OVERRIDE\"\n  [Service]\n  ExecStart=\n  ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \\$TERM\nEOF\n    systemctl daemon-reload\n    systemctl restart \"$(basename \"$(dirname \"$GETTY_OVERRIDE\")\" | sed 's/\\.d//')\"\n    msg_ok \"Customized Container\"\n  fi\n  echo \"bash -c \\\"\\$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/ct/${app}.sh)\\\"\" >/usr/bin/update\n  chmod +x /usr/bin/update\n\n  if [[ -n \"${SSH_AUTHORIZED_KEY}\" ]]; then\n    mkdir -p /root/.ssh\n    echo \"${SSH_AUTHORIZED_KEY}\" >/root/.ssh/authorized_keys\n    chmod 700 /root/.ssh\n    chmod 600 /root/.ssh/authorized_keys\n  fi\n  post_progress_to_api\n}\n"
  },
  {
    "path": "misc/test-automation.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Automated Container Testing Script\n\nset -eEuo pipefail\n\n# Detect if running from ./misc directory and adjust to project root\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nif [[ \"$(basename \"$SCRIPT_DIR\")\" == \"misc\" ]]; then\n    # Running from ./misc, change to parent directory\n    cd \"$SCRIPT_DIR/..\"\n    echo \"Detected running from ./misc directory, switching to project root: $(pwd)\"\nfi\n\n# Color codes\nRD='\\033[01;31m'\nGN='\\033[1;92m'\nYW='\\033[1;93m'\nBL='\\033[36m'\nCL='\\033[m'\nBOLD='\\033[1m'\n\n# Setup timestamp and logging\nTIMESTAMP=$(date +\"%Y%m%d_%H%M%S\")\nLOG_DIR=\"./logs/${TIMESTAMP}\"\nSCRIPT_LOG_DIR=\"${LOG_DIR}/scripts\"\nOUTPUT_LOG=\"${LOG_DIR}/output.log\"\nSUMMARY_LOG=\"${LOG_DIR}/summary.log\"\n\n# Concurrency / UI controls\nMAX_CONCURRENCY=\"${MAX_CONCURRENCY:-4}\"\nREFRESH_INTERVAL=\"${REFRESH_INTERVAL:-1}\"\nLINES_PER_PANE=\"${LINES_PER_PANE:-10}\"\nUSE_UI=\"${USE_UI:-auto}\"  # auto|yes|no\n\n# Result tracking\ndeclare -a ACCESSIBLE_CONTAINERS=()\ndeclare -a INACCESSIBLE_CONTAINERS=()\ndeclare -a NOT_TESTED_CONTAINERS=()\ndeclare -a SKIPPED_SCRIPTS=()\ndeclare -a FAILED_CONTAINERS=()\n\n# Create log directories\nmkdir -p \"${SCRIPT_LOG_DIR}\"\n\n# Function to log messages\nlog_msg() {\n    echo -e \"$1\" | tee -a \"${OUTPUT_LOG}\"\n}\n\nlog_info() {\n    log_msg \"${BL}[INFO]${CL} $1\"\n}\n\nlog_ok() {\n    log_msg \"${GN}[OK]${CL} $1\"\n}\n\nlog_error() {\n    log_msg \"${RD}[ERROR]${CL} $1\"\n}\n\nlog_warn() {\n    log_msg \"${YW}[WARN]${CL} $1\"\n}\n\n# Check if running on Proxmox\nif ! command -v pveversion >/dev/null 2>&1; then\n    log_error \"This script must be run on a Proxmox VE host\"\n    exit 1\nfi\n\n# Parse statuses.json for 🧪 scripts\nlog_info \"Reading statuses.json for test scripts (🧪 status)...\"\n\nif [[ ! -f \"frontend/public/json/statuses.json\" ]]; then\n    log_error \"statuses.json not found at frontend/public/json/statuses.json\"\n    exit 1\nfi\n\n# Extract scripts with 🧪 status\nTEST_SCRIPTS=$(jq -r 'to_entries[] | select(.value == \"🧪\") | .key' frontend/public/json/statuses.json | sed 's/\\.json$//')\n\nif [[ -z \"$TEST_SCRIPTS\" ]]; then\n    log_warn \"No scripts found with 🧪 status\"\n    exit 0\nfi\n\nlog_ok \"Found $(echo \"$TEST_SCRIPTS\" | wc -l) scripts to test\"\n\n# Convert test scripts to array for scheduling\nmapfile -t SCRIPT_LIST <<< \"$TEST_SCRIPTS\"\n\n# Determine if we should render UI (only if stdout is a TTY)\nif [[ \"$USE_UI\" == \"auto\" ]]; then\n    if [ -t 1 ]; then\n        USE_UI=\"yes\"\n    else\n        USE_UI=\"no\"\n    fi\nfi\n\n# Initialize base ID for concurrent-safe allocation\nBASE_ID=$(pvesh get /cluster/nextid)\nID_LOCK_FILE=\"${LOG_DIR}/.id.lock\"\nID_COUNTER_FILE=\"${LOG_DIR}/.id.counter\"\necho -n \"${BASE_ID}\" > \"${ID_COUNTER_FILE}\"\n\n# Allocate a unique, currently free CT ID (concurrency-safe)\nallocate_container_id() {\n    local fd id\n    exec {fd}>\"${ID_LOCK_FILE}\"\n    flock \"${fd}\"\n    id=$(cat \"${ID_COUNTER_FILE}\" 2>/dev/null || echo \"${BASE_ID}\")\n    # Find the next free ID\n    while pct config \"$id\" &>/dev/null; do\n        id=$((id+1))\n    done\n    # Reserve next ID for subsequent calls\n    echo -n $((id+1)) > \"${ID_COUNTER_FILE}\"\n    flock -u \"${fd}\"\n    eval \"exec ${fd}>&-\"\n    echo \"$id\"\n}\n\n# UI helpers\nhide_cursor() { tput civis 2>/dev/null || true; }\nshow_cursor() { tput cnorm 2>/dev/null || true; }\nclear_screen() { tput clear 2>/dev/null || printf \"\\033c\"; }\nmove_cursor() { tput cup \"$1\" \"$2\" 2>/dev/null || printf \"\\033[$1;$2H\"; }\n\n# Draw the dashboard with a fixed number of panes equal to MAX_CONCURRENCY\ndraw_dashboard() {\n    local cols=$(tput cols 2>/dev/null || echo 120)\n    local rows=$(tput lines 2>/dev/null || echo 40)\n\n    # Layout: up to 3 columns depending on terminal width\n    local num_cols\n    if (( cols >= 180 )); then\n        num_cols=3\n    elif (( cols >= 100 )); then\n        num_cols=2\n    else\n        num_cols=1\n    fi\n    if (( MAX_CONCURRENCY < num_cols )); then\n        num_cols=$MAX_CONCURRENCY\n    fi\n    (( num_cols == 0 )) && return\n\n    local total=$MAX_CONCURRENCY\n    local num_rows=$(( (total + num_cols - 1) / num_cols ))\n    local header_rows=2\n    local footer_rows=1\n    local cell_h=$(( (rows - header_rows - footer_rows) / (num_rows > 0 ? num_rows : 1) ))\n    local cell_w=$(( cols / (num_cols > 0 ? num_cols : 1) ))\n    local content_h=$(( cell_h - 2 ))\n    local content_w=$(( cell_w - 2 ))\n    local print_w=$(( cell_w - 1 ))\n    if (( content_h < 2 )); then content_h=2; fi\n    if (( content_w < 20 )); then content_w=20; fi\n\n    clear_screen\n    move_cursor 0 0; printf \"Concurrent Test Runner  |  Concurrency: %s  |  %s\\n\" \"$MAX_CONCURRENCY\" \"$(date +%H:%M:%S)\"\n    printf \"%.0s-\" $(seq 1 \"$cols\"); printf \"\\n\"\n\n    local idx=0\n    while (( idx < total )); do\n        local r=$(( idx / num_cols ))\n        local c=$(( idx % num_cols ))\n        local top=$(( header_rows + r * cell_h ))\n        local left=$(( c * cell_w ))\n\n        local script=\"${SLOT_SCRIPT[$idx]:-}\"\n        local title=\"\"\n        local status=\"IDLE\"\n        local log_file=\"\"\n        local status_file=\"\"\n        local s_line=\"\" f2=\"\" f3=\"\" f4=\"\"\n        if [[ -n \"$script\" ]]; then\n            title=\"$script\"\n            log_file=\"${SCRIPT_LOG_DIR}/${script}.log\"\n            status_file=\"${SCRIPT_LOG_DIR}/${script}.status\"\n            if [[ -f \"$status_file\" ]]; then\n                s_line=$(head -n1 \"$status_file\")\n                IFS='|' read -r status f2 f3 f4 <<< \"$s_line\"\n            else\n                status=\"RUNNING\"\n            fi\n        else\n            title=\"Idle Slot\"\n        fi\n\n        move_cursor \"$top\" \"$left\"; printf \"%-${print_w}s\\n\" \"[$status] $title\"\n        local start_y=$(( top + 1 ))\n        local display_line=\"\"\n        case \"$status\" in\n            RUNNING)\n                if [[ -n \"$script\" && -f \"$log_file\" ]]; then\n                    local raw_last\n                    raw_last=$(tail -n 1 \"$log_file\" 2>/dev/null)\n                    # Strip carriage returns and ANSI escape sequences, expand tabs\n                    last_line=$(echo -n \"$raw_last\" | tr -d '\\r' | sed -r 's/\\x1B\\[[0-9;]*[A-Za-z]//g' | sed -e $'s/\\t/  /g')\n                    if [[ -z \"${last_line//[[:space:]]/}\" ]]; then\n                        display_line=\"(waiting for output)\"\n                    else\n                        display_line=\"$last_line\"\n                    fi\n                else\n                    display_line=\"(starting)\"\n                fi\n                ;;\n            QUEUED)\n                display_line=\"(queued)\"\n                ;;\n            SKIPPED)\n                display_line=\"${f2:-skipped}\"\n                ;;\n            FAILED)\n                display_line=\"Last: ${f4:-failed}\"\n                ;;\n            ACCESSIBLE)\n                display_line=\"${f3:-accessible}\"\n                ;;\n            INACCESSIBLE)\n                display_line=\"${f3:-inaccessible}\"\n                ;;\n            NOT_TESTED)\n                display_line=\"Last: ${f4:-no http}\"\n                ;;\n            *)\n                display_line=\"\"\n                ;;\n        esac\n        move_cursor \"$start_y\" \"$left\"; printf \"%-${print_w}s\" \"${display_line:0:$print_w}\"\n        idx=$(( idx + 1 ))\n    done\n\n    # Footer: show currently testing scripts or next queued\n    local footer_y=$(( rows - 1 ))\n    local testing_line=\"Testing: \"\n    local any_running=0\n    for i in $(seq 0 $(( MAX_CONCURRENCY - 1 ))); do\n        if [[ -n \"${SLOT_SCRIPT[$i]:-}\" ]]; then\n            testing_line+=\"${SLOT_SCRIPT[$i]}  \"\n            any_running=1\n        fi\n    done\n    if (( any_running == 0 )); then\n        if (( started_jobs < total_jobs )); then\n            testing_line+=\"${SCRIPT_LIST[$started_jobs]} (queued)\"\n        else\n            testing_line+=\"Done\"\n        fi\n    fi\n    move_cursor \"$footer_y\" 0\n    testing_line+=\"  |  Running: ${#JOB_PIDS[@]}  |  Finished: ${finished_jobs}  |  Total: ${total_jobs}\"\n    printf \"%-${cols}s\" \"$testing_line\"\n}\n\n# Function to extract HTTP URL from log file\nextract_http_url() {\n    local log_file=\"$1\"\n    # Look for http:// or https:// URLs in the log (with IP:PORT format)\n    # Common patterns: http://192.168.1.100:8080, https://10.0.0.1:3000\n    # Now supports HTTPS endpoints with self-signed certificates\n    grep -oP 'https?://[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+' \"$log_file\" | tail -1\n}\n\n# Function to extract last completed step from log file\nextract_last_step() {\n    local log_file=\"$1\"\n    # Look for msg_ok messages (success indicators)\n    # Also look for common success patterns\n    local last_step=$(grep -E '\\[OK\\]|\\[✓\\]|msg_ok|✔️|Completed Successfully' \"$log_file\" | tail -1 | sed 's/.*\\(msg_ok\\|OK\\|✓\\|✔️\\)//' | sed 's/^[^a-zA-Z]*//' | cut -c1-80)\n    \n    if [[ -n \"$last_step\" ]]; then\n        echo \"$last_step\"\n    else\n        echo \"No step information available\"\n    fi\n}\n\n# Function to monitor log and show current step\nmonitor_current_step() {\n    local log_file=\"$1\"\n    local script_name=\"$2\"\n    local monitor_file=\"/tmp/monitor_${script_name}_$$.txt\"\n    \n    # Background process to monitor log file\n    (\n        while [ -f \"$monitor_file\" ]; do\n            if [ -f \"$log_file\" ]; then\n                # Look for lines with hourglass emoji (⏳) which indicates current operation\n                # Also look for msg_info patterns as fallback\n                local current=$(grep -E '⏳|msg_info|Installing|Setting up|Configuring|Downloading|Building|Starting' \"$log_file\" | tail -1 | sed 's/.*⏳[[:space:]]*//' | sed 's/.*msg_info[[:space:]]*//' | sed 's/^[[:space:]]*//' | cut -c1-70)\n                if [[ -n \"$current\" ]]; then\n                    printf \"\\r\\033[K%b[CURRENT]%b %s: %s\" \"${BL}\" \"${CL}\" \"${script_name}\" \"${current}\"\n                fi\n            fi\n            sleep 1\n        done\n        printf \"\\r\\033[K\"\n    ) &\n    \n    echo \"$!\" > \"$monitor_file\"\n}\n\n# Function to stop monitoring\nstop_monitor() {\n    local script_name=\"$1\"\n    local monitor_file=\"/tmp/monitor_${script_name}_$$.txt\"\n    \n    if [ -f \"$monitor_file\" ]; then\n        local pid=$(cat \"$monitor_file\")\n        kill \"$pid\" 2>/dev/null || true\n        rm -f \"$monitor_file\"\n    fi\n    printf \"\\r\\033[K\"\n}\n\n# Function to test HTTP endpoint\ntest_http_endpoint() {\n    local url=\"$1\"\n    local timeout=10\n    \n    # Try to connect to the URL\n    # -k/--insecure allows HTTPS with invalid/self-signed certificates\n    if curl -s -k --max-time \"$timeout\" --connect-timeout \"$timeout\" -o /dev/null -w \"%{http_code}\" \"$url\" | grep -qE \"^(200|301|302|401|403)\"; then\n        return 0\n    else\n        return 1\n    fi\n}\n\n# Function to cleanup container on failure\ncleanup_container() {\n    local ctid=\"$1\"\n    if pct status \"$ctid\" &>/dev/null; then\n        log_warn \"Cleaning up container $ctid\"\n        pct stop \"$ctid\" 2>/dev/null || true\n        sleep 2\n        pct destroy \"$ctid\" 2>/dev/null || true\n    fi\n}\n\n# Function to create wrapper script for automated testing\ncreate_wrapper_script() {\n    local original_script=\"$1\"\n    local wrapper_script=\"$2\"\n    \n    # Create a wrapper that sources auto-build.func instead of build.func\n    cat > \"${wrapper_script}\" <<'WRAPPER_EOF'\n#!/usr/bin/env bash\n# Automated testing wrapper - sources auto-build.func instead of build.func\n\n# Read the original script and replace build.func with auto-build.func\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nORIGINAL_SCRIPT=\"ORIGINAL_SCRIPT_PATH\"\n\n# Create temp file with modified source\nTEMP_SCRIPT=$(mktemp)\nsed 's|https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func|file://'\"${SCRIPT_DIR}\"'/misc/auto-build.func|g' \"$ORIGINAL_SCRIPT\" > \"$TEMP_SCRIPT\"\n\n# Execute the modified script\nbash \"$TEMP_SCRIPT\"\nEXIT_CODE=$?\n\n# Cleanup\nrm -f \"$TEMP_SCRIPT\"\nexit $EXIT_CODE\nWRAPPER_EOF\n    \n    # Replace placeholder with actual path\n    sed -i \"s|ORIGINAL_SCRIPT_PATH|${original_script}|g\" \"${wrapper_script}\"\n    chmod +x \"${wrapper_script}\"\n}\n\n# Function to test a single script\ntest_script() {\n    local script_name=\"$1\"\n    local script_path=\"ct/${script_name}.sh\"\n    local script_log=\"${SCRIPT_LOG_DIR}/${script_name}.log\"\n    local wrapper_script=\"/tmp/test_wrapper_${script_name}_$$.sh\"\n    local status_file=\"${SCRIPT_LOG_DIR}/${script_name}.status\"\n    \n    log_info \"==================== Testing: ${script_name} ====================\"\n    \n    # Mark as running\n    echo \"RUNNING\" > \"$status_file\"\n\n    # Skip alpine scripts\n    if [[ \"$script_name\" =~ alpine ]]; then\n        log_warn \"Skipping alpine script: ${script_name}\"\n        echo \"SKIPPED|alpine\" > \"$status_file\"\n        SKIPPED_SCRIPTS+=(\"${script_name} (alpine - skipped)\")\n        return\n    fi\n    \n    # Check if script exists\n    if [[ ! -f \"$script_path\" ]]; then\n        log_warn \"Script not found: ${script_path}\"\n        echo \"SKIPPED|not_found\" > \"$status_file\"\n        SKIPPED_SCRIPTS+=(\"${script_name} (not found)\")\n        return\n    fi\n    \n    # Get next available container ID\n    local next_id=$(allocate_container_id)\n    log_info \"Using container ID: ${next_id}\"\n    \n    # Set environment variables for non-interactive execution\n    export VERBOSE=\"yes\"\n    export DIAGNOSTICS=\"no\"\n    export var_verbose=\"yes\"\n    export AUTO_TEST_MODE=\"yes\"\n    \n    # Create wrapper script (but use direct execution for simplicity)\n    log_info \"Starting container creation for ${script_name}...\"\n    \n    # Temporarily replace build.func reference in script\n    TEMP_SCRIPT=$(mktemp)\n    sed 's|source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)|source misc/auto-build.func|g' \"${script_path}\" > \"$TEMP_SCRIPT\"\n    \n    # Ensure child uses our allocated CTID\n    export var_ctid=\"${next_id}\"\n\n    # Run the script and capture output\n    if bash \"${TEMP_SCRIPT}\" > \"${script_log}\" 2>&1; then\n        rm -f \"$TEMP_SCRIPT\"\n        log_ok \"Container creation completed for ${script_name}\"\n        \n        # Extract last completed step\n        local last_step=$(extract_last_step \"${script_log}\")\n        log_info \"Last step: ${last_step}\"\n        \n        # Extract HTTP URL from log\n        local http_url=$(extract_http_url \"${script_log}\")\n        \n        if [[ -n \"$http_url\" ]]; then\n            log_info \"Found HTTP endpoint: ${http_url}\"\n            \n            # Wait a bit for service to start\n            log_info \"Waiting 5 seconds for service to initialize...\"\n            sleep 5\n            \n            # Test the endpoint\n            if test_http_endpoint \"$http_url\"; then\n                log_ok \"HTTP endpoint is accessible: ${http_url}\"\n                echo \"ACCESSIBLE|${next_id}|${http_url}|${last_step}\" > \"$status_file\"\n                ACCESSIBLE_CONTAINERS+=(\"${script_name}:${next_id}:${http_url}:${last_step}\")\n            else\n                log_error \"HTTP endpoint is not accessible: ${http_url}\"\n                echo \"INACCESSIBLE|${next_id}|${http_url}|${last_step}\" > \"$status_file\"\n                INACCESSIBLE_CONTAINERS+=(\"${script_name}:${next_id}:${http_url}:${last_step}\")\n            fi\n        else\n            log_warn \"No HTTP endpoint found in output\"\n            echo \"NOT_TESTED|${next_id}||${last_step}\" > \"$status_file\"\n            NOT_TESTED_CONTAINERS+=(\"${script_name}:${next_id}:${last_step}\")\n        fi\n        \n        # Stop the container after testing\n        log_info \"Stopping container ${next_id}...\"\n        pct stop \"${next_id}\" 2>/dev/null || true\n        \n    else\n        log_error \"Container creation failed for ${script_name}\"\n        \n        # Extract last completed step even on failure\n        local last_step=$(extract_last_step \"${script_log}\")\n        log_error \"Last completed step: ${last_step}\"\n        \n        echo \"FAILED|${next_id}||${last_step}\" > \"$status_file\"\n        FAILED_CONTAINERS+=(\"${script_name}:${next_id}:${last_step}\")\n        rm -f \"$TEMP_SCRIPT\"\n        \n        # Try to cleanup failed container\n        cleanup_container \"${next_id}\"\n    fi\n    \n    # Cleanup wrapper if it exists\n    rm -f \"${wrapper_script}\"\n    \n    log_info \"==================== Finished: ${script_name} ====================\\n\"\n}\n\n# Main concurrent testing controller\nlog_info \"Starting automated container testing...\"\nlog_info \"Log directory: ${LOG_DIR}\"\nlog_info \"\"\n\ndeclare -A JOB_PIDS=()\ndeclare -A JOB_STARTED=()\ndeclare -a SLOT_SCRIPT=()\nfor i in $(seq 0 $(( MAX_CONCURRENCY - 1 ))); do SLOT_SCRIPT[$i]=\"\"; done\n\ntotal_jobs=${#SCRIPT_LIST[@]}\nstarted_jobs=0\nfinished_jobs=0\n\nif [[ \"$USE_UI\" == \"yes\" ]]; then\n    hide_cursor\n    clear_screen\nfi\n\nwhile (( finished_jobs < total_jobs )); do\n    # Launch new jobs up to MAX_CONCURRENCY\n    while (( started_jobs < total_jobs )) && (( ${#JOB_PIDS[@]} < MAX_CONCURRENCY )); do\n        # Find a free slot\n        free_slot=-1\n        for i in $(seq 0 $(( MAX_CONCURRENCY - 1 ))); do\n            if [[ -z \"${SLOT_SCRIPT[$i]}\" ]]; then\n                free_slot=$i; break\n            fi\n        done\n        (( free_slot == -1 )) && break\n\n        script=\"${SCRIPT_LIST[$started_jobs]}\"\n        JOB_STARTED[\"$script\"]=1\n        SLOT_SCRIPT[$free_slot]=\"$script\"\n        # Ensure log/status files exist\n        : > \"${SCRIPT_LOG_DIR}/${script}.log\"\n        echo \"QUEUED\" > \"${SCRIPT_LOG_DIR}/${script}.status\"\n        # Start job\n        test_script \"$script\" &\n        pid=$!\n        JOB_PIDS[\"$script\"]=$pid\n        started_jobs=$(( started_jobs + 1 ))\n        # Small stagger to avoid thundering herd\n        sleep 1\n    done\n\n    # Draw UI\n    if [[ \"$USE_UI\" == \"yes\" ]]; then\n        draw_dashboard SCRIPT_LIST\n    fi\n\n    # Check for finished jobs\n    for s in \"${!JOB_PIDS[@]}\"; do\n        if ! kill -0 \"${JOB_PIDS[$s]}\" 2>/dev/null; then\n            unset 'JOB_PIDS[$s]'\n            # free its slot\n            for i in $(seq 0 $(( MAX_CONCURRENCY - 1 ))); do\n                if [[ \"${SLOT_SCRIPT[$i]:-}\" == \"$s\" ]]; then\n                    SLOT_SCRIPT[$i]=\"\"\n                    break\n                fi\n            done\n            finished_jobs=$(( finished_jobs + 1 ))\n        fi\n    done\n\n    sleep \"$REFRESH_INTERVAL\"\ndone\n\nif [[ \"$USE_UI\" == \"yes\" ]]; then\n    show_cursor\n    printf \"\\n\"\nfi\n\n# Regenerate arrays from status files for summary\nACCESSIBLE_CONTAINERS=()\nINACCESSIBLE_CONTAINERS=()\nNOT_TESTED_CONTAINERS=()\nFAILED_CONTAINERS=()\nSKIPPED_SCRIPTS=()\n\nfor script in \"${SCRIPT_LIST[@]}\"; do\n    status_file=\"${SCRIPT_LOG_DIR}/${script}.status\"\n    [[ ! -f \"$status_file\" ]] && continue\n    IFS='|' read -r status id url last_step < \"$status_file\"\n    case \"$status\" in\n        ACCESSIBLE)\n            ACCESSIBLE_CONTAINERS+=(\"${script}:${id}:${url}:${last_step}\")\n            ;;\n        INACCESSIBLE)\n            INACCESSIBLE_CONTAINERS+=(\"${script}:${id}:${url}:${last_step}\")\n            ;;\n        NOT_TESTED)\n            NOT_TESTED_CONTAINERS+=(\"${script}:${id}:${last_step}\")\n            ;;\n        FAILED)\n            FAILED_CONTAINERS+=(\"${script}:${id}:${last_step}\")\n            ;;\n        SKIPPED)\n            SKIPPED_SCRIPTS+=(\"${script} ($(echo \"$id\"))\")\n            ;;\n    esac\ndone\n\n# Generate summary report\nlog_info \"\"\nlog_info \"==================== TESTING SUMMARY ====================\"\n\n# Generate detailed summary for terminal/summary.log\n{\n    echo \"==========================================\"\n    echo \"Automated Container Testing Summary\"\n    echo \"Timestamp: ${TIMESTAMP}\"\n    echo \"==========================================\"\n    echo \"\"\n    \n    echo \"ACCESSIBLE CONTAINERS (${#ACCESSIBLE_CONTAINERS[@]}):\"\n    if [[ ${#ACCESSIBLE_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${ACCESSIBLE_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id url last_step <<< \"$item\"\n            echo \"  ✅ ${name} (CT:${id}) - ${url}\"\n            echo \"     Last step: ${last_step}\"\n        done\n    else\n        echo \"  (none)\"\n    fi\n    echo \"\"\n    \n    echo \"INACCESSIBLE CONTAINERS (${#INACCESSIBLE_CONTAINERS[@]}):\"\n    if [[ ${#INACCESSIBLE_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${INACCESSIBLE_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id url last_step <<< \"$item\"\n            echo \"  ❌ ${name} (CT:${id}) - ${url}\"\n            echo \"     Last step: ${last_step}\"\n        done\n    else\n        echo \"  (none)\"\n    fi\n    echo \"\"\n    \n    echo \"NOT TESTED (no HTTP endpoint found) (${#NOT_TESTED_CONTAINERS[@]}):\"\n    if [[ ${#NOT_TESTED_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${NOT_TESTED_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id last_step <<< \"$item\"\n            echo \"  ⚠️  ${name} (CT:${id})\"\n            echo \"     Last step: ${last_step}\"\n        done\n    else\n        echo \"  (none)\"\n    fi\n    echo \"\"\n    \n    echo \"FAILED CONTAINERS (${#FAILED_CONTAINERS[@]}):\"\n    if [[ ${#FAILED_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${FAILED_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id last_step <<< \"$item\"\n            echo \"  💥 ${name} (CT:${id})\"\n            echo \"     Last completed step: ${last_step}\"\n        done\n    else\n        echo \"  (none)\"\n    fi\n    echo \"\"\n    \n    echo \"SKIPPED SCRIPTS (${#SKIPPED_SCRIPTS[@]}):\"\n    if [[ ${#SKIPPED_SCRIPTS[@]} -gt 0 ]]; then\n        for item in \"${SKIPPED_SCRIPTS[@]}\"; do\n            echo \"  ⏭️  ${item}\"\n        done\n    else\n        echo \"  (none)\"\n    fi\n    echo \"\"\n    \n    echo \"==========================================\"\n    echo \"Total Scripts Processed: $(( ${#ACCESSIBLE_CONTAINERS[@]} + ${#INACCESSIBLE_CONTAINERS[@]} + ${#NOT_TESTED_CONTAINERS[@]} + ${#FAILED_CONTAINERS[@]} + ${#SKIPPED_SCRIPTS[@]} ))\"\n    echo \"Success Rate: $(( ${#ACCESSIBLE_CONTAINERS[@]} * 100 / (${#ACCESSIBLE_CONTAINERS[@]} + ${#INACCESSIBLE_CONTAINERS[@]} + ${#NOT_TESTED_CONTAINERS[@]} + ${#FAILED_CONTAINERS[@]} + 1) ))%\"\n    echo \"==========================================\"\n    \n} | tee \"${SUMMARY_LOG}\"\n\n# Generate simplified output.log in the requested format\n{\n    echo \"successful and http accessible:\"\n    if [[ ${#ACCESSIBLE_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${ACCESSIBLE_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id url last_step <<< \"$item\"\n            echo \"$name\"\n        done\n    fi\n    echo \"\"\n    \n    echo \"successful and not accessible:\"\n    if [[ ${#INACCESSIBLE_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${INACCESSIBLE_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id url last_step <<< \"$item\"\n            echo \"$name\"\n        done\n    fi\n    if [[ ${#NOT_TESTED_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${NOT_TESTED_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id last_step <<< \"$item\"\n            echo \"$name\"\n        done\n    fi\n    echo \"\"\n    \n    echo \"failed:\"\n    if [[ ${#FAILED_CONTAINERS[@]} -gt 0 ]]; then\n        for item in \"${FAILED_CONTAINERS[@]}\"; do\n            IFS=':' read -r name id last_step <<< \"$item\"\n            echo \"$name\"\n        done\n    fi\n    echo \"\"\n    \n    echo \"skipped:\"\n    if [[ ${#SKIPPED_SCRIPTS[@]} -gt 0 ]]; then\n        for item in \"${SKIPPED_SCRIPTS[@]}\"; do\n            # Extract just the script name from \"name (reason)\"\n            echo \"$item\" | cut -d' ' -f1\n        done\n    fi\n} > \"${OUTPUT_LOG}\"\n\nlog_ok \"Testing completed. Results saved to:\"\nlog_info \"  - Summary: ${SUMMARY_LOG}\"\nlog_info \"  - Full log: ${OUTPUT_LOG}\"\nlog_info \"  - Script logs: ${SCRIPT_LOG_DIR}/\"\n\n# Ask user if they want to destroy test containers\necho \"\"\nread -p \"Do you want to destroy all test containers? (y/N): \" -n 1 -r\necho\nif [[ $REPLY =~ ^[Yy]$ ]]; then\n    log_info \"Destroying test containers...\"\n    \n    for item in \"${ACCESSIBLE_CONTAINERS[@]}\" \"${INACCESSIBLE_CONTAINERS[@]}\" \"${NOT_TESTED_CONTAINERS[@]}\" \"${FAILED_CONTAINERS[@]}\"; do\n        IFS=':' read -r name id _ <<< \"$item\"\n        if [[ -n \"$id\" ]] && pct status \"$id\" &>/dev/null; then\n            log_info \"Destroying container ${id} (${name})...\"\n            pct stop \"$id\" 2>/dev/null || true\n            sleep 2\n            pct destroy \"$id\" 2>/dev/null || true\n        fi\n    done\n    \n    log_ok \"All test containers destroyed\"\nelse\n    log_info \"Test containers left running for manual inspection\"\nfi\n\nlog_ok \"Automation complete!\"\n"
  },
  {
    "path": "misc/test-build.func",
    "content": "# Copyright (c) 2021-2025 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nvariables() {\n  NSAPP=$(echo ${APP,,} | tr -d ' ')                # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.\n  var_install=\"${NSAPP}-install\"                    # sets the var_install variable by appending \"-install\" to the value of NSAPP.\n  INTEGER='^[0-9]+([.][0-9]+)?$'                    # it defines the INTEGER regular expression pattern.\n  PVEHOST_NAME=$(hostname)                          # gets the Proxmox Hostname and sets it to Uppercase\n  DIAGNOSTICS=\"yes\"                                 # sets the DIAGNOSTICS variable to \"yes\", used for the API call.\n  METHOD=\"default\"                                  # sets the METHOD variable to \"default\", used for the API call.\n  RANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\" # generates a random UUID and sets it to the RANDOM_UUID variable.\n}\n\nsource <(curl -s https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\n# This function sets various color variables using ANSI escape codes for formatting text in the terminal.\ncolor() {\n  # Colors\n  YW=$(echo \"\\033[33m\")\n  YWB=$(echo \"\\033[93m\")\n  BL=$(echo \"\\033[36m\")\n  RD=$(echo \"\\033[01;31m\")\n  BGN=$(echo \"\\033[4;92m\")\n  GN=$(echo \"\\033[1;92m\")\n  DGN=$(echo \"\\033[32m\")\n\n  # Formatting\n  CL=$(echo \"\\033[m\")\n  BOLD=$(echo \"\\033[1m\")\n  HOLD=\" \"\n  TAB=\"  \"\n\n  # Icons\n  CM=\"${TAB}✔️${TAB}\"\n  CROSS=\"${TAB}✖️${TAB}\"\n  INFO=\"${TAB}💡${TAB}${CL}\"\n  OS=\"${TAB}🖥️${TAB}${CL}\"\n  OSVERSION=\"${TAB}🌟${TAB}${CL}\"\n  CONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\n  DISKSIZE=\"${TAB}💾${TAB}${CL}\"\n  CPUCORE=\"${TAB}🧠${TAB}${CL}\"\n  RAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\n  SEARCH=\"${TAB}🔍${TAB}${CL}\"\n  VERBOSE_CROPPED=\"🔍${TAB}\"\n  VERIFYPW=\"${TAB}🔐${TAB}${CL}\"\n  CONTAINERID=\"${TAB}🆔${TAB}${CL}\"\n  HOSTNAME=\"${TAB}🏠${TAB}${CL}\"\n  BRIDGE=\"${TAB}🌉${TAB}${CL}\"\n  NETWORK=\"${TAB}📡${TAB}${CL}\"\n  GATEWAY=\"${TAB}🌐${TAB}${CL}\"\n  DISABLEIPV6=\"${TAB}🚫${TAB}${CL}\"\n  DEFAULT=\"${TAB}⚙️${TAB}${CL}\"\n  MACADDRESS=\"${TAB}🔗${TAB}${CL}\"\n  VLANTAG=\"${TAB}🏷️${TAB}${CL}\"\n  ROOTSSH=\"${TAB}🔑${TAB}${CL}\"\n  CREATING=\"${TAB}🚀${TAB}${CL}\"\n  ADVANCED=\"${TAB}🧩${TAB}${CL}\"\n}\n\n# This function enables error handling in the script by setting options and defining a trap for the ERR signal.\ncatch_errors() {\n  set -Eeuo pipefail\n  trap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\n}\n\n# This function is called when an error occurs. It receives the exit code, line number, and command that caused the error, and displays an error message.\nerror_handler() {\n  source /dev/stdin <<<$(wget -qLO - https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n  if [ -n \"$SPINNER_PID\" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi\n  printf \"\\e[?25h\"\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${command}\"\n  echo -e \"\\n$error_message\\n\"\n}\n\n# This function displays an informational message with logging support.\ndeclare -A MSG_INFO_SHOWN\nSPINNER_ACTIVE=0\nSPINNER_PID=\"\"\nSPINNER_MSG=\"\"\n\ntrap 'stop_spinner' EXIT INT TERM HUP\n\nstart_spinner() {\n  local msg=\"$1\"\n  local frames=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)\n  local spin_i=0\n  local interval=0.1\n\n  SPINNER_MSG=\"$msg\"\n  printf \"\\r\\e[2K\" >&2\n\n  {\n    while [[ \"$SPINNER_ACTIVE\" -eq 1 ]]; do\n      printf \"\\r\\e[2K%s %b\" \"${frames[spin_i]}\" \"${YW}${SPINNER_MSG}${CL}\" >&2\n      spin_i=$(((spin_i + 1) % ${#frames[@]}))\n      sleep \"$interval\"\n    done\n  } &\n\n  SPINNER_PID=$!\n  disown \"$SPINNER_PID\"\n}\n\nstop_spinner() {\n  if [[ ${SPINNER_PID+v} && -n \"$SPINNER_PID\" ]] && kill -0 \"$SPINNER_PID\" 2>/dev/null; then\n    kill \"$SPINNER_PID\" 2>/dev/null\n    sleep 0.1\n    kill -0 \"$SPINNER_PID\" 2>/dev/null && kill -9 \"$SPINNER_PID\" 2>/dev/null\n    wait \"$SPINNER_PID\" 2>/dev/null || true\n  fi\n  SPINNER_ACTIVE=0\n  unset SPINNER_PID\n}\n\nspinner_guard() {\n  if [[ \"$SPINNER_ACTIVE\" -eq 1 ]] && [[ -n \"$SPINNER_PID\" ]]; then\n    kill \"$SPINNER_PID\" 2>/dev/null\n    wait \"$SPINNER_PID\" 2>/dev/null || true\n    SPINNER_ACTIVE=0\n    unset SPINNER_PID\n  fi\n}\n\nmsg_info() {\n  local msg=\"$1\"\n  [[ -n \"${MSG_INFO_SHOWN[\"$msg\"]+x}\" ]] && return\n  MSG_INFO_SHOWN[\"$msg\"]=1\n\n  spinner_guard\n  SPINNER_ACTIVE=1\n  start_spinner \"$msg\"\n}\n\nmsg_ok() {\n  local msg=\"$1\"\n  stop_spinner\n  printf \"\\r\\e[2K%s %b\\n\" \"${CM}\" \"${GN}${msg}${CL}\" >&2\n  unset MSG_INFO_SHOWN[\"$msg\"]\n}\n\nmsg_error() {\n  stop_spinner\n  local msg=\"$1\"\n  printf \"\\r\\e[2K%s %b\\n\" \"${CROSS}\" \"${RD}${msg}${CL}\" >&2\n  log_message \"ERROR\" \"$msg\"\n}\n\nlog_message() {\n  local level=\"$1\"\n  local message=\"$2\"\n  local timestamp\n  local logdate\n  timestamp=$(date '+%Y-%m-%d %H:%M:%S')\n  logdate=$(date '+%Y-%m-%d')\n\n  LOGDIR=\"/usr/local/community-scripts/logs\"\n  mkdir -p \"$LOGDIR\"\n\n  LOGFILE=\"${LOGDIR}/${logdate}_${NSAPP}.log\"\n  echo \"$timestamp - $level: $message\" >>\"$LOGFILE\"\n}\n\n# Check if the shell is using bash\nshell_check() {\n  if [[ \"$(basename \"$SHELL\")\" != \"bash\" ]]; then\n    clear\n    msg_error \"Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# Run as root only\nroot_check() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\npve_check() {\n  if ! pveversion | grep -Eq \"pve-manager/8\\.[1-3](\\.[0-9]+)*\"; then\n    msg_error \"${CROSS}${RD}This version of Proxmox Virtual Environment is not supported\"\n    echo -e \"Requires Proxmox Virtual Environment Version 8.1 or later.\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# When a node is running tens of containers, it's possible to exceed the kernel's cryptographic key storage allocations.\n# These are tuneable, so verify if the currently deployment is approaching the limits, advise the user on how to tune the limits, and exit the script.\n# https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html\nmaxkeys_check() {\n  # Read kernel parameters\n  per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0)\n  per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0)\n\n  # Exit if kernel parameters are unavailable\n  if [[ \"$per_user_maxkeys\" -eq 0 || \"$per_user_maxbytes\" -eq 0 ]]; then\n    echo -e \"${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}\"\n    exit 1\n  fi\n\n  # Fetch key usage for user ID 100000 (typical for containers)\n  used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0)\n  used_lxc_bytes=$(awk '/100000:/ {split($5, a, \"/\"); print a[1]}' /proc/key-users 2>/dev/null || echo 0)\n\n  # Calculate thresholds and suggested new limits\n  threshold_keys=$((per_user_maxkeys - 100))\n  threshold_bytes=$((per_user_maxbytes - 1000))\n  new_limit_keys=$((per_user_maxkeys * 2))\n  new_limit_bytes=$((per_user_maxbytes * 2))\n\n  # Check if key or byte usage is near limits\n  failure=0\n  if [[ \"$used_lxc_keys\" -gt \"$threshold_keys\" ]]; then\n    echo -e \"${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}\"\n    echo -e \"${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}.\"\n    failure=1\n  fi\n  if [[ \"$used_lxc_bytes\" -gt \"$threshold_bytes\" ]]; then\n    echo -e \"${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}\"\n    echo -e \"${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}.\"\n    failure=1\n  fi\n\n  # Provide next steps if issues are detected\n  if [[ \"$failure\" -eq 1 ]]; then\n    echo -e \"${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}\"\n    exit 1\n  fi\n\n  echo -e \"${CM}${GN} All kernel key limits are within safe thresholds.${CL}\"\n}\n\n# This function checks the system architecture and exits if it's not \"arm64\".\narch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work on non arm64 systems! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for AMD64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# Function to get the current IP address based on the distribution\nget_current_ip() {\n  if [ -f /etc/os-release ]; then\n    # Check for Debian/Ubuntu (uses hostname -I)\n    if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n      CURRENT_IP=$(hostname -I | awk '{print $1}')\n    # Check for Alpine (uses ip command)\n    elif grep -q 'ID=alpine' /etc/os-release; then\n      CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)\n    else\n      CURRENT_IP=\"Unknown\"\n    fi\n  fi\n  echo \"$CURRENT_IP\"\n}\n\n# Function to update the IP address in the MOTD file\nupdate_motd_ip() {\n  MOTD_FILE=\"/etc/motd\"\n\n  if [ -f \"$MOTD_FILE\" ]; then\n    # Remove existing IP Address lines to prevent duplication\n    sed -i '/IP Address:/d' \"$MOTD_FILE\"\n\n    IP=$(get_current_ip)\n    # Add the new IP address\n    echo -e \"${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}\" >>\"$MOTD_FILE\"\n  fi\n}\n\n# Function to download & save header files\nget_header() {\n    local app_name=$(echo \"${APP,,}\" | tr -d ' ')\n    local header_url=\"https://raw.githubusercontent.com/asylumexp/Proxmox/main/ct/headers/${app_name}\"\n    local local_header_path=\"/usr/local/community-scripts/headers/${app_name}\"\n\n    mkdir -p \"$(dirname \"$local_header_path\")\"\n\n    if [ ! -s \"$local_header_path\" ]; then\n        if ! curl -fsSL \"$header_url\" -o \"$local_header_path\"; then\n            echo -e \"Failed to download header for ${app_name}. No header will be displayed.\"\n            return 1\n        fi\n    fi\n\n    cat \"$local_header_path\"\n}\n\n# This function sets the APP-Name into an ASCII Header in Slant, figlet needed on proxmox main node.\nheader_info() {\n  local app_name=$(echo ${APP,,} | tr -d ' ')\n  local header_content\n\n  # Download & save Header-File locally\n  header_content=$(get_header \"$app_name\")\n  if [ $? -ne 0 ]; then\n    # Fallback: Doesn't show Header\n    return 0\n  fi\n\n  # Show ASCII-Header\n  term_width=$(tput cols 2>/dev/null || echo 120)\n  clear\n  echo \"$header_content\"\n}\n\n# This function checks if the script is running through SSH and prompts the user to confirm if they want to proceed or exit.\nssh_check() {\n  if [ -n \"${SSH_CLIENT:+x}\" ]; then\n    if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's advisable to utilize the Proxmox shell rather than SSH, as there may be potential complications with variable retrieval. Proceed using SSH?\" 10 72; then\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Proceed using SSH\" \"You've chosen to proceed using SSH. If any issues arise, please run the script in the Proxmox shell before creating a repository issue.\" 10 72\n    else\n      clear\n      echo \"Exiting due to SSH usage. Please consider using the Proxmox shell.\"\n      exit\n    fi\n  fi\n}\n\nbase_settings() {\n  # Default Settings\n  CT_TYPE=\"1\"\n  DISK_SIZE=\"4\"\n  CORE_COUNT=\"1\"\n  RAM_SIZE=\"1024\"\n  VERBOSE=\"${1:-no}\"\n  PW=\"\"\n  CT_ID=$NEXTID\n  HN=$NSAPP\n  BRG=\"vmbr0\"\n  NET=\"dhcp\"\n  GATE=\"\"\n  APT_CACHER=\"\"\n  APT_CACHER_IP=\"\"\n  DISABLEIP6=\"no\"\n  MTU=\"\"\n  SD=\"\"\n  NS=\"\"\n  MAC=\"\"\n  VLAN=\"\"\n  SSH=\"no\"\n  SSH_AUTHORIZED_KEY=\"\"\n  TAGS=\"community-script;\"\n\n  # Override default settings with variables from ct script\n  CT_TYPE=${var_unprivileged:-$CT_TYPE}\n  DISK_SIZE=${var_disk:-$DISK_SIZE}\n  CORE_COUNT=${var_cpu:-$CORE_COUNT}\n  RAM_SIZE=${var_ram:-$RAM_SIZE}\n  VERB=${var_verbose:-$VERBOSE}\n  TAGS=\"${TAGS}${var_tags:-}\"\n\n  # Since these 2 are only defined outside of default_settings function, we add a temporary fallback. TODO: To align everything, we should add these as constant variables (e.g. OSTYPE and OSVERSION), but that would currently require updating the default_settings function for all existing scripts\n  if [ -z \"$var_os\" ]; then\n    var_os=\"debian\"\n  fi\n  if [ -z \"$var_version\" ]; then\n    var_version=\"12\"\n  fi\n}\n\n# This function displays the default values for various settings.\necho_default() {\n  # Convert CT_TYPE to description\n  CT_TYPE_DESC=\"Unprivileged\"\n  if [ \"$CT_TYPE\" -eq 0 ]; then\n    CT_TYPE_DESC=\"Privileged\"\n  fi\n\n  # Output the selected values with icons\n  echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}\"\n  echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}\"\n  if [ \"$VERB\" == \"yes\" ]; then\n    echo -e \"${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}\"\n  fi\n  echo -e \"${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}\"\n  echo -e \"  \"\n}\n\n# This function is called when the user decides to exit the script. It clears the screen and displays an exit message.\nexit_script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\n# This function allows the user to configure advanced settings for the script.\nadvanced_settings() {\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Here is an instructional tip:\" \"To make a selection, use the Spacebar.\" 8 58\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Default distribution for $APP\" \"Default is: ${var_os} ${var_version} \\n \\nIf the default Linux distribution is not adhered to, script support will be discontinued. \\n\" 10 58\n  if [ \"$var_os\" != \"alpine\" ]; then\n    var_default_os=\"${var_os}\"\n    var_os=\"\"\n    while [ -z \"$var_os\" ]; do\n      if [ \"$var_default_os\" == \"debian\" ]; then\n        if var_os=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISTRIBUTION\" --radiolist \"Choose Distribution\" 10 58 2 \\\n          \"debian\" \"\" ON \\\n          \"ubuntu\" \"\" OFF \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_os\" ]; then\n            echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      fi\n      if [ \"$var_default_os\" == \"ubuntu\" ]; then\n        if var_os=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISTRIBUTION\" --radiolist \"Choose Distribution\" 10 58 2 \\\n          \"debian\" \"\" OFF \\\n          \"ubuntu\" \"\" ON \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_os\" ]; then\n            echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      fi\n    done\n  fi\n\n  if [ \"$var_os\" == \"debian\" ]; then\n    var_default_version=\"${var_version}\"\n    var_version=\"\"\n    while [ -z \"$var_version\" ]; do\n      if [ \"$var_default_version\" == \"11\" ]; then\n        if var_version=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DEBIAN VERSION\" --radiolist \"Choose Version\" 10 58 2 \\\n          \"11\" \"Bullseye\" ON \\\n          \"12\" \"Bookworm\" OFF \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_version\" ]; then\n            echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      fi\n      if [ \"$var_default_version\" == \"12\" ]; then\n        if var_version=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DEBIAN VERSION\" --radiolist \"Choose Version\" 10 58 2 \\\n          \"11\" \"Bullseye\" OFF \\\n          \"12\" \"Bookworm\" ON \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_version\" ]; then\n            echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      fi\n    done\n  fi\n\n  if [ \"$var_os\" == \"ubuntu\" ]; then\n    var_default_version=\"${var_version}\"\n    var_version=\"\"\n    while [ -z \"$var_version\" ]; do\n      if [ \"$var_default_version\" == \"20.04\" ]; then\n        if var_version=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UBUNTU VERSION\" --radiolist \"Choose Version\" 10 58 4 \\\n          \"20.04\" \"Focal\" ON \\\n          \"22.04\" \"Jammy\" OFF \\\n          \"24.04\" \"Noble\" OFF \\\n          \"24.10\" \"Oracular\" OFF \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_version\" ]; then\n            echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      elif [ \"$var_default_version\" == \"22.04\" ]; then\n        if var_version=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UBUNTU VERSION\" --radiolist \"Choose Version\" 10 58 4 \\\n          \"20.04\" \"Focal\" OFF \\\n          \"22.04\" \"Jammy\" ON \\\n          \"24.04\" \"Noble\" OFF \\\n          \"24.10\" \"Oracular\" OFF \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_version\" ]; then\n            echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      elif [ \"$var_default_version\" == \"24.04\" ]; then\n        if var_version=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UBUNTU VERSION\" --radiolist \"Choose Version\" 10 58 4 \\\n          \"20.04\" \"Focal\" OFF \\\n          \"22.04\" \"Jammy\" OFF \\\n          \"24.04\" \"Noble\" ON \\\n          \"24.10\" \"Oracular\" OFF \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_version\" ]; then\n            echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      else\n        if var_version=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UBUNTU VERSION\" --radiolist \"Choose Version\" 10 58 4 \\\n          \"20.04\" \"Focal\" OFF \\\n          \"22.04\" \"Jammy\" OFF \\\n          \"24.04\" \"Noble\" OFF \\\n          \"24.10\" \"Oracular\" ON \\\n          3>&1 1>&2 2>&3); then\n          if [ -n \"$var_version\" ]; then\n            echo -e \"${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}\"\n          fi\n        else\n          exit_script\n        fi\n      fi\n    done\n  fi\n  # Setting Default Tag for Advanced Settings\n  TAGS=\"community-script;${var_tags:-}\"\n  CT_DEFAULT_TYPE=\"${CT_TYPE}\"\n  CT_TYPE=\"\"\n  while [ -z \"$CT_TYPE\" ]; do\n    if [ \"$CT_DEFAULT_TYPE\" == \"1\" ]; then\n      if CT_TYPE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CONTAINER TYPE\" --radiolist \"Choose Type\" 10 58 2 \\\n        \"1\" \"Unprivileged\" ON \\\n        \"0\" \"Privileged\" OFF \\\n        3>&1 1>&2 2>&3); then\n        if [ -n \"$CT_TYPE\" ]; then\n          CT_TYPE_DESC=\"Unprivileged\"\n          if [ \"$CT_TYPE\" -eq 0 ]; then\n            CT_TYPE_DESC=\"Privileged\"\n          fi\n          echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}\"\n        fi\n      else\n        exit_script\n      fi\n    fi\n    if [ \"$CT_DEFAULT_TYPE\" == \"0\" ]; then\n      if CT_TYPE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CONTAINER TYPE\" --radiolist \"Choose Type\" 10 58 2 \\\n        \"1\" \"Unprivileged\" OFF \\\n        \"0\" \"Privileged\" ON \\\n        3>&1 1>&2 2>&3); then\n        if [ -n \"$CT_TYPE\" ]; then\n          CT_TYPE_DESC=\"Unprivileged\"\n          if [ \"$CT_TYPE\" -eq 0 ]; then\n            CT_TYPE_DESC=\"Privileged\"\n          fi\n          echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}\"\n        fi\n      else\n        exit_script\n      fi\n    fi\n  done\n\n  while true; do\n    if PW1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --passwordbox \"\\nSet Root Password (needed for root ssh access)\" 9 58 --title \"PASSWORD (leave blank for automatic login)\" 3>&1 1>&2 2>&3); then\n      if [[ ! -z \"$PW1\" ]]; then\n        if [[ \"$PW1\" == *\" \"* ]]; then\n          whiptail --msgbox \"Password cannot contain spaces. Please try again.\" 8 58\n        elif [ ${#PW1} -lt 5 ]; then\n          whiptail --msgbox \"Password must be at least 5 characters long. Please try again.\" 8 58\n        else\n          if PW2=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --passwordbox \"\\nVerify Root Password\" 9 58 --title \"PASSWORD VERIFICATION\" 3>&1 1>&2 2>&3); then\n            if [[ \"$PW1\" == \"$PW2\" ]]; then\n              PW=\"-password $PW1\"\n              echo -e \"${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}\"\n              break\n            else\n              whiptail --msgbox \"Passwords do not match. Please try again.\" 8 58\n            fi\n          else\n            exit_script\n          fi\n        fi\n      else\n        PW1=\"Automatic Login\"\n        PW=\"\"\n        echo -e \"${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}\"\n        break\n      fi\n    else\n      exit_script\n    fi\n  done\n\n  if CT_ID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Container ID\" 8 58 $NEXTID --title \"CONTAINER ID\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$CT_ID\" ]; then\n      CT_ID=\"$NEXTID\"\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}\"\n    else\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}\"\n    fi\n  else\n    exit\n  fi\n\n  if CT_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 $NSAPP --title \"HOSTNAME\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$CT_NAME\" ]; then\n      HN=\"$NSAPP\"\n    else\n      HN=$(echo ${CT_NAME,,} | tr -d ' ')\n    fi\n    echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n  else\n    exit_script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GB\" 8 58 $var_disk --title \"DISK SIZE\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$DISK_SIZE\" ]; then\n      DISK_SIZE=\"$var_disk\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}\"\n    else\n      if ! [[ $DISK_SIZE =~ $INTEGER ]]; then\n        echo -e \"{INFO}${HOLD}${RD} DISK SIZE MUST BE AN INTEGER NUMBER!${CL}\"\n        advanced_settings\n      fi\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}\"\n    fi\n  else\n    exit_script\n  fi\n\n  if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 $var_cpu --title \"CORE COUNT\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$CORE_COUNT\" ]; then\n      CORE_COUNT=\"$var_cpu\"\n      echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n    else\n      echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n    fi\n  else\n    exit_script\n  fi\n\n  if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 $var_ram --title \"RAM\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$RAM_SIZE\" ]; then\n      RAM_SIZE=\"$var_ram\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}\"\n    fi\n  else\n    exit_script\n  fi\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$BRG\" ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit_script\n  fi\n\n  while true; do\n    NET=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Static IPv4 CIDR Address (/24)\" 8 58 dhcp --title \"IP ADDRESS\" 3>&1 1>&2 2>&3)\n    exit_status=$?\n    if [ $exit_status -eq 0 ]; then\n      if [ \"$NET\" = \"dhcp\" ]; then\n        echo -e \"${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}\"\n        break\n      else\n        if [[ \"$NET\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then\n          echo -e \"${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}\"\n          break\n        else\n          whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox \"$NET is an invalid IPv4 CIDR address. Please enter a valid IPv4 CIDR address or 'dhcp'\" 8 58\n        fi\n      fi\n    else\n      exit_script\n    fi\n  done\n\n  if [ \"$NET\" != \"dhcp\" ]; then\n    while true; do\n      GATE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Enter gateway IP address\" 8 58 --title \"Gateway IP\" 3>&1 1>&2 2>&3)\n      if [ -z \"$GATE1\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox \"Gateway IP address cannot be empty\" 8 58\n      elif [[ ! \"$GATE1\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox \"Invalid IP address format\" 8 58\n      else\n        GATE=\",gw=$GATE1\"\n        echo -e \"${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}\"\n        break\n      fi\n    done\n  else\n    GATE=\"\"\n    echo -e \"${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}\"\n  fi\n\n  if [ \"$var_os\" == \"alpine\" ]; then\n    APT_CACHER=\"\"\n    APT_CACHER_IP=\"\"\n  else\n    if APT_CACHER_IP=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set APT-Cacher IP (leave blank for none)\" 8 58 --title \"APT-Cacher IP\" 3>&1 1>&2 2>&3); then\n      APT_CACHER=\"${APT_CACHER_IP:+yes}\"\n      echo -e \"${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}\"\n    else\n      exit_script\n    fi\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"IPv6\" --yesno \"Disable IPv6?\" 10 58); then\n    DISABLEIP6=\"yes\"\n  else\n    DISABLEIP6=\"no\"\n  fi\n  echo -e \"${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}$DISABLEIP6${CL}\"\n\n  if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])\" 8 58 --title \"MTU SIZE\" 3>&1 1>&2 2>&3); then\n    if [ -z $MTU1 ]; then\n      MTU1=\"Default\"\n      MTU=\"\"\n    else\n      MTU=\",mtu=$MTU1\"\n    fi\n    echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n  else\n    exit_script\n  fi\n\n  if SD=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a DNS Search Domain (leave blank for HOST)\" 8 58 --title \"DNS Search Domain\" 3>&1 1>&2 2>&3); then\n    if [ -z $SD ]; then\n      SX=Host\n      SD=\"\"\n    else\n      SX=$SD\n      SD=\"-searchdomain=$SD\"\n    fi\n    echo -e \"${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}\"\n  else\n    exit_script\n  fi\n\n  if NX=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a DNS Server IP (leave blank for HOST)\" 8 58 --title \"DNS SERVER IP\" 3>&1 1>&2 2>&3); then\n    if [ -z $NX ]; then\n      NX=Host\n      NS=\"\"\n    else\n      NS=\"-nameserver=$NX\"\n    fi\n    echo -e \"${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}\"\n  else\n    exit_script\n  fi\n\n  if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address(leave blank for generated MAC)\" 8 58 --title \"MAC ADDRESS\" 3>&1 1>&2 2>&3); then\n    if [ -z $MAC1 ]; then\n      MAC1=\"Default\"\n      MAC=\"\"\n    else\n      MAC=\",hwaddr=$MAC1\"\n      echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n    fi\n  else\n    exit_script\n  fi\n\n  if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for no VLAN)\" 8 58 --title \"VLAN\" 3>&1 1>&2 2>&3); then\n    if [ -z $VLAN1 ]; then\n      VLAN1=\"Default\"\n      VLAN=\"\"\n    else\n      VLAN=\",tag=$VLAN1\"\n    fi\n    echo -e \"${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}\"\n  else\n    exit_script\n  fi\n\n  if ADV_TAGS=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Custom Tags?[If you remove all, there will be no tags!]\" 8 58 ${TAGS} --title \"Advanced Tags\" 3>&1 1>&2 2>&3); then\n    if [ -n \"${ADV_TAGS}\" ]; then\n      ADV_TAGS=$(echo \"$ADV_TAGS\" | tr -d '[:space:]')\n      TAGS=\"${ADV_TAGS}\"\n    else\n      TAGS=\";\"\n    fi\n    echo -e \"${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}\"\n  else\n    exit_script\n  fi\n\n  if [[ \"$PW\" == -password* ]]; then\n    if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH ACCESS\" --yesno \"Enable Root SSH Access?\" 10 58); then\n      SSH=\"yes\"\n    else\n      SSH=\"no\"\n    fi\n    echo -e \"${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}\"\n  else\n    SSH=\"no\"\n    echo -e \"${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}\"\n  fi\n\n  if [[ \"${SSH}\" == \"yes\" ]]; then\n    SSH_AUTHORIZED_KEY=\"$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"SSH Authorized key for root (leave empty for none)\" 8 58 --title \"SSH Key\" 3>&1 1>&2 2>&3)\"\n\n    if [[ -z \"${SSH_AUTHORIZED_KEY}\" ]]; then\n      echo \"Warning: No SSH key provided.\"\n    fi\n  else\n    SSH_AUTHORIZED_KEY=\"\"\n  fi\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"VERBOSE MODE\" --yesno \"Enable Verbose Mode?\" 10 58); then\n    VERB=\"yes\"\n  else\n    VERB=\"no\"\n  fi\n  echo -e \"${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERB${CL}\"\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create ${APP} LXC?\" 10 58); then\n    echo -e \"${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above advanced settings${CL}\"\n  else\n    clear\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}\"\n    advanced_settings\n  fi\n}\n\ndiagnostics_check() {\n  if ! [ -d \"/usr/local/community-scripts\" ]; then\n    mkdir -p /usr/local/community-scripts\n  fi\n\n  if ! [ -f \"/usr/local/community-scripts/diagnostics\" ]; then\n    if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DIAGNOSTICS\" --yesno \"Send Diagnostics of LXC Installation?\\n\\n(This only transmits data without user data, just RAM, CPU, LXC name, ...)\" 10 58); then\n      cat <<EOF >/usr/local/community-scripts/diagnostics\nDIAGNOSTICS=yes\n\n#This file is used to store the diagnostics settings for the Community-Scripts API.\n#https://github.com/community-scripts/ProxmoxVE/discussions/1836\n#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes.\n#You can review the data at https://community-scripts.github.io/ProxmoxVE/data\n#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to \"no\" in /usr/local/community-scripts/diagnostics, or use the menue.\n#This will disable the diagnostics feature.\n#To send diagnostics, set the variable 'DIAGNOSTICS' to \"yes\" in /usr/local/community-scripts/diagnostics, or use the menue.\n#This will enable the diagnostics feature.\n#The following information will be sent:\n#\"ct_type\"\n#\"disk_size\"\n#\"core_count\"\n#\"ram_size\"\n#\"os_type\"\n#\"os_version\"\n#\"disableip6\"\n#\"nsapp\"\n#\"method\"\n#\"pve_version\"\n#\"status\"\n#If you have any concerns, please review the source code at /misc/build.func\nEOF\n      DIAGNOSTICS=\"yes\"\n    else\n      cat <<EOF >/usr/local/community-scripts/diagnostics\nDIAGNOSTICS=no\n\n#This file is used to store the diagnostics settings for the Community-Scripts API.\n#https://github.com/community-scripts/ProxmoxVE/discussions/1836\n#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes.\n#You can review the data at https://community-scripts.github.io/ProxmoxVE/data\n#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to \"no\" in /usr/local/community-scripts/diagnostics, or use the menue.\n#This will disable the diagnostics feature.\n#To send diagnostics, set the variable 'DIAGNOSTICS' to \"yes\" in /usr/local/community-scripts/diagnostics, or use the menue.\n#This will enable the diagnostics feature.\n#The following information will be sent:\n#\"ct_type\"\n#\"disk_size\"\n#\"core_count\"\n#\"ram_size\"\n#\"os_type\"\n#\"os_version\"\n#\"disableip6\"\n#\"nsapp\"\n#\"method\"\n#\"pve_version\"\n#\"status\"\n#If you have any concerns, please review the source code at /misc/build.func\nEOF\n      DIAGNOSTICS=\"no\"\n    fi\n  else\n    DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' /usr/local/community-scripts/diagnostics)\n\n  fi\n\n}\n\ninstall_script() {\n  pve_check\n  shell_check\n  root_check\n  ssh_check\n  maxkeys_check\n  diagnostics_check\n\n  if systemctl is-active -q ping-instances.service; then\n    systemctl -q stop ping-instances.service\n  fi\n\n  NEXTID=$(pvesh get /cluster/nextid)\n  timezone=$(cat /etc/timezone)\n  header_info\n\n  echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME (${VERBOSE_CROPPED}Verbose)${CL}\"\n  VERB=\"yes\"\n  METHOD=\"default\"\n  base_settings \"$VERB\"\n  echo_default\n}\n\n\ncheck_container_resources() {\n  # Check actual RAM & Cores\n  current_ram=$(free -m | awk 'NR==2{print $2}')\n  current_cpu=$(nproc)\n\n  # Check whether the current RAM is less than the required RAM or the CPU cores are less than required\n  if [[ \"$current_ram\" -lt \"$var_ram\" ]] || [[ \"$current_cpu\" -lt \"$var_cpu\" ]]; then\n    echo -e \"\\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}\"\n    echo -e \"${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\\n\"\n    echo -ne \"${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? <yes/No>  \"\n    read -r prompt\n    # Check if the input is 'yes', otherwise exit with status 1\n    if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then\n      echo -e \"${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}\"\n      exit 1\n    fi\n  else\n    echo -e \"\"\n  fi\n}\n\ncheck_container_storage() {\n  # Check if the /boot partition is more than 80% full\n  total_size=$(df /boot --output=size | tail -n 1)\n  local used_size=$(df /boot --output=used | tail -n 1)\n  usage=$((100 * used_size / total_size))\n  if ((usage > 80)); then\n    # Prompt the user for confirmation to continue\n    echo -e \"${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}\"\n    echo -ne \"Continue anyway? <y/N>  \"\n    read -r prompt\n    # Check if the input is 'y' or 'yes', otherwise exit with status 1\n    if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then\n      echo -e \"${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}\"\n      exit 1\n    fi\n  fi\n}\n\nstart() {\n  LOGDIR=\"/usr/local/community-scripts/logs\"\n  mkdir -p \"$LOGDIR\"\n\n  if command -v pveversion >/dev/null 2>&1; then\n    VERB=\"yes\"\n    METHOD=\"default\"\n    NEXTID=$(pvesh get /cluster/nextid)\n    timezone=$(cat /etc/timezone)\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME (${VERBOSE_CROPPED}Verbose)${CL}\"\n    base_settings \"$VERB\"\n    echo_default\n    install_script\n    build_container\n    description\n  fi\n}\n\n# This function collects user settings and integrates all the collected information.\nbuild_container() {\n  #  if [ \"$VERB\" == \"yes\" ]; then set -x; fi\n\n  if [ \"$CT_TYPE\" == \"1\" ]; then\n    FEATURES=\"keyctl=1,nesting=1\"\n  else\n    FEATURES=\"nesting=1\"\n  fi\n\n  if [[ $DIAGNOSTICS == \"yes\" ]]; then\n    post_to_api\n  fi\n\n  TEMP_DIR=$(mktemp -d)\n  pushd $TEMP_DIR >/dev/null\n  if [ \"$var_os\" == \"alpine\" ]; then\n    export FUNCTIONS_FILE_PATH=\"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/alpine-install.func)\"\n  else\n    export FUNCTIONS_FILE_PATH=\"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/install.func)\"\n  fi\n  export RANDOM_UUID=\"$RANDOM_UUID\"\n  export CACHER=\"$APT_CACHER\"\n  export CACHER_IP=\"$APT_CACHER_IP\"\n  export tz=\"$timezone\"\n  export DISABLEIPV6=\"$DISABLEIP6\"\n  export APPLICATION=\"$APP\"\n  export app=\"$NSAPP\"\n  export PASSWORD=\"$PW\"\n  export VERBOSE=\"$VERB\"\n  export SSH_ROOT=\"${SSH}\"\n  export SSH_AUTHORIZED_KEY\n  export CTID=\"$CT_ID\"\n  export CTTYPE=\"$CT_TYPE\"\n  export PCT_OSTYPE=\"$var_os\"\n  export PCT_OSVERSION=\"$var_version\"\n  export PCT_DISK_SIZE=\"$DISK_SIZE\"\n  export PCT_OPTIONS=\"\n    -features $FEATURES\n    -hostname $HN\n    -tags $TAGS\n    $SD\n    $NS\n    -net0 name=eth0,bridge=$BRG$MAC,ip=$NET$GATE$VLAN$MTU\n    -onboot 1\n    -cores $CORE_COUNT\n    -memory $RAM_SIZE\n    -unprivileged $CT_TYPE\n    $PW\n  \"\n  # This executes create_lxc.sh and creates the container and .conf file\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/ct/create_lxc.sh)\" || exit $?\n\n  LXC_CONFIG=/etc/pve/lxc/${CTID}.conf\n  if [ \"$CT_TYPE\" == \"0\" ]; then\n    cat <<EOF >>$LXC_CONFIG\n# USB passthrough\nlxc.cgroup2.devices.allow: a\nlxc.cap.drop:\nlxc.cgroup2.devices.allow: c 188:* rwm\nlxc.cgroup2.devices.allow: c 189:* rwm\nlxc.mount.entry: /dev/serial/by-id  dev/serial/by-id  none bind,optional,create=dir\nlxc.mount.entry: /dev/ttyUSB0       dev/ttyUSB0       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyUSB1       dev/ttyUSB1       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyACM0       dev/ttyACM0       none bind,optional,create=file\nlxc.mount.entry: /dev/ttyACM1       dev/ttyACM1       none bind,optional,create=file\nEOF\n  fi\n\n  if [ \"$CT_TYPE\" == \"0\" ]; then\n    if [[ \"$APP\" == \"Channels\" || \"$APP\" == \"Emby\" || \"$APP\" == \"ErsatzTV\" || \"$APP\" == \"Frigate\" || \"$APP\" == \"Jellyfin\" || \"$APP\" == \"Plex\" || \"$APP\" == \"Scrypted\" || \"$APP\" == \"Tdarr\" || \"$APP\" == \"Unmanic\" || \"$APP\" == \"Ollama\" || \"$APP\" == \"FileFlows\" ]]; then\n      cat <<EOF >>$LXC_CONFIG\n# VAAPI hardware transcoding\nlxc.cgroup2.devices.allow: c 226:0 rwm\nlxc.cgroup2.devices.allow: c 226:128 rwm\nlxc.cgroup2.devices.allow: c 29:0 rwm\nlxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file\nlxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir\nlxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file\nEOF\n    fi\n  else\n    if [[ \"$APP\" == \"Channels\" || \"$APP\" == \"Emby\" || \"$APP\" == \"ErsatzTV\" || \"$APP\" == \"Frigate\" || \"$APP\" == \"Jellyfin\" || \"$APP\" == \"Plex\" || \"$APP\" == \"Scrypted\" || \"$APP\" == \"Tdarr\" || \"$APP\" == \"Unmanic\" || \"$APP\" == \"Ollama\" || \"$APP\" == \"FileFlows\" ]]; then\n      if [[ -e \"/dev/dri/renderD128\" ]]; then\n        if [[ -e \"/dev/dri/card0\" ]]; then\n          cat <<EOF >>$LXC_CONFIG\n# VAAPI hardware transcoding\ndev0: /dev/dri/card0,gid=44\ndev1: /dev/dri/renderD128,gid=104\nEOF\n        else\n          cat <<EOF >>$LXC_CONFIG\n# VAAPI hardware transcoding\ndev0: /dev/dri/card1,gid=44\ndev1: /dev/dri/renderD128,gid=104\nEOF\n        fi\n      fi\n    fi\n  fi\n\n  # This starts the container and executes <app>-install.sh\n  msg_info \"Starting LXC Container\"\n  pct start \"$CTID\"\n  msg_ok \"Started LXC Container\"\n  if [ \"$var_os\" == \"alpine\" ]; then\n    sleep 3\n    pct exec \"$CTID\" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories\nhttp://dl-cdn.alpinelinux.org/alpine/latest-stable/main\nhttp://dl-cdn.alpinelinux.org/alpine/latest-stable/community\nEOF'\n    pct exec \"$CTID\" -- ash -c \"apk add bash >/dev/null\"\n  fi\n  lxc-attach -n \"$CTID\" -- bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/install/$var_install.sh)\" || exit $?\n\n}\n\n# This function sets the description of the container.\ndescription() {\n  IP=$(pct exec \"$CTID\" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)\n\n  # Generate LXC Description\n  DESCRIPTION=$(\n    cat <<EOF\n<div align='center'>\n  <a href='https://pimox-scripts.com' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/asylumexp/Proxmox' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/asylumexp/Proxmox/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/asylumexp/Proxmox/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n  )\n\n  # Set Description in LXC\n  pct set \"$CTID\" -description \"$DESCRIPTION\"\n\n  if [[ -f /etc/systemd/system/ping-instances.service ]]; then\n    systemctl start ping-instances.service\n  fi\n\n  post_update_to_api \"done\" \"none\"\n}\n\nset_std_mode() {\n  if [ \"$VERB\" = \"yes\" ]; then\n    STD=\"\"\n  else\n    STD=\"silent\"\n  fi\n}\n\n# Silent execution function\nsilent() {\n  if [ \"$VERB\" = \"no\" ]; then\n    \"$@\" >>\"$LOGFILE\" 2>&1\n  else\n    \"$@\" 2>&1 | tee -a \"$LOGFILE\"\n  fi\n}\n\nexit_script() {\n  exit_code=$? # Capture the exit status of the last executed command\n  #200 exit codes indicate error in create_lxc.sh\n  #100 exit codes indicate error in install.func\n\n  if [ $exit_code -ne 0 ]; then\n    case $exit_code in\n    100) post_update_to_api \"failed\" \"100: Unexpected error in create_lxc.sh\" ;;\n    101) post_update_to_api \"failed\" \"101: No network connection detected in create_lxc.sh\" ;;\n    200) post_update_to_api \"failed\" \"200: LXC creation failed in create_lxc.sh\" ;;\n    201) post_update_to_api \"failed\" \"201: Invalid Storage class in create_lxc.sh\" ;;\n    202) post_update_to_api \"failed\" \"202: User aborted menu in create_lxc.sh\" ;;\n    203) post_update_to_api \"failed\" \"203: CTID not set in create_lxc.sh\" ;;\n    204) post_update_to_api \"failed\" \"204: PCT_OSTYPE not set in create_lxc.sh\" ;;\n    205) post_update_to_api \"failed\" \"205: CTID cannot be less than 100 in create_lxc.sh\" ;;\n    206) post_update_to_api \"failed\" \"206: CTID already in use in create_lxc.sh\" ;;\n    207) post_update_to_api \"failed\" \"207: Template not found in create_lxc.sh\" ;;\n    208) post_update_to_api \"failed\" \"208: Error downloading template in create_lxc.sh\" ;;\n    209) post_update_to_api \"failed\" \"209: Container creation failed, but template is intact in create_lxc.sh\" ;;\n    *) post_update_to_api \"failed\" \"Unknown error, exit code: $exit_code in create_lxc.sh\" ;;\n    esac\n  fi\n}\n\ntrap 'exit_script' EXIT\ntrap 'post_update_to_api \"failed\" \"$BASH_COMMAND\"' ERR\ntrap 'post_update_to_api \"failed\" \"INTERRUPTED\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"TERMINATED\"' SIGTERM\n"
  },
  {
    "path": "misc/tools.func",
    "content": "#!/bin/bash\n\n# ==============================================================================\n# HELPER FUNCTIONS FOR PACKAGE MANAGEMENT\n# ==============================================================================\n#\n# This file provides unified helper functions for robust package installation\n# and repository management across Debian/Ubuntu OS upgrades.\n#\n# Key Features:\n#   - Automatic retry logic for transient APT/network failures\n#   - Unified keyring cleanup from all 3 locations\n#   - Legacy installation cleanup (nvm, rbenv, rustup)\n#   - OS-upgrade-safe repository preparation\n#   - Service pattern matching for multi-version tools\n#   - Debug mode for troubleshooting (TOOLS_DEBUG=true)\n#\n# Usage in install scripts:\n#   source /dev/stdin <<< \"$FUNCTIONS\"  # Load from build.func\n#   prepare_repository_setup \"mysql\"\n#   install_packages_with_retry \"mysql-server\" \"mysql-client\"\n#\n# Quick Reference (Core Helpers):\n#   cleanup_tool_keyrings()          - Remove keyrings from all 3 locations\n#   stop_all_services()              - Stop services by pattern (e.g. \"php*-fpm\")\n#   verify_tool_version()            - Validate installed version matches expected\n#   cleanup_legacy_install()         - Remove nvm, rbenv, rustup, etc.\n#   prepare_repository_setup()       - Cleanup repos + keyrings + validate APT\n#   install_packages_with_retry()    - Install with 3 retries and APT refresh\n#   upgrade_packages_with_retry()    - Upgrade with 3 retries and APT refresh\n#   curl_with_retry()                - Curl with retry logic and timeouts\n#\n# Debug Mode:\n#   TOOLS_DEBUG=true ./script.sh     - Enable verbose output for troubleshooting\n#\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# Debug helper - outputs to stderr when TOOLS_DEBUG is enabled\n# Usage: debug_log \"message\"\n# ------------------------------------------------------------------------------\ndebug_log() {\n  if [[ \"${TOOLS_DEBUG:-false}\" == \"true\" || \"${TOOLS_DEBUG:-0}\" == \"1\" || \"${DEBUG:-0}\" == \"1\" ]]; then\n    echo \"[DEBUG] $*\" >&2\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Robust curl wrapper with retry logic, timeouts, and error handling\n#\n# Usage:\n#   curl_with_retry \"https://example.com/file\" \"/tmp/output\"\n#   curl_with_retry \"https://api.github.com/...\" \"-\" | jq .\n#   CURL_RETRIES=5 curl_with_retry \"https://slow.server/file\" \"/tmp/out\"\n#\n# Parameters:\n#   $1 - URL to download\n#   $2 - Output file path (use \"-\" for stdout)\n#   $3 - (optional) Additional curl options as string\n#\n# Variables:\n#   CURL_RETRIES     - Number of retries (default: 3)\n#   CURL_TIMEOUT     - Max time per attempt in seconds (default: 60)\n#   CURL_CONNECT_TO  - Connection timeout in seconds (default: 10)\n#\n# Returns: 0 on success, 1 on failure after all retries\n# ------------------------------------------------------------------------------\ncurl_with_retry() {\n  local url=\"$1\"\n  local output=\"${2:--}\"\n  local extra_opts=\"${3:-}\"\n  local retries=\"${CURL_RETRIES:-3}\"\n  local timeout=\"${CURL_TIMEOUT:-60}\"\n  local connect_timeout=\"${CURL_CONNECT_TO:-10}\"\n\n  local attempt=1\n  local success=false\n  local backoff=1\n\n  # Extract hostname for DNS pre-check\n  local host\n  host=$(echo \"$url\" | sed -E 's|^https?://([^/:]+).*|\\1|')\n\n  # DNS pre-check - fail fast if host is unresolvable\n  if ! getent hosts \"$host\" &>/dev/null; then\n    debug_log \"DNS resolution failed for $host\"\n    return 6\n  fi\n\n  while [[ $attempt -le $retries ]]; do\n    debug_log \"curl attempt $attempt/$retries: $url\"\n\n    local curl_cmd=\"curl -fsSL --connect-timeout $connect_timeout --max-time $timeout\"\n    [[ -n \"$extra_opts\" ]] && curl_cmd=\"$curl_cmd $extra_opts\"\n\n    if [[ \"$output\" == \"-\" ]]; then\n      if $curl_cmd \"$url\"; then\n        success=true\n        break\n      fi\n    else\n      if $curl_cmd -o \"$output\" \"$url\"; then\n        success=true\n        break\n      fi\n    fi\n\n    debug_log \"curl attempt $attempt failed (timeout=${timeout}s), waiting ${backoff}s before retry...\"\n    sleep \"$backoff\"\n    # Exponential backoff: 1, 2, 4, 8... capped at 30s\n    backoff=$((backoff * 2))\n    ((backoff > 30)) && backoff=30\n    # Double --max-time on each retry so slow connections can finish\n    timeout=$((timeout * 2))\n    ((attempt++))\n  done\n\n  if [[ \"$success\" == \"true\" ]]; then\n    debug_log \"curl successful: $url\"\n    return 0\n  else\n    debug_log \"curl FAILED after $retries attempts: $url\"\n    return 7\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Robust curl wrapper for API calls (returns HTTP code + body)\n#\n# Usage:\n#   response=$(curl_api_with_retry \"https://api.github.com/repos/owner/repo/releases/latest\")\n#   http_code=$(curl_api_with_retry \"https://api.github.com/...\" \"/tmp/body.json\")\n#\n# Parameters:\n#   $1 - URL to call\n#   $2 - (optional) Output file for body (default: stdout)\n#   $3 - (optional) Additional curl options as string\n#\n# Returns: HTTP status code, body in file or stdout\n# ------------------------------------------------------------------------------\ncurl_api_with_retry() {\n  local url=\"$1\"\n  local body_file=\"${2:-}\"\n  local extra_opts=\"${3:-}\"\n  local retries=\"${CURL_RETRIES:-3}\"\n  local timeout=\"${CURL_TIMEOUT:-60}\"\n  local connect_timeout=\"${CURL_CONNECT_TO:-10}\"\n\n  local attempt=1\n  local http_code=\"\"\n\n  while [[ $attempt -le $retries ]]; do\n    debug_log \"curl API attempt $attempt/$retries: $url\"\n\n    local curl_cmd=\"curl -fsSL --connect-timeout $connect_timeout --max-time $timeout -w '%{http_code}'\"\n    [[ -n \"$extra_opts\" ]] && curl_cmd=\"$curl_cmd $extra_opts\"\n\n    if [[ -n \"$body_file\" ]]; then\n      http_code=$($curl_cmd -o \"$body_file\" \"$url\" 2>/dev/null) || true\n    else\n      # Capture body and http_code separately\n      local tmp_body=\"/tmp/curl_api_body_$$\"\n      http_code=$($curl_cmd -o \"$tmp_body\" \"$url\" 2>/dev/null) || true\n      if [[ -f \"$tmp_body\" ]]; then\n        cat \"$tmp_body\"\n        rm -f \"$tmp_body\"\n      fi\n    fi\n\n    # Success on 2xx codes\n    if [[ \"$http_code\" =~ ^2[0-9]{2}$ ]]; then\n      debug_log \"curl API successful: $url (HTTP $http_code)\"\n      echo \"$http_code\"\n      return 0\n    fi\n\n    debug_log \"curl API attempt $attempt failed (HTTP $http_code, timeout=${timeout}s), waiting ${attempt}s...\"\n    sleep \"$attempt\"\n    # Double --max-time on each retry so slow connections can finish\n    timeout=$((timeout * 2))\n    ((attempt++))\n  done\n\n  debug_log \"curl API FAILED after $retries attempts: $url\"\n  echo \"$http_code\"\n  return 7\n}\n\n# ------------------------------------------------------------------------------\n# Download and install GPG key with retry logic and validation\n#\n# Usage:\n#   download_gpg_key \"https://example.com/key.gpg\" \"/etc/apt/keyrings/example.gpg\"\n#   download_gpg_key \"https://example.com/key.asc\" \"/etc/apt/keyrings/example.gpg\" \"dearmor\"\n#\n# Parameters:\n#   $1 - URL to GPG key\n#   $2 - Output path for keyring file\n#   $3 - (optional) \"dearmor\" to convert ASCII-armored key to binary\n#\n# Features:\n#   - Auto-detects key format (binary vs armored)\n#   - Validates downloaded key\n#   - Multiple mirror fallback support\n#\n# Returns: 0 on success, 1 on failure\n# ------------------------------------------------------------------------------\ndownload_gpg_key() {\n  local url=\"$1\"\n  local output=\"$2\"\n  local mode=\"${3:-auto}\" # auto, dearmor, or binary\n  local retries=\"${CURL_RETRIES:-3}\"\n  local timeout=\"${CURL_TIMEOUT:-30}\"\n  local temp_key\n  temp_key=$(mktemp)\n\n  mkdir -p \"$(dirname \"$output\")\"\n\n  local attempt=1\n  while [[ $attempt -le $retries ]]; do\n    debug_log \"GPG key download attempt $attempt/$retries: $url\"\n\n    # Download to temp file first\n    if ! curl -fsSL --connect-timeout 10 --max-time \"$timeout\" -o \"$temp_key\" \"$url\" 2>/dev/null; then\n      debug_log \"GPG key download attempt $attempt failed, waiting ${attempt}s...\"\n      sleep \"$attempt\"\n      ((attempt++))\n      continue\n    fi\n\n    # Auto-detect key format if mode is auto\n    if [[ \"$mode\" == \"auto\" ]]; then\n      if file \"$temp_key\" 2>/dev/null | grep -qi \"pgp\\\\|gpg\\\\|public key\"; then\n        mode=\"binary\"\n      elif grep -q \"BEGIN PGP\" \"$temp_key\" 2>/dev/null; then\n        mode=\"dearmor\"\n      else\n        # Try to detect by extension\n        [[ \"$url\" == *.asc || \"$url\" == *.txt ]] && mode=\"dearmor\" || mode=\"binary\"\n      fi\n    fi\n\n    # Process based on mode\n    if [[ \"$mode\" == \"dearmor\" ]]; then\n      if gpg --dearmor --yes -o \"$output\" <\"$temp_key\" 2>/dev/null && [[ -s \"$output\" ]]; then\n        rm -f \"$temp_key\"\n        debug_log \"GPG key installed (dearmored): $output\"\n        return 0\n      fi\n    else\n      if mv \"$temp_key\" \"$output\" 2>/dev/null; then\n        chmod 644 \"$output\"\n        debug_log \"GPG key installed: $output\"\n        return 0\n      fi\n    fi\n\n    debug_log \"GPG key processing attempt $attempt failed\"\n    sleep \"$attempt\"\n    ((attempt++))\n  done\n\n  rm -f \"$temp_key\"\n  debug_log \"GPG key download FAILED after $retries attempts: $url\"\n  return 7\n}\n\n# ------------------------------------------------------------------------------\n# Cache installed version to avoid repeated checks\n# ------------------------------------------------------------------------------\ncache_installed_version() {\n  local app=\"$1\"\n  local version=\"$2\"\n  mkdir -p /var/cache/app-versions\n  echo \"$version\" >\"/var/cache/app-versions/${app}_version.txt\"\n}\n\nget_cached_version() {\n  local app=\"$1\"\n  mkdir -p /var/cache/app-versions\n  if [[ -f \"/var/cache/app-versions/${app}_version.txt\" ]]; then\n    cat \"/var/cache/app-versions/${app}_version.txt\"\n    return 0\n  fi\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Clean up ALL keyring locations for a tool (unified helper)\n# Usage: cleanup_tool_keyrings \"mariadb\" \"mysql\" \"postgresql\"\n# ------------------------------------------------------------------------------\ncleanup_tool_keyrings() {\n  local tool_patterns=(\"$@\")\n\n  for pattern in \"${tool_patterns[@]}\"; do\n    rm -f /usr/share/keyrings/${pattern}*.gpg \\\n      /etc/apt/keyrings/${pattern}*.gpg \\\n      /etc/apt/trusted.gpg.d/${pattern}*.gpg 2>/dev/null || true\n  done\n}\n\n# ------------------------------------------------------------------------------\n# Stop and disable all service instances matching a pattern\n# Usage: stop_all_services \"php*-fpm\" \"mysql\" \"mariadb\"\n# ------------------------------------------------------------------------------\nstop_all_services() {\n  local service_patterns=(\"$@\")\n\n  for pattern in \"${service_patterns[@]}\"; do\n    # Find all matching services (grep || true to handle no matches)\n    local services\n    services=$(systemctl list-units --type=service --all 2>/dev/null |\n      grep -oE \"${pattern}[^ ]*\\.service\" 2>/dev/null | sort -u) || true\n\n    if [[ -n \"$services\" ]]; then\n      while read -r service; do\n        $STD systemctl stop \"$service\" 2>/dev/null || true\n        $STD systemctl disable \"$service\" 2>/dev/null || true\n      done <<<\"$services\"\n    fi\n  done\n\n}\n\n# ------------------------------------------------------------------------------\n# Verify installed tool version matches expected version\n# Returns: 0 if match, 1 if mismatch (with warning)\n# Usage: verify_tool_version \"nodejs\" \"22\" \"$(node -v | grep -oP '^v\\K[0-9]+')\"\n# ------------------------------------------------------------------------------\nverify_tool_version() {\n  local tool_name=\"$1\"\n  local expected_version=\"$2\"\n  local installed_version=\"$3\"\n\n  # Extract major version for comparison\n  local expected_major=\"${expected_version%%.*}\"\n  local installed_major=\"${installed_version%%.*}\"\n\n  if [[ \"$installed_major\" != \"$expected_major\" ]]; then\n    msg_warn \"$tool_name version mismatch: expected $expected_version, got $installed_version\"\n    return 1\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Clean up legacy installation methods (nvm, rbenv, rustup, etc.)\n# Usage: cleanup_legacy_install \"nodejs\" -> removes nvm\n# ------------------------------------------------------------------------------\ncleanup_legacy_install() {\n  local tool_name=\"$1\"\n\n  case \"$tool_name\" in\n  nodejs | node)\n    if [[ -d \"$HOME/.nvm\" ]]; then\n      msg_info \"Removing legacy nvm installation\"\n      rm -rf \"$HOME/.nvm\" \"$HOME/.npm\" \"$HOME/.bower\" \"$HOME/.config/yarn\" 2>/dev/null || true\n      sed -i '/NVM_DIR/d' \"$HOME/.bashrc\" \"$HOME/.profile\" 2>/dev/null || true\n      msg_ok \"Legacy nvm installation removed\"\n    fi\n    ;;\n  ruby)\n    if [[ -d \"$HOME/.rbenv\" ]]; then\n      msg_info \"Removing legacy rbenv installation\"\n      rm -rf \"$HOME/.rbenv\" 2>/dev/null || true\n      sed -i '/rbenv/d' \"$HOME/.bashrc\" \"$HOME/.profile\" 2>/dev/null || true\n      msg_ok \"Legacy rbenv installation removed\"\n    fi\n    ;;\n  rust)\n    if [[ -d \"$HOME/.cargo\" ]] || [[ -d \"$HOME/.rustup\" ]]; then\n      msg_info \"Removing legacy rustup installation\"\n      rm -rf \"$HOME/.cargo\" \"$HOME/.rustup\" 2>/dev/null || true\n      sed -i '/cargo/d' \"$HOME/.bashrc\" \"$HOME/.profile\" 2>/dev/null || true\n      msg_ok \"Legacy rustup installation removed\"\n    fi\n    ;;\n  go | golang)\n    if [[ -d \"$HOME/go\" ]]; then\n      msg_info \"Removing legacy Go workspace\"\n      # Keep user code, just remove GOPATH env\n      sed -i '/GOPATH/d' \"$HOME/.bashrc\" \"$HOME/.profile\" 2>/dev/null || true\n      msg_ok \"Legacy Go workspace cleaned\"\n    fi\n    ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# Unified repository preparation before setup\n# Cleans up old repos, keyrings, and ensures APT is working\n# Usage: prepare_repository_setup \"mariadb\" \"mysql\"\n# ------------------------------------------------------------------------------\nprepare_repository_setup() {\n  local repo_names=(\"$@\")\n\n  # Clean up all old repository files\n  for repo in \"${repo_names[@]}\"; do\n    cleanup_old_repo_files \"$repo\"\n  done\n\n  # Clean up all keyrings\n  cleanup_tool_keyrings \"${repo_names[@]}\"\n\n  # Ensure APT is in working state\n  ensure_apt_working || return 100\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Install packages with retry logic\n# Usage: install_packages_with_retry \"mysql-server\" \"mysql-client\"\n# Features:\n#   - Automatic dpkg recovery on failure\n#   - Individual package fallback if batch fails\n#   - Dependency resolution with apt-get -f install\n# ------------------------------------------------------------------------------\ninstall_packages_with_retry() {\n  local packages=(\"$@\")\n  local max_retries=3\n  local retry=0\n\n  # Pre-check: ensure dpkg is not in a broken state\n  if dpkg --audit 2>&1 | grep -q .; then\n    $STD dpkg --configure -a 2>/dev/null || true\n  fi\n\n  while [[ $retry -le $max_retries ]]; do\n    if DEBIAN_FRONTEND=noninteractive $STD apt install -y \\\n      -o Dpkg::Options::=\"--force-confdef\" \\\n      -o Dpkg::Options::=\"--force-confold\" \\\n      \"${packages[@]}\" 2>/dev/null; then\n      return 0\n    fi\n\n    retry=$((retry + 1))\n    if [[ $retry -le $max_retries ]]; then\n      msg_warn \"Package installation failed, retrying ($retry/$max_retries)...\"\n\n      # Progressive recovery steps based on retry count\n      case $retry in\n      1)\n        # First retry: just fix dpkg and update\n        $STD dpkg --configure -a 2>/dev/null || true\n        $STD apt update 2>/dev/null || true\n        ;;\n      2)\n        # Second retry: fix broken dependencies\n        $STD apt --fix-broken install -y 2>/dev/null || true\n        $STD apt update 2>/dev/null || true\n        ;;\n      3)\n        # Third retry: try installing packages one by one\n        local failed=()\n        for pkg in \"${packages[@]}\"; do\n          if ! $STD apt install -y \"$pkg\" 2>/dev/null; then\n            # Try with --fix-missing\n            if ! $STD apt install -y --fix-missing \"$pkg\" 2>/dev/null; then\n              failed+=(\"$pkg\")\n            fi\n          fi\n        done\n        # If some packages installed, consider partial success\n        if [[ ${#failed[@]} -lt ${#packages[@]} ]]; then\n          if [[ ${#failed[@]} -gt 0 ]]; then\n            msg_warn \"Partially installed. Failed packages: ${failed[*]}\"\n          fi\n          return 0\n        fi\n        ;;\n      esac\n\n      sleep $((retry * 2))\n    fi\n  done\n\n  msg_error \"Failed to install packages after $((max_retries + 1)) attempts: ${packages[*]}\"\n  return 100\n}\n\n# ------------------------------------------------------------------------------\n# Upgrade specific packages with retry logic\n# Usage: upgrade_packages_with_retry \"mariadb-server\" \"mariadb-client\"\n# ------------------------------------------------------------------------------\nupgrade_packages_with_retry() {\n  local packages=(\"$@\")\n  local max_retries=2\n  local retry=0\n\n  while [[ $retry -le $max_retries ]]; do\n    if DEBIAN_FRONTEND=noninteractive $STD apt install --only-upgrade -y \\\n      -o Dpkg::Options::=\"--force-confdef\" \\\n      -o Dpkg::Options::=\"--force-confold\" \\\n      \"${packages[@]}\" 2>/dev/null; then\n      return 0\n    fi\n\n    retry=$((retry + 1))\n    if [[ $retry -le $max_retries ]]; then\n      msg_warn \"Package upgrade failed, retrying ($retry/$max_retries)...\"\n      sleep 2\n      # Fix any interrupted dpkg operations before retry\n      $STD dpkg --configure -a 2>/dev/null || true\n      $STD apt update 2>/dev/null || true\n    fi\n  done\n\n  msg_error \"Failed to upgrade packages after $((max_retries + 1)) attempts: ${packages[*]}\"\n  return 100\n}\n\n# ------------------------------------------------------------------------------\n# Check if tool is already installed and optionally verify exact version\n# Returns: 0 if installed (with optional version match), 1 if not installed\n# Usage: is_tool_installed \"mariadb\" \"11.4\" || echo \"Not installed\"\n# ------------------------------------------------------------------------------\nis_tool_installed() {\n  local tool_name=\"$1\"\n  local required_version=\"${2:-}\"\n  local installed_version=\"\"\n\n  case \"$tool_name\" in\n  mariadb)\n    if command -v mariadb >/dev/null 2>&1; then\n      installed_version=$(mariadb --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -1 || true)\n    fi\n    ;;\n  mysql)\n    if command -v mysql >/dev/null 2>&1; then\n      installed_version=$(mysql --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -1 || true)\n    fi\n    ;;\n  mongodb | mongod)\n    if command -v mongod >/dev/null 2>&1; then\n      installed_version=$(mongod --version 2>/dev/null | awk '/db version/{print $3}' | cut -d. -f1,2)\n    fi\n    ;;\n  node | nodejs)\n    if command -v node >/dev/null 2>&1; then\n      installed_version=$(node -v 2>/dev/null | grep -oP '^v\\K[0-9]+' || true)\n    fi\n    ;;\n  php)\n    if command -v php >/dev/null 2>&1; then\n      installed_version=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)\n    fi\n    ;;\n  postgres | postgresql)\n    if command -v psql >/dev/null 2>&1; then\n      installed_version=$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1)\n    fi\n    ;;\n  ruby)\n    if command -v ruby >/dev/null 2>&1; then\n      installed_version=$(ruby --version 2>/dev/null | awk '{print $2}' | cut -d. -f1,2)\n    fi\n    ;;\n  rust | rustc)\n    if command -v rustc >/dev/null 2>&1; then\n      installed_version=$(rustc --version 2>/dev/null | awk '{print $2}')\n    fi\n    ;;\n  go | golang)\n    if command -v go >/dev/null 2>&1; then\n      installed_version=$(go version 2>/dev/null | awk '{print $3}' | sed 's/go//')\n    fi\n    ;;\n  clickhouse)\n    if command -v clickhouse >/dev/null 2>&1; then\n      installed_version=$(clickhouse --version 2>/dev/null | awk '{print $2}')\n    fi\n    ;;\n  esac\n\n  if [[ -z \"$installed_version\" ]]; then\n    return 1 # Not installed\n  fi\n\n  if [[ -n \"$required_version\" && \"$installed_version\" != \"$required_version\" ]]; then\n    echo \"$installed_version\"\n    return 1 # Version mismatch\n  fi\n\n  echo \"$installed_version\"\n  return 0 # Installed and version matches (if specified)\n}\n\n# ------------------------------------------------------------------------------\n# Remove old tool version completely (purge + cleanup repos)\n# Usage: remove_old_tool_version \"mariadb\" \"repository-name\"\n# ------------------------------------------------------------------------------\nremove_old_tool_version() {\n  local tool_name=\"$1\"\n  local repo_name=\"${2:-$tool_name}\"\n\n  case \"$tool_name\" in\n  mariadb)\n    stop_all_services \"mariadb\"\n    $STD apt purge -y 'mariadb*' >/dev/null 2>&1 || true\n    cleanup_tool_keyrings \"mariadb\"\n    ;;\n  mysql)\n    stop_all_services \"mysql\"\n    $STD apt purge -y 'mysql*' >/dev/null 2>&1 || true\n    rm -rf /var/lib/mysql 2>/dev/null || true\n    cleanup_tool_keyrings \"mysql\"\n    ;;\n  mongodb)\n    stop_all_services \"mongod\"\n    $STD apt purge -y 'mongodb*' >/dev/null 2>&1 || true\n    rm -rf /var/lib/mongodb 2>/dev/null || true\n    cleanup_tool_keyrings \"mongodb\"\n    ;;\n  node | nodejs)\n    $STD apt purge -y nodejs npm >/dev/null 2>&1 || true\n    # Clean up npm global modules\n    if command -v npm >/dev/null 2>&1; then\n      npm list -g 2>/dev/null | grep -oE '^  \\S+' | awk '{print $1}' 2>/dev/null | while read -r module; do\n        npm uninstall -g \"$module\" >/dev/null 2>&1 || true\n      done || true\n    fi\n    cleanup_legacy_install \"nodejs\"\n    cleanup_tool_keyrings \"nodesource\"\n    ;;\n  php)\n    stop_all_services \"php.*-fpm\"\n    $STD apt purge -y 'php*' >/dev/null 2>&1 || true\n    rm -rf /etc/php 2>/dev/null || true\n    cleanup_tool_keyrings \"deb.sury.org-php\" \"php\"\n    ;;\n  postgresql)\n    stop_all_services \"postgresql\"\n    $STD apt purge -y 'postgresql*' >/dev/null 2>&1 || true\n    # Keep data directory for safety (can be removed manually if needed)\n    # rm -rf /var/lib/postgresql 2>/dev/null || true\n    cleanup_tool_keyrings \"postgresql\" \"pgdg\"\n    ;;\n  java)\n    $STD apt purge -y 'temurin*' 'adoptium*' 'openjdk*' >/dev/null 2>&1 || true\n    cleanup_tool_keyrings \"adoptium\"\n    ;;\n  ruby)\n    cleanup_legacy_install \"ruby\"\n    $STD apt purge -y 'ruby*' >/dev/null 2>&1 || true\n    ;;\n  rust)\n    cleanup_legacy_install \"rust\"\n    ;;\n  go | golang)\n    rm -rf /usr/local/go 2>/dev/null || true\n    cleanup_legacy_install \"golang\"\n    ;;\n  clickhouse)\n    stop_all_services \"clickhouse-server\"\n    $STD apt purge -y 'clickhouse*' >/dev/null 2>&1 || true\n    rm -rf /var/lib/clickhouse 2>/dev/null || true\n    cleanup_tool_keyrings \"clickhouse\"\n    ;;\n  esac\n\n  # Clean up old repository files (both .list and .sources)\n  cleanup_old_repo_files \"$repo_name\"\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Determine if tool update/upgrade is needed\n# Returns: 0 (update needed), 1 (already up-to-date)\n# Usage: if should_update_tool \"mariadb\" \"11.4\"; then ... fi\n# ------------------------------------------------------------------------------\nshould_update_tool() {\n  local tool_name=\"$1\"\n  local target_version=\"$2\"\n  local current_version=\"\"\n\n  # Get currently installed version\n  current_version=$(is_tool_installed \"$tool_name\" 2>/dev/null) || return 0 # Not installed = needs install\n\n  # If versions are identical, no update needed\n  if [[ \"$current_version\" == \"$target_version\" ]]; then\n    return 1 # No update needed\n  fi\n\n  return 0 # Update needed\n}\n\n# ------------------------------------------------------------------------------\n# Unified repository management for tools\n# Handles adding, updating, and verifying tool repositories\n# Usage: manage_tool_repository \"mariadb\" \"11.4\" \"https://repo...\" \"GPG_key_url\"\n# Supports: mariadb, mongodb, nodejs, postgresql, php, mysql\n# ------------------------------------------------------------------------------\nmanage_tool_repository() {\n  local tool_name=\"$1\"\n  local version=\"$2\"\n  local repo_url=\"$3\"\n  local gpg_key_url=\"${4:-}\"\n  local distro_id repo_component suite\n\n  distro_id=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '\"')\n\n  case \"$tool_name\" in\n  mariadb)\n    if [[ -z \"$repo_url\" || -z \"$gpg_key_url\" ]]; then\n      msg_error \"MariaDB repository requires repo_url and gpg_key_url\"\n      return 65\n    fi\n\n    # Clean old repos first\n    cleanup_old_repo_files \"mariadb\"\n\n    # Get suite for fallback handling\n    local distro_codename\n    distro_codename=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n    suite=$(get_fallback_suite \"$distro_id\" \"$distro_codename\" \"$repo_url/$distro_id\")\n\n    # Setup new repository using deb822 format\n    setup_deb822_repo \\\n      \"mariadb\" \\\n      \"$gpg_key_url\" \\\n      \"$repo_url/$distro_id\" \\\n      \"$suite\" \\\n      \"main\"\n    return 0\n    ;;\n\n  mongodb)\n    if [[ -z \"$repo_url\" || -z \"$gpg_key_url\" ]]; then\n      msg_error \"MongoDB repository requires repo_url and gpg_key_url\"\n      return 65\n    fi\n\n    # Clean old repos first\n    cleanup_old_repo_files \"mongodb\"\n\n    # Import GPG key with retry logic\n    if ! download_gpg_key \"$gpg_key_url\" \"/etc/apt/keyrings/mongodb-server-${version}.gpg\" \"dearmor\"; then\n      msg_error \"Failed to download MongoDB GPG key\"\n      return 7\n    fi\n    chmod 644 \"/etc/apt/keyrings/mongodb-server-${version}.gpg\"\n\n    # Setup repository\n    local distro_codename\n    distro_codename=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n\n    # Suite mapping with fallback for newer releases not yet supported by upstream\n    if [[ \"$distro_id\" == \"debian\" ]]; then\n      case \"$distro_codename\" in\n      trixie | forky | sid)\n        # Testing/unstable releases fallback to latest stable suite\n        suite=\"bookworm\"\n        ;;\n      bookworm)\n        suite=\"bookworm\"\n        ;;\n      bullseye)\n        suite=\"bullseye\"\n        ;;\n      *)\n        # Unknown release: fallback to latest stable suite\n        msg_warn \"Unknown Debian release '${distro_codename}', using bookworm\"\n        suite=\"bookworm\"\n        ;;\n      esac\n    elif [[ \"$distro_id\" == \"ubuntu\" ]]; then\n      case \"$distro_codename\" in\n      oracular | plucky)\n        # Newer releases fallback to latest LTS\n        suite=\"noble\"\n        ;;\n      noble)\n        suite=\"noble\"\n        ;;\n      jammy)\n        suite=\"jammy\"\n        ;;\n      focal)\n        suite=\"focal\"\n        ;;\n      *)\n        # Unknown release: fallback to latest LTS\n        msg_warn \"Unknown Ubuntu release '${distro_codename}', using noble\"\n        suite=\"noble\"\n        ;;\n      esac\n    else\n      # For other distros, try generic fallback\n      suite=$(get_fallback_suite \"$distro_id\" \"$distro_codename\" \"$repo_url\")\n    fi\n\n    repo_component=\"main\"\n    [[ \"$distro_id\" == \"ubuntu\" ]] && repo_component=\"multiverse\"\n\n    cat <<EOF >/etc/apt/sources.list.d/mongodb-org-${version}.sources\nTypes: deb\nURIs: ${repo_url}\nSuites: ${suite}/mongodb-org/${version}\nComponents: ${repo_component}\nArchitectures: $(dpkg --print-architecture)\nSigned-By: /etc/apt/keyrings/mongodb-server-${version}.gpg\nEOF\n    return 0\n    ;;\n\n  nodejs)\n    if [[ -z \"$repo_url\" || -z \"$gpg_key_url\" ]]; then\n      msg_error \"Node.js repository requires repo_url and gpg_key_url\"\n      return 65\n    fi\n\n    cleanup_old_repo_files \"nodesource\"\n\n    # NodeSource uses deb822 format with GPG from repo\n    local distro_codename\n    distro_codename=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n\n    # Download GPG key from NodeSource with retry logic\n    if ! download_gpg_key \"$gpg_key_url\" \"/etc/apt/keyrings/nodesource.gpg\" \"dearmor\"; then\n      msg_error \"Failed to import NodeSource GPG key\"\n      return 7\n    fi\n\n    cat <<EOF >/etc/apt/sources.list.d/nodesource.sources\nTypes: deb\nURIs: $repo_url\nSuites: nodistro\nComponents: main\nArchitectures: $(dpkg --print-architecture)\nSigned-By: /etc/apt/keyrings/nodesource.gpg\nEOF\n    return 0\n    ;;\n\n  php)\n    if [[ -z \"$gpg_key_url\" ]]; then\n      msg_error \"PHP repository requires gpg_key_url\"\n      return 65\n    fi\n\n    cleanup_old_repo_files \"php\"\n\n    # Download and install keyring with retry logic\n    if ! curl_with_retry \"$gpg_key_url\" \"/tmp/debsuryorg-archive-keyring.deb\"; then\n      msg_error \"Failed to download PHP keyring\"\n      return 7\n    fi\n    # Don't use /dev/null redirection for dpkg as it may use background processes\n    dpkg -i /tmp/debsuryorg-archive-keyring.deb >>\"$(get_active_logfile)\" 2>&1 || {\n      msg_error \"Failed to install PHP keyring\"\n      rm -f /tmp/debsuryorg-archive-keyring.deb\n      return 100\n    }\n    rm -f /tmp/debsuryorg-archive-keyring.deb\n\n    # Setup repository\n    local distro_codename\n    distro_codename=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n    cat <<EOF >/etc/apt/sources.list.d/php.sources\nTypes: deb\nURIs: https://packages.sury.org/php\nSuites: $distro_codename\nComponents: main\nArchitectures: $(dpkg --print-architecture)\nSigned-By: /usr/share/keyrings/deb.sury.org-php.gpg\nEOF\n    return 0\n    ;;\n\n  postgresql)\n    if [[ -z \"$gpg_key_url\" ]]; then\n      msg_error \"PostgreSQL repository requires gpg_key_url\"\n      return 65\n    fi\n\n    cleanup_old_repo_files \"postgresql\"\n\n    # Import PostgreSQL key with retry logic\n    if ! download_gpg_key \"$gpg_key_url\" \"/etc/apt/keyrings/postgresql.gpg\" \"dearmor\"; then\n      msg_error \"Failed to import PostgreSQL GPG key\"\n      return 7\n    fi\n\n    # Setup repository\n    local distro_codename\n    distro_codename=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n    cat <<EOF >/etc/apt/sources.list.d/postgresql.sources\nTypes: deb\nURIs: http://apt.postgresql.org/pub/repos/apt\nSuites: $distro_codename-pgdg\nComponents: main\nArchitectures: $(dpkg --print-architecture)\nSigned-By: /etc/apt/keyrings/postgresql.gpg\nEOF\n    return 0\n    ;;\n\n  *)\n    msg_error \"Unknown tool repository: $tool_name\"\n    return 65\n    ;;\n  esac\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Unified package upgrade function (with apt update caching)\n# ------------------------------------------------------------------------------\nupgrade_package() {\n  local package=\"$1\"\n\n  # Use same caching logic as ensure_dependencies\n  local apt_cache_file=\"/var/cache/apt-update-timestamp\"\n  local current_time=$(date +%s)\n  local last_update=0\n\n  if [[ -f \"$apt_cache_file\" ]]; then\n    last_update=$(cat \"$apt_cache_file\" 2>/dev/null || echo 0)\n  fi\n\n  if ((current_time - last_update > 300)); then\n    $STD apt update || {\n      msg_warn \"APT update failed in upgrade_package - continuing with cached packages\"\n    }\n    echo \"$current_time\" >\"$apt_cache_file\"\n  fi\n\n  $STD apt install --only-upgrade -y \"$package\" || {\n    msg_warn \"Failed to upgrade $package\"\n    return 100\n  }\n}\n\n# ------------------------------------------------------------------------------\n# Repository availability check with caching\n# ------------------------------------------------------------------------------\n# Note: Must use -gA (global) because tools.func is sourced inside update_os()\n# function scope. Plain 'declare -A' would create a local variable that gets\n# destroyed when update_os() returns, causing \"unbound variable\" errors later\n# when setup_postgresql/verify_repo_available tries to access the cache key.\ndeclare -gA _REPO_CACHE 2>/dev/null || declare -A _REPO_CACHE 2>/dev/null || true\n\nverify_repo_available() {\n  local repo_url=\"$1\"\n  local suite=\"$2\"\n  local cache_key=\"${repo_url}|${suite}\"\n  local cache_ttl=300 # 5 minutes\n\n  # Check cache first (avoid repeated HTTP requests)\n  if [[ -n \"${_REPO_CACHE[$cache_key]:-}\" ]]; then\n    local cached_time cached_result\n    cached_time=$(echo \"${_REPO_CACHE[$cache_key]}\" | cut -d'|' -f1)\n    cached_result=$(echo \"${_REPO_CACHE[$cache_key]}\" | cut -d'|' -f2)\n    if (($(date +%s) - cached_time < cache_ttl)); then\n      [[ \"$cached_result\" == \"1\" ]] && return 0 || return 1\n    fi\n  fi\n\n  # Perform actual check with short timeout\n  local result=1\n  if curl -fsSL --max-time 5 --connect-timeout 3 \"${repo_url}/dists/${suite}/Release\" &>/dev/null; then\n    result=0\n  fi\n\n  # Cache the result\n  _REPO_CACHE[$cache_key]=\"$(date +%s)|$result\"\n\n  return $result\n}\n\n# ------------------------------------------------------------------------------\n# Ensure dependencies are installed (with apt/apk update caching)\n# Supports both Debian (apt/dpkg) and Alpine (apk) systems\n# ------------------------------------------------------------------------------\nensure_dependencies() {\n  local deps=(\"$@\")\n  local missing=()\n\n  # Detect Alpine Linux\n  if [[ -f /etc/alpine-release ]]; then\n    for dep in \"${deps[@]}\"; do\n      if command -v \"$dep\" &>/dev/null; then\n        continue\n      fi\n      if apk info -e \"$dep\" &>/dev/null; then\n        continue\n      fi\n      missing+=(\"$dep\")\n    done\n\n    if [[ ${#missing[@]} -gt 0 ]]; then\n      $STD apk add --no-cache \"${missing[@]}\" || {\n        local failed=()\n        for pkg in \"${missing[@]}\"; do\n          if ! $STD apk add --no-cache \"$pkg\" 2>/dev/null; then\n            failed+=(\"$pkg\")\n          fi\n        done\n        if [[ ${#failed[@]} -gt 0 ]]; then\n          msg_error \"Failed to install dependencies: ${failed[*]}\"\n          return 1\n        fi\n      }\n    fi\n    return 0\n  fi\n\n  # Debian/Ubuntu: Fast batch check using dpkg-query\n  local installed_pkgs\n  installed_pkgs=$(dpkg-query -W -f='${Package}\\n' 2>/dev/null | sort -u)\n\n  for dep in \"${deps[@]}\"; do\n    # First check if command exists (for binaries like jq, curl)\n    if command -v \"$dep\" &>/dev/null; then\n      continue\n    fi\n    # Then check if package is installed\n    if echo \"$installed_pkgs\" | grep -qx \"$dep\"; then\n      continue\n    fi\n    missing+=(\"$dep\")\n  done\n\n  if [[ ${#missing[@]} -gt 0 ]]; then\n    # Only run apt update if not done recently (within last 5 minutes)\n    local apt_cache_file=\"/var/cache/apt-update-timestamp\"\n    local current_time\n    current_time=$(date +%s)\n    local last_update=0\n\n    if [[ -f \"$apt_cache_file\" ]]; then\n      last_update=$(cat \"$apt_cache_file\" 2>/dev/null || echo 0)\n    fi\n\n    if ((current_time - last_update > 300)); then\n      # Ensure orphaned sources are cleaned before updating\n      cleanup_orphaned_sources 2>/dev/null || true\n\n      if ! $STD apt update; then\n        ensure_apt_working || return 100\n      fi\n      echo \"$current_time\" >\"$apt_cache_file\"\n    fi\n\n    $STD apt install -y \"${missing[@]}\" || {\n      # Fallback: try installing one by one to identify problematic package\n      local failed=()\n      for pkg in \"${missing[@]}\"; do\n        if ! $STD apt install -y \"$pkg\" 2>/dev/null; then\n          failed+=(\"$pkg\")\n        fi\n      done\n      if [[ ${#failed[@]} -gt 0 ]]; then\n        msg_error \"Failed to install dependencies: ${failed[*]}\"\n        return 100\n      fi\n    }\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Smart version comparison\n# ------------------------------------------------------------------------------\nversion_gt() {\n  test \"$(printf '%s\\n' \"$@\" | sort -V | head -n 1)\" != \"$1\"\n}\n\n# ------------------------------------------------------------------------------\n# Get system architecture (normalized)\n# ------------------------------------------------------------------------------\nget_system_arch() {\n  local arch_type=\"${1:-dpkg}\" # dpkg, uname, or both\n  local arch\n\n  case \"$arch_type\" in\n  dpkg)\n    arch=$(dpkg --print-architecture 2>/dev/null)\n    ;;\n  uname)\n    arch=$(uname -m)\n    [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n    [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n    ;;\n  both | *)\n    arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n    [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n    [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n    ;;\n  esac\n\n  echo \"$arch\"\n}\n\n# ------------------------------------------------------------------------------\n# Create temporary directory with automatic cleanup\n# ------------------------------------------------------------------------------\ncreate_temp_dir() {\n  local tmp_dir=$(mktemp -d)\n  # Set trap to cleanup on EXIT, ERR, INT, TERM\n  trap \"rm -rf '$tmp_dir'\" EXIT ERR INT TERM\n  echo \"$tmp_dir\"\n}\n\n# ------------------------------------------------------------------------------\n# Check if package is installed (supports both Debian and Alpine)\n# ------------------------------------------------------------------------------\nis_package_installed() {\n  local package=\"$1\"\n  if [[ -f /etc/alpine-release ]]; then\n    apk info -e \"$package\" &>/dev/null\n  else\n    dpkg-query -W -f='${Status}' \"$package\" 2>/dev/null | grep -q \"^install ok installed$\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# validate_github_token()\n# Checks a GitHub token via the /user endpoint.\n# Prints a status message and returns:\n#   0 - token is valid\n#   1 - token is invalid / expired (HTTP 401)\n#   2 - token has no public repo scope (HTTP 200 but missing scope)\n#   3 - network/API error\n# Also reports expiry date if the token carries an x-oauth-expiry header.\n# ------------------------------------------------------------------------------\nvalidate_github_token() {\n  local token=\"${1:-${GITHUB_TOKEN:-}}\"\n  [[ -z \"$token\" ]] && return 3\n\n  local response headers http_code expiry_date scopes\n  headers=$(mktemp)\n  response=$(curl -sSL -w \"%{http_code}\" \\\n    -D \"$headers\" \\\n    -o /dev/null \\\n    -H \"Authorization: Bearer $token\" \\\n    -H \"Accept: application/vnd.github+json\" \\\n    -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n    \"https://api.github.com/user\" 2>/dev/null) || {\n    rm -f \"$headers\"\n    return 3\n  }\n  http_code=\"$response\"\n\n  # Read expiry header (fine-grained PATs carry this)\n  expiry_date=$(grep -i '^github-authentication-token-expiration:' \"$headers\" |\n    sed 's/.*: *//' | tr -d '\\r\\n' || true)\n  # Read token scopes (classic PATs)\n  scopes=$(grep -i '^x-oauth-scopes:' \"$headers\" |\n    sed 's/.*: *//' | tr -d '\\r\\n' || true)\n  rm -f \"$headers\"\n\n  case \"$http_code\" in\n  200)\n    if [[ -n \"$expiry_date\" ]]; then\n      msg_ok \"GitHub token is valid (expires: $expiry_date).\"\n    else\n      msg_ok \"GitHub token is valid (no expiry / fine-grained PAT).\"\n    fi\n    # Warn if classic PAT has no public_repo scope\n    if [[ -n \"$scopes\" && \"$scopes\" != *\"public_repo\"* && \"$scopes\" != *\"repo\"* ]]; then\n      msg_warn \"Token has no 'public_repo' scope - private repos and some release APIs may fail.\"\n      return 2\n    fi\n    return 0\n    ;;\n  401)\n    msg_error \"GitHub token is invalid or expired (HTTP 401).\"\n    return 1\n    ;;\n  *)\n    msg_warn \"GitHub token validation returned HTTP $http_code - treating as valid.\"\n    return 0\n    ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# Prompt user to enter a GitHub Personal Access Token (PAT) interactively\n# Returns 0 if a valid token was provided, 1 otherwise\n# ------------------------------------------------------------------------------\nprompt_for_github_token() {\n  if [[ ! -t 0 ]]; then\n    # Non-interactive: pick up var_github_token if set (from default.vars / app.vars / env)\n    if [[ -z \"${GITHUB_TOKEN:-}\" && -n \"${var_github_token:-}\" ]]; then\n      export GITHUB_TOKEN=\"${var_github_token}\"\n      msg_ok \"GitHub token loaded from var_github_token.\"\n      return 0\n    fi\n    return 1\n  fi\n\n  # Prefer var_github_token when already set and no interactive override needed\n  if [[ -z \"${GITHUB_TOKEN:-}\" && -n \"${var_github_token:-}\" ]]; then\n    export GITHUB_TOKEN=\"${var_github_token}\"\n    msg_ok \"GitHub token loaded from var_github_token.\"\n    validate_github_token || true\n    return 0\n  fi\n\n  local reply\n  read -rp \"${TAB}Would you like to enter a GitHub Personal Access Token (PAT)? [y/N]: \" reply\n  reply=\"${reply:-n}\"\n\n  if [[ ! \"${reply,,}\" =~ ^(y|yes)$ ]]; then\n    return 1\n  fi\n\n  local token\n  while true; do\n    read -rp \"${TAB}Enter your GitHub PAT: \" token\n    # Trim leading/trailing whitespace\n    token=\"$(echo \"$token\" | xargs)\"\n    if [[ -z \"$token\" ]]; then\n      msg_warn \"Token cannot be empty. Please try again.\"\n      continue\n    fi\n    if [[ \"$token\" =~ [[:space:]] ]]; then\n      msg_warn \"Token must not contain spaces. Please try again.\"\n      continue\n    fi\n    # Validate before accepting\n    export GITHUB_TOKEN=\"$token\"\n    if validate_github_token \"$token\"; then\n      break\n    else\n      msg_warn \"Please enter a valid token, or press Ctrl+C to abort.\"\n      unset GITHUB_TOKEN\n    fi\n  done\n\n  msg_ok \"GitHub token has been set.\"\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# GitHub API call with authentication and rate limit handling\n# ------------------------------------------------------------------------------\ngithub_api_call() {\n  local url=\"$1\"\n  local output_file=\"${2:-/dev/stdout}\"\n  local max_retries=3\n  local retry_delay=2\n\n  local header_args=()\n  [[ -n \"${GITHUB_TOKEN:-}\" ]] && header_args=(-H \"Authorization: Bearer $GITHUB_TOKEN\")\n\n  local attempt=1\n  while ((attempt <= max_retries)); do\n    local http_code\n    http_code=$(curl -sSL -w \"%{http_code}\" -o \"$output_file\" \\\n      -H \"Accept: application/vnd.github+json\" \\\n      -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n      \"${header_args[@]}\" \\\n      \"$url\" 2>/dev/null) || true\n\n    case \"$http_code\" in\n    200)\n      return 0\n      ;;\n    401)\n      msg_error \"GitHub API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITHUB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication.\"\n      fi\n      if prompt_for_github_token; then\n        header_args=(-H \"Authorization: Bearer $GITHUB_TOKEN\")\n        continue\n      fi\n      return 22\n      ;;\n    403)\n      # Rate limit - check if we can retry\n      if [[ $attempt -lt $max_retries ]]; then\n        msg_warn \"GitHub API rate limit, waiting ${retry_delay}s... (attempt $attempt/$max_retries)\"\n        sleep \"$retry_delay\"\n        retry_delay=$((retry_delay * 2))\n        ((attempt++))\n        continue\n      fi\n      msg_error \"GitHub API rate limit exceeded (HTTP 403).\"\n      if prompt_for_github_token; then\n        header_args=(-H \"Authorization: Bearer $GITHUB_TOKEN\")\n        retry_delay=2\n        attempt=1\n        continue\n      fi\n      msg_error \"To increase the limit, export a GitHub token before running the script:\"\n      msg_error \"  export GITHUB_TOKEN=\\\"ghp_your_token_here\\\"\"\n      return 22\n      ;;\n    404)\n      msg_error \"GitHub repository or release not found (HTTP 404): $url\"\n      return 22\n      ;;\n    000 | \"\")\n      if [[ $attempt -lt $max_retries ]]; then\n        sleep \"$retry_delay\"\n        ((attempt++))\n        continue\n      fi\n      msg_error \"GitHub API connection failed (no response).\"\n      msg_error \"Check your network/DNS: curl -sSL https://api.github.com/rate_limit\"\n      return 22\n      ;;\n    *)\n      if [[ $attempt -lt $max_retries ]]; then\n        sleep \"$retry_delay\"\n        ((attempt++))\n        continue\n      fi\n      msg_error \"GitHub API call failed (HTTP $http_code).\"\n      return 22\n      ;;\n    esac\n    ((attempt++))\n  done\n\n  msg_error \"GitHub API call failed after ${max_retries} attempts: ${url}\"\n  return 22\n}\n\n# ------------------------------------------------------------------------------\n# Codeberg API call with retry logic\n# ------------------------------------------------------------------------------\ncodeberg_api_call() {\n  local url=\"$1\"\n  local output_file=\"${2:-/dev/stdout}\"\n  local max_retries=3\n  local retry_delay=2\n\n  for attempt in $(seq 1 $max_retries); do\n    local http_code\n    http_code=$(curl -sSL -w \"%{http_code}\" -o \"$output_file\" \\\n      -H \"Accept: application/json\" \\\n      \"$url\" 2>/dev/null) || true\n\n    case \"$http_code\" in\n    200)\n      return 0\n      ;;\n    401)\n      msg_error \"Codeberg API authentication failed (HTTP 401).\"\n      return 22\n      ;;\n    403)\n      # Rate limit - retry\n      if [[ $attempt -lt $max_retries ]]; then\n        msg_warn \"Codeberg API rate limit, waiting ${retry_delay}s... (attempt $attempt/$max_retries)\"\n        sleep \"$retry_delay\"\n        retry_delay=$((retry_delay * 2))\n        continue\n      fi\n      msg_error \"Codeberg API rate limit exceeded (HTTP 403).\"\n      return 22\n      ;;\n    404)\n      msg_error \"Codeberg repository or release not found (HTTP 404): $url\"\n      return 22\n      ;;\n    000 | \"\")\n      if [[ $attempt -lt $max_retries ]]; then\n        sleep \"$retry_delay\"\n        continue\n      fi\n      msg_error \"Codeberg API connection failed (no response).\"\n      msg_error \"Check your network/DNS: curl -sSL https://codeberg.org\"\n      return 22\n      ;;\n    *)\n      if [[ $attempt -lt $max_retries ]]; then\n        sleep \"$retry_delay\"\n        continue\n      fi\n      msg_error \"Codeberg API call failed (HTTP $http_code).\"\n      return 22\n      ;;\n    esac\n  done\n\n  msg_error \"Codeberg API call failed after ${max_retries} attempts: ${url}\"\n  return 22\n}\n\nshould_upgrade() {\n  local current=\"$1\"\n  local target=\"$2\"\n\n  [[ -z \"$current\" ]] && return 0\n  version_gt \"$target\" \"$current\" && return 0\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Get OS information (cached for performance)\n# ------------------------------------------------------------------------------\nget_os_info() {\n  local field=\"${1:-all}\" # id, codename, version, version_id, all\n\n  # Cache OS info to avoid repeated file reads\n  if [[ -z \"${_OS_ID:-}\" ]]; then\n    export _OS_ID=$(awk -F= '/^ID=/{gsub(/\"/,\"\",$2); print $2}' /etc/os-release)\n    export _OS_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{gsub(/\"/,\"\",$2); print $2}' /etc/os-release)\n    export _OS_VERSION=$(awk -F= '/^VERSION_ID=/{gsub(/\"/,\"\",$2); print $2}' /etc/os-release)\n    export _OS_VERSION_FULL=$(awk -F= '/^VERSION=/{gsub(/\"/,\"\",$2); print $2}' /etc/os-release)\n  fi\n\n  case \"$field\" in\n  id) echo \"$_OS_ID\" ;;\n  codename) echo \"$_OS_CODENAME\" ;;\n  version) echo \"$_OS_VERSION\" ;;\n  version_id) echo \"$_OS_VERSION\" ;;\n  version_full) echo \"$_OS_VERSION_FULL\" ;;\n  all) echo \"ID=$_OS_ID CODENAME=$_OS_CODENAME VERSION=$_OS_VERSION\" ;;\n  *) echo \"$_OS_ID\" ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# Check if running on specific OS\n# ------------------------------------------------------------------------------\nis_debian() {\n  [[ \"$(get_os_info id)\" == \"debian\" ]]\n}\n\nis_ubuntu() {\n  [[ \"$(get_os_info id)\" == \"ubuntu\" ]]\n}\n\nis_alpine() {\n  [[ \"$(get_os_info id)\" == \"alpine\" ]]\n}\n\n# ------------------------------------------------------------------------------\n# Get Debian/Ubuntu major version\n# ------------------------------------------------------------------------------\nget_os_version_major() {\n  local version=$(get_os_info version)\n  echo \"${version%%.*}\"\n}\n\n# ------------------------------------------------------------------------------\n# Download file with retry logic and progress\n# ------------------------------------------------------------------------------\ndownload_file() {\n  local url=\"$1\"\n  local output=\"$2\"\n  local max_retries=\"${3:-3}\"\n  local show_progress=\"${4:-false}\"\n\n  local curl_opts=(-fsSL)\n  [[ \"$show_progress\" == \"true\" ]] && curl_opts=(-fL#)\n\n  for attempt in $(seq 1 $max_retries); do\n    if curl \"${curl_opts[@]}\" -o \"$output\" \"$url\"; then\n      return 0\n    fi\n\n    if [[ $attempt -lt $max_retries ]]; then\n      msg_warn \"Download failed, retrying... (attempt $attempt/$max_retries)\"\n      sleep 2\n    fi\n  done\n\n  msg_error \"Failed to download: $url\"\n  return 250\n}\n\n# ------------------------------------------------------------------------------\n# Get fallback suite for repository (comprehensive mapping)\n# ------------------------------------------------------------------------------\nget_fallback_suite() {\n  local distro_id=\"$1\"\n  local distro_codename=\"$2\"\n  local repo_base_url=\"$3\"\n\n  # Check if current codename works\n  if verify_repo_available \"$repo_base_url\" \"$distro_codename\"; then\n    echo \"$distro_codename\"\n    return 0\n  fi\n\n  # Build fallback chain based on distro\n  local fallback_chain=()\n  case \"$distro_id\" in\n  debian)\n    case \"$distro_codename\" in\n    trixie | forky | sid)\n      fallback_chain=(\"bookworm\" \"bullseye\")\n      ;;\n    bookworm)\n      fallback_chain=(\"bookworm\" \"bullseye\")\n      ;;\n    bullseye)\n      fallback_chain=(\"bullseye\" \"buster\")\n      ;;\n    *)\n      fallback_chain=(\"bookworm\" \"bullseye\")\n      ;;\n    esac\n    ;;\n  ubuntu)\n    case \"$distro_codename\" in\n    oracular | plucky)\n      fallback_chain=(\"noble\" \"jammy\" \"focal\")\n      ;;\n    noble)\n      fallback_chain=(\"noble\" \"jammy\")\n      ;;\n    mantic | lunar)\n      fallback_chain=(\"jammy\" \"focal\")\n      ;;\n    jammy)\n      fallback_chain=(\"jammy\" \"focal\")\n      ;;\n    focal)\n      fallback_chain=(\"focal\" \"bionic\")\n      ;;\n    *)\n      fallback_chain=(\"jammy\" \"focal\")\n      ;;\n    esac\n    ;;\n  *)\n    echo \"$distro_codename\"\n    return 0\n    ;;\n  esac\n\n  # Try each fallback suite with actual HTTP check\n  for suite in \"${fallback_chain[@]}\"; do\n    if verify_repo_available \"$repo_base_url\" \"$suite\"; then\n      debug_log \"Fallback suite found: $suite for $distro_codename\"\n      echo \"$suite\"\n      return 0\n    fi\n  done\n\n  # Last resort: return first fallback without verification\n  echo \"${fallback_chain[0]:-$distro_codename}\"\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Verify package source and version\n# ------------------------------------------------------------------------------\nverify_package_source() {\n  local package=\"$1\"\n  local expected_version=\"$2\"\n\n  if apt-cache policy \"$package\" 2>/dev/null | grep -q \"$expected_version\"; then\n    return 0\n  fi\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Check if running on LTS version\n# ------------------------------------------------------------------------------\nis_lts_version() {\n  local os_id=$(get_os_info id)\n  local codename=$(get_os_info codename)\n\n  if [[ \"$os_id\" == \"ubuntu\" ]]; then\n    case \"$codename\" in\n    focal | jammy | noble) return 0 ;; # 20.04, 22.04, 24.04\n    *) return 1 ;;\n    esac\n  elif [[ \"$os_id\" == \"debian\" ]]; then\n    # Debian releases are all \"stable\"\n    case \"$codename\" in\n    bullseye | bookworm | trixie) return 0 ;;\n    *) return 1 ;;\n    esac\n  fi\n\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Get optimal number of parallel jobs (cached)\n# Features:\n#   - CPU count detection\n#   - Memory-based limiting (1.5GB per job for safety)\n#   - Current load awareness\n#   - Container/VM detection for conservative limits\n# ------------------------------------------------------------------------------\nget_parallel_jobs() {\n  if [[ -z \"${_PARALLEL_JOBS:-}\" ]]; then\n    local cpu_count\n    cpu_count=$(nproc 2>/dev/null || grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 1)\n\n    local mem_mb\n    mem_mb=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}' || echo 1024)\n\n    # Assume 1.5GB per compilation job for safety margin\n    local max_by_mem=$((mem_mb / 1536))\n    ((max_by_mem < 1)) && max_by_mem=1\n\n    # Check current system load - reduce jobs if already loaded\n    local load_1m\n    load_1m=$(awk '{print int($1)}' /proc/loadavg 2>/dev/null || echo 0)\n    local available_cpus=$((cpu_count - load_1m))\n    ((available_cpus < 1)) && available_cpus=1\n\n    # Take minimum of: available CPUs, memory-limited, and total CPUs\n    local max_jobs=$cpu_count\n    ((max_by_mem < max_jobs)) && max_jobs=$max_by_mem\n    ((available_cpus < max_jobs)) && max_jobs=$available_cpus\n\n    # Container detection - be more conservative in containers\n    if [[ -f /.dockerenv ]] || grep -q 'lxc\\|docker\\|container' /proc/1/cgroup 2>/dev/null; then\n      # Reduce by 25% in containers to leave headroom\n      max_jobs=$((max_jobs * 3 / 4))\n      ((max_jobs < 1)) && max_jobs=1\n    fi\n\n    # Final bounds check\n    ((max_jobs < 1)) && max_jobs=1\n    ((max_jobs > cpu_count)) && max_jobs=$cpu_count\n\n    export _PARALLEL_JOBS=$max_jobs\n    debug_log \"Parallel jobs: $_PARALLEL_JOBS (CPUs: $cpu_count, mem-limit: $max_by_mem, load: $load_1m)\"\n  fi\n  echo \"$_PARALLEL_JOBS\"\n}\n\n# ------------------------------------------------------------------------------\n# Get default PHP version for OS\n# Updated for latest distro releases\n# ------------------------------------------------------------------------------\nget_default_php_version() {\n  local os_id\n  os_id=$(get_os_info id)\n  local os_version\n  os_version=$(get_os_version_major)\n\n  case \"$os_id\" in\n  debian)\n    case \"$os_version\" in\n    14) echo \"8.4\" ;; # Debian 14 (Forky) - future\n    13) echo \"8.3\" ;; # Debian 13 (Trixie)\n    12) echo \"8.2\" ;; # Debian 12 (Bookworm)\n    11) echo \"7.4\" ;; # Debian 11 (Bullseye)\n    *) echo \"8.3\" ;;  # Default to latest stable\n    esac\n    ;;\n  ubuntu)\n    case \"$os_version\" in\n    26) echo \"8.4\" ;; # Ubuntu 26.04 - future\n    24) echo \"8.3\" ;; # Ubuntu 24.04 LTS (Noble)\n    22) echo \"8.1\" ;; # Ubuntu 22.04 LTS (Jammy)\n    20) echo \"7.4\" ;; # Ubuntu 20.04 LTS (Focal)\n    *) echo \"8.3\" ;;  # Default to latest stable\n    esac\n    ;;\n  *)\n    echo \"8.3\"\n    ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# Get default Python version for OS\n# Updated for latest distro releases\n# ------------------------------------------------------------------------------\nget_default_python_version() {\n  local os_id\n  os_id=$(get_os_info id)\n  local os_version\n  os_version=$(get_os_version_major)\n\n  case \"$os_id\" in\n  debian)\n    case \"$os_version\" in\n    14) echo \"3.13\" ;; # Debian 14 (Forky) - future\n    13) echo \"3.12\" ;; # Debian 13 (Trixie)\n    12) echo \"3.11\" ;; # Debian 12 (Bookworm)\n    11) echo \"3.9\" ;;  # Debian 11 (Bullseye)\n    *) echo \"3.12\" ;;  # Default to latest stable\n    esac\n    ;;\n  ubuntu)\n    case \"$os_version\" in\n    26) echo \"3.13\" ;; # Ubuntu 26.04 - future\n    24) echo \"3.12\" ;; # Ubuntu 24.04 LTS\n    22) echo \"3.10\" ;; # Ubuntu 22.04 LTS\n    20) echo \"3.8\" ;;  # Ubuntu 20.04 LTS\n    *) echo \"3.12\" ;;  # Default to latest stable\n    esac\n    ;;\n  *)\n    echo \"3.12\"\n    ;;\n  esac\n}\n\n# ------------------------------------------------------------------------------\n# Get default Node.js LTS version\n# ------------------------------------------------------------------------------\nget_default_nodejs_version() {\n  # Current LTS as of January 2026 (Node.js 24 LTS)\n  echo \"24\"\n}\n\n# ------------------------------------------------------------------------------\n# Check if package manager is locked\n# ------------------------------------------------------------------------------\nis_apt_locked() {\n  if fuser /var/lib/dpkg/lock-frontend &>/dev/null ||\n    fuser /var/lib/apt/lists/lock &>/dev/null ||\n    fuser /var/cache/apt/archives/lock &>/dev/null; then\n    return 0\n  fi\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Wait for apt to be available\n# ------------------------------------------------------------------------------\nwait_for_apt() {\n  local max_wait=\"${1:-300}\" # 5 minutes default\n  local waited=0\n\n  while is_apt_locked; do\n    if [[ $waited -ge $max_wait ]]; then\n      msg_error \"Timeout waiting for apt to be available\"\n      return 100\n    fi\n\n    sleep 5\n    waited=$((waited + 5))\n  done\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Cleanup old repository files (migration helper)\n# ------------------------------------------------------------------------------\ncleanup_old_repo_files() {\n  local app=\"$1\"\n\n  # Remove old-style .list files (including backups)\n  rm -f /etc/apt/sources.list.d/\"${app}\"*.list\n  rm -f /etc/apt/sources.list.d/\"${app}\"*.list.save\n  rm -f /etc/apt/sources.list.d/\"${app}\"*.list.distUpgrade\n  rm -f /etc/apt/sources.list.d/\"${app}\"*.list.dpkg-*\n\n  # Remove old GPG keys from trusted.gpg.d\n  rm -f /etc/apt/trusted.gpg.d/\"${app}\"*.gpg\n\n  # Remove keyrings from /etc/apt/keyrings\n  rm -f /etc/apt/keyrings/\"${app}\"*.gpg\n\n  # Remove ALL .sources files for this app (including the main one)\n  # This ensures no orphaned .sources files reference deleted keyrings\n  rm -f /etc/apt/sources.list.d/\"${app}\"*.sources\n}\n\n# ------------------------------------------------------------------------------\n# Cleanup orphaned .sources files that reference missing keyrings\n# This prevents APT signature verification errors\n# Call this at the start of any setup function to ensure APT is in a clean state\n# ------------------------------------------------------------------------------\ncleanup_orphaned_sources() {\n  local sources_dir=\"/etc/apt/sources.list.d\"\n  local keyrings_dir=\"/etc/apt/keyrings\"\n\n  [[ ! -d \"$sources_dir\" ]] && return 0\n\n  while IFS= read -r -d '' sources_file; do\n    local basename_file\n    basename_file=$(basename \"$sources_file\")\n\n    # NEVER remove debian.sources - this is the standard Debian repository\n    if [[ \"$basename_file\" == \"debian.sources\" ]]; then\n      continue\n    fi\n\n    # Extract Signed-By path from .sources file\n    local keyring_path\n    keyring_path=$(grep -E '^Signed-By:' \"$sources_file\" 2>/dev/null | awk '{print $2}' 2>/dev/null || true)\n\n    # If keyring doesn't exist, remove the .sources file\n    if [[ -n \"$keyring_path\" ]] && [[ ! -f \"$keyring_path\" ]]; then\n      rm -f \"$sources_file\"\n    fi\n  done < <(find \"$sources_dir\" -name \"*.sources\" -print0 2>/dev/null)\n\n  # Also check for broken symlinks in keyrings directory\n  if [[ -d \"$keyrings_dir\" ]]; then\n    find \"$keyrings_dir\" -type l ! -exec test -e {} \\; -delete 2>/dev/null || true\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Ensure APT is in a working state before installing packages\n# This should be called at the start of any setup function\n# Features:\n#   - Fixes interrupted dpkg operations\n#   - Removes orphaned sources\n#   - Handles lock file contention\n#   - Progressive recovery with fallbacks\n# ------------------------------------------------------------------------------\nensure_apt_working() {\n  local max_wait=60 # Maximum seconds to wait for apt lock\n\n  # Wait for any existing apt/dpkg processes to finish\n  local waited=0\n  while fuser /var/lib/dpkg/lock-frontend &>/dev/null ||\n    fuser /var/lib/apt/lists/lock &>/dev/null ||\n    fuser /var/cache/apt/archives/lock &>/dev/null; do\n    if ((waited >= max_wait)); then\n      msg_warn \"APT lock held for ${max_wait}s, attempting to continue anyway\"\n      break\n    fi\n    debug_log \"Waiting for APT lock (${waited}s)...\"\n    sleep 2\n    ((waited += 2))\n  done\n\n  # Fix interrupted dpkg operations first\n  # This can happen if a previous installation was interrupted (e.g., by script error)\n  if dpkg --audit 2>&1 | grep -q .; then\n    debug_log \"Fixing interrupted dpkg operations\"\n    $STD dpkg --configure -a 2>/dev/null || true\n  fi\n\n  # Clean up orphaned sources first\n  cleanup_orphaned_sources\n\n  # Try to update package lists\n  if ! $STD apt update 2>/dev/null; then\n    debug_log \"First apt update failed, trying recovery steps\"\n\n    # Step 1: Clear apt lists cache\n    rm -rf /var/lib/apt/lists/* 2>/dev/null || true\n    mkdir -p /var/lib/apt/lists/partial\n\n    # Step 2: Clean up potentially broken sources\n    cleanup_orphaned_sources\n\n    # Step 3: Try again\n    if ! $STD apt update 2>/dev/null; then\n      # Step 4: More aggressive - remove all third-party sources\n      msg_warn \"APT update still failing, removing third-party sources\"\n      find /etc/apt/sources.list.d/ -type f \\( -name \"*.sources\" -o -name \"*.list\" \\) \\\n        ! -name \"debian.sources\" -delete 2>/dev/null || true\n\n      # Final attempt\n      if ! $STD apt update; then\n        msg_error \"Cannot update package lists - APT is critically broken\"\n        return 100\n      fi\n    fi\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Standardized deb822 repository setup (with optional Architectures)\n# Always runs apt update after repo creation to ensure package availability\n# ------------------------------------------------------------------------------\nsetup_deb822_repo() {\n  local name=\"$1\"\n  local gpg_url=\"$2\"\n  local repo_url=\"$3\"\n  local suite=\"$4\"\n  local component=\"${5:-main}\"\n  local architectures=\"${6-}\" # optional\n  local enabled=\"${7-}\"       # optional: \"true\" or \"false\"\n\n  # Validate required parameters\n  if [[ -z \"$name\" || -z \"$gpg_url\" || -z \"$repo_url\" || -z \"$suite\" ]]; then\n    msg_error \"setup_deb822_repo: missing required parameters (name=$name repo=$repo_url suite=$suite)\"\n    return 65\n  fi\n\n  # Cleanup\n  cleanup_old_repo_files \"$name\"\n  cleanup_orphaned_sources\n\n  mkdir -p /etc/apt/keyrings || {\n    msg_error \"Failed to create /etc/apt/keyrings\"\n    return 252\n  }\n\n  # Import GPG key (auto-detect binary vs ASCII-armored format)\n  local tmp_gpg\n  tmp_gpg=$(mktemp) || return 252\n  curl -fsSL \"$gpg_url\" -o \"$tmp_gpg\" || {\n    msg_error \"Failed to download GPG key for ${name}\"\n    rm -f \"$tmp_gpg\"\n    return 7\n  }\n\n  if grep -q \"BEGIN PGP\" \"$tmp_gpg\" 2>/dev/null; then\n    # ASCII-armored — dearmor to binary\n    gpg --dearmor --yes -o \"/etc/apt/keyrings/${name}.gpg\" <\"$tmp_gpg\" || {\n      msg_error \"Failed to install GPG key for ${name}\"\n      rm -f \"$tmp_gpg\"\n      return 251\n    }\n  else\n    # Already binary — copy directly\n    cp -f \"$tmp_gpg\" \"/etc/apt/keyrings/${name}.gpg\" || {\n      msg_error \"Failed to install GPG key for ${name}\"\n      rm -f \"$tmp_gpg\"\n      return 252\n    }\n  fi\n  rm -f \"$tmp_gpg\"\n  chmod 644 \"/etc/apt/keyrings/${name}.gpg\"\n\n  # Write deb822\n  {\n    echo \"Types: deb\"\n    echo \"URIs: $repo_url\"\n    echo \"Suites: $suite\"\n    # Flat repositories (suite ending with \"/\" or \"./\") must not have Components\n    if [[ \"$suite\" != *\"/\" && -n \"$component\" ]]; then\n      echo \"Components: $component\"\n    fi\n    [[ -n \"$architectures\" ]] && echo \"Architectures: $architectures\"\n    echo \"Signed-By: /etc/apt/keyrings/${name}.gpg\"\n    [[ -n \"$enabled\" ]] && echo \"Enabled: $enabled\"\n  } >/etc/apt/sources.list.d/${name}.sources\n\n  $STD apt update || {\n    msg_warn \"apt update failed after adding repository: ${name}\"\n  }\n}\n\n# ------------------------------------------------------------------------------\n# Package version hold/unhold helpers\n# ------------------------------------------------------------------------------\nhold_package_version() {\n  local package=\"$1\"\n  $STD apt-mark hold \"$package\" || {\n    msg_warn \"Failed to hold package version: ${package}\"\n  }\n}\n\nunhold_package_version() {\n  local package=\"$1\"\n  $STD apt-mark unhold \"$package\" || {\n    msg_warn \"Failed to unhold package version: ${package}\"\n  }\n}\n\n# ------------------------------------------------------------------------------\n# Safe service restart with verification\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\n# Safe service restart with retry logic and wait-for-ready\n# Usage: safe_service_restart \"nginx\" [timeout_seconds]\n# ------------------------------------------------------------------------------\nsafe_service_restart() {\n  local service=\"$1\"\n  local timeout=\"${2:-30}\" # Default 30 second timeout\n  local max_retries=2\n  local retry=0\n\n  while [[ $retry -le $max_retries ]]; do\n    if systemctl is-active --quiet \"$service\"; then\n      $STD systemctl restart \"$service\"\n    else\n      $STD systemctl start \"$service\"\n    fi\n\n    # Wait for service to become active with timeout\n    local waited=0\n    while [[ $waited -lt $timeout ]]; do\n      if systemctl is-active --quiet \"$service\"; then\n        return 0\n      fi\n      sleep 1\n      ((waited++))\n    done\n\n    retry=$((retry + 1))\n    if [[ $retry -le $max_retries ]]; then\n      debug_log \"Service $service failed to start, retrying ($retry/$max_retries)...\"\n      # Try to stop completely before retry\n      systemctl stop \"$service\" 2>/dev/null || true\n      sleep 2\n    fi\n  done\n\n  msg_error \"Failed to start $service after $max_retries retries\"\n  systemctl status \"$service\" --no-pager -l 2>/dev/null | head -20 || true\n  return 150\n}\n\n# ------------------------------------------------------------------------------\n# Enable and start service (with error handling)\n# ------------------------------------------------------------------------------\nenable_and_start_service() {\n  local service=\"$1\"\n\n  if ! systemctl enable \"$service\" &>/dev/null; then\n    msg_error \"Failed to enable service: $service\"\n    return 150\n  fi\n\n  if ! systemctl start \"$service\" &>/dev/null; then\n    msg_error \"Failed to start $service\"\n    systemctl status \"$service\" --no-pager\n    return 150\n  fi\n\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Check if service is enabled\n# ------------------------------------------------------------------------------\nis_service_enabled() {\n  local service=\"$1\"\n  systemctl is-enabled --quiet \"$service\" 2>/dev/null\n}\n\n# ------------------------------------------------------------------------------\n# Check if service is running\n# ------------------------------------------------------------------------------\nis_service_running() {\n  local service=\"$1\"\n  systemctl is-active --quiet \"$service\" 2>/dev/null\n}\n\n# ------------------------------------------------------------------------------\n# Extract version from JSON (GitHub releases)\n# ------------------------------------------------------------------------------\nextract_version_from_json() {\n  local json=\"$1\"\n  local field=\"${2:-tag_name}\"\n  local strip_v=\"${3:-true}\"\n\n  ensure_dependencies jq\n\n  local version\n  version=$(echo \"$json\" | jq -r \".${field} // empty\")\n\n  if [[ -z \"$version\" ]]; then\n    msg_warn \"JSON field '${field}' is empty in API response\"\n    return 250\n  fi\n\n  if [[ \"$strip_v\" == \"true\" ]]; then\n    echo \"${version#v}\"\n  else\n    echo \"$version\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Get latest GitHub tag (for repos that only publish tags, not releases).\n#\n# Usage:\n#   get_latest_gh_tag \"owner/repo\" [prefix]\n#\n# Arguments:\n#   $1 - GitHub repo (owner/repo)\n#   $2 - Optional prefix filter (e.g., \"v\" to only match tags starting with \"v\")\n#\n# Returns:\n#   Latest tag name (stdout), or returns 1 on failure\n# ------------------------------------------------------------------------------\nget_latest_gh_tag() {\n  local repo=\"$1\"\n  local prefix=\"${2:-}\"\n  local temp_file\n  temp_file=$(mktemp)\n\n  local tag=\"\"\n\n  if [[ -n \"$prefix\" ]]; then\n    # Use git/matching-refs API for server-side prefix filtering. This avoids\n    # paging through unrelated tags (e.g. mongodb/mongo-tools where 100.x tags\n    # only appear after page 4 of /tags). Returns ALL tags matching the prefix\n    # in a single call, sorted lexicographically ascending; we pick the\n    # highest version using `sort -V`.\n    if ! github_api_call \"https://api.github.com/repos/${repo}/git/matching-refs/tags/${prefix}\" \"$temp_file\"; then\n      rm -f \"$temp_file\"\n      return 22\n    fi\n\n    local count\n    count=$(jq 'length' \"$temp_file\" 2>/dev/null || echo 0)\n    if [[ \"$count\" -gt 0 ]]; then\n      tag=$(jq -r '.[].ref' \"$temp_file\" |\n        sed 's|^refs/tags/||' |\n        sort -V |\n        tail -n1)\n    fi\n  else\n    # No prefix: just take the first (newest) tag from /tags\n    if ! github_api_call \"https://api.github.com/repos/${repo}/tags?per_page=1\" \"$temp_file\"; then\n      rm -f \"$temp_file\"\n      return 22\n    fi\n    tag=$(jq -r '.[0].name // empty' \"$temp_file\")\n  fi\n\n  rm -f \"$temp_file\"\n\n  if [[ -z \"$tag\" ]]; then\n    msg_error \"No tags found for ${repo}\"\n    return 250\n  fi\n\n  echo \"$tag\"\n}\n\n# ------------------------------------------------------------------------------\n# Get latest GitHub release version with fallback to tags\n# Usage: get_latest_github_release \"owner/repo\" [strip_v] [include_prerelease]\n# ------------------------------------------------------------------------------\nget_latest_github_release() {\n  local repo=\"$1\"\n  local strip_v=\"${2:-true}\"\n  local temp_file=$(mktemp)\n\n  if ! github_api_call \"https://api.github.com/repos/${repo}/releases/latest\" \"$temp_file\"; then\n    msg_warn \"GitHub API call failed for ${repo}\"\n    rm -f \"$temp_file\"\n    return 22\n  fi\n\n  local version\n  version=$(extract_version_from_json \"$(cat \"$temp_file\")\" \"tag_name\" \"$strip_v\")\n  rm -f \"$temp_file\"\n\n  if [[ -z \"$version\" ]]; then\n    msg_error \"Could not determine latest version for ${repo}\"\n    return 250\n  fi\n\n  echo \"$version\"\n}\n\n# ------------------------------------------------------------------------------\n# Get latest Codeberg release version\n# ------------------------------------------------------------------------------\nget_latest_codeberg_release() {\n  local repo=\"$1\"\n  local strip_v=\"${2:-true}\"\n  local temp_file=$(mktemp)\n\n  # Codeberg API: get all releases and pick the first non-draft/non-prerelease\n  if ! codeberg_api_call \"https://codeberg.org/api/v1/repos/${repo}/releases\" \"$temp_file\"; then\n    msg_warn \"Codeberg API call failed for ${repo}\"\n    rm -f \"$temp_file\"\n    return 22\n  fi\n\n  local version\n  # Codeberg uses same JSON structure but releases endpoint returns array\n  version=$(jq -r '[.[] | select(.draft==false and .prerelease==false)][0].tag_name // empty' \"$temp_file\")\n\n  if [[ \"$strip_v\" == \"true\" ]]; then\n    version=\"${version#v}\"\n  fi\n\n  rm -f \"$temp_file\"\n\n  if [[ -z \"$version\" ]]; then\n    msg_error \"Could not determine latest version for ${repo}\"\n    return 250\n  fi\n\n  echo \"$version\"\n}\n\n# ------------------------------------------------------------------------------\n# Debug logging - using main debug_log function (line 40)\n# Supports both TOOLS_DEBUG and DEBUG environment variables\n# ------------------------------------------------------------------------------\n\n# ------------------------------------------------------------------------------\n# Performance timing helper\n# ------------------------------------------------------------------------------\nstart_timer() {\n  echo $(date +%s)\n}\n\nend_timer() {\n  local start_time=\"$1\"\n  local label=\"${2:-Operation}\"\n  local end_time=$(date +%s)\n  local duration=$((end_time - start_time))\n}\n\n# ------------------------------------------------------------------------------\n# GPG key fingerprint verification\n# ------------------------------------------------------------------------------\nverify_gpg_fingerprint() {\n  local key_file=\"$1\"\n  local expected_fingerprint=\"$2\"\n\n  local actual_fingerprint\n  actual_fingerprint=$(gpg --show-keys --with-fingerprint --with-colons \"$key_file\" 2>&1 | grep -m1 '^fpr:' | cut -d: -f10)\n\n  if [[ \"$actual_fingerprint\" == \"$expected_fingerprint\" ]]; then\n    return 0\n  fi\n\n  msg_error \"GPG fingerprint mismatch! Expected: $expected_fingerprint, Got: $actual_fingerprint\"\n  return 65\n}\n\n# ------------------------------------------------------------------------------\n# Fetches and deploys a GitHub tag-based source tarball.\n#\n# Description:\n#   - Downloads the source tarball for a given tag from GitHub\n#   - Extracts to the target directory\n#   - Writes the version to ~/.<app>\n#\n# Usage:\n#     fetch_and_deploy_gh_tag \"guacd\" \"apache/guacamole-server\"\n#     fetch_and_deploy_gh_tag \"guacd\" \"apache/guacamole-server\" \"latest\" \"/opt/guacamole-server\"\n#\n# Arguments:\n#   $1 - App name (used for version file ~/.<app>)\n#   $2 - GitHub repo (owner/repo)\n#   $3 - Tag version (default: \"latest\" → auto-detect via get_latest_gh_tag)\n#   $4 - Target directory (default: /opt/$app)\n#\n# Notes:\n#   - Supports CLEAN_INSTALL=1 to wipe target before extracting\n#   - For repos that only publish tags, not GitHub Releases\n# ------------------------------------------------------------------------------\nfetch_and_deploy_gh_tag() {\n  local app=\"$1\"\n  local repo=\"$2\"\n  local version=\"${3:-latest}\"\n  local target=\"${4:-/opt/$app}\"\n  local app_lc=\"\"\n  app_lc=\"$(echo \"${app,,}\" | tr -d ' ')\"\n  local version_file=\"$HOME/.${app_lc}\"\n\n  if [[ \"$version\" == \"latest\" ]]; then\n    version=$(get_latest_gh_tag \"$repo\") || {\n      msg_error \"Failed to determine latest tag for ${repo}\"\n      return 250\n    }\n  fi\n\n  local current_version=\"\"\n  [[ -f \"$version_file\" ]] && current_version=$(<\"$version_file\")\n\n  if [[ \"$current_version\" == \"$version\" ]]; then\n    msg_ok \"$app is already up-to-date ($version)\"\n    return 0\n  fi\n\n  local tmpdir\n  tmpdir=$(mktemp -d) || return 1\n  local tarball_url=\"https://github.com/${repo}/archive/refs/tags/${version}.tar.gz\"\n  local filename=\"${app_lc}-${version}.tar.gz\"\n\n  msg_info \"Fetching GitHub tag: ${app} (${version})\"\n\n  download_file \"$tarball_url\" \"$tmpdir/$filename\" || {\n    msg_error \"Download failed: $tarball_url\"\n    rm -rf \"$tmpdir\"\n    return 7\n  }\n\n  mkdir -p \"$target\"\n  if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n    rm -rf \"${target:?}/\"*\n  fi\n\n  tar --no-same-owner -xzf \"$tmpdir/$filename\" -C \"$tmpdir\" || {\n    msg_error \"Failed to extract tarball\"\n    rm -rf \"$tmpdir\"\n    return 251\n  }\n\n  local unpack_dir\n  unpack_dir=$(find \"$tmpdir\" -mindepth 1 -maxdepth 1 -type d | head -n1)\n\n  shopt -s dotglob nullglob\n  cp -r \"$unpack_dir\"/* \"$target/\"\n  shopt -u dotglob nullglob\n\n  rm -rf \"$tmpdir\"\n  echo \"$version\" >\"$version_file\"\n  msg_ok \"Deployed ${app} ${version} to ${target}\"\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Checks for new GitHub tag (for repos without releases).\n#\n# Description:\n#   - Uses get_latest_gh_tag to fetch the latest tag\n#   - Compares it to a local cached version (~/.<app>)\n#   - If newer, sets global CHECK_UPDATE_RELEASE and returns 0\n#\n# Usage:\n#     if check_for_gh_tag \"guacd\" \"apache/guacamole-server\"; then\n#       fetch_and_deploy_gh_tag \"guacd\" \"apache/guacamole-server\" \"/opt/guacamole-server\"\n#     fi\n#\n# Notes:\n#   - For repos that only publish tags, not GitHub Releases\n#   - Same interface as check_for_gh_release\n# ------------------------------------------------------------------------------\ncheck_for_gh_tag() {\n  local app=\"$1\"\n  local repo=\"$2\"\n  local prefix=\"${3:-}\"\n  local app_lc=\"\"\n  app_lc=\"$(echo \"${app,,}\" | tr -d ' ')\"\n  local current_file=\"$HOME/.${app_lc}\"\n\n  msg_info \"Checking for update: ${app}\"\n\n  local latest=\"\"\n  latest=$(get_latest_gh_tag \"$repo\" \"$prefix\") || return 22\n\n  local current=\"\"\n  [[ -f \"$current_file\" ]] && current=\"$(<\"$current_file\")\"\n\n  if [[ -z \"$current\" || \"$current\" != \"$latest\" ]]; then\n    CHECK_UPDATE_RELEASE=\"$latest\"\n    msg_ok \"Update available: ${app} ${current:-not installed} → ${latest}\"\n    return 0\n  fi\n\n  msg_ok \"No update available: ${app} (${latest})\"\n  return 1\n}\n\n# ==============================================================================\n# INSTALL FUNCTIONS\n# ==============================================================================\n\n# ------------------------------------------------------------------------------\n# Checks for new GitHub release (latest tag).\n#\n# Description:\n#   - Queries the GitHub API for the latest release tag\n#   - Compares it to a local cached version (~/.<app>)\n#   - If newer, sets global CHECK_UPDATE_RELEASE and returns 0\n#\n# Usage:\n#     if check_for_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\" [optional] \"v1.1.1\"; then\n#       # trigger update...\n#     fi\n#     exit 0\n#     } (end of update_script not from the function)\n#\n# Notes:\n#   - Requires `jq` (auto-installed if missing)\n#   - Does not modify anything, only checks version state\n#   - Does not support pre-releases\n# ------------------------------------------------------------------------------\ncheck_for_gh_release() {\n  local app=\"$1\"\n  local source=\"$2\"\n  local pinned_version_in=\"${3:-}\" # optional\n  local pin_reason=\"${4:-}\"        # optional reason shown to user\n  local app_lc=\"\"\n  app_lc=\"$(echo \"${app,,}\" | tr -d ' ')\"\n  local current_file=\"$HOME/.${app_lc}\"\n\n  msg_info \"Checking for update: ${app}\"\n\n  # DNS check\n  if ! getent hosts api.github.com >/dev/null 2>&1; then\n    msg_error \"Network error: cannot resolve api.github.com\"\n    return 6\n  fi\n\n  ensure_dependencies jq\n\n  # Build auth header if token is available\n  local header_args=()\n  [[ -n \"${GITHUB_TOKEN:-}\" ]] && header_args=(-H \"Authorization: Bearer $GITHUB_TOKEN\")\n\n  # Try /latest endpoint for non-pinned versions (most efficient)\n  local releases_json=\"\" http_code=\"\"\n\n  # For pinned versions, query the specific release tag directly\n  if [[ -n \"$pinned_version_in\" ]]; then\n    http_code=$(curl -sSL --max-time 20 -w \"%{http_code}\" -o /tmp/gh_check.json \\\n      -H 'Accept: application/vnd.github+json' \\\n      -H 'X-GitHub-Api-Version: 2022-11-28' \\\n      \"${header_args[@]}\" \\\n      \"https://api.github.com/repos/${source}/releases/tags/${pinned_version_in}\" 2>/dev/null) || true\n\n    if [[ \"$http_code\" == \"200\" ]] && [[ -s /tmp/gh_check.json ]]; then\n      releases_json=\"[$(</tmp/gh_check.json)]\"\n    elif [[ \"$http_code\" == \"401\" ]]; then\n      msg_error \"GitHub API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITHUB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication. Try: export GITHUB_TOKEN=\\\"ghp_your_token\\\"\"\n      fi\n      rm -f /tmp/gh_check.json\n      return 22\n    elif [[ \"$http_code\" == \"403\" ]]; then\n      msg_error \"GitHub API rate limit exceeded (HTTP 403).\"\n      msg_error \"To increase the limit, export a GitHub token before running the script:\"\n      msg_error \"  export GITHUB_TOKEN=\\\"ghp_your_token_here\\\"\"\n      rm -f /tmp/gh_check.json\n      return 22\n    fi\n    rm -f /tmp/gh_check.json\n  fi\n\n  if [[ -z \"$pinned_version_in\" ]]; then\n    http_code=$(curl -sSL --max-time 20 -w \"%{http_code}\" -o /tmp/gh_check.json \\\n      -H 'Accept: application/vnd.github+json' \\\n      -H 'X-GitHub-Api-Version: 2022-11-28' \\\n      \"${header_args[@]}\" \\\n      \"https://api.github.com/repos/${source}/releases/latest\" 2>/dev/null) || true\n\n    if [[ \"$http_code\" == \"200\" ]] && [[ -s /tmp/gh_check.json ]]; then\n      releases_json=\"[$(</tmp/gh_check.json)]\"\n    elif [[ \"$http_code\" == \"401\" ]]; then\n      msg_error \"GitHub API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITHUB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication. Try: export GITHUB_TOKEN=\\\"ghp_your_token\\\"\"\n      fi\n      rm -f /tmp/gh_check.json\n      return 22\n    elif [[ \"$http_code\" == \"403\" ]]; then\n      msg_error \"GitHub API rate limit exceeded (HTTP 403).\"\n      msg_error \"To increase the limit, export a GitHub token before running the script:\"\n      msg_error \"  export GITHUB_TOKEN=\\\"ghp_your_token_here\\\"\"\n      rm -f /tmp/gh_check.json\n      return 22\n    fi\n    rm -f /tmp/gh_check.json\n  fi\n\n  # If no releases yet (pinned version OR /latest failed), fetch up to 100\n  if [[ -z \"$releases_json\" ]]; then\n    http_code=$(curl -sSL --max-time 20 -w \"%{http_code}\" -o /tmp/gh_check.json \\\n      -H 'Accept: application/vnd.github+json' \\\n      -H 'X-GitHub-Api-Version: 2022-11-28' \\\n      \"${header_args[@]}\" \\\n      \"https://api.github.com/repos/${source}/releases?per_page=100\" 2>/dev/null) || true\n\n    if [[ \"$http_code\" == \"200\" ]] && [[ -s /tmp/gh_check.json ]]; then\n      releases_json=$(</tmp/gh_check.json)\n    elif [[ \"$http_code\" == \"401\" ]]; then\n      msg_error \"GitHub API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITHUB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication. Try: export GITHUB_TOKEN=\\\"ghp_your_token\\\"\"\n      fi\n      rm -f /tmp/gh_check.json\n      return 22\n    elif [[ \"$http_code\" == \"403\" ]]; then\n      msg_error \"GitHub API rate limit exceeded (HTTP 403).\"\n      msg_error \"To increase the limit, export a GitHub token before running the script:\"\n      msg_error \"  export GITHUB_TOKEN=\\\"ghp_your_token_here\\\"\"\n      rm -f /tmp/gh_check.json\n      return 22\n    elif [[ \"$http_code\" == \"000\" || -z \"$http_code\" ]]; then\n      msg_error \"GitHub API connection failed (no response).\"\n      msg_error \"Check your network/DNS: curl -sSL https://api.github.com/rate_limit\"\n      rm -f /tmp/gh_check.json\n      return 7\n    else\n      msg_error \"Unable to fetch releases for ${app} (HTTP ${http_code})\"\n      rm -f /tmp/gh_check.json\n      return 22\n    fi\n    rm -f /tmp/gh_check.json\n  fi\n\n  mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<\"$releases_json\")\n  if ((${#raw_tags[@]} == 0)); then\n    msg_error \"No stable releases found for ${app}\"\n    return 250\n  fi\n\n  local clean_tags=()\n  for t in \"${raw_tags[@]}\"; do\n    # Only strip leading 'v' when followed by a digit (e.g. v1.2.3)\n    if [[ \"$t\" =~ ^v[0-9] ]]; then\n      clean_tags+=(\"${t:1}\")\n    else\n      clean_tags+=(\"$t\")\n    fi\n  done\n\n  local latest_raw=\"${raw_tags[0]}\"\n  local latest_clean=\"${clean_tags[0]}\"\n\n  # current installed (stored without v)\n  local current=\"\"\n  if [[ -f \"$current_file\" ]]; then\n    current=\"$(<\"$current_file\")\"\n  else\n    # Migration: search for any /opt/*_version.txt\n    local legacy_files\n    mapfile -t legacy_files < <(find /opt -maxdepth 1 -type f -name \"*_version.txt\" 2>/dev/null)\n    if ((${#legacy_files[@]} == 1)); then\n      current=\"$(<\"${legacy_files[0]}\")\"\n      echo \"${current#v}\" >\"$current_file\"\n      rm -f \"${legacy_files[0]}\"\n    fi\n  fi\n  current=\"${current#v}\"\n\n  # Pinned version handling\n  if [[ -n \"$pinned_version_in\" ]]; then\n    local pin_clean=\"${pinned_version_in#v}\"\n    local match_raw=\"\"\n    for i in \"${!clean_tags[@]}\"; do\n      if [[ \"${clean_tags[$i]}\" == \"$pin_clean\" ]]; then\n        match_raw=\"${raw_tags[$i]}\"\n        break\n      fi\n    done\n\n    if [[ -z \"$match_raw\" ]]; then\n      msg_error \"Pinned version ${pinned_version_in} not found upstream\"\n      return 250\n    fi\n\n    if [[ \"$current\" != \"$pin_clean\" ]]; then\n      CHECK_UPDATE_RELEASE=\"$match_raw\"\n      msg_ok \"Update available: ${app} ${current:-not installed} → ${pin_clean}\"\n      return 0\n    fi\n\n    if [[ -n \"$pin_reason\" ]]; then\n      msg_ok \"No update available: ${app} (${current}) - update held back: ${pin_reason}\"\n    else\n      msg_ok \"No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases\"\n    fi\n    return 1\n  fi\n\n  # No pinning → use latest\n  if [[ -z \"$current\" || \"$current\" != \"$latest_clean\" ]]; then\n    CHECK_UPDATE_RELEASE=\"$latest_raw\"\n    msg_ok \"Update available: ${app} ${current:-not installed} → ${latest_clean}\"\n    return 0\n  fi\n\n  msg_ok \"No update available: ${app} (${latest_clean})\"\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Checks for new Codeberg release (latest tag).\n#\n# Description:\n#   - Queries the Codeberg API for the latest release tag\n#   - Compares it to a local cached version (~/.<app>)\n#   - If newer, sets global CHECK_UPDATE_RELEASE and returns 0\n#\n# Usage:\n#     if check_for_codeberg_release \"autocaliweb\" \"gelbphoenix/autocaliweb\" [optional] \"v0.11.3\"; then\n#       # trigger update...\n#     fi\n#     exit 0\n#     } (end of update_script not from the function)\n#\n# Notes:\n#   - Requires `jq` (auto-installed if missing)\n#   - Does not modify anything, only checks version state\n#   - Does not support pre-releases\n# ------------------------------------------------------------------------------\ncheck_for_codeberg_release() {\n  local app=\"$1\"\n  local source=\"$2\"\n  local pinned_version_in=\"${3:-}\" # optional\n  local pin_reason=\"${4:-}\"        # optional reason shown to user\n  local app_lc=\"${app,,}\"\n  local current_file=\"$HOME/.${app_lc}\"\n\n  msg_info \"Checking for update: ${app}\"\n\n  # DNS check\n  if ! getent hosts codeberg.org >/dev/null 2>&1; then\n    msg_error \"Network error: cannot resolve codeberg.org\"\n    return 6\n  fi\n\n  ensure_dependencies jq\n\n  # Fetch releases from Codeberg API\n  local releases_json=\"\"\n  releases_json=$(curl -fsSL --max-time 20 \\\n    -H 'Accept: application/json' \\\n    \"https://codeberg.org/api/v1/repos/${source}/releases\" 2>/dev/null) || {\n    msg_error \"Unable to fetch releases for ${app} (codeberg.org/api/v1/repos/${source}/releases)\"\n    return 22\n  }\n\n  mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<\"$releases_json\")\n  if ((${#raw_tags[@]} == 0)); then\n    msg_error \"No stable releases found for ${app}\"\n    return 250\n  fi\n\n  local clean_tags=()\n  for t in \"${raw_tags[@]}\"; do\n    # Only strip leading 'v' when followed by a digit (e.g. v1.2.3)\n    if [[ \"$t\" =~ ^v[0-9] ]]; then\n      clean_tags+=(\"${t:1}\")\n    else\n      clean_tags+=(\"$t\")\n    fi\n  done\n\n  local latest_raw=\"${raw_tags[0]}\"\n  local latest_clean=\"${clean_tags[0]}\"\n\n  # current installed (stored without v)\n  local current=\"\"\n  if [[ -f \"$current_file\" ]]; then\n    current=\"$(<\"$current_file\")\"\n  else\n    # Migration: search for any /opt/*_version.txt\n    local legacy_files\n    mapfile -t legacy_files < <(find /opt -maxdepth 1 -type f -name \"*_version.txt\" 2>/dev/null)\n    if ((${#legacy_files[@]} == 1)); then\n      current=\"$(<\"${legacy_files[0]}\")\"\n      echo \"${current#v}\" >\"$current_file\"\n      rm -f \"${legacy_files[0]}\"\n    fi\n  fi\n  current=\"${current#v}\"\n\n  # Pinned version handling\n  if [[ -n \"$pinned_version_in\" ]]; then\n    local pin_clean=\"${pinned_version_in#v}\"\n    local match_raw=\"\"\n    for i in \"${!clean_tags[@]}\"; do\n      if [[ \"${clean_tags[$i]}\" == \"$pin_clean\" ]]; then\n        match_raw=\"${raw_tags[$i]}\"\n        break\n      fi\n    done\n\n    if [[ -z \"$match_raw\" ]]; then\n      msg_error \"Pinned version ${pinned_version_in} not found upstream\"\n      return 250\n    fi\n\n    if [[ \"$current\" != \"$pin_clean\" ]]; then\n      CHECK_UPDATE_RELEASE=\"$match_raw\"\n      msg_ok \"Update available: ${app} ${current:-not installed} → ${pin_clean}\"\n      return 0\n    fi\n\n    if [[ -n \"$pin_reason\" ]]; then\n      msg_ok \"No update available: ${app} (${current}) - update held back: ${pin_reason}\"\n    else\n      msg_ok \"No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases\"\n    fi\n    return 1\n  fi\n\n  # No pinning → use latest\n  if [[ -z \"$current\" || \"$current\" != \"$latest_clean\" ]]; then\n    CHECK_UPDATE_RELEASE=\"$latest_raw\"\n    msg_ok \"Update available: ${app} ${current:-not installed} → ${latest_clean}\"\n    return 0\n  fi\n\n  msg_ok \"No update available: ${app} (${latest_clean})\"\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Creates and installs self-signed certificates.\n#\n# Description:\n#   - Create a self-signed certificate with option to override application name\n#\n# Variables:\n#   APP   - Application name (default: $APPLICATION variable)\n# ------------------------------------------------------------------------------\ncreate_self_signed_cert() {\n  local APP_NAME=\"${1:-${APPLICATION}}\"\n  local HOSTNAME=\"$(hostname -f)\"\n  local IP=\"$(hostname -I | awk '{print $1}')\"\n  local APP_NAME_LC=$(echo \"${APP_NAME,,}\" | tr -d ' ')\n  local CERT_DIR=\"/etc/ssl/${APP_NAME_LC}\"\n  local CERT_KEY=\"${CERT_DIR}/${APP_NAME_LC}.key\"\n  local CERT_CRT=\"${CERT_DIR}/${APP_NAME_LC}.crt\"\n\n  if [[ -f \"$CERT_CRT\" && -f \"$CERT_KEY\" ]]; then\n    return 0\n  fi\n\n  # Use ensure_dependencies for cleaner handling\n  ensure_dependencies openssl || {\n    msg_error \"Failed to install OpenSSL\"\n    return 100\n  }\n\n  mkdir -p \"$CERT_DIR\"\n  $STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \\\n    -subj \"/CN=${HOSTNAME}\" \\\n    -addext \"subjectAltName=DNS:${HOSTNAME},DNS:localhost,IP:${IP},IP:127.0.0.1\" \\\n    -keyout \"$CERT_KEY\" \\\n    -out \"$CERT_CRT\" || {\n    msg_error \"Failed to create self-signed certificate\"\n    return 150\n  }\n\n  chmod 600 \"$CERT_KEY\"\n  chmod 644 \"$CERT_CRT\"\n}\n\n# ------------------------------------------------------------------------------\n# Downloads file with optional progress indicator using pv.\n#\n# Arguments:\n#   $1 - URL\n#   $2 - Destination path\n# ------------------------------------------------------------------------------\n\nfunction download_with_progress() {\n  local url=\"$1\"\n  local output=\"$2\"\n  if [ -n \"$SPINNER_PID\" ] && ps -p \"$SPINNER_PID\" >/dev/null; then kill \"$SPINNER_PID\" >/dev/null; fi\n\n  ensure_dependencies pv\n  set -o pipefail\n\n  # Content-Length aus HTTP-Header holen\n  local content_length\n  content_length=$(curl -fsSLI \"$url\" | awk '/Content-Length/ {print $2}' | tr -d '\\r' || true)\n\n  if [[ -z \"$content_length\" ]]; then\n    if ! curl -fL# -o \"$output\" \"$url\"; then\n      msg_error \"Download failed: $url\"\n      return 7\n    fi\n  else\n    if ! curl -fsSL \"$url\" | pv -s \"$content_length\" >\"$output\"; then\n      msg_error \"Download failed: $url\"\n      return 7\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Ensures /usr/local/bin is permanently in system PATH.\n#\n# Description:\n#   - Adds to /etc/profile.d for login shells (SSH, noVNC)\n#   - Adds to /root/.bashrc for non-login shells (pct enter)\n# ------------------------------------------------------------------------------\n\nfunction ensure_usr_local_bin_persist() {\n  # Skip on Proxmox host\n  command -v pveversion &>/dev/null && return\n\n  # Login shells: /etc/profile.d/\n  local PROFILE_FILE=\"/etc/profile.d/custom_path.sh\"\n  if [[ ! -f \"$PROFILE_FILE\" ]]; then\n    echo 'export PATH=\"/usr/local/bin:$PATH\"' >\"$PROFILE_FILE\"\n    chmod +x \"$PROFILE_FILE\"\n  fi\n\n  # Non-login shells (pct enter): /root/.bashrc\n  local BASHRC=\"/root/.bashrc\"\n  if [[ -f \"$BASHRC\" ]] && ! grep -q '/usr/local/bin' \"$BASHRC\"; then\n    echo 'export PATH=\"/usr/local/bin:$PATH\"' >>\"$BASHRC\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# curl_download - Downloads a file with automatic retry and exponential backoff.\n#\n# Usage: curl_download <output_file> <url>\n#\n# Retries up to 5 times with increasing --max-time (60/120/240/480/960s).\n# Returns 0 on success, 1 if all attempts fail.\n# ------------------------------------------------------------------------------\nfunction curl_download() {\n  local output=\"$1\"\n  local url=\"$2\"\n  local timeouts=(60 120 240 480 960)\n\n  for i in \"${!timeouts[@]}\"; do\n    if curl --connect-timeout 15 --max-time \"${timeouts[$i]}\" -fsSL -o \"$output\" \"$url\"; then\n      return 0\n    fi\n    if ((i < ${#timeouts[@]} - 1)); then\n      msg_warn \"Download timed out after ${timeouts[$i]}s, retrying... (attempt $((i + 2))/${#timeouts[@]})\"\n    fi\n  done\n  return 7\n}\n\n# ------------------------------------------------------------------------------\n# Downloads and deploys latest Codeberg release (source, binary, tarball, asset).\n#\n# Description:\n#   - Fetches latest release metadata from Codeberg API\n#   - Supports the following modes:\n#       - tarball: Source code tarball (default if omitted)\n#       - source: Alias for tarball (same behavior)\n#       - binary: .deb package install (arch-dependent)\n#       - prebuild: Prebuilt .tar.gz archive (e.g. Go binaries)\n#       - singlefile: Standalone binary (no archive, direct chmod +x install)\n#       - tag: Direct tag download (bypasses Release API)\n#   - Handles download, extraction/installation and version tracking in ~/.<app>\n#\n# Parameters:\n#   $1   APP               - Application name (used for install path and version file)\n#   $2   REPO              - Codeberg repository in form user/repo\n#   $3   MODE              - Release type:\n#                              tarball   → source tarball (.tar.gz)\n#                              binary    → .deb file (auto-arch matched)\n#                              prebuild  → prebuilt archive (e.g. tar.gz)\n#                              singlefile→ standalone binary (chmod +x)\n#                              tag       → direct tag (bypasses Release API)\n#   $4   VERSION           - Optional release tag (default: latest)\n#   $5   TARGET_DIR        - Optional install path (default: /opt/<app>)\n#   $6   ASSET_FILENAME    - Required for:\n#                              - prebuild  → archive filename or pattern\n#                              - singlefile→ binary filename or pattern\n#\n# Examples:\n#   # 1. Minimal: Fetch and deploy source tarball\n#   fetch_and_deploy_codeberg_release \"autocaliweb\" \"gelbphoenix/autocaliweb\"\n#\n#   # 2. Binary install via .deb asset (architecture auto-detected)\n#   fetch_and_deploy_codeberg_release \"myapp\" \"myuser/myapp\" \"binary\"\n#\n#   # 3. Prebuilt archive (.tar.gz) with asset filename match\n#   fetch_and_deploy_codeberg_release \"myapp\" \"myuser/myapp\" \"prebuild\" \"latest\" \"/opt/myapp\" \"myapp_Linux_x86_64.tar.gz\"\n#\n#   # 4. Single binary (chmod +x)\n#   fetch_and_deploy_codeberg_release \"myapp\" \"myuser/myapp\" \"singlefile\" \"v1.0.0\" \"/opt/myapp\" \"myapp-linux-amd64\"\n#\n#   # 5. Explicit tag version\n#   fetch_and_deploy_codeberg_release \"autocaliweb\" \"gelbphoenix/autocaliweb\" \"tag\" \"v0.11.3\" \"/opt/autocaliweb\"\n# ------------------------------------------------------------------------------\n\nfunction fetch_and_deploy_codeberg_release() {\n  local app=\"$1\"\n  local repo=\"$2\"\n  local mode=\"${3:-tarball}\" # tarball | binary | prebuild | singlefile | tag\n  local version=\"${var_appversion:-${4:-latest}}\"\n  local target=\"${5:-/opt/$app}\"\n  local asset_pattern=\"${6:-}\"\n\n  local app_lc=$(echo \"${app,,}\" | tr -d ' ')\n  local version_file=\"$HOME/.${app_lc}\"\n\n  local api_timeouts=(60 120 240)\n\n  local current_version=\"\"\n  [[ -f \"$version_file\" ]] && current_version=$(<\"$version_file\")\n\n  ensure_dependencies jq\n\n  ### Tag Mode (bypass Release API) ###\n  if [[ \"$mode\" == \"tag\" ]]; then\n    if [[ \"$version\" == \"latest\" ]]; then\n      msg_error \"Mode 'tag' requires explicit version (not 'latest')\"\n      return 65\n    fi\n\n    local tag_name=\"$version\"\n    [[ \"$tag_name\" =~ ^v ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n\n    if [[ \"$current_version\" == \"$version\" ]]; then\n      $STD msg_ok \"$app is already up-to-date (v$version)\"\n      return 0\n    fi\n\n    # DNS check\n    if ! getent hosts \"codeberg.org\" &>/dev/null; then\n      msg_error \"DNS resolution failed for codeberg.org – check /etc/resolv.conf or networking\"\n      return 6\n    fi\n\n    local tmpdir\n    tmpdir=$(mktemp -d) || return 252\n\n    msg_info \"Fetching Codeberg tag: $app ($tag_name)\"\n\n    local safe_version=\"${version//@/_}\"\n    safe_version=\"${safe_version//\\//_}\"\n    local filename=\"${app_lc}-${safe_version}.tar.gz\"\n    local download_success=false\n\n    # Codeberg archive URL format: https://codeberg.org/{owner}/{repo}/archive/{tag}.tar.gz\n    local archive_url=\"https://codeberg.org/$repo/archive/${tag_name}.tar.gz\"\n    if curl_download \"$tmpdir/$filename\" \"$archive_url\"; then\n      download_success=true\n    fi\n\n    if [[ \"$download_success\" != \"true\" ]]; then\n      msg_error \"Download failed for $app ($tag_name)\"\n      rm -rf \"$tmpdir\"\n      return 250\n    fi\n\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    tar --no-same-owner -xzf \"$tmpdir/$filename\" -C \"$tmpdir\" || {\n      msg_error \"Failed to extract tarball\"\n      rm -rf \"$tmpdir\"\n      return 251\n    }\n\n    local unpack_dir\n    unpack_dir=$(find \"$tmpdir\" -mindepth 1 -maxdepth 1 -type d | head -n1)\n\n    shopt -s dotglob nullglob\n    cp -r \"$unpack_dir\"/* \"$target/\"\n    shopt -u dotglob nullglob\n\n    echo \"$version\" >\"$version_file\"\n    msg_ok \"Deployed: $app ($version)\"\n    rm -rf \"$tmpdir\"\n    return 0\n  fi\n\n  # Codeberg API: https://codeberg.org/api/v1/repos/{owner}/{repo}/releases\n  local api_url=\"https://codeberg.org/api/v1/repos/$repo/releases\"\n  if [[ \"$version\" != \"latest\" ]]; then\n    # Get release by tag: /repos/{owner}/{repo}/releases/tags/{tag}\n    api_url=\"https://codeberg.org/api/v1/repos/$repo/releases/tags/$version\"\n  fi\n\n  # dns pre check\n  if ! getent hosts \"codeberg.org\" &>/dev/null; then\n    msg_error \"DNS resolution failed for codeberg.org – check /etc/resolv.conf or networking\"\n    return 6\n  fi\n\n  local attempt=0 success=false resp http_code\n\n  while ((attempt < ${#api_timeouts[@]})); do\n    resp=$(curl --connect-timeout 10 --max-time \"${api_timeouts[$attempt]}\" -fsSL -w \"%{http_code}\" -o /tmp/codeberg_rel.json \"$api_url\") && success=true && break\n    attempt=$((attempt + 1))\n    if ((attempt < ${#api_timeouts[@]})); then\n      msg_warn \"API request timed out after ${api_timeouts[$((attempt - 1))]}s, retrying... (attempt $((attempt + 1))/${#api_timeouts[@]})\"\n    fi\n  done\n\n  if ! $success; then\n    msg_error \"Failed to fetch release metadata from $api_url after ${#api_timeouts[@]} attempts\"\n    return 22\n  fi\n\n  http_code=\"${resp:(-3)}\"\n  [[ \"$http_code\" != \"200\" ]] && {\n    msg_error \"Codeberg API returned HTTP $http_code\"\n    return 22\n  }\n\n  local json tag_name\n  json=$(</tmp/codeberg_rel.json)\n\n  # For \"latest\", the API returns an array - take the first (most recent) release\n  if [[ \"$version\" == \"latest\" ]]; then\n    json=$(echo \"$json\" | jq '.[0]')\n  fi\n\n  tag_name=$(echo \"$json\" | jq -r '.tag_name // .name // empty')\n  [[ \"$tag_name\" =~ ^v ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n\n  if [[ \"$current_version\" == \"$version\" ]]; then\n    $STD msg_ok \"$app is already up-to-date (v$version)\"\n    return 0\n  fi\n\n  local tmpdir\n  tmpdir=$(mktemp -d) || return 252\n  local filename=\"\" url=\"\"\n\n  msg_info \"Fetching Codeberg release: $app ($version)\"\n\n  ### Tarball Mode ###\n  if [[ \"$mode\" == \"tarball\" || \"$mode\" == \"source\" ]]; then\n    local safe_version=\"${version//@/_}\"\n    safe_version=\"${safe_version//\\//_}\"\n    filename=\"${app_lc}-${safe_version}.tar.gz\"\n    local download_success=false\n\n    # Codeberg archive URL format\n    local archive_url=\"https://codeberg.org/$repo/archive/${tag_name}.tar.gz\"\n    if curl_download \"$tmpdir/$filename\" \"$archive_url\"; then\n      download_success=true\n    fi\n\n    if [[ \"$download_success\" != \"true\" ]]; then\n      msg_error \"Download failed for $app ($tag_name)\"\n      rm -rf \"$tmpdir\"\n      return 250\n    fi\n\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    tar --no-same-owner -xzf \"$tmpdir/$filename\" -C \"$tmpdir\" || {\n      msg_error \"Failed to extract tarball\"\n      rm -rf \"$tmpdir\"\n      return 251\n    }\n    local unpack_dir\n    unpack_dir=$(find \"$tmpdir\" -mindepth 1 -maxdepth 1 -type d | head -n1)\n\n    shopt -s dotglob nullglob\n    cp -r \"$unpack_dir\"/* \"$target/\"\n    shopt -u dotglob nullglob\n\n  ### Binary Mode ###\n  elif [[ \"$mode\" == \"binary\" ]]; then\n    local arch\n    arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n    [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n    [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n\n    local assets url_match=\"\"\n    # Codeberg assets are in .assets[].browser_download_url\n    assets=$(echo \"$json\" | jq -r '.assets[].browser_download_url')\n\n    # If explicit filename pattern is provided, match that first\n    if [[ -n \"$asset_pattern\" ]]; then\n      for u in $assets; do\n        case \"${u##*/}\" in\n        $asset_pattern)\n          url_match=\"$u\"\n          break\n          ;;\n        esac\n      done\n    fi\n\n    # Fall back to architecture heuristic\n    if [[ -z \"$url_match\" ]]; then\n      for u in $assets; do\n        if [[ \"$u\" =~ ($arch|amd64|x86_64|aarch64|arm64).*\\.deb$ ]]; then\n          url_match=\"$u\"\n          break\n        fi\n      done\n    fi\n\n    # Fallback: any .deb file\n    if [[ -z \"$url_match\" ]]; then\n      for u in $assets; do\n        [[ \"$u\" =~ \\.deb$ ]] && url_match=\"$u\" && break\n      done\n    fi\n\n    if [[ -z \"$url_match\" ]]; then\n      msg_error \"No suitable .deb asset found for $app\"\n      rm -rf \"$tmpdir\"\n      return 252\n    fi\n\n    filename=\"${url_match##*/}\"\n    curl_download \"$tmpdir/$filename\" \"$url_match\" || {\n      msg_error \"Download failed: $url_match\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    chmod 644 \"$tmpdir/$filename\"\n    $STD apt install -y \"$tmpdir/$filename\" || {\n      $STD dpkg -i \"$tmpdir/$filename\" || {\n        msg_error \"Both apt and dpkg installation failed\"\n        rm -rf \"$tmpdir\"\n        return 100\n      }\n    }\n\n  ### Prebuild Mode ###\n  elif [[ \"$mode\" == \"prebuild\" ]]; then\n    local pattern=\"${6%\\\"}\"\n    pattern=\"${pattern#\\\"}\"\n    [[ -z \"$pattern\" ]] && {\n      msg_error \"Mode 'prebuild' requires 6th parameter (asset filename pattern)\"\n      rm -rf \"$tmpdir\"\n      return 65\n    }\n\n    local asset_url=\"\"\n    for u in $(echo \"$json\" | jq -r '.assets[].browser_download_url'); do\n      filename_candidate=\"${u##*/}\"\n      case \"$filename_candidate\" in\n      $pattern)\n        asset_url=\"$u\"\n        break\n        ;;\n      esac\n    done\n\n    [[ -z \"$asset_url\" ]] && {\n      msg_error \"No asset matching '$pattern' found\"\n      rm -rf \"$tmpdir\"\n      return 252\n    }\n\n    filename=\"${asset_url##*/}\"\n    curl_download \"$tmpdir/$filename\" \"$asset_url\" || {\n      msg_error \"Download failed: $asset_url\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    local unpack_tmp\n    unpack_tmp=$(mktemp -d)\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    if [[ \"$filename\" == *.zip ]]; then\n      ensure_dependencies unzip\n      unzip -q \"$tmpdir/$filename\" -d \"$unpack_tmp\" || {\n        msg_error \"Failed to extract ZIP archive\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 251\n      }\n    elif [[ \"$filename\" == *.tar.* || \"$filename\" == *.tgz ]]; then\n      tar --no-same-owner -xf \"$tmpdir/$filename\" -C \"$unpack_tmp\" || {\n        msg_error \"Failed to extract TAR archive\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 251\n      }\n    else\n      msg_error \"Unsupported archive format: $filename\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 251\n    fi\n\n    local top_dirs\n    top_dirs=$(find \"$unpack_tmp\" -mindepth 1 -maxdepth 1 -type d | wc -l)\n    local top_entries inner_dir\n    top_entries=$(find \"$unpack_tmp\" -mindepth 1 -maxdepth 1)\n    if [[ \"$(echo \"$top_entries\" | wc -l)\" -eq 1 && -d \"$top_entries\" ]]; then\n      inner_dir=\"$top_entries\"\n      shopt -s dotglob nullglob\n      if compgen -G \"$inner_dir/*\" >/dev/null; then\n        cp -r \"$inner_dir\"/* \"$target/\" || {\n          msg_error \"Failed to copy contents from $inner_dir to $target\"\n          rm -rf \"$tmpdir\" \"$unpack_tmp\"\n          return 252\n        }\n      else\n        msg_error \"Inner directory is empty: $inner_dir\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 252\n      fi\n      shopt -u dotglob nullglob\n    else\n      shopt -s dotglob nullglob\n      if compgen -G \"$unpack_tmp/*\" >/dev/null; then\n        cp -r \"$unpack_tmp\"/* \"$target/\" || {\n          msg_error \"Failed to copy contents to $target\"\n          rm -rf \"$tmpdir\" \"$unpack_tmp\"\n          return 252\n        }\n      else\n        msg_error \"Unpacked archive is empty\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 252\n      fi\n      shopt -u dotglob nullglob\n    fi\n\n  ### Singlefile Mode ###\n  elif [[ \"$mode\" == \"singlefile\" ]]; then\n    local pattern=\"${6%\\\"}\"\n    pattern=\"${pattern#\\\"}\"\n    [[ -z \"$pattern\" ]] && {\n      msg_error \"Mode 'singlefile' requires 6th parameter (asset filename pattern)\"\n      rm -rf \"$tmpdir\"\n      return 65\n    }\n\n    local asset_url=\"\"\n    for u in $(echo \"$json\" | jq -r '.assets[].browser_download_url'); do\n      filename_candidate=\"${u##*/}\"\n      case \"$filename_candidate\" in\n      $pattern)\n        asset_url=\"$u\"\n        break\n        ;;\n      esac\n    done\n\n    [[ -z \"$asset_url\" ]] && {\n      msg_error \"No asset matching '$pattern' found\"\n      rm -rf \"$tmpdir\"\n      return 252\n    }\n\n    filename=\"${asset_url##*/}\"\n    mkdir -p \"$target\"\n\n    local use_filename=\"${USE_ORIGINAL_FILENAME:-false}\"\n    local target_file=\"$app\"\n    [[ \"$use_filename\" == \"true\" ]] && target_file=\"$filename\"\n\n    curl_download \"$target/$target_file\" \"$asset_url\" || {\n      msg_error \"Download failed: $asset_url\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    if [[ \"$target_file\" != *.jar && -f \"$target/$target_file\" ]]; then\n      chmod +x \"$target/$target_file\"\n    fi\n\n  else\n    msg_error \"Unknown mode: $mode\"\n    rm -rf \"$tmpdir\"\n    return 65\n  fi\n\n  echo \"$version\" >\"$version_file\"\n  msg_ok \"Deployed: $app ($version)\"\n  rm -rf \"$tmpdir\"\n}\n\n# ------------------------------------------------------------------------------\n# Downloads and deploys latest GitHub release (source, binary, tarball, asset).\n#\n# Description:\n#   - Fetches latest release metadata from GitHub API\n#   - Supports the following modes:\n#       - tarball: Source code tarball (default if omitted)\n#       - source: Alias for tarball (same behavior)\n#       - binary: .deb package install (arch-dependent)\n#       - prebuild: Prebuilt .tar.gz archive (e.g. Go binaries)\n#       - singlefile: Standalone binary (no archive, direct chmod +x install)\n#   - Handles download, extraction/installation and version tracking in ~/.<app>\n#\n# Parameters:\n#   $1   APP               - Application name (used for install path and version file)\n#   $2   REPO              - GitHub repository in form user/repo\n#   $3   MODE              - Release type:\n#                              tarball   → source tarball (.tar.gz)\n#                              binary    → .deb file (auto-arch matched)\n#                              prebuild  → prebuilt archive (e.g. tar.gz)\n#                              singlefile→ standalone binary (chmod +x)\n#   $4   VERSION           - Optional release tag (default: latest)\n#   $5   TARGET_DIR        - Optional install path (default: /opt/<app>)\n#   $6   ASSET_FILENAME    - Required for:\n#                              - prebuild  → archive filename or pattern\n#                              - singlefile→ binary filename or pattern\n#\n# Optional:\n#   - Set GITHUB_TOKEN env var to increase API rate limit (recommended for CI/CD).\n#\n# Examples:\n#   # 1. Minimal: Fetch and deploy source tarball\n#   fetch_and_deploy_gh_release \"myapp\" \"myuser/myapp\"\n#\n#   # 2. Binary install via .deb asset (architecture auto-detected)\n#   fetch_and_deploy_gh_release \"myapp\" \"myuser/myapp\" \"binary\"\n#\n#   # 3. Prebuilt archive (.tar.gz) with asset filename match\n#   fetch_and_deploy_gh_release \"hanko\" \"teamhanko/hanko\" \"prebuild\" \"latest\" \"/opt/hanko\" \"hanko_Linux_x86_64.tar.gz\"\n#\n#   # 4. Single binary (chmod +x) like Argus, Promtail etc.\n#   fetch_and_deploy_gh_release \"argus\" \"release-argus/Argus\" \"singlefile\" \"0.26.3\" \"/opt/argus\" \"Argus-.*linux-amd64\"\n#\n# Notes:\n#   - For binary/prebuild/singlefile modes: if the target release has no\n#     matching asset, the function scans older releases and prompts the user\n#     (60s timeout, default yes) to use a previous version that has the asset.\n# ------------------------------------------------------------------------------\n\n# ------------------------------------------------------------------------------\n# Scans older GitHub releases for a matching asset when the latest release\n# is missing the expected file. Used internally by fetch_and_deploy_gh_release.\n#\n# Arguments:\n#   $1 - GitHub repo (owner/repo)\n#   $2 - mode (binary|prebuild|singlefile)\n#   $3 - asset_pattern (glob pattern for asset filename)\n#   $4 - tag to skip (the already-checked release)\n#\n# Output:\n#   Prints the release JSON of the first older release that has a matching asset.\n#   Returns 0 on success, 1 if no matching release found or user declined.\n# ------------------------------------------------------------------------------\n_gh_scan_older_releases() {\n  local repo=\"$1\"\n  local mode=\"$2\"\n  local asset_pattern=\"$3\"\n  local skip_tag=\"$4\"\n\n  local header=()\n  [[ -n \"${GITHUB_TOKEN:-}\" ]] && header=(-H \"Authorization: token $GITHUB_TOKEN\")\n\n  local releases_list\n  releases_list=$(curl --connect-timeout 10 --max-time 30 -fsSL \\\n    -H 'Accept: application/vnd.github+json' \\\n    -H 'X-GitHub-Api-Version: 2022-11-28' \\\n    \"${header[@]}\" \\\n    \"https://api.github.com/repos/${repo}/releases?per_page=15\" 2>/dev/null) || {\n    msg_warn \"Failed to fetch older releases for ${repo}\"\n    return 22\n  }\n\n  local count\n  count=$(echo \"$releases_list\" | jq 'length')\n\n  for ((i = 0; i < count; i++)); do\n    local rel_tag rel_draft rel_prerelease\n    rel_tag=$(echo \"$releases_list\" | jq -r \".[$i].tag_name\")\n    rel_draft=$(echo \"$releases_list\" | jq -r \".[$i].draft\")\n    rel_prerelease=$(echo \"$releases_list\" | jq -r \".[$i].prerelease\")\n\n    # Skip drafts, prereleases, and the tag we already checked\n    [[ \"$rel_draft\" == \"true\" || \"$rel_prerelease\" == \"true\" ]] && continue\n    [[ \"$rel_tag\" == \"$skip_tag\" ]] && continue\n\n    local has_match=false\n\n    if [[ \"$mode\" == \"binary\" ]]; then\n      local arch\n      arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n      [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n      [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n\n      # Check with explicit pattern first, then arch heuristic, then any .deb\n      if [[ -n \"$asset_pattern\" ]]; then\n        has_match=$(echo \"$releases_list\" | jq -r --arg pat \"$asset_pattern\" \".[$i].assets[].name\" | while read -r name; do\n          case \"$name\" in $asset_pattern)\n            echo true\n            break\n            ;;\n          esac\n        done)\n      fi\n      if [[ \"$has_match\" != \"true\" ]]; then\n        has_match=$(echo \"$releases_list\" | jq -r \".[$i].assets[].browser_download_url\" | grep -qE \"($arch|amd64|x86_64|aarch64|arm64).*\\.deb$\" && echo true)\n      fi\n      if [[ \"$has_match\" != \"true\" ]]; then\n        has_match=$(echo \"$releases_list\" | jq -r \".[$i].assets[].browser_download_url\" | grep -qE '\\.deb$' && echo true)\n      fi\n\n    elif [[ \"$mode\" == \"prebuild\" || \"$mode\" == \"singlefile\" ]]; then\n      has_match=$(echo \"$releases_list\" | jq -r \".[$i].assets[].name\" | while read -r name; do\n        case \"$name\" in $asset_pattern)\n          echo true\n          break\n          ;;\n        esac\n      done)\n    fi\n\n    if [[ \"$has_match\" == \"true\" ]]; then\n      local rel_version=\"$rel_tag\"\n      [[ \"$rel_tag\" =~ ^v ]] && rel_version=\"${rel_tag:1}\"\n\n      local use_fallback=\"y\"\n      if [[ -t 0 ]]; then\n        msg_warn \"Release ${skip_tag} has no matching asset. Previous release ${rel_tag} has a compatible asset.\"\n        read -rp \"Use version ${rel_tag} instead? [Y/n] (auto-yes in 60s): \" -t 60 use_fallback || use_fallback=\"y\"\n        use_fallback=\"${use_fallback:-y}\"\n      fi\n\n      if [[ \"${use_fallback,,}\" == \"y\" || \"${use_fallback,,}\" == \"yes\" ]]; then\n        echo \"$releases_list\" | jq \".[$i]\"\n        return 0\n      else\n        return 250\n      fi\n    fi\n  done\n\n  return 250\n}\n\nfunction fetch_and_deploy_gh_release() {\n  local app=\"$1\"\n  local repo=\"$2\"\n  local mode=\"${3:-tarball}\" # tarball | binary | prebuild | singlefile\n  local version=\"${var_appversion:-${4:-latest}}\"\n  local target=\"${5:-/opt/$app}\"\n  local asset_pattern=\"${6:-}\"\n\n  # Validate app name to prevent /root/. directory issues\n  if [[ -z \"$app\" ]]; then\n    # Derive app name from repo if not provided\n    app=\"${repo##*/}\"\n    if [[ -z \"$app\" ]]; then\n      msg_error \"fetch_and_deploy_gh_release requires app name or valid repo\"\n      return 65\n    fi\n  fi\n\n  local app_lc=$(echo \"${app,,}\" | tr -d ' ')\n  local version_file=\"$HOME/.${app_lc}\"\n\n  local api_timeouts=(60 120 240)\n\n  local current_version=\"\"\n  [[ -f \"$version_file\" ]] && current_version=$(<\"$version_file\")\n\n  ensure_dependencies jq\n\n  local api_url=\"https://api.github.com/repos/$repo/releases\"\n  [[ \"$version\" != \"latest\" ]] && api_url=\"$api_url/tags/$version\" || api_url=\"$api_url/latest\"\n  local header=()\n  [[ -n \"${GITHUB_TOKEN:-}\" ]] && header=(-H \"Authorization: token $GITHUB_TOKEN\")\n\n  # dns pre check\n  local gh_host\n  gh_host=$(awk -F/ '{print $3}' <<<\"$api_url\")\n  if ! getent hosts \"$gh_host\" &>/dev/null; then\n    msg_error \"DNS resolution failed for $gh_host – check /etc/resolv.conf or networking\"\n    return 6\n  fi\n\n  local max_retries=${#api_timeouts[@]} retry_delay=2 attempt=1 success=false http_code\n\n  while ((attempt <= max_retries)); do\n    http_code=$(curl --connect-timeout 10 --max-time \"${api_timeouts[$((attempt - 1))]:-240}\" -sSL -w \"%{http_code}\" -o /tmp/gh_rel.json \"${header[@]}\" \"$api_url\" 2>/dev/null) || true\n    if [[ \"$http_code\" == \"200\" ]]; then\n      success=true\n      break\n    elif [[ \"$http_code\" == \"401\" ]]; then\n      msg_error \"GitHub API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITHUB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication.\"\n      fi\n      if prompt_for_github_token; then\n        header=(-H \"Authorization: token $GITHUB_TOKEN\")\n        continue\n      fi\n      break\n    elif [[ \"$http_code\" == \"403\" ]]; then\n      if ((attempt < max_retries)); then\n        msg_warn \"GitHub API rate limit hit, retrying in ${retry_delay}s... (attempt $attempt/$max_retries)\"\n        sleep \"$retry_delay\"\n        retry_delay=$((retry_delay * 2))\n      else\n        msg_error \"GitHub API rate limit exceeded (HTTP 403).\"\n        if prompt_for_github_token; then\n          header=(-H \"Authorization: token $GITHUB_TOKEN\")\n          retry_delay=2\n          attempt=1\n          continue\n        fi\n      fi\n    else\n      sleep \"$retry_delay\"\n    fi\n    ((attempt++))\n  done\n\n  if ! $success; then\n    if [[ \"$http_code\" == \"000\" || -z \"$http_code\" ]]; then\n      msg_error \"GitHub API connection failed (no response).\"\n      msg_error \"Check your network/DNS: curl -sSL https://api.github.com/rate_limit\"\n    elif [[ \"$http_code\" != \"401\" ]]; then\n      msg_error \"Failed to fetch release metadata (HTTP $http_code)\"\n    fi\n    return 22\n  fi\n\n  local json tag_name\n  json=$(</tmp/gh_rel.json)\n  tag_name=$(echo \"$json\" | jq -r '.tag_name // .name // empty')\n  # Only strip leading 'v' when followed by a digit (e.g. v1.2.3), not words like \"version/...\"\n  [[ \"$tag_name\" =~ ^v[0-9] ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n  # Sanitize version for use in filenames (replace / with -)\n  local version_safe=\"${version//\\//-}\"\n\n  if [[ \"$current_version\" == \"$version\" ]]; then\n    $STD msg_ok \"$app is already up-to-date (v$version)\"\n    return 0\n  fi\n\n  local tmpdir\n  tmpdir=$(mktemp -d) || return 1\n  local filename=\"\" url=\"\"\n\n  msg_info \"Fetching GitHub release: $app ($version)\"\n\n  local clean_install=false\n  [[ -n \"${CLEAN_INSTALL:-}\" && \"$CLEAN_INSTALL\" == \"1\" ]] && clean_install=true\n\n  ### Tarball Mode ###\n  if [[ \"$mode\" == \"tarball\" || \"$mode\" == \"source\" ]]; then\n    # GitHub API's tarball_url/zipball_url can return HTTP 300 Multiple Choices\n    # when a branch and tag share the same name. Use explicit refs/tags/ URL instead.\n    local direct_tarball_url=\"https://github.com/$repo/archive/refs/tags/$tag_name.tar.gz\"\n    filename=\"${app_lc}-${version_safe}.tar.gz\"\n\n    curl_download \"$tmpdir/$filename\" \"$direct_tarball_url\" || {\n      msg_error \"Download failed: $direct_tarball_url\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    tar --no-same-owner -xzf \"$tmpdir/$filename\" -C \"$tmpdir\" || {\n      msg_error \"Failed to extract tarball\"\n      rm -rf \"$tmpdir\"\n      return 251\n    }\n    local unpack_dir\n    unpack_dir=$(find \"$tmpdir\" -mindepth 1 -maxdepth 1 -type d | head -n1)\n\n    shopt -s dotglob nullglob\n    cp -r \"$unpack_dir\"/* \"$target/\"\n    shopt -u dotglob nullglob\n\n    ### Binary Mode ###\n  elif [[ \"$mode\" == \"binary\" ]]; then\n    local arch\n    arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n    [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n    [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n\n    local assets url_match=\"\"\n    assets=$(echo \"$json\" | jq -r '.assets[].browser_download_url')\n\n    # If explicit filename pattern is provided (param $6), match that first\n    if [[ -n \"$asset_pattern\" ]]; then\n      for u in $assets; do\n        case \"${u##*/}\" in\n        $asset_pattern)\n          url_match=\"$u\"\n          break\n          ;;\n        esac\n      done\n    fi\n\n    # If no match via explicit pattern, fall back to architecture heuristic\n    if [[ -z \"$url_match\" ]]; then\n      for u in $assets; do\n        if [[ \"$u\" =~ ($arch|aarch64|arm64).*\\.deb$ ]]; then\n          url_match=\"$u\"\n          break\n        fi\n      done\n    fi\n\n    # Fallback: any .deb file\n    if [[ -z \"$url_match\" ]]; then\n      for u in $assets; do\n        [[ \"$u\" =~ \\.deb$ ]] && url_match=\"$u\" && break\n      done\n    fi\n\n    # Fallback: scan older releases for a matching .deb asset\n    if [[ -z \"$url_match\" ]]; then\n      local fallback_json\n      if fallback_json=$(_gh_scan_older_releases \"$repo\" \"binary\" \"$asset_pattern\" \"$tag_name\"); then\n        json=\"$fallback_json\"\n        tag_name=$(echo \"$json\" | jq -r '.tag_name // .name // empty')\n        [[ \"$tag_name\" =~ ^v ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n        msg_info \"Fetching GitHub release: $app ($version)\"\n        assets=$(echo \"$json\" | jq -r '.assets[].browser_download_url')\n        if [[ -n \"$asset_pattern\" ]]; then\n          for u in $assets; do\n            case \"${u##*/}\" in $asset_pattern)\n              url_match=\"$u\"\n              break\n              ;;\n            esac\n          done\n        fi\n        if [[ -z \"$url_match\" ]]; then\n          for u in $assets; do\n            if [[ \"$u\" =~ ($arch|amd64|x86_64|aarch64|arm64).*\\.deb$ ]]; then\n              url_match=\"$u\"\n              break\n            fi\n          done\n        fi\n        if [[ -z \"$url_match\" ]]; then\n          for u in $assets; do\n            [[ \"$u\" =~ \\.deb$ ]] && url_match=\"$u\" && break\n          done\n        fi\n      fi\n    fi\n\n    if [[ -z \"$url_match\" ]]; then\n      msg_error \"No suitable .deb asset found for $app\"\n      rm -rf \"$tmpdir\"\n      return 252\n    fi\n\n    filename=\"${url_match##*/}\"\n    curl_download \"$tmpdir/$filename\" \"$url_match\" || {\n      msg_error \"Download failed: $url_match\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    chmod 644 \"$tmpdir/$filename\"\n    # SYSTEMD_OFFLINE=1 prevents systemd-tmpfiles failures in unprivileged LXC (Debian 13+/systemd 257+)\n    # Support DPKG_CONFOLD/DPKG_CONFNEW env vars for config file handling during .deb upgrades\n    local dpkg_opts=\"\"\n    [[ \"${DPKG_FORCE_CONFOLD:-}\" == \"1\" ]] && dpkg_opts=\"-o Dpkg::Options::=--force-confold\"\n    [[ \"${DPKG_FORCE_CONFNEW:-}\" == \"1\" ]] && dpkg_opts=\"-o Dpkg::Options::=--force-confnew\"\n    DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts \"$tmpdir/$filename\" || {\n      SYSTEMD_OFFLINE=1 $STD dpkg -i \"$tmpdir/$filename\" || {\n        msg_error \"Both apt and dpkg installation failed\"\n        rm -rf \"$tmpdir\"\n        return 100\n      }\n    }\n\n    ### Prebuild Mode ###\n  elif [[ \"$mode\" == \"prebuild\" ]]; then\n    local pattern=\"${6%\\\"}\"\n    pattern=\"${pattern#\\\"}\"\n    [[ -z \"$pattern\" ]] && {\n      msg_error \"Mode 'prebuild' requires 6th parameter (asset filename pattern)\"\n      rm -rf \"$tmpdir\"\n      return 65\n    }\n\n    local asset_url=\"\"\n    for u in $(echo \"$json\" | jq -r '.assets[].browser_download_url'); do\n      filename_candidate=\"${u##*/}\"\n      case \"$filename_candidate\" in\n      $pattern)\n        asset_url=\"$u\"\n        break\n        ;;\n      esac\n    done\n\n    # Fallback: scan older releases for a matching asset\n    if [[ -z \"$asset_url\" ]]; then\n      local fallback_json\n      if fallback_json=$(_gh_scan_older_releases \"$repo\" \"prebuild\" \"$pattern\" \"$tag_name\"); then\n        json=\"$fallback_json\"\n        tag_name=$(echo \"$json\" | jq -r '.tag_name // .name // empty')\n        [[ \"$tag_name\" =~ ^v ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n        msg_info \"Fetching GitHub release: $app ($version)\"\n        for u in $(echo \"$json\" | jq -r '.assets[].browser_download_url'); do\n          filename_candidate=\"${u##*/}\"\n          case \"$filename_candidate\" in $pattern)\n            asset_url=\"$u\"\n            break\n            ;;\n          esac\n        done\n      fi\n    fi\n\n    [[ -z \"$asset_url\" ]] && {\n      msg_error \"No asset matching '$pattern' found\"\n      rm -rf \"$tmpdir\"\n      return 252\n    }\n\n    filename=\"${asset_url##*/}\"\n    curl_download \"$tmpdir/$filename\" \"$asset_url\" || {\n      msg_error \"Download failed: $asset_url\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    local unpack_tmp\n    unpack_tmp=$(mktemp -d)\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    if [[ \"$filename\" == *.zip ]]; then\n      ensure_dependencies unzip\n      unzip -q \"$tmpdir/$filename\" -d \"$unpack_tmp\" || {\n        msg_error \"Failed to extract ZIP archive\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 251\n      }\n    elif [[ \"$filename\" == *.tar.* || \"$filename\" == *.tgz || \"$filename\" == *.txz ]]; then\n      tar --no-same-owner -xf \"$tmpdir/$filename\" -C \"$unpack_tmp\" || {\n        msg_error \"Failed to extract TAR archive\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 251\n      }\n    else\n      msg_error \"Unsupported archive format: $filename\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 65\n    fi\n\n    local top_dirs\n    top_dirs=$(find \"$unpack_tmp\" -mindepth 1 -maxdepth 1 -type d | wc -l)\n    local top_entries inner_dir\n    top_entries=$(find \"$unpack_tmp\" -mindepth 1 -maxdepth 1)\n    if [[ \"$(echo \"$top_entries\" | wc -l)\" -eq 1 && -d \"$top_entries\" ]]; then\n      # Strip leading folder\n      inner_dir=\"$top_entries\"\n      shopt -s dotglob nullglob\n      if compgen -G \"$inner_dir/*\" >/dev/null; then\n        cp -r \"$inner_dir\"/* \"$target/\" || {\n          msg_error \"Failed to copy contents from $inner_dir to $target\"\n          rm -rf \"$tmpdir\" \"$unpack_tmp\"\n          return 252\n        }\n      else\n        msg_error \"Inner directory is empty: $inner_dir\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 252\n      fi\n      shopt -u dotglob nullglob\n    else\n      # Copy all contents\n      shopt -s dotglob nullglob\n      if compgen -G \"$unpack_tmp/*\" >/dev/null; then\n        cp -r \"$unpack_tmp\"/* \"$target/\" || {\n          msg_error \"Failed to copy contents to $target\"\n          rm -rf \"$tmpdir\" \"$unpack_tmp\"\n          return 252\n        }\n      else\n        msg_error \"Unpacked archive is empty\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 252\n      fi\n      shopt -u dotglob nullglob\n    fi\n\n    ### Singlefile Mode ###\n  elif [[ \"$mode\" == \"singlefile\" ]]; then\n    local pattern=\"${6%\\\"}\"\n    pattern=\"${pattern#\\\"}\"\n    [[ -z \"$pattern\" ]] && {\n      msg_error \"Mode 'singlefile' requires 6th parameter (asset filename pattern)\"\n      rm -rf \"$tmpdir\"\n      return 65\n    }\n\n    local asset_url=\"\"\n    for u in $(echo \"$json\" | jq -r '.assets[].browser_download_url'); do\n      filename_candidate=\"${u##*/}\"\n      case \"$filename_candidate\" in\n      $pattern)\n        asset_url=\"$u\"\n        break\n        ;;\n      esac\n    done\n\n    # Fallback: scan older releases for a matching asset\n    if [[ -z \"$asset_url\" ]]; then\n      local fallback_json\n      if fallback_json=$(_gh_scan_older_releases \"$repo\" \"singlefile\" \"$pattern\" \"$tag_name\"); then\n        json=\"$fallback_json\"\n        tag_name=$(echo \"$json\" | jq -r '.tag_name // .name // empty')\n        [[ \"$tag_name\" =~ ^v ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n        msg_info \"Fetching GitHub release: $app ($version)\"\n        for u in $(echo \"$json\" | jq -r '.assets[].browser_download_url'); do\n          filename_candidate=\"${u##*/}\"\n          case \"$filename_candidate\" in $pattern)\n            asset_url=\"$u\"\n            break\n            ;;\n          esac\n        done\n      fi\n    fi\n    [[ -z \"$asset_url\" ]] && {\n      msg_error \"No asset matching '$pattern' found\"\n      rm -rf \"$tmpdir\"\n      return 252\n    }\n\n    filename=\"${asset_url##*/}\"\n    mkdir -p \"$target\"\n\n    local use_filename=\"${USE_ORIGINAL_FILENAME:-false}\"\n    local target_file=\"$app\"\n    [[ \"$use_filename\" == \"true\" ]] && target_file=\"$filename\"\n\n    curl_download \"$target/$target_file\" \"$asset_url\" || {\n      msg_error \"Download failed: $asset_url\"\n      rm -rf \"$tmpdir\"\n      return 250\n    }\n\n    if [[ \"$target_file\" != *.jar && -f \"$target/$target_file\" ]]; then\n      chmod +x \"$target/$target_file\"\n    fi\n\n  else\n    msg_error \"Unknown mode: $mode\"\n    rm -rf \"$tmpdir\"\n    return 65\n  fi\n\n  echo \"$version\" >\"$version_file\"\n  msg_ok \"Deployed: $app ($version)\"\n  rm -rf \"$tmpdir\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs Adminer (Debian/Ubuntu via APT, Alpine via direct download).\n#\n# Description:\n#   - Adds Adminer to Apache or web root\n#   - Supports Alpine and Debian-based systems\n# ------------------------------------------------------------------------------\n\nfunction setup_adminer() {\n  if grep -qi alpine /etc/os-release; then\n    msg_info \"Setup Adminer (Alpine)\"\n    mkdir -p /var/www/localhost/htdocs/adminer\n    if ! curl_with_retry \"https://github.com/vrana/adminer/releases/latest/download/adminer.php\" \"/var/www/localhost/htdocs/adminer/index.php\"; then\n      msg_error \"Failed to download Adminer\"\n      return 250\n    fi\n    cache_installed_version \"adminer\" \"latest-alpine\"\n    msg_ok \"Setup Adminer (Alpine)\"\n  else\n    msg_info \"Setup Adminer (Debian/Ubuntu)\"\n    ensure_dependencies adminer\n    $STD a2enconf adminer || {\n      msg_error \"Failed to enable Adminer Apache config\"\n      return 150\n    }\n    $STD systemctl reload apache2 || {\n      msg_error \"Failed to reload Apache\"\n      return 150\n    }\n    local VERSION\n    VERSION=$(dpkg -s adminer 2>/dev/null | grep '^Version:' | awk '{print $2}' 2>/dev/null || echo 'unknown')\n    cache_installed_version \"adminer\" \"${VERSION:-unknown}\"\n    msg_ok \"Setup Adminer (Debian/Ubuntu)\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Installs or updates Composer globally (robust, idempotent).\n#\n# - Installs to /usr/local/bin/composer\n# - Removes old binaries/symlinks in /usr/bin, /bin, /root/.composer, etc.\n# - Ensures /usr/local/bin is in PATH (permanent)\n# - Auto-updates to latest version\n# ------------------------------------------------------------------------------\n\nfunction setup_composer() {\n  local COMPOSER_BIN=\"/usr/local/bin/composer\"\n  export COMPOSER_ALLOW_SUPERUSER=1\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  if [[ -x \"$COMPOSER_BIN\" ]]; then\n    INSTALLED_VERSION=$(\"$COMPOSER_BIN\" --version 2>/dev/null | awk '{print $3}')\n  fi\n\n  # Scenario 1: Already installed - just self-update\n  if [[ -n \"$INSTALLED_VERSION\" ]]; then\n    msg_info \"Update Composer $INSTALLED_VERSION\"\n    $STD \"$COMPOSER_BIN\" self-update --no-interaction || {\n      msg_warn \"Composer self-update failed, continuing with current version\"\n    }\n    local UPDATED_VERSION\n    UPDATED_VERSION=$(\"$COMPOSER_BIN\" --version 2>/dev/null | awk '{print $3}')\n    cache_installed_version \"composer\" \"$UPDATED_VERSION\"\n    msg_ok \"Update Composer $UPDATED_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Fresh install\n  msg_info \"Setup Composer\"\n\n  for old in /usr/bin/composer /bin/composer /root/.composer/vendor/bin/composer; do\n    [[ -e \"$old\" && \"$old\" != \"$COMPOSER_BIN\" ]] && rm -f \"$old\"\n  done\n\n  ensure_usr_local_bin_persist\n  export PATH=\"/usr/local/bin:$PATH\"\n\n  if ! curl_with_retry \"https://getcomposer.org/installer\" \"/tmp/composer-setup.php\"; then\n    msg_error \"Failed to download Composer installer\"\n    return 250\n  fi\n\n  $STD php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer || {\n    msg_error \"Failed to install Composer\"\n    rm -f /tmp/composer-setup.php\n    return 150\n  }\n  rm -f /tmp/composer-setup.php\n\n  if [[ ! -x \"$COMPOSER_BIN\" ]]; then\n    msg_error \"Composer installation failed\"\n    return 127\n  fi\n\n  chmod +x \"$COMPOSER_BIN\"\n  $STD \"$COMPOSER_BIN\" self-update --no-interaction || {\n    msg_warn \"Composer self-update failed after fresh install\"\n  }\n\n  local FINAL_VERSION\n  FINAL_VERSION=$(\"$COMPOSER_BIN\" --version 2>/dev/null | awk '{print $3}')\n  cache_installed_version \"composer\" \"$FINAL_VERSION\"\n  msg_ok \"Setup Composer\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs FFmpeg from source or prebuilt binary (Debian/Ubuntu only).\n#\n# Description:\n#   - Downloads and builds FFmpeg from GitHub (https://github.com/FFmpeg/FFmpeg)\n#   - Supports specific version override via FFMPEG_VERSION (e.g. n7.1.1)\n#   - Supports build profile via FFMPEG_TYPE:\n#       - minimal : x264, vpx, mp3 only\n#       - medium  : adds subtitles, fonts, opus, vorbis\n#       - full    : adds dav1d, svt-av1, zlib, numa\n#       - binary  : downloads static build (johnvansickle.com)\n#   - Defaults to latest stable version and full feature set\n#\n# Notes:\n#   - Requires: curl, jq, build-essential, and matching codec libraries\n#   - Result is installed to /usr/local/bin/ffmpeg\n# ------------------------------------------------------------------------------\n\nfunction setup_ffmpeg() {\n  local TMP_DIR=$(mktemp -d)\n  local GITHUB_REPO=\"FFmpeg/FFmpeg\"\n  local VERSION=\"${FFMPEG_VERSION:-latest}\"\n  local TYPE=\"${FFMPEG_TYPE:-full}\"\n  local BIN_PATH=\"/usr/local/bin/ffmpeg\"\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  if command -v ffmpeg &>/dev/null; then\n    INSTALLED_VERSION=$(ffmpeg -version 2>/dev/null | head -n1 | awk '{print $3}')\n  fi\n\n  msg_info \"Setup FFmpeg ${VERSION} ($TYPE)\"\n\n  # Binary fallback mode\n  if [[ \"$TYPE\" == \"binary\" ]]; then\n    if ! CURL_TIMEOUT=300 curl_with_retry \"https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz\" \"$TMP_DIR/ffmpeg.tar.xz\"; then\n      msg_error \"Failed to download FFmpeg binary\"\n      rm -rf \"$TMP_DIR\"\n      return 250\n    fi\n    tar -xf \"$TMP_DIR/ffmpeg.tar.xz\" -C \"$TMP_DIR\" || {\n      msg_error \"Failed to extract FFmpeg binary\"\n      rm -rf \"$TMP_DIR\"\n      return 251\n    }\n    local EXTRACTED_DIR\n    EXTRACTED_DIR=$(find \"$TMP_DIR\" -maxdepth 1 -type d -name \"ffmpeg-*\")\n    cp \"$EXTRACTED_DIR/ffmpeg\" \"$BIN_PATH\"\n    cp \"$EXTRACTED_DIR/ffprobe\" /usr/local/bin/ffprobe\n    chmod +x \"$BIN_PATH\" /usr/local/bin/ffprobe\n    local FINAL_VERSION=$($BIN_PATH -version 2>/dev/null | head -n1 | awk '{print $3}')\n    rm -rf \"$TMP_DIR\"\n    cache_installed_version \"ffmpeg\" \"$FINAL_VERSION\"\n    ensure_usr_local_bin_persist\n    [[ -n \"$INSTALLED_VERSION\" ]] && msg_ok \"Upgrade FFmpeg $INSTALLED_VERSION → $FINAL_VERSION\" || msg_ok \"Setup FFmpeg $FINAL_VERSION\"\n    return 0\n  fi\n\n  ensure_dependencies jq\n\n  # Auto-detect latest stable version if none specified\n  if [[ \"$VERSION\" == \"latest\" || -z \"$VERSION\" ]]; then\n    local ffmpeg_tags\n    ffmpeg_tags=$(curl -fsSL --max-time 15 \"https://api.github.com/repos/${GITHUB_REPO}/tags\" 2>/dev/null || echo \"\")\n\n    if [[ -z \"$ffmpeg_tags\" ]]; then\n      msg_warn \"Could not fetch FFmpeg versions from GitHub, trying binary fallback\"\n      VERSION=\"\" # Will trigger binary fallback below\n    else\n      VERSION=$(echo \"$ffmpeg_tags\" | jq -r '.[].name' 2>/dev/null |\n        grep -E '^n[0-9]+\\.[0-9]+\\.[0-9]+$' |\n        sort -V | tail -n1 || echo \"\")\n    fi\n  fi\n\n  if [[ -z \"$VERSION\" ]]; then\n    msg_info \"Could not determine FFmpeg source version, using pre-built binary\"\n    VERSION=\"\" # Will use binary fallback\n  fi\n\n  # Dependency selection\n  local DEPS=(build-essential yasm nasm pkg-config)\n  case \"$TYPE\" in\n  minimal)\n    DEPS+=(libx264-dev libvpx-dev libmp3lame-dev)\n    ;;\n  medium)\n    DEPS+=(libx264-dev libvpx-dev libmp3lame-dev libfreetype6-dev libass-dev libopus-dev libvorbis-dev)\n    ;;\n  full)\n    DEPS+=(\n      libx264-dev libx265-dev libvpx-dev libmp3lame-dev\n      libfreetype6-dev libass-dev libopus-dev libvorbis-dev\n      libdav1d-dev zlib1g-dev libnuma-dev\n      libva-dev libdrm-dev\n    )\n    if apt-cache show libsvtav1enc-dev &>/dev/null; then\n      DEPS+=(libsvtav1enc-dev)\n    elif apt-cache show libsvtav1-dev &>/dev/null; then\n      DEPS+=(libsvtav1-dev)\n    fi\n    ;;\n  *)\n    msg_error \"Invalid FFMPEG_TYPE: $TYPE\"\n    rm -rf \"$TMP_DIR\"\n    return 65\n    ;;\n  esac\n\n  ensure_dependencies \"${DEPS[@]}\"\n\n  # Try to download source if VERSION is set\n  if [[ -n \"$VERSION\" ]]; then\n    if ! CURL_TIMEOUT=300 curl_with_retry \"https://github.com/${GITHUB_REPO}/archive/refs/tags/${VERSION}.tar.gz\" \"$TMP_DIR/ffmpeg.tar.gz\"; then\n      msg_warn \"Failed to download FFmpeg source ${VERSION}, falling back to pre-built binary\"\n      VERSION=\"\"\n    fi\n  fi\n\n  # If no source download (either VERSION empty or download failed), use binary\n  if [[ -z \"$VERSION\" ]]; then\n    msg_info \"Setup FFmpeg from pre-built binary\"\n    if ! CURL_TIMEOUT=300 curl_with_retry \"https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz\" \"$TMP_DIR/ffmpeg.tar.xz\"; then\n      msg_error \"Failed to download FFmpeg pre-built binary\"\n      rm -rf \"$TMP_DIR\"\n      return 250\n    fi\n\n    tar -xJf \"$TMP_DIR/ffmpeg.tar.xz\" -C \"$TMP_DIR\" || {\n      msg_error \"Failed to extract FFmpeg binary archive\"\n      rm -rf \"$TMP_DIR\"\n      return 251\n    }\n\n    if ! cp \"$TMP_DIR/ffmpeg-\"*/ffmpeg /usr/local/bin/ffmpeg 2>/dev/null; then\n      msg_error \"Failed to install FFmpeg binary\"\n      rm -rf \"$TMP_DIR\"\n      return 150\n    fi\n\n    cache_installed_version \"ffmpeg\" \"static\"\n    rm -rf \"$TMP_DIR\"\n    msg_ok \"Setup FFmpeg from pre-built binary\"\n    return 0\n  fi\n\n  tar -xzf \"$TMP_DIR/ffmpeg.tar.gz\" -C \"$TMP_DIR\" || {\n    msg_error \"Failed to extract FFmpeg source\"\n    rm -rf \"$TMP_DIR\"\n    return 251\n  }\n\n  cd \"$TMP_DIR/FFmpeg-\"* || {\n    msg_error \"Source extraction failed\"\n    rm -rf \"$TMP_DIR\"\n    return 251\n  }\n\n  local args=(\n    --enable-gpl\n    --enable-shared\n    --enable-nonfree\n    --disable-static\n    --enable-libx264\n    --enable-libvpx\n    --enable-libmp3lame\n  )\n\n  if [[ \"$TYPE\" != \"minimal\" ]]; then\n    args+=(--enable-libfreetype --enable-libass --enable-libopus --enable-libvorbis)\n  fi\n\n  if [[ \"$TYPE\" == \"full\" ]]; then\n    args+=(--enable-libx265 --enable-libdav1d --enable-zlib)\n    args+=(--enable-vaapi --enable-libdrm)\n  fi\n\n  if [[ ${#args[@]} -eq 0 ]]; then\n    msg_error \"FFmpeg configure args array is empty\"\n    rm -rf \"$TMP_DIR\"\n    return 65\n  fi\n\n  $STD ./configure \"${args[@]}\" || {\n    msg_error \"FFmpeg configure failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD make -j\"$(nproc)\" || {\n    msg_error \"FFmpeg compilation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD make install || {\n    msg_error \"FFmpeg installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  echo \"/usr/local/lib\" >/etc/ld.so.conf.d/ffmpeg.conf\n  $STD ldconfig\n\n  ldconfig -p 2>/dev/null | grep libavdevice >/dev/null || {\n    msg_error \"libavdevice not registered with dynamic linker\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n\n  if ! command -v ffmpeg &>/dev/null; then\n    msg_error \"FFmpeg installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  fi\n\n  local FINAL_VERSION\n  FINAL_VERSION=$(ffmpeg -version 2>/dev/null | head -n1 | awk '{print $3}')\n  rm -rf \"$TMP_DIR\"\n  cache_installed_version \"ffmpeg\" \"$FINAL_VERSION\"\n  ensure_usr_local_bin_persist\n  [[ -n \"$INSTALLED_VERSION\" ]] && msg_ok \"Upgrade FFmpeg $INSTALLED_VERSION → $FINAL_VERSION\" || msg_ok \"Setup FFmpeg $FINAL_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs Go (Golang) from official tarball.\n#\n# Description:\n#   - Determines system architecture\n#   - Downloads latest version if GO_VERSION not set\n#\n# Variables:\n#   GO_VERSION     - Version to install (e.g. 1.22.2 or latest)\n# ------------------------------------------------------------------------------\n\nfunction setup_go() {\n  local ARCH\n  case \"$(uname -m)\" in\n  x86_64) ARCH=\"amd64\" ;;\n  aarch64) ARCH=\"arm64\" ;;\n  *)\n    msg_error \"Unsupported architecture: $(uname -m)\"\n    return 236\n    ;;\n  esac\n\n  # Resolve \"latest\" version\n  local GO_VERSION=\"${GO_VERSION:-latest}\"\n  if [[ \"$GO_VERSION\" == \"latest\" ]]; then\n    local go_version_tmp\n    go_version_tmp=$(curl_with_retry \"https://go.dev/VERSION?m=text\" \"-\" 2>/dev/null | head -n1 | sed 's/^go//') || true\n    if [[ -z \"$go_version_tmp\" ]]; then\n      msg_error \"Could not determine latest Go version\"\n      return 250\n    fi\n    GO_VERSION=\"$go_version_tmp\"\n  fi\n\n  local GO_BIN=\"/usr/local/bin/go\"\n  local GO_INSTALL_DIR=\"/usr/local/go\"\n\n  # Get currently installed version\n  local CURRENT_VERSION=\"\"\n  if [[ -x \"$GO_BIN\" ]]; then\n    CURRENT_VERSION=$(\"$GO_BIN\" version 2>/dev/null | awk '{print $3}' | sed 's/go//')\n  fi\n\n  # Scenario 1: Already at target version\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" == \"$GO_VERSION\" ]]; then\n    cache_installed_version \"go\" \"$GO_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Different version or not installed\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" != \"$GO_VERSION\" ]]; then\n    msg_info \"Upgrade Go from $CURRENT_VERSION to $GO_VERSION\"\n    remove_old_tool_version \"go\"\n  else\n    msg_info \"Setup Go $GO_VERSION\"\n  fi\n\n  local TARBALL=\"go${GO_VERSION}.linux-${ARCH}.tar.gz\"\n  local URL=\"https://go.dev/dl/${TARBALL}\"\n  local TMP_TAR=$(mktemp)\n\n  if ! CURL_TIMEOUT=300 curl_with_retry \"$URL\" \"$TMP_TAR\"; then\n    msg_error \"Failed to download Go $GO_VERSION\"\n    rm -f \"$TMP_TAR\"\n    return 250\n  fi\n\n  $STD tar -C /usr/local -xzf \"$TMP_TAR\" || {\n    msg_error \"Failed to extract Go tarball\"\n    rm -f \"$TMP_TAR\"\n    return 251\n  }\n\n  ln -sf /usr/local/go/bin/go /usr/local/bin/go\n  ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt\n  rm -f \"$TMP_TAR\"\n\n  cache_installed_version \"go\" \"$GO_VERSION\"\n  ensure_usr_local_bin_persist\n  msg_ok \"Setup Go $GO_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs or updates Ghostscript (gs) from source.\n#\n# Description:\n#   - Fetches latest release\n#   - Builds and installs system-wide\n# ------------------------------------------------------------------------------\n\nfunction setup_gs() {\n  local TMP_DIR=$(mktemp -d)\n  local CURRENT_VERSION=$(gs --version 2>/dev/null || echo \"0\")\n\n  ensure_dependencies jq\n\n  local RELEASE_JSON\n  RELEASE_JSON=$(curl -fsSL --max-time 15 https://api.github.com/repos/ArtifexSoftware/ghostpdl-downloads/releases/latest 2>/dev/null || echo \"\")\n\n  if [[ -z \"$RELEASE_JSON\" ]]; then\n    msg_warn \"Cannot fetch latest Ghostscript version from GitHub API\"\n    # Try to get from current version\n    if command -v gs &>/dev/null; then\n      gs --version | head -n1\n      cache_installed_version \"ghostscript\" \"$CURRENT_VERSION\"\n      return 0\n    fi\n    msg_error \"Cannot determine Ghostscript version and no existing installation found\"\n    return 250\n  fi\n  local LATEST_VERSION\n  LATEST_VERSION=$(echo \"$RELEASE_JSON\" | jq -r '.tag_name' | sed 's/^gs//')\n  local LATEST_VERSION_DOTTED\n  LATEST_VERSION_DOTTED=$(echo \"$RELEASE_JSON\" | jq -r '.name' | grep -o '[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+')\n\n  if [[ -z \"$LATEST_VERSION\" || -z \"$LATEST_VERSION_DOTTED\" ]]; then\n    msg_warn \"Could not determine latest Ghostscript version from GitHub - checking system\"\n    # Fallback: try to use system version or return error\n    if [[ \"$CURRENT_VERSION\" == \"0\" ]]; then\n      msg_error \"Ghostscript not installed and cannot determine latest version\"\n      rm -rf \"$TMP_DIR\"\n      return 250\n    fi\n    rm -rf \"$TMP_DIR\"\n    return 0\n  fi\n\n  # Scenario 1: Already at latest version\n  if [[ -n \"$LATEST_VERSION_DOTTED\" ]] && dpkg --compare-versions \"$CURRENT_VERSION\" ge \"$LATEST_VERSION_DOTTED\" 2>/dev/null; then\n    cache_installed_version \"ghostscript\" \"$LATEST_VERSION_DOTTED\"\n    rm -rf \"$TMP_DIR\"\n    return 0\n  fi\n\n  # Scenario 2: New install or upgrade\n  if [[ \"$CURRENT_VERSION\" != \"0\" && \"$CURRENT_VERSION\" != \"$LATEST_VERSION_DOTTED\" ]]; then\n    msg_info \"Upgrade Ghostscript from $CURRENT_VERSION to $LATEST_VERSION_DOTTED\"\n  else\n    msg_info \"Setup Ghostscript $LATEST_VERSION_DOTTED\"\n  fi\n\n  if ! CURL_TIMEOUT=180 curl_with_retry \"https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs${LATEST_VERSION}/ghostscript-${LATEST_VERSION_DOTTED}.tar.gz\" \"$TMP_DIR/ghostscript.tar.gz\"; then\n    msg_error \"Failed to download Ghostscript\"\n    rm -rf \"$TMP_DIR\"\n    return 250\n  fi\n\n  if ! tar -xzf \"$TMP_DIR/ghostscript.tar.gz\" -C \"$TMP_DIR\"; then\n    msg_error \"Failed to extract Ghostscript archive\"\n    rm -rf \"$TMP_DIR\"\n    return 251\n  fi\n\n  # Verify directory exists before cd\n  if [[ ! -d \"$TMP_DIR/ghostscript-${LATEST_VERSION_DOTTED}\" ]]; then\n    msg_error \"Ghostscript source directory not found: $TMP_DIR/ghostscript-${LATEST_VERSION_DOTTED}\"\n    rm -rf \"$TMP_DIR\"\n    return 252\n  fi\n\n  cd \"$TMP_DIR/ghostscript-${LATEST_VERSION_DOTTED}\" || {\n    msg_error \"Failed to enter Ghostscript source directory\"\n    rm -rf \"$TMP_DIR\"\n    return 252\n  }\n\n  ensure_dependencies build-essential libpng-dev zlib1g-dev\n\n  $STD ./configure || {\n    msg_error \"Ghostscript configure failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD make -j\"$(nproc)\" || {\n    msg_error \"Ghostscript compilation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD make install || {\n    msg_error \"Ghostscript installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n\n  hash -r\n  if [[ ! -x \"$(command -v gs)\" ]]; then\n    if [[ -x /usr/local/bin/gs ]]; then\n      ln -sf /usr/local/bin/gs /usr/bin/gs\n    fi\n  fi\n\n  rm -rf \"$TMP_DIR\"\n  cache_installed_version \"ghostscript\" \"$LATEST_VERSION_DOTTED\"\n  ensure_usr_local_bin_persist\n  msg_ok \"Setup Ghostscript $LATEST_VERSION_DOTTED\"\n}\n\n# ------------------------------------------------------------------------------\n# Sets up Hardware Acceleration on debian or ubuntu.\n#\n# Description:\n#   - Detects all available GPUs (Intel, AMD, NVIDIA)\n#   - Allows user to select which GPU(s) to configure (with 60s timeout)\n#   - Installs the correct libraries and packages for each GPU type\n#   - Supports: Debian 11/12/13, Ubuntu 22.04/24.04\n#   - Intel: Legacy (Gen 6-8), Modern (Gen 9+), Arc\n#   - AMD: Discrete GPUs, APUs, ROCm compute\n#   - NVIDIA: Version-matched drivers from CUDA repository\n#\n# Notes:\n#   - Some Intel packages are fetched from GitHub due to missing Debian packages\n#   - NVIDIA requires matching host driver version\n# ------------------------------------------------------------------------------\nfunction setup_hwaccel() {\n  local service_user=\"${1:-}\"\n\n  # Check if user explicitly disabled GPU in advanced settings\n  # ENABLE_GPU is exported from build.func\n  if [[ \"${ENABLE_GPU:-no}\" == \"no\" ]]; then\n    return 0\n  fi\n\n  # Check if GPU passthrough is enabled (device nodes must exist)\n  if [[ ! -d /dev/dri && ! -e /dev/nvidia0 && ! -e /dev/kfd ]]; then\n    msg_warn \"No GPU passthrough detected (/dev/dri, /dev/nvidia*, /dev/kfd not found) - skipping hardware acceleration setup\"\n    return 0\n  fi\n\n  msg_info \"Setup Hardware Acceleration\"\n\n  # Install pciutils if needed\n  if ! command -v lspci &>/dev/null; then\n    $STD apt -y update || {\n      msg_warn \"Failed to update package list\"\n      return 0\n    }\n    $STD apt -y install pciutils || {\n      msg_warn \"Failed to install pciutils\"\n      return 0\n    }\n  fi\n\n  # ═══════════════════════════════════════════════════════════════════════════\n  # GPU Detection - Build list of all available GPUs with details\n  # ═══════════════════════════════════════════════════════════════════════════\n  local -a GPU_LIST=()\n  local -a GPU_TYPES=()\n  local -a GPU_NAMES=()\n  local gpu_count=0\n\n  # Get all GPU entries from lspci\n  while IFS= read -r line; do\n    [[ -z \"$line\" ]] && continue\n    local pci_addr gpu_name gpu_type=\"\"\n\n    pci_addr=$(echo \"$line\" | awk '{print $1}')\n    gpu_name=$(echo \"$line\" | sed 's/^[^ ]* [^:]*: //')\n\n    # Determine GPU type\n    # Note: Use -w (word boundary) for ATI to avoid matching \"CorporATIon\"\n    if echo \"$gpu_name\" | grep -qi 'Intel'; then\n      gpu_type=\"INTEL\"\n      # Subtype detection for Intel\n      # Order matters: Check Arc first, then Gen9+ (UHD/Iris/HD 5xx-6xx), then Legacy (HD 2xxx-5xxx)\n      # HD Graphics 530/630 = Gen 9 (Skylake/Kaby Lake) - 3 digits\n      # HD Graphics 4600/5500 = Gen 7-8 (Haswell/Broadwell) - 4 digits starting with 2-5\n      if echo \"$gpu_name\" | grep -qiE 'Arc|DG[12]'; then\n        gpu_type=\"INTEL_ARC\"\n      elif echo \"$gpu_name\" | grep -qiE 'UHD|Iris|HD Graphics [5-6][0-9]{2}[^0-9]|HD Graphics [5-6][0-9]{2}$'; then\n        # HD Graphics 5xx/6xx (3 digits) = Gen 9+ (Skylake onwards)\n        gpu_type=\"INTEL_GEN9+\"\n      elif echo \"$gpu_name\" | grep -qiE 'HD Graphics [2-5][0-9]{3}'; then\n        # HD Graphics 2xxx-5xxx (4 digits) = Gen 6-8 Legacy\n        gpu_type=\"INTEL_LEGACY\"\n      fi\n    elif echo \"$gpu_name\" | grep -qiwE 'AMD|ATI|Radeon|Advanced Micro Devices'; then\n      gpu_type=\"AMD\"\n    elif echo \"$gpu_name\" | grep -qi 'NVIDIA'; then\n      gpu_type=\"NVIDIA\"\n    fi\n\n    if [[ -n \"$gpu_type\" ]]; then\n      GPU_LIST+=(\"$pci_addr\")\n      GPU_TYPES+=(\"$gpu_type\")\n      GPU_NAMES+=(\"$gpu_name\")\n      ((gpu_count++)) || true\n    fi\n  done < <(lspci 2>/dev/null | grep -Ei 'vga|3d|display')\n\n  # Check for AMD APU via CPU vendor if no discrete GPU found\n  local cpu_vendor\n  cpu_vendor=$(lscpu 2>/dev/null | grep -i 'Vendor ID' | awk '{print $3}' 2>/dev/null || echo \"\")\n\n  if [[ $gpu_count -eq 0 ]]; then\n    if [[ \"$cpu_vendor\" == \"AuthenticAMD\" ]]; then\n      GPU_LIST+=(\"integrated\")\n      GPU_TYPES+=(\"AMD_APU\")\n      GPU_NAMES+=(\"AMD APU (Integrated Graphics)\")\n      ((gpu_count++)) || true\n    else\n      msg_warn \"No GPU detected - skipping hardware acceleration setup\"\n      return 0\n    fi\n  fi\n\n  # ═══════════════════════════════════════════════════════════════════════════\n  # GPU Selection - Let user choose which GPU(s) to configure\n  # ═══════════════════════════════════════════════════════════════════════════\n  local -a SELECTED_INDICES=()\n  local install_nvidia_drivers=\"yes\"\n\n  if [[ $gpu_count -eq 1 ]]; then\n    # Single GPU - auto-select\n    SELECTED_INDICES=(0)\n    msg_ok \"Detected GPU: ${GPU_NAMES[0]} (${GPU_TYPES[0]})\"\n  else\n    # Multiple GPUs - show selection menu\n    echo \"\"\n    msg_custom \"⚠\" \"${YW}\" \"Multiple GPUs detected:\"\n    echo \"\"\n    for i in \"${!GPU_LIST[@]}\"; do\n      local type_display=\"${GPU_TYPES[$i]}\"\n      case \"${GPU_TYPES[$i]}\" in\n      INTEL_ARC) type_display=\"Intel Arc\" ;;\n      INTEL_GEN9+) type_display=\"Intel Gen9+\" ;;\n      INTEL_LEGACY) type_display=\"Intel Legacy\" ;;\n      INTEL) type_display=\"Intel\" ;;\n      AMD) type_display=\"AMD\" ;;\n      AMD_APU) type_display=\"AMD APU\" ;;\n      NVIDIA) type_display=\"NVIDIA\" ;;\n      esac\n      printf \"  %d) [%s] %s\\n\" \"$((i + 1))\" \"$type_display\" \"${GPU_NAMES[$i]}\"\n    done\n    printf \"  A) Configure ALL GPUs\\n\"\n    echo \"\"\n\n    # Read with 60 second timeout\n    local selection=\"\"\n    echo -n \"Select GPU(s) to configure (1-${gpu_count}, A=all) [timeout 60s, default=all]: \"\n    if read -r -t 60 selection; then\n      selection=\"${selection^^}\" # uppercase\n    else\n      echo \"\"\n      msg_info \"Timeout - configuring all GPUs automatically\"\n      selection=\"A\"\n    fi\n\n    # Parse selection\n    if [[ \"$selection\" == \"A\" || -z \"$selection\" ]]; then\n      # Select all\n      for i in \"${!GPU_LIST[@]}\"; do\n        SELECTED_INDICES+=(\"$i\")\n      done\n    elif [[ \"$selection\" =~ ^[0-9,]+$ ]]; then\n      # Parse comma-separated numbers\n      IFS=',' read -ra nums <<<\"$selection\"\n      for num in \"${nums[@]}\"; do\n        num=$(echo \"$num\" | tr -d ' ')\n        if [[ \"$num\" =~ ^[0-9]+$ ]] && ((num >= 1 && num <= gpu_count)); then\n          SELECTED_INDICES+=(\"$((num - 1))\")\n        fi\n      done\n    else\n      # Invalid - default to all\n      msg_warn \"Invalid selection - configuring all GPUs\"\n      for i in \"${!GPU_LIST[@]}\"; do\n        SELECTED_INDICES+=(\"$i\")\n      done\n    fi\n  fi\n\n  # Ask whether to install NVIDIA drivers in the container\n  local nvidia_selected=\"no\"\n  for idx in \"${SELECTED_INDICES[@]}\"; do\n    if [[ \"${GPU_TYPES[$idx]}\" == \"NVIDIA\" ]]; then\n      nvidia_selected=\"yes\"\n      break\n    fi\n  done\n\n  if [[ \"$nvidia_selected\" == \"yes\" ]]; then\n    if [[ -n \"${INSTALL_NVIDIA_DRIVERS:-}\" ]]; then\n      install_nvidia_drivers=\"${INSTALL_NVIDIA_DRIVERS}\"\n    else\n      echo \"\"\n      msg_custom \"🎮\" \"${GN}\" \"NVIDIA GPU passthrough detected\"\n      local nvidia_reply=\"\"\n      read -r -t 60 -p \"${TAB3}⚙️ Install NVIDIA driver libraries in the container? [Y/n] (auto-yes in 60s): \" nvidia_reply || nvidia_reply=\"\"\n      case \"${nvidia_reply,,}\" in\n      n | no) install_nvidia_drivers=\"no\" ;;\n      *) install_nvidia_drivers=\"yes\" ;;\n      esac\n    fi\n  fi\n\n  # ═══════════════════════════════════════════════════════════════════════════\n  # OS Detection\n  # ═══════════════════════════════════════════════════════════════════════════\n  local os_id os_codename os_version\n  os_id=$(grep -oP '(?<=^ID=).+' /etc/os-release 2>/dev/null | tr -d '\"' || echo \"debian\")\n  os_codename=$(grep -oP '(?<=^VERSION_CODENAME=).+' /etc/os-release 2>/dev/null | tr -d '\"' || echo \"unknown\")\n  os_version=$(grep -oP '(?<=^VERSION_ID=).+' /etc/os-release 2>/dev/null | tr -d '\"' || echo \"\")\n  [[ -z \"$os_id\" ]] && os_id=\"debian\"\n\n  local in_ct=\"${CTTYPE:-0}\"\n\n  # ═══════════════════════════════════════════════════════════════════════════\n  # Process Selected GPUs\n  # ═══════════════════════════════════════════════════════════════════════════\n  for idx in \"${SELECTED_INDICES[@]}\"; do\n    local gpu_type=\"${GPU_TYPES[$idx]}\"\n    local gpu_name=\"${GPU_NAMES[$idx]}\"\n\n    msg_info \"Configuring: ${gpu_name}\"\n\n    case \"$gpu_type\" in\n    # ─────────────────────────────────────────────────────────────────────────\n    # Intel Arc GPUs (DG1, DG2, Arc A-series)\n    # ─────────────────────────────────────────────────────────────────────────\n    INTEL_ARC)\n      _setup_intel_arc \"$os_id\" \"$os_codename\"\n      ;;\n\n    # ─────────────────────────────────────────────────────────────────────────\n    # Intel Gen 9+ (Skylake 2015+: UHD, Iris, HD 6xx+)\n    # ─────────────────────────────────────────────────────────────────────────\n    INTEL_GEN9+ | INTEL)\n      _setup_intel_modern \"$os_id\" \"$os_codename\"\n      ;;\n\n    # ─────────────────────────────────────────────────────────────────────────\n    # Intel Legacy (Gen 6-8: HD 2000-5999, Sandy Bridge to Broadwell)\n    # ─────────────────────────────────────────────────────────────────────────\n    INTEL_LEGACY)\n      _setup_intel_legacy \"$os_id\" \"$os_codename\"\n      ;;\n\n    # ─────────────────────────────────────────────────────────────────────────\n    # AMD Discrete GPUs\n    # ─────────────────────────────────────────────────────────────────────────\n    AMD)\n      _setup_amd_gpu \"$os_id\" \"$os_codename\"\n      ;;\n\n    # ─────────────────────────────────────────────────────────────────────────\n    # AMD APU (Integrated Graphics)\n    # ─────────────────────────────────────────────────────────────────────────\n    AMD_APU)\n      _setup_amd_apu \"$os_id\" \"$os_codename\"\n      ;;\n\n    # ─────────────────────────────────────────────────────────────────────────\n    # NVIDIA GPUs\n    # ─────────────────────────────────────────────────────────────────────────\n    NVIDIA)\n      if [[ \"$install_nvidia_drivers\" == \"yes\" ]]; then\n        _setup_nvidia_gpu \"$os_id\" \"$os_codename\" \"$os_version\"\n      else\n        msg_warn \"Skipping NVIDIA driver installation (user opted to install manually)\"\n      fi\n      ;;\n    esac\n  done\n\n  # ═══════════════════════════════════════════════════════════════════════════\n  # Device Permissions\n  # ═══════════════════════════════════════════════════════════════════════════\n  _setup_gpu_permissions \"$in_ct\" \"$service_user\"\n\n  cache_installed_version \"hwaccel\" \"1.0\"\n  msg_ok \"Setup Hardware Acceleration\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Resolve the IGC tag that the latest compute-runtime was built against.\n# Must be called AFTER a fetch_and_deploy_gh_release for intel/compute-runtime\n# so that /tmp/gh_rel.json contains the compute-runtime release metadata.\n# Sets the variable named by $1 (default: igc_tag) to the discovered tag.\n# ══════════════════════════════════════════════════════════════════════════════\n_resolve_igc_tag() {\n  local -n _out_ref=\"${1:-igc_tag}\"\n  _out_ref=\"latest\"\n  if [[ -f /tmp/gh_rel.json ]]; then\n    local _body _parsed\n    _body=$(jq -r '.body // empty' /tmp/gh_rel.json 2>/dev/null) || return 0\n    _parsed=$(grep -oP 'intel-graphics-compiler/releases/tag/\\K[^\\s\\)]+' <<<\"$_body\" | head -1)\n    [[ -n \"$_parsed\" ]] && _out_ref=\"$_parsed\"\n  fi\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Intel Arc GPU Setup\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_intel_arc() {\n  local os_id=\"$1\" os_codename=\"$2\"\n\n  msg_info \"Installing Intel Arc GPU drivers\"\n\n  if [[ \"$os_id\" == \"ubuntu\" ]]; then\n    # Ubuntu 22.04+ has Arc support in HWE kernel\n    $STD apt -y install \\\n      intel-media-va-driver-non-free \\\n      intel-opencl-icd \\\n      libmfx-gen1.2 \\\n      vainfo \\\n      intel-gpu-tools 2>/dev/null || msg_warn \"Some Intel Arc packages failed\"\n\n  elif [[ \"$os_id\" == \"debian\" ]]; then\n    # Add non-free repos\n    _add_debian_nonfree \"$os_codename\"\n\n    # For Trixie/Sid: Fetch latest drivers from GitHub (Debian repo packages may be too old or missing)\n    # For Bookworm: Use repo packages (GitHub latest requires libstdc++6 >= 13.1, unavailable on Bookworm)\n    if [[ \"$os_codename\" == \"trixie\" || \"$os_codename\" == \"sid\" ]]; then\n      msg_info \"Fetching Intel compute-runtime from GitHub for Arc support\"\n\n      # Fetch a compute-runtime package first so /tmp/gh_rel.json is populated,\n      # then resolve the matching IGC tag from the release notes.\n      # libigdgmm - bundled in compute-runtime releases\n      fetch_and_deploy_gh_release \"libigdgmm12\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"libigdgmm12_*_amd64.deb\" || true\n\n      local igc_tag\n      _resolve_igc_tag igc_tag\n\n      # Intel Graphics Compiler – pinned to the version compute-runtime expects\n      fetch_and_deploy_gh_release \"intel-igc-core\" \"intel/intel-graphics-compiler\" \"binary\" \"$igc_tag\" \"\" \"intel-igc-core-2_*_amd64.deb\" || true\n      fetch_and_deploy_gh_release \"intel-igc-opencl\" \"intel/intel-graphics-compiler\" \"binary\" \"$igc_tag\" \"\" \"intel-igc-opencl-2_*_amd64.deb\" || true\n\n      # Compute Runtime (depends on IGC and gmmlib)\n      fetch_and_deploy_gh_release \"intel-opencl-icd\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"intel-opencl-icd_*_amd64.deb\" || true\n      fetch_and_deploy_gh_release \"intel-level-zero-gpu\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"libze-intel-gpu1_*_amd64.deb\" || true\n    fi\n\n    $STD apt -y install \\\n      intel-media-va-driver-non-free \\\n      ocl-icd-libopencl1 \\\n      libvpl2 \\\n      libmfx-gen1.2 \\\n      vainfo \\\n      intel-gpu-tools 2>/dev/null || msg_warn \"Some Intel Arc packages failed\"\n\n    # Bookworm has compatible versions of these packages in repos\n    [[ \"$os_codename\" == \"bookworm\" ]] && $STD apt -y install intel-opencl-icd libigdgmm12 2>/dev/null || true\n  fi\n\n  msg_ok \"Intel Arc GPU configured\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Intel Modern GPU Setup (Gen 9+)\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_intel_modern() {\n  local os_id=\"$1\" os_codename=\"$2\"\n\n  msg_info \"Installing Intel Gen 9+ GPU drivers\"\n\n  if [[ \"$os_id\" == \"ubuntu\" ]]; then\n    $STD apt -y install \\\n      va-driver-all \\\n      intel-media-va-driver \\\n      ocl-icd-libopencl1 \\\n      vainfo \\\n      intel-gpu-tools 2>/dev/null || msg_warn \"Some Intel packages failed\"\n\n    # Try non-free driver for better codec support\n    $STD apt -y install intel-media-va-driver-non-free 2>/dev/null || true\n    $STD apt -y install intel-opencl-icd 2>/dev/null || true\n    $STD apt -y install libmfx-gen1.2 2>/dev/null || true\n\n  elif [[ \"$os_id\" == \"debian\" ]]; then\n    _add_debian_nonfree \"$os_codename\"\n\n    # For Trixie/Sid: Fetch from GitHub (Debian packages too old or missing)\n    if [[ \"$os_codename\" == \"trixie\" || \"$os_codename\" == \"sid\" ]]; then\n      msg_info \"Fetching Intel compute-runtime from GitHub\"\n\n      # Fetch a compute-runtime package first so /tmp/gh_rel.json is populated,\n      # then resolve the matching IGC tag from the release notes.\n      # libigdgmm first (bundled in compute-runtime releases)\n      fetch_and_deploy_gh_release \"libigdgmm12\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"libigdgmm12_*_amd64.deb\" || true\n\n      local igc_tag\n      _resolve_igc_tag igc_tag\n\n      # Intel Graphics Compiler – pinned to the version compute-runtime expects\n      fetch_and_deploy_gh_release \"intel-igc-core\" \"intel/intel-graphics-compiler\" \"binary\" \"$igc_tag\" \"\" \"intel-igc-core-2_*_amd64.deb\" || true\n      fetch_and_deploy_gh_release \"intel-igc-opencl\" \"intel/intel-graphics-compiler\" \"binary\" \"$igc_tag\" \"\" \"intel-igc-opencl-2_*_amd64.deb\" || true\n\n      # Compute Runtime\n      fetch_and_deploy_gh_release \"intel-opencl-icd\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"intel-opencl-icd_*_amd64.deb\" || true\n    fi\n\n    $STD apt -y install \\\n      intel-media-va-driver-non-free \\\n      ocl-icd-libopencl1 \\\n      vainfo \\\n      libmfx-gen1.2 \\\n      intel-gpu-tools 2>/dev/null || msg_warn \"Some Intel packages failed\"\n\n    # Bookworm has intel-opencl-icd in repos (compatible version)\n    [[ \"$os_codename\" == \"bookworm\" ]] && $STD apt -y install intel-opencl-icd libigdgmm12 2>/dev/null || true\n  fi\n\n  msg_ok \"Intel Gen 9+ GPU configured\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Intel Legacy GPU Setup (Gen 6-8)\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_intel_legacy() {\n  local os_id=\"$1\" os_codename=\"$2\"\n\n  msg_info \"Installing Intel Legacy GPU drivers (Gen 6-8)\"\n\n  # Legacy GPUs use i965 driver - stable repo packages only\n  $STD apt -y install \\\n    va-driver-all \\\n    i965-va-driver \\\n    mesa-va-drivers \\\n    ocl-icd-libopencl1 \\\n    vainfo \\\n    intel-gpu-tools 2>/dev/null || msg_warn \"Some Intel legacy packages failed\"\n\n  # beignet provides OpenCL for older Intel GPUs (Sandy Bridge to Broadwell)\n  # Note: beignet-opencl-icd was removed in Debian 12+ and Ubuntu 22.04+\n  # Check if package is available before attempting installation\n  if apt-cache show beignet-opencl-icd &>/dev/null; then\n    $STD apt -y install beignet-opencl-icd 2>/dev/null || msg_warn \"beignet-opencl-icd installation failed (optional)\"\n  else\n    msg_warn \"beignet-opencl-icd not available - OpenCL support for legacy Intel GPU limited\"\n    msg_warn \"Note: Hardware video encoding/decoding (VA-API) still works without OpenCL\"\n  fi\n\n  msg_ok \"Intel Legacy GPU configured\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# AMD Discrete GPU Setup\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_amd_gpu() {\n  local os_id=\"$1\" os_codename=\"$2\"\n\n  msg_info \"Installing AMD GPU drivers\"\n\n  # Core Mesa drivers\n  $STD apt -y install \\\n    mesa-va-drivers \\\n    mesa-vdpau-drivers \\\n    mesa-opencl-icd \\\n    ocl-icd-libopencl1 \\\n    libdrm-amdgpu1 \\\n    vainfo \\\n    clinfo 2>/dev/null || msg_warn \"Some AMD packages failed\"\n\n  # Firmware for AMD GPUs\n  if [[ \"$os_id\" == \"debian\" ]]; then\n    _add_debian_nonfree_firmware \"$os_codename\"\n    $STD apt -y install firmware-amd-graphics 2>/dev/null || msg_warn \"AMD firmware not available\"\n  fi\n  # Ubuntu includes AMD firmware in linux-firmware by default\n\n  # ROCm compute stack (OpenCL + HIP)\n  _setup_rocm \"$os_id\" \"$os_codename\"\n\n  msg_ok \"AMD GPU configured\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# AMD APU Setup (Integrated Graphics)\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_amd_apu() {\n  local os_id=\"$1\" os_codename=\"$2\"\n\n  msg_info \"Installing AMD APU drivers\"\n\n  $STD apt -y install \\\n    mesa-va-drivers \\\n    mesa-vdpau-drivers \\\n    mesa-opencl-icd \\\n    ocl-icd-libopencl1 \\\n    vainfo 2>/dev/null || msg_warn \"Some AMD APU packages failed\"\n\n  if [[ \"$os_id\" == \"debian\" ]]; then\n    _add_debian_nonfree_firmware \"$os_codename\"\n    $STD apt -y install firmware-amd-graphics 2>/dev/null || true\n  fi\n\n  msg_ok \"AMD APU configured\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# AMD ROCm Compute Setup\n# Adds ROCm repository and installs the ROCm compute stack for AMD GPUs/APUs.\n# Provides: OpenCL, HIP, rocm-smi, rocminfo\n# Supported: Debian 12/13, Ubuntu 22.04/24.04 (amd64 only)\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_rocm() {\n  local os_id=\"$1\" os_codename=\"$2\"\n\n  # Only amd64 is supported\n  if [[ \"$(dpkg --print-architecture 2>/dev/null)\" != \"amd64\" ]]; then\n    msg_warn \"ROCm is only available for amd64 — skipping\"\n    return 0\n  fi\n\n  local ROCM_VERSION=\"7.2\"\n  local ROCM_REPO_CODENAME\n\n  # Map OS codename to ROCm repository codename (Ubuntu-based repos)\n  case \"${os_id}-${os_codename}\" in\n  debian-bookworm) ROCM_REPO_CODENAME=\"jammy\" ;;\n  debian-trixie | debian-sid) ROCM_REPO_CODENAME=\"noble\" ;;\n  ubuntu-jammy) ROCM_REPO_CODENAME=\"jammy\" ;;\n  ubuntu-noble) ROCM_REPO_CODENAME=\"noble\" ;;\n  *)\n    msg_warn \"ROCm not supported on ${os_id} ${os_codename} — skipping\"\n    return 0\n    ;;\n  esac\n\n  msg_info \"Installing ROCm ${ROCM_VERSION} compute stack\"\n\n  # ROCm main repository (userspace compute libs)\n  setup_deb822_repo \\\n    \"rocm\" \\\n    \"https://repo.radeon.com/rocm/rocm.gpg.key\" \\\n    \"https://repo.radeon.com/rocm/apt/${ROCM_VERSION}\" \\\n    \"${ROCM_REPO_CODENAME}\" \\\n    \"main\" \\\n    \"amd64\" || {\n    msg_warn \"Failed to add ROCm repository — skipping ROCm\"\n    return 0\n  }\n\n  # Note: The amdgpu/latest/ubuntu repo (kernel driver packages) is intentionally\n  # omitted — kernel drivers are managed by the Proxmox host, not the LXC container.\n  # Only the ROCm userspace compute stack is needed inside the container.\n\n  # Pin ROCm packages to prefer radeon repo\n  cat <<EOF >/etc/apt/preferences.d/rocm-pin-600\nPackage: *\nPin: release o=repo.radeon.com\nPin-Priority: 600\nEOF\n\n  # apt update with retry — repo.radeon.com CDN can be mid-sync (transient size mismatches).\n  # Run with ERR trap disabled so a transient failure does not abort the entire install.\n  local _apt_ok=0\n  for _attempt in 1 2 3; do\n    if (\n      set +e\n      apt-get update -qq 2>&1\n      exit $?\n    ) 2>/dev/null; then\n      _apt_ok=1\n      break\n    fi\n    msg_warn \"apt update failed (attempt ${_attempt}/3) — AMD repo may be temporarily unavailable, retrying in 30s…\"\n    sleep 30\n  done\n  if [[ $_apt_ok -eq 0 ]]; then\n    msg_warn \"apt update still failing after 3 attempts — skipping ROCm install\"\n    return 0\n  fi\n\n  # Install only runtime packages — full 'rocm' meta-package includes 15GB+ dev tools\n  $STD apt install -y rocm-opencl-runtime rocm-hip-runtime rocm-smi-lib 2>/dev/null || {\n    msg_warn \"ROCm runtime install failed — trying minimal set\"\n    $STD apt install -y rocm-opencl-runtime rocm-smi-lib 2>/dev/null || msg_warn \"ROCm minimal install also failed\"\n  }\n\n  # Group membership for GPU access\n  usermod -aG render,video root 2>/dev/null || true\n\n  # Environment (PATH + LD_LIBRARY_PATH)\n  if [[ -d /opt/rocm ]]; then\n    cat <<'ENVEOF' >/etc/profile.d/rocm.sh\nexport PATH=\"$PATH:/opt/rocm/bin\"\nexport LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}/opt/rocm/lib\"\nENVEOF\n    chmod +x /etc/profile.d/rocm.sh\n    # Also make available for current session / systemd services\n    echo \"/opt/rocm/lib\" >/etc/ld.so.conf.d/rocm.conf\n    ldconfig 2>/dev/null || true\n  fi\n\n  if [[ -x /opt/rocm/bin/rocminfo ]]; then\n    msg_ok \"ROCm ${ROCM_VERSION} installed\"\n  else\n    msg_warn \"ROCm installed but rocminfo not found — GPU may not be available in container\"\n  fi\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# NVIDIA GPU Setup\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_nvidia_gpu() {\n  local os_id=\"$1\" os_codename=\"$2\" os_version=\"$3\"\n\n  msg_info \"Installing NVIDIA GPU drivers\"\n\n  # Prevent interactive dialogs (e.g., \"Mismatching nvidia kernel module\" whiptail)\n  export DEBIAN_FRONTEND=noninteractive\n  export NEEDRESTART_MODE=a\n\n  # Detect host driver version (passed through via /proc)\n  # Format varies by driver type:\n  #   Proprietary: \"NVRM version: NVIDIA UNIX x86_64 Kernel Module  550.54.14  Thu...\"\n  #   Open:        \"NVRM version: NVIDIA UNIX Open Kernel Module for x86_64  590.48.01  Release...\"\n  # Use regex to extract version number (###.##.## or ###.## pattern)\n  local nvidia_host_version=\"\"\n  if [[ -f /proc/driver/nvidia/version ]]; then\n    nvidia_host_version=$(grep -oP '\\d{3,}\\.\\d+(\\.\\d+)?' /proc/driver/nvidia/version 2>/dev/null | head -1 || true)\n  fi\n\n  if [[ -z \"$nvidia_host_version\" ]]; then\n    msg_warn \"NVIDIA host driver version not found in /proc/driver/nvidia/version\"\n    msg_warn \"Ensure NVIDIA drivers are installed on host and GPU passthrough is enabled\"\n    $STD apt-get -y install va-driver-all vainfo 2>/dev/null || true\n    return 0\n  fi\n\n  msg_info \"Host NVIDIA driver version: ${nvidia_host_version}\"\n\n  if [[ \"$os_id\" == \"debian\" ]]; then\n    # Enable non-free components\n    if [[ -f /etc/apt/sources.list.d/debian.sources ]]; then\n      if ! grep -q \"non-free\" /etc/apt/sources.list.d/debian.sources 2>/dev/null; then\n        sed -i -E 's/Components: (.*)$/Components: \\1 contrib non-free non-free-firmware/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || true\n      fi\n    fi\n    $STD apt-get -y update 2>/dev/null || msg_warn \"apt update failed - continuing anyway\"\n\n    # For Debian 13 Trixie/Sid: Use Debian's own nvidia packages first (better compatibility)\n    # NVIDIA's CUDA repo targets Debian 12 and may not have amd64 packages for Trixie\n    if [[ \"$os_codename\" == \"trixie\" || \"$os_codename\" == \"sid\" ]]; then\n      msg_info \"Debian ${os_codename}: Using Debian's NVIDIA packages\"\n\n      # Extract major version for flexible matching (580.126.09 -> 580)\n      local nvidia_major_version=\"${nvidia_host_version%%.*}\"\n\n      # Check what versions are actually available\n      local available_version=\"\"\n      available_version=$(apt-cache madison libcuda1 2>/dev/null | awk '{print $3}' | grep -E \"^${nvidia_major_version}\\.\" | head -1 || true)\n\n      if [[ -n \"$available_version\" ]]; then\n        msg_info \"Found available NVIDIA version: ${available_version}\"\n        local nvidia_pkgs=\"libcuda1=${available_version} libnvcuvid1=${available_version} libnvidia-encode1=${available_version} libnvidia-ml1=${available_version}\"\n        if $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends $nvidia_pkgs 2>/dev/null; then\n          msg_ok \"Installed NVIDIA libraries (${available_version})\"\n        else\n          msg_warn \"Failed to install NVIDIA ${available_version} - trying unversioned\"\n          $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends libcuda1 libnvcuvid1 libnvidia-encode1 libnvidia-ml1 2>/dev/null || true\n        fi\n      else\n        # No matching major version - try latest available or unversioned\n        msg_warn \"No NVIDIA packages for version ${nvidia_major_version}.x found in repos\"\n        available_version=$(apt-cache madison libcuda1 2>/dev/null | awk '{print $3}' | head -1 || true)\n        if [[ -n \"$available_version\" ]]; then\n          msg_info \"Trying latest available: ${available_version} (may cause version mismatch)\"\n          $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends \\\n            libcuda1=\"${available_version}\" libnvcuvid1=\"${available_version}\" \\\n            libnvidia-encode1=\"${available_version}\" libnvidia-ml1=\"${available_version}\" 2>/dev/null ||\n            $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends \\\n              libcuda1 libnvcuvid1 libnvidia-encode1 libnvidia-ml1 2>/dev/null ||\n            msg_warn \"NVIDIA library installation failed - GPU compute may not work\"\n        else\n          msg_warn \"No NVIDIA packages available in Debian repos - GPU support disabled\"\n        fi\n      fi\n      $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends nvidia-smi 2>/dev/null || true\n\n    else\n      # Debian 11/12: Use NVIDIA CUDA repository for version matching\n      local cuda_repo=\"debian12\"\n      case \"$os_codename\" in\n      bullseye) cuda_repo=\"debian11\" ;;\n      bookworm) cuda_repo=\"debian12\" ;;\n      esac\n\n      # Add NVIDIA CUDA repository\n      if [[ ! -f /usr/share/keyrings/cuda-archive-keyring.gpg ]]; then\n        msg_info \"Adding NVIDIA CUDA repository (${cuda_repo})\"\n        local cuda_keyring\n        cuda_keyring=\"$(mktemp)\"\n        if curl -fsSL -o \"$cuda_keyring\" \"https://developer.download.nvidia.com/compute/cuda/repos/${cuda_repo}/x86_64/cuda-keyring_1.1-1_all.deb\" 2>/dev/null; then\n          $STD dpkg -i \"$cuda_keyring\" 2>/dev/null || true\n        else\n          msg_warn \"Failed to download NVIDIA CUDA keyring\"\n        fi\n        rm -f \"$cuda_keyring\"\n      fi\n\n      # Pin NVIDIA repo for version matching\n      cat <<'NVIDIA_PIN' >/etc/apt/preferences.d/nvidia-cuda-pin\nPackage: *\nPin: origin developer.download.nvidia.com\nPin-Priority: 1001\nNVIDIA_PIN\n\n      $STD apt-get -y update 2>/dev/null || msg_warn \"apt update failed - continuing anyway\"\n\n      # Extract major version for flexible matching (580.126.09 -> 580)\n      local nvidia_major_version=\"${nvidia_host_version%%.*}\"\n\n      # Check what versions are actually available in CUDA repo\n      local available_version=\"\"\n      available_version=$(apt-cache madison libcuda1 2>/dev/null | awk '{print $3}' | grep -E \"^${nvidia_major_version}\\.\" | head -1 || true)\n\n      if [[ -n \"$available_version\" ]]; then\n        msg_info \"Installing NVIDIA libraries (version ${available_version})\"\n        local nvidia_pkgs=\"libcuda1=${available_version} libnvcuvid1=${available_version} libnvidia-encode1=${available_version} libnvidia-ml1=${available_version}\"\n        if $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends $nvidia_pkgs 2>/dev/null; then\n          msg_ok \"Installed version-matched NVIDIA libraries\"\n        else\n          msg_warn \"Version-pinned install failed - trying unpinned\"\n          $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends libcuda1 libnvcuvid1 libnvidia-encode1 libnvidia-ml1 2>/dev/null ||\n            msg_warn \"NVIDIA library installation failed\"\n        fi\n      else\n        msg_warn \"No NVIDIA packages for version ${nvidia_major_version}.x in CUDA repo (host: ${nvidia_host_version})\"\n        # Try latest available version\n        available_version=$(apt-cache madison libcuda1 2>/dev/null | awk '{print $3}' | head -1 || true)\n        if [[ -n \"$available_version\" ]]; then\n          msg_info \"Trying latest available: ${available_version} (version mismatch warning)\"\n          if $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends \\\n            libcuda1=\"${available_version}\" libnvcuvid1=\"${available_version}\" \\\n            libnvidia-encode1=\"${available_version}\" libnvidia-ml1=\"${available_version}\" 2>/dev/null; then\n            msg_ok \"Installed NVIDIA libraries (${available_version}) - version differs from host\"\n          else\n            $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends libcuda1 libnvcuvid1 libnvidia-encode1 libnvidia-ml1 2>/dev/null ||\n              msg_warn \"NVIDIA library installation failed\"\n          fi\n        else\n          msg_warn \"No NVIDIA packages available in CUDA repo - GPU support disabled\"\n        fi\n      fi\n\n      $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends nvidia-smi 2>/dev/null || true\n    fi\n\n  elif [[ \"$os_id\" == \"ubuntu\" ]]; then\n    # Ubuntu versioning\n    local ubuntu_cuda_repo=\"\"\n    case \"$os_version\" in\n    22.04) ubuntu_cuda_repo=\"ubuntu2204\" ;;\n    24.04) ubuntu_cuda_repo=\"ubuntu2404\" ;;\n    *) ubuntu_cuda_repo=\"ubuntu2204\" ;; # Fallback\n    esac\n\n    # Add NVIDIA CUDA repository for Ubuntu\n    if [[ ! -f /usr/share/keyrings/cuda-archive-keyring.gpg ]]; then\n      msg_info \"Adding NVIDIA CUDA repository (${ubuntu_cuda_repo})\"\n      local cuda_keyring\n      cuda_keyring=\"$(mktemp)\"\n      if curl -fsSL -o \"$cuda_keyring\" \"https://developer.download.nvidia.com/compute/cuda/repos/${ubuntu_cuda_repo}/x86_64/cuda-keyring_1.1-1_all.deb\" 2>/dev/null; then\n        $STD dpkg -i \"$cuda_keyring\" 2>/dev/null || true\n      else\n        msg_warn \"Failed to download NVIDIA CUDA keyring\"\n      fi\n      rm -f \"$cuda_keyring\"\n    fi\n\n    $STD apt-get -y update 2>/dev/null || msg_warn \"apt update failed - continuing anyway\"\n\n    # Extract major version for flexible matching\n    local nvidia_major_version=\"${nvidia_host_version%%.*}\"\n\n    # Check what versions are available\n    local available_version=\"\"\n    available_version=$(apt-cache madison libcuda1 2>/dev/null | awk '{print $3}' | grep -E \"^${nvidia_major_version}\\.\" | head -1 || true)\n\n    if [[ -n \"$available_version\" ]]; then\n      msg_info \"Installing NVIDIA libraries (version ${available_version})\"\n      local nvidia_pkgs=\"libcuda1=${available_version} libnvcuvid1=${available_version} libnvidia-encode1=${available_version} libnvidia-ml1=${available_version}\"\n      if $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends $nvidia_pkgs 2>/dev/null; then\n        msg_ok \"Installed version-matched NVIDIA libraries\"\n      else\n        # Fallback to Ubuntu repo packages with versioned nvidia-utils\n        msg_warn \"CUDA repo install failed - trying Ubuntu native packages (nvidia-utils-${nvidia_major_version})\"\n        if $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends \\\n          libnvidia-decode-${nvidia_major_version} libnvidia-encode-${nvidia_major_version} nvidia-utils-${nvidia_major_version} 2>/dev/null; then\n          msg_ok \"Installed Ubuntu NVIDIA packages (${nvidia_major_version})\"\n        else\n          msg_warn \"NVIDIA driver installation failed - please install manually: apt install nvidia-utils-${nvidia_major_version}\"\n        fi\n      fi\n    else\n      msg_warn \"No NVIDIA packages for version ${nvidia_major_version}.x in CUDA repo\"\n      # Fallback to Ubuntu repo packages with versioned nvidia-utils\n      msg_info \"Trying Ubuntu native packages (nvidia-utils-${nvidia_major_version})\"\n      if $STD apt-get -y -o Dpkg::Options::=\"--force-confold\" install --no-install-recommends \\\n        libnvidia-decode-${nvidia_major_version} libnvidia-encode-${nvidia_major_version} nvidia-utils-${nvidia_major_version} 2>/dev/null; then\n        msg_ok \"Installed Ubuntu NVIDIA packages (${nvidia_major_version})\"\n      else\n        msg_warn \"NVIDIA driver installation failed - please install manually: apt install nvidia-utils-${nvidia_major_version}\"\n      fi\n    fi\n  fi\n\n  # VA-API for hybrid setups (Intel + NVIDIA)\n  $STD apt-get -y install va-driver-all vainfo 2>/dev/null || true\n\n  # Fix GLX alternatives: nvidia-alternative diverts mesa libs but in LXC\n  # containers the nvidia GLX libs are typically missing, leaving libGL.so.1\n  # pointing nowhere. Fall back to mesa if nvidia GLX dir is empty/missing.\n  if command -v update-glx &>/dev/null; then\n    local nvidia_glx_dir=\"/usr/lib/nvidia\"\n    if [[ ! -f \"${nvidia_glx_dir}/libGL.so.1\" ]] && [[ -d /usr/lib/mesa-diverted ]]; then\n      msg_info \"NVIDIA GLX libs missing in container - falling back to mesa\"\n      $STD update-glx --set glx /usr/lib/mesa-diverted 2>/dev/null || true\n      ldconfig 2>/dev/null || true\n    fi\n  fi\n\n  msg_ok \"NVIDIA GPU configured\"\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Helper: Add Debian non-free repositories\n# ══════════════════════════════════════════════════════════════════════════════\n_add_debian_nonfree() {\n  local os_codename=\"$1\"\n\n  [[ -f /etc/apt/sources.list.d/non-free.sources ]] && return 0\n\n  case \"$os_codename\" in\n  bullseye)\n    cat <<'EOF' >/etc/apt/sources.list.d/non-free.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bullseye bullseye-updates\nComponents: non-free\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    ;;\n  bookworm)\n    cat <<'EOF' >/etc/apt/sources.list.d/non-free.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bookworm bookworm-updates\nComponents: non-free non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    ;;\n  trixie | sid)\n    cat <<'EOF' >/etc/apt/sources.list.d/non-free.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie trixie-updates\nComponents: non-free non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian-security\nSuites: trixie-security\nComponents: non-free non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    ;;\n  esac\n  $STD apt -y update\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Helper: Add Debian non-free-firmware repository\n# ══════════════════════════════════════════════════════════════════════════════\n_add_debian_nonfree_firmware() {\n  local os_codename=\"$1\"\n\n  [[ -f /etc/apt/sources.list.d/non-free-firmware.sources ]] && return 0\n\n  case \"$os_codename\" in\n  bullseye)\n    # Debian 11 uses 'non-free' component (no separate non-free-firmware)\n    cat <<'EOF' >/etc/apt/sources.list.d/non-free-firmware.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bullseye bullseye-updates\nComponents: non-free\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian-security\nSuites: bullseye-security\nComponents: non-free\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    ;;\n  bookworm)\n    cat <<'EOF' >/etc/apt/sources.list.d/non-free-firmware.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: bookworm bookworm-updates\nComponents: non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian-security\nSuites: bookworm-security\nComponents: non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    ;;\n  trixie | sid)\n    cat <<'EOF' >/etc/apt/sources.list.d/non-free-firmware.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie trixie-updates\nComponents: non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian-security\nSuites: trixie-security\nComponents: non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    ;;\n  esac\n  $STD apt -y update\n}\n\n# ══════════════════════════════════════════════════════════════════════════════\n# Helper: Setup GPU device permissions\n# ══════════════════════════════════════════════════════════════════════════════\n_setup_gpu_permissions() {\n  local in_ct=\"$1\"\n  local service_user=\"${2:-}\"\n\n  # /dev/dri permissions (Intel/AMD)\n  if [[ \"$in_ct\" == \"0\" && -d /dev/dri ]]; then\n    if ls /dev/dri/card* /dev/dri/renderD* &>/dev/null; then\n      chgrp video /dev/dri 2>/dev/null || true\n      chmod 755 /dev/dri 2>/dev/null || true\n      chmod 660 /dev/dri/* 2>/dev/null || true\n      $STD adduser \"$(id -u -n)\" video 2>/dev/null || true\n      $STD adduser \"$(id -u -n)\" render 2>/dev/null || true\n\n      # Sync GID with host\n      local host_video_gid host_render_gid\n      host_video_gid=$(getent group video | cut -d: -f3)\n      host_render_gid=$(getent group render | cut -d: -f3)\n      if [[ -n \"$host_video_gid\" ]]; then\n        sed -i \"s/^video:x:[0-9]*:/video:x:$host_video_gid:/\" /etc/group 2>/dev/null || true\n      fi\n      if [[ -n \"$host_render_gid\" ]]; then\n        sed -i \"s/^render:x:[0-9]*:/render:x:$host_render_gid:/\" /etc/group 2>/dev/null || true\n      fi\n\n      # Verify VA-API\n      if command -v vainfo &>/dev/null; then\n        if vainfo &>/dev/null; then\n          msg_info \"VA-API verified and working\"\n        else\n          msg_warn \"vainfo test failed - check GPU passthrough\"\n        fi\n      fi\n    fi\n  fi\n\n  # /dev/nvidia* permissions (NVIDIA)\n  if ls /dev/nvidia* &>/dev/null 2>&1; then\n    msg_info \"Configuring NVIDIA device permissions\"\n    for nvidia_dev in /dev/nvidia*; do\n      [[ -e \"$nvidia_dev\" ]] && {\n        chgrp video \"$nvidia_dev\" 2>/dev/null || true\n        chmod 660 \"$nvidia_dev\" 2>/dev/null || true\n      }\n    done\n    if [[ -d /dev/nvidia-caps ]]; then\n      chmod 755 /dev/nvidia-caps 2>/dev/null || true\n      for caps_dev in /dev/nvidia-caps/*; do\n        [[ -e \"$caps_dev\" ]] && {\n          chgrp video \"$caps_dev\" 2>/dev/null || true\n          chmod 660 \"$caps_dev\" 2>/dev/null || true\n        }\n      done\n    fi\n\n    # Verify nvidia-smi\n    if command -v nvidia-smi &>/dev/null; then\n      if nvidia-smi &>/dev/null; then\n        msg_info \"nvidia-smi verified and working\"\n      else\n        msg_warn \"nvidia-smi test failed - check driver version match\"\n      fi\n    fi\n  fi\n\n  # /dev/kfd permissions (AMD ROCm)\n  if [[ -e /dev/kfd ]]; then\n    chgrp render /dev/kfd 2>/dev/null || true\n    chmod 660 /dev/kfd 2>/dev/null || true\n    msg_info \"AMD ROCm compute device configured\"\n  fi\n\n  # Add service user to render and video groups for GPU hardware acceleration\n  if [[ -n \"$service_user\" ]]; then\n    usermod -aG render \"$service_user\" 2>/dev/null || true\n    usermod -aG video \"$service_user\" 2>/dev/null || true\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Installs ImageMagick 7 from source (Debian/Ubuntu only).\n#\n# Description:\n#   - Downloads the latest ImageMagick source tarball\n#   - Builds and installs ImageMagick to /usr/local\n#   - Configures dynamic linker (ldconfig)\n#\n# Notes:\n#   - Requires: build-essential, libtool, libjpeg-dev, libpng-dev, etc.\n# ------------------------------------------------------------------------------\nfunction setup_imagemagick() {\n  local TMP_DIR=$(mktemp -d)\n  local BINARY_PATH=\"/usr/local/bin/magick\"\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  if command -v magick &>/dev/null; then\n    INSTALLED_VERSION=$(magick -version | awk '/^Version/ {print $3}')\n  fi\n\n  msg_info \"Setup ImageMagick\"\n\n  ensure_dependencies \\\n    build-essential \\\n    libtool \\\n    libjpeg-dev \\\n    libpng-dev \\\n    libtiff-dev \\\n    libwebp-dev \\\n    libheif-dev \\\n    libde265-dev \\\n    libopenjp2-7-dev \\\n    libxml2-dev \\\n    liblcms2-dev \\\n    libfreetype6-dev \\\n    libraw-dev \\\n    libfftw3-dev \\\n    liblqr-1-0-dev \\\n    libgsl-dev \\\n    pkg-config \\\n    ghostscript\n\n  if ! CURL_TIMEOUT=180 curl_with_retry \"https://imagemagick.org/archive/ImageMagick.tar.gz\" \"$TMP_DIR/ImageMagick.tar.gz\"; then\n    msg_error \"Failed to download ImageMagick\"\n    rm -rf \"$TMP_DIR\"\n    return 250\n  fi\n\n  tar -xzf \"$TMP_DIR/ImageMagick.tar.gz\" -C \"$TMP_DIR\" || {\n    msg_error \"Failed to extract ImageMagick\"\n    rm -rf \"$TMP_DIR\"\n    return 251\n  }\n\n  cd \"$TMP_DIR\"/ImageMagick-* || {\n    msg_error \"Source extraction failed\"\n    rm -rf \"$TMP_DIR\"\n    return 251\n  }\n\n  $STD ./configure --disable-static || {\n    msg_error \"ImageMagick configure failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD make -j\"$(nproc)\" || {\n    msg_error \"ImageMagick compilation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD make install || {\n    msg_error \"ImageMagick installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n  $STD ldconfig /usr/local/lib\n\n  if [[ ! -x \"$BINARY_PATH\" ]]; then\n    msg_error \"ImageMagick installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  fi\n\n  local FINAL_VERSION\n  FINAL_VERSION=$(\"$BINARY_PATH\" -version | awk '/^Version/ {print $3}')\n  rm -rf \"$TMP_DIR\"\n  cache_installed_version \"imagemagick\" \"$FINAL_VERSION\"\n  ensure_usr_local_bin_persist\n\n  if [[ -n \"$INSTALLED_VERSION\" ]]; then\n    msg_ok \"Upgrade ImageMagick $INSTALLED_VERSION → $FINAL_VERSION\"\n  else\n    msg_ok \"Setup ImageMagick $FINAL_VERSION\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Installs Temurin JDK via Adoptium APT repository.\n#\n# Description:\n#   - Removes previous JDK if version mismatch\n#   - Installs or upgrades to specified JAVA_VERSION\n#\n# Variables:\n#   JAVA_VERSION   - Temurin JDK version to install (e.g. 17, 21)\n# ------------------------------------------------------------------------------\n\nfunction setup_java() {\n  local JAVA_VERSION=\"${JAVA_VERSION:-21}\"\n  local DISTRO_ID DISTRO_CODENAME\n  DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '\"')\n  DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)\n  local DESIRED_PACKAGE=\"temurin-${JAVA_VERSION}-jdk\"\n\n  # Prepare repository (cleanup + validation)\n  prepare_repository_setup \"adoptium\" || {\n    msg_error \"Failed to prepare Adoptium repository\"\n    return 100\n  }\n\n  # Add repo if needed\n  if [[ ! -f /etc/apt/sources.list.d/adoptium.sources ]]; then\n    local SUITE\n    SUITE=$(get_fallback_suite \"$DISTRO_ID\" \"$DISTRO_CODENAME\" \"https://packages.adoptium.net/artifactory/deb\")\n    setup_deb822_repo \\\n      \"adoptium\" \\\n      \"https://packages.adoptium.net/artifactory/api/gpg/key/public\" \\\n      \"https://packages.adoptium.net/artifactory/deb\" \\\n      \"$SUITE\" \\\n      \"main\"\n  fi\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  INSTALLED_VERSION=$(dpkg-query -W -f '${Package}\\n' 2>/dev/null | grep -oP '^temurin-\\K[0-9]+(?=-jdk$)' | head -n1 || echo \"\")\n\n  # Scenario 1: Already at correct version\n  if [[ \"$INSTALLED_VERSION\" == \"$JAVA_VERSION\" ]]; then\n    msg_info \"Update Temurin JDK $JAVA_VERSION\"\n    ensure_apt_working || return 100\n    upgrade_packages_with_retry \"$DESIRED_PACKAGE\" || {\n      msg_error \"Failed to update Temurin JDK\"\n      return 100\n    }\n    cache_installed_version \"temurin-jdk\" \"$JAVA_VERSION\"\n    msg_ok \"Update Temurin JDK $JAVA_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Different version - remove old and install new\n  if [[ -n \"$INSTALLED_VERSION\" ]]; then\n    msg_info \"Upgrade Temurin JDK from $INSTALLED_VERSION to $JAVA_VERSION\"\n    $STD apt purge -y \"temurin-${INSTALLED_VERSION}-jdk\" || true\n  else\n    msg_info \"Setup Temurin JDK $JAVA_VERSION\"\n  fi\n\n  ensure_apt_working || return 100\n\n  # Install with retry logic\n  install_packages_with_retry \"$DESIRED_PACKAGE\" || {\n    msg_error \"Failed to install Temurin JDK $JAVA_VERSION\"\n    return 100\n  }\n\n  cache_installed_version \"temurin-jdk\" \"$JAVA_VERSION\"\n  msg_ok \"Setup Temurin JDK $JAVA_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs a local IP updater script using networkd-dispatcher.\n#\n# Description:\n#   - Stores current IP in /run/local-ip.env\n#   - Automatically runs on network changes\n# ------------------------------------------------------------------------------\n\nfunction setup_local_ip_helper() {\n  local BASE_DIR=\"/usr/local/community-scripts/ip-management\"\n  local SCRIPT_PATH=\"$BASE_DIR/update_local_ip.sh\"\n  local IP_FILE=\"/run/local-ip.env\"\n  local DISPATCHER_SCRIPT=\"/etc/networkd-dispatcher/routable.d/10-update-local-ip.sh\"\n\n  # Check if already set up\n  if [[ -f \"$SCRIPT_PATH\" && -f \"$DISPATCHER_SCRIPT\" ]]; then\n    msg_info \"Update Local IP Helper\"\n    cache_installed_version \"local-ip-helper\" \"1.0\"\n    msg_ok \"Update Local IP Helper\"\n  else\n    msg_info \"Setup Local IP Helper\"\n  fi\n\n  mkdir -p \"$BASE_DIR\"\n\n  # Install networkd-dispatcher if not present\n  if ! dpkg -s networkd-dispatcher >/dev/null 2>&1; then\n    ensure_dependencies networkd-dispatcher || {\n      msg_error \"Failed to install networkd-dispatcher\"\n      return 100\n    }\n  fi\n\n  # Write update_local_ip.sh\n  cat <<'EOF' >\"$SCRIPT_PATH\"\n#!/bin/bash\nset -euo pipefail\n\nIP_FILE=\"/run/local-ip.env\"\nmkdir -p \"$(dirname \"$IP_FILE\")\"\n\nget_current_ip() {\n    local ip\n\n    # Try IPv4 targets first\n    local ipv4_targets=(\"8.8.8.8\" \"1.1.1.1\" \"192.168.1.1\" \"10.0.0.1\" \"172.16.0.1\" \"default\")\n    for target in \"${ipv4_targets[@]}\"; do\n        if [[ \"$target\" == \"default\" ]]; then\n            ip=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i==\"src\") print $(i+1)}')\n        else\n            ip=$(ip route get \"$target\" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i==\"src\") print $(i+1)}')\n        fi\n        if [[ -n \"$ip\" ]]; then\n            echo \"$ip\"\n            return 0\n        fi\n    done\n\n    # IPv6 fallback: Try direct interface lookup for eth0\n    ip=$(ip -6 addr show eth0 scope global 2>/dev/null | awk '/inet6 / {print $2}' | cut -d/ -f1 | head -n1)\n    if [[ -n \"$ip\" && \"$ip\" =~ : ]]; then\n        echo \"$ip\"\n        return 0\n    fi\n\n    # IPv6 fallback: Use routing table with IPv6 targets (Google DNS, Cloudflare DNS)\n    local ipv6_targets=(\"2001:4860:4860::8888\" \"2606:4700:4700::1111\")\n    for target in \"${ipv6_targets[@]}\"; do\n        ip=$(ip -6 route get \"$target\" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i==\"src\") print $(i+1)}')\n        if [[ -n \"$ip\" && \"$ip\" =~ : ]]; then\n            echo \"$ip\"\n            return 0\n        fi\n    done\n\n    return 1\n}\n\ncurrent_ip=\"$(get_current_ip)\"\n\nif [[ -z \"$current_ip\" ]]; then\n    echo \"[ERROR] Could not detect local IP\" >&2\n    exit 123\nfi\n\nif [[ -f \"$IP_FILE\" ]]; then\n    source \"$IP_FILE\"\n    [[ \"$LOCAL_IP\" == \"$current_ip\" ]] && exit 0\nfi\n\necho \"LOCAL_IP=$current_ip\" > \"$IP_FILE\"\necho \"[INFO] LOCAL_IP updated to $current_ip\"\nEOF\n\n  chmod +x \"$SCRIPT_PATH\"\n\n  # Install dispatcher hook\n  mkdir -p \"$(dirname \"$DISPATCHER_SCRIPT\")\"\n  cat <<EOF >\"$DISPATCHER_SCRIPT\"\n#!/bin/bash\n$SCRIPT_PATH\nEOF\n\n  chmod +x \"$DISPATCHER_SCRIPT\"\n  systemctl enable -q --now networkd-dispatcher.service || {\n    msg_warn \"Failed to enable networkd-dispatcher service\"\n  }\n\n  cache_installed_version \"local-ip-helper\" \"1.0\"\n  msg_ok \"Setup Local IP Helper\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs or updates MariaDB.\n#\n# Description:\n#   - Uses Debian/Ubuntu distribution packages by default (most reliable)\n#   - Only uses official MariaDB repository when a specific version is requested\n#   - Detects current MariaDB version and replaces it if necessary\n#   - Preserves existing database data\n#\n# Variables:\n#   MARIADB_VERSION - MariaDB version to install (optional)\n#                     - Not set or \"latest\": Uses distribution packages (recommended)\n#                     - Specific version (e.g. \"11.4\", \"12.2\"): Uses MariaDB official repo\n# ------------------------------------------------------------------------------\n\nsetup_mariadb() {\n  local MARIADB_VERSION=\"${MARIADB_VERSION:-latest}\"\n  local USE_DISTRO_PACKAGES=false\n\n  # Ensure non-interactive mode for all apt operations\n  export DEBIAN_FRONTEND=noninteractive\n  export NEEDRESTART_MODE=a\n  export NEEDRESTART_SUSPEND=1\n\n  # Determine installation method:\n  # - \"latest\" or empty: Use distribution packages (avoids mirror issues)\n  # - Specific version: Use MariaDB official repository\n  if [[ \"$MARIADB_VERSION\" == \"latest\" || -z \"$MARIADB_VERSION\" ]]; then\n    USE_DISTRO_PACKAGES=true\n    msg_info \"Setup MariaDB (distribution packages)\"\n  else\n    msg_info \"Setup MariaDB $MARIADB_VERSION (official repository)\"\n  fi\n\n  # Get currently installed version\n  local CURRENT_VERSION=\"\"\n  CURRENT_VERSION=$(is_tool_installed \"mariadb\" 2>/dev/null) || true\n\n  # Pre-configure debconf to prevent any interactive prompts during install/upgrade\n  debconf-set-selections <<EOF\nmariadb-server mariadb-server/feedback boolean false\nmariadb-server mariadb-server/root_password password\nmariadb-server mariadb-server/root_password_again password\nEOF\n\n  # If specific version requested, also configure version-specific debconf\n  if [[ \"$USE_DISTRO_PACKAGES\" == \"false\" ]]; then\n    local MARIADB_MAJOR_MINOR\n    MARIADB_MAJOR_MINOR=$(echo \"$MARIADB_VERSION\" | awk -F. '{print $1\".\"$2}')\n    if [[ -n \"$MARIADB_MAJOR_MINOR\" ]]; then\n      debconf-set-selections <<EOF\nmariadb-server-$MARIADB_MAJOR_MINOR mariadb-server/feedback boolean false\nmariadb-server-$MARIADB_MAJOR_MINOR mariadb-server/root_password password\nmariadb-server-$MARIADB_MAJOR_MINOR mariadb-server/root_password_again password\nEOF\n    fi\n  fi\n\n  # ============================================================================\n  # DISTRIBUTION PACKAGES PATH (default, most reliable)\n  # ============================================================================\n  if [[ \"$USE_DISTRO_PACKAGES\" == \"true\" ]]; then\n    # Check if MariaDB was previously installed from official repo\n    local HAD_MARIADB_REPO=false\n    if [[ -f /etc/apt/sources.list.d/mariadb.sources ]] || [[ -f /etc/apt/sources.list.d/mariadb.list ]]; then\n      HAD_MARIADB_REPO=true\n      msg_info \"Removing MariaDB official repository (switching to distribution packages)\"\n    fi\n\n    # Clean up any existing MariaDB repository files to avoid conflicts\n    cleanup_old_repo_files \"mariadb\"\n\n    # If we had a repo, we need to refresh APT cache\n    if [[ \"$HAD_MARIADB_REPO\" == \"true\" ]]; then\n      $STD apt update || msg_warn \"APT update had issues, continuing...\"\n    fi\n\n    # Ensure APT is working\n    ensure_apt_working || return 100\n\n    # Check if installed version is from official repo and higher than distro version\n    # In this case, we keep the existing installation to avoid data issues\n    if [[ -n \"$CURRENT_VERSION\" ]]; then\n      # Get available distro version\n      local DISTRO_VERSION=\"\"\n      DISTRO_VERSION=$(apt-cache policy mariadb-server 2>/dev/null | grep -E \"Candidate:\" | awk '{print $2}' | grep -oP '^\\d+:\\K\\d+\\.\\d+\\.\\d+' || echo \"\")\n\n      if [[ -n \"$DISTRO_VERSION\" ]]; then\n        # Compare versions - if current is higher, keep it\n        local CURRENT_MAJOR DISTRO_MAJOR\n        CURRENT_MAJOR=$(echo \"$CURRENT_VERSION\" | awk -F. '{print $1}')\n        DISTRO_MAJOR=$(echo \"$DISTRO_VERSION\" | awk -F. '{print $1}')\n\n        if [[ \"$CURRENT_MAJOR\" -gt \"$DISTRO_MAJOR\" ]]; then\n          msg_warn \"MariaDB $CURRENT_VERSION is already installed (higher than distro $DISTRO_VERSION)\"\n          msg_warn \"Keeping existing installation to preserve data integrity\"\n          msg_warn \"To use distribution packages, manually remove MariaDB first\"\n          _setup_mariadb_runtime_dir\n          cache_installed_version \"mariadb\" \"$CURRENT_VERSION\"\n          msg_ok \"Setup MariaDB $CURRENT_VERSION (existing installation kept)\"\n          return 0\n        fi\n      fi\n    fi\n\n    # Install or upgrade MariaDB from distribution packages\n    if ! install_packages_with_retry \"mariadb-server\" \"mariadb-client\"; then\n      msg_error \"Failed to install MariaDB packages from distribution\"\n      return 100\n    fi\n\n    # Get installed version for caching\n    local INSTALLED_VERSION=\"\"\n    INSTALLED_VERSION=$(mariadb --version 2>/dev/null | grep -oP '\\d+\\.\\d+\\.\\d+' | head -n1 || echo \"distro\")\n\n    # Configure runtime directory and finish\n    _setup_mariadb_runtime_dir\n    cache_installed_version \"mariadb\" \"$INSTALLED_VERSION\"\n    msg_ok \"Setup MariaDB $INSTALLED_VERSION (distribution packages)\"\n    return 0\n  fi\n\n  # ============================================================================\n  # OFFICIAL REPOSITORY PATH (only when specific version requested)\n  # ============================================================================\n\n  # First, check if there's an old/broken repository that needs cleanup\n  if [[ -f /etc/apt/sources.list.d/mariadb.sources ]] || [[ -f /etc/apt/sources.list.d/mariadb.list ]]; then\n    local OLD_REPO_VERSION=\"\"\n    OLD_REPO_VERSION=$(grep -oP 'repo/\\K[0-9]+\\.[0-9]+(\\.[0-9]+)?' /etc/apt/sources.list.d/mariadb.sources 2>/dev/null ||\n      grep -oP 'repo/\\K[0-9]+\\.[0-9]+(\\.[0-9]+)?' /etc/apt/sources.list.d/mariadb.list 2>/dev/null || echo \"\")\n\n    # Check if old repo points to a different version\n    if [[ -n \"$OLD_REPO_VERSION\" ]] && [[ \"${OLD_REPO_VERSION%.*}\" != \"${MARIADB_VERSION%.*}\" ]]; then\n      msg_info \"Cleaning up old MariaDB repository (was: $OLD_REPO_VERSION, requested: $MARIADB_VERSION)\"\n      cleanup_old_repo_files \"mariadb\"\n      $STD apt update || msg_warn \"APT update had issues, continuing...\"\n    fi\n  fi\n\n  # Scenario 1: Already installed at target version - just update packages\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" == \"$MARIADB_VERSION\" ]]; then\n    msg_info \"Update MariaDB $MARIADB_VERSION\"\n\n    # Ensure APT is working\n    ensure_apt_working || return 100\n\n    # Check if repository needs to be refreshed\n    if [[ -f /etc/apt/sources.list.d/mariadb.sources ]]; then\n      local REPO_VERSION=\"\"\n      REPO_VERSION=$(grep -oP 'repo/\\K[0-9]+\\.[0-9]+' /etc/apt/sources.list.d/mariadb.sources 2>/dev/null || echo \"\")\n      if [[ -n \"$REPO_VERSION\" && \"$REPO_VERSION\" != \"${MARIADB_VERSION%.*}\" ]]; then\n        msg_warn \"Repository version mismatch, updating...\"\n        manage_tool_repository \"mariadb\" \"$MARIADB_VERSION\" \"http://mirror.mariadb.org/repo/$MARIADB_VERSION\" \\\n          \"https://mariadb.org/mariadb_release_signing_key.asc\" || {\n          msg_error \"Failed to update MariaDB repository\"\n          return 100\n        }\n      fi\n    fi\n\n    # Perform upgrade with retry logic\n    ensure_apt_working || return 100\n    upgrade_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n      msg_error \"Failed to upgrade MariaDB packages\"\n      return 100\n    }\n    cache_installed_version \"mariadb\" \"$MARIADB_VERSION\"\n    msg_ok \"Update MariaDB $MARIADB_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2b: Different version installed - clean upgrade\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" != \"$MARIADB_VERSION\" ]]; then\n    msg_info \"Upgrade MariaDB from $CURRENT_VERSION to $MARIADB_VERSION\"\n    remove_old_tool_version \"mariadb\"\n  fi\n\n  # Scenario 3: Fresh install or version change with specific version\n  # Prepare repository (cleanup + validation)\n  prepare_repository_setup \"mariadb\" || {\n    msg_error \"Failed to prepare MariaDB repository\"\n    return 100\n  }\n\n  # Install required dependencies first\n  local mariadb_deps=()\n  for dep in gawk rsync socat libdbi-perl pv; do\n    if apt-cache search \"^${dep}$\" 2>/dev/null | grep -q .; then\n      mariadb_deps+=(\"$dep\")\n    fi\n  done\n\n  if [[ ${#mariadb_deps[@]} -gt 0 ]]; then\n    $STD apt install -y \"${mariadb_deps[@]}\" 2>/dev/null || true\n  fi\n\n  # Setup repository\n  manage_tool_repository \"mariadb\" \"$MARIADB_VERSION\" \"http://mirror.mariadb.org/repo/$MARIADB_VERSION\" \\\n    \"https://mariadb.org/mariadb_release_signing_key.asc\" || {\n    msg_error \"Failed to setup MariaDB repository\"\n    return 100\n  }\n\n  # Install packages with retry logic\n  if ! install_packages_with_retry \"mariadb-server\" \"mariadb-client\"; then\n    # Fallback: try distribution packages\n    msg_warn \"Failed to install MariaDB $MARIADB_VERSION from official repo, falling back to distribution packages...\"\n    cleanup_old_repo_files \"mariadb\"\n    $STD apt update || {\n      msg_warn \"APT update also failed, continuing with cache\"\n    }\n    if install_packages_with_retry \"mariadb-server\" \"mariadb-client\"; then\n      local FALLBACK_VERSION=\"\"\n      FALLBACK_VERSION=$(mariadb --version 2>/dev/null | grep -oP '\\d+\\.\\d+\\.\\d+' | head -n1 || echo \"distro\")\n      msg_warn \"Installed MariaDB $FALLBACK_VERSION from distribution instead of requested $MARIADB_VERSION\"\n      _setup_mariadb_runtime_dir\n      cache_installed_version \"mariadb\" \"$FALLBACK_VERSION\"\n      msg_ok \"Setup MariaDB $FALLBACK_VERSION (fallback to distribution packages)\"\n      return 0\n    else\n      msg_error \"Failed to install MariaDB packages (both official repo and distribution)\"\n      return 100\n    fi\n  fi\n\n  _setup_mariadb_runtime_dir\n  cache_installed_version \"mariadb\" \"$MARIADB_VERSION\"\n  msg_ok \"Setup MariaDB $MARIADB_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Helper function: Configure MariaDB runtime directory persistence\n# ------------------------------------------------------------------------------\n_setup_mariadb_runtime_dir() {\n  # Configure tmpfiles.d to ensure /run/mysqld directory is created on boot\n  # This fixes the issue where MariaDB fails to start after container reboot\n\n  # Create tmpfiles.d configuration with error handling\n  if ! printf '# Ensure /run/mysqld directory exists with correct permissions for MariaDB\\nd /run/mysqld 0755 mysql mysql -\\n' >/etc/tmpfiles.d/mariadb.conf; then\n    msg_warn \"Failed to create /etc/tmpfiles.d/mariadb.conf - runtime directory may not persist on reboot\"\n  fi\n\n  # Create the directory now if it doesn't exist\n  # Verify mysql user exists before attempting ownership change\n  if [[ ! -d /run/mysqld ]]; then\n    mkdir -p /run/mysqld\n    # Set permissions first (works regardless of user existence)\n    chmod 755 /run/mysqld\n    # Set ownership only if mysql user exists\n    if getent passwd mysql >/dev/null 2>&1; then\n      chown mysql:mysql /run/mysqld\n    else\n      msg_warn \"mysql user not found - directory created with correct permissions but ownership not set\"\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Creates MariaDB database with user, charset and optional extra grants/modes\n#\n# Description:\n#   - Generates password if empty\n#   - Creates database with utf8mb4_unicode_ci\n#   - Creates local user with password\n#   - Grants full access to this DB\n#   - Optional: apply extra GRANT statements (comma-separated)\n#   - Optional: apply custom GLOBAL sql_mode\n#   - Saves credentials to file\n#   - Exports variables for use in calling script\n#\n# Usage:\n#   MARIADB_DB_NAME=\"myapp_db\" MARIADB_DB_USER=\"myapp_user\" setup_mariadb_db\n#   MARIADB_DB_NAME=\"domain_monitor\" MARIADB_DB_USER=\"domainmonitor\" setup_mariadb_db\n#   MARIADB_DB_NAME=\"myapp\" MARIADB_DB_USER=\"myapp\" MARIADB_DB_EXTRA_GRANTS=\"GRANT SELECT ON \\`mysql\\`.\\`time_zone_name\\`\" setup_mariadb_db\n#   MARIADB_DB_NAME=\"ghostfolio\" MARIADB_DB_USER=\"ghostfolio\" MARIADB_DB_SQL_MODE=\"\" setup_mariadb_db\n#\n# Variables:\n#   MARIADB_DB_NAME         - Database name (required)\n#   MARIADB_DB_USER         - Database user (required)\n#   MARIADB_DB_PASS         - User password (optional, auto-generated if empty)\n#   MARIADB_DB_EXTRA_GRANTS - Comma-separated GRANT statements (optional)\n#                             Example: \"GRANT SELECT ON \\`mysql\\`.\\`time_zone_name\\`\"\n#   MARIADB_DB_SQL_MODE     - Optional global sql_mode override (e.g. \"\", \"STRICT_TRANS_TABLES\")\n#   MARIADB_DB_CREDS_FILE   - Credentials file path (optional, default: ~/${APPLICATION}.creds)\n#\n# Exports:\n#   MARIADB_DB_NAME, MARIADB_DB_USER, MARIADB_DB_PASS\n# ------------------------------------------------------------------------------\n\nfunction setup_mariadb_db() {\n  if [[ -z \"${MARIADB_DB_NAME:-}\" || -z \"${MARIADB_DB_USER:-}\" ]]; then\n    msg_error \"MARIADB_DB_NAME and MARIADB_DB_USER must be set before calling setup_mariadb_db\"\n    return 65\n  fi\n\n  if [[ -z \"${MARIADB_DB_PASS:-}\" ]]; then\n    MARIADB_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n  fi\n\n  msg_info \"Setting up MariaDB Database\"\n\n  $STD mariadb -u root -e \"CREATE DATABASE \\`$MARIADB_DB_NAME\\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\"\n  $STD mariadb -u root -e \"CREATE USER '$MARIADB_DB_USER'@'localhost' IDENTIFIED BY '$MARIADB_DB_PASS';\"\n  $STD mariadb -u root -e \"GRANT ALL ON \\`$MARIADB_DB_NAME\\`.* TO '$MARIADB_DB_USER'@'localhost';\"\n\n  # Optional extra grants\n  if [[ -n \"${MARIADB_DB_EXTRA_GRANTS:-}\" ]]; then\n    IFS=',' read -ra G_LIST <<<\"${MARIADB_DB_EXTRA_GRANTS:-}\"\n    for g in \"${G_LIST[@]}\"; do\n      g=$(echo \"$g\" | xargs)\n      $STD mariadb -u root -e \"$g TO '$MARIADB_DB_USER'@'localhost';\"\n    done\n  fi\n\n  # Optional sql_mode override\n  if [[ -n \"${MARIADB_DB_SQL_MODE:-}\" ]]; then\n    $STD mariadb -u root -e \"SET GLOBAL sql_mode='${MARIADB_DB_SQL_MODE:-}';\"\n  fi\n\n  $STD mariadb -u root -e \"FLUSH PRIVILEGES;\"\n\n  local app_name=\"${APPLICATION,,}\"\n  local CREDS_FILE=\"${MARIADB_DB_CREDS_FILE:-${HOME}/${app_name}.creds}\"\n  {\n    echo \"MariaDB Credentials\"\n    echo \"Database: $MARIADB_DB_NAME\"\n    echo \"User: $MARIADB_DB_USER\"\n    echo \"Password: $MARIADB_DB_PASS\"\n  } >>\"$CREDS_FILE\"\n\n  msg_ok \"Set up MariaDB Database\"\n\n  export MARIADB_DB_NAME\n  export MARIADB_DB_USER\n  export MARIADB_DB_PASS\n}\n\n# ------------------------------------------------------------------------------\n# Installs or updates MongoDB to specified version.\n#\n# Description:\n#   - Preserves data across installations\n#   - Adds official MongoDB repo\n#\n# Variables:\n#   MONGO_VERSION  - MongoDB version to install (e.g. 7.0, 8.2)\n# ------------------------------------------------------------------------------\n\nfunction setup_mongodb() {\n  local MONGO_VERSION=\"${MONGO_VERSION:-8.0}\"\n  local DISTRO_ID DISTRO_CODENAME\n  DISTRO_ID=$(get_os_info id)\n  DISTRO_CODENAME=$(get_os_info codename)\n\n  # Ensure non-interactive mode for all apt operations\n  export DEBIAN_FRONTEND=noninteractive\n  export NEEDRESTART_MODE=a\n  export NEEDRESTART_SUSPEND=1\n\n  # Check AVX support\n  if ! grep -qm1 'avx[^ ]*' /proc/cpuinfo; then\n    local major=\"${MONGO_VERSION%%.*}\"\n    if ((major > 5)); then\n      msg_error \"MongoDB ${MONGO_VERSION} requires AVX support, which is not available on this system.\"\n      return 236\n    fi\n  fi\n\n  case \"$DISTRO_ID\" in\n  ubuntu)\n    MONGO_BASE_URL=\"https://repo.mongodb.org/apt/ubuntu\"\n    ;;\n  debian)\n    MONGO_BASE_URL=\"https://repo.mongodb.org/apt/debian\"\n    ;;\n  *)\n    msg_error \"Unsupported distribution: $DISTRO_ID\"\n    return 238\n    ;;\n  esac\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  INSTALLED_VERSION=$(is_tool_installed \"mongodb\" 2>/dev/null) || true\n\n  # Scenario 1: Already at target version - just update packages\n  if [[ -n \"$INSTALLED_VERSION\" && \"$INSTALLED_VERSION\" == \"$MONGO_VERSION\" ]]; then\n    msg_info \"Update MongoDB $MONGO_VERSION\"\n\n    ensure_apt_working || return 100\n\n    # Perform upgrade with retry logic\n    upgrade_packages_with_retry \"mongodb-org\" || {\n      msg_error \"Failed to upgrade MongoDB\"\n      return 100\n    }\n    cache_installed_version \"mongodb\" \"$MONGO_VERSION\"\n    msg_ok \"Update MongoDB $MONGO_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Different version installed - clean upgrade\n  if [[ -n \"$INSTALLED_VERSION\" && \"$INSTALLED_VERSION\" != \"$MONGO_VERSION\" ]]; then\n    msg_info \"Upgrade MongoDB from $INSTALLED_VERSION to $MONGO_VERSION\"\n    remove_old_tool_version \"mongodb\"\n  else\n    msg_info \"Setup MongoDB $MONGO_VERSION\"\n  fi\n\n  cleanup_orphaned_sources\n\n  # Prepare repository (cleanup + validation)\n  prepare_repository_setup \"mongodb\" || {\n    msg_error \"Failed to prepare MongoDB repository\"\n    return 100\n  }\n\n  # Setup repository\n  # MongoDB 8.x versions beyond 8.0 reuse the server-8.0.asc PGP key\n  local MONGO_KEY_VERSION=\"${MONGO_VERSION}\"\n  [[ \"${MONGO_VERSION}\" == 8.[1-9]* ]] && MONGO_KEY_VERSION=\"8.0\"\n  manage_tool_repository \"mongodb\" \"$MONGO_VERSION\" \"$MONGO_BASE_URL\" \\\n    \"https://www.mongodb.org/static/pgp/server-${MONGO_KEY_VERSION}.asc\" || {\n    msg_error \"Failed to setup MongoDB repository\"\n    return 100\n  }\n\n  # Wait for repo to settle\n  $STD apt update || {\n    msg_error \"APT update failed — invalid MongoDB repo for ${DISTRO_ID}-${DISTRO_CODENAME}?\"\n    return 100\n  }\n\n  # Install MongoDB with retry logic\n  install_packages_with_retry \"mongodb-org\" || {\n    msg_error \"Failed to install MongoDB packages\"\n    return 100\n  }\n\n  if ! command -v mongod >/dev/null 2>&1; then\n    msg_error \"MongoDB binary not found after installation\"\n    return 127\n  fi\n\n  mkdir -p /var/lib/mongodb\n  chown -R mongodb:mongodb /var/lib/mongodb\n\n  $STD systemctl enable mongod || {\n    msg_warn \"Failed to enable mongod service\"\n  }\n  safe_service_restart mongod\n\n  # Verify MongoDB version\n  local INSTALLED_VERSION\n  INSTALLED_VERSION=$(mongod --version 2>/dev/null | grep -oP 'db version v\\K[0-9]+\\.[0-9]+' | head -n1 || echo \"0.0\")\n  verify_tool_version \"MongoDB\" \"$MONGO_VERSION\" \"$INSTALLED_VERSION\" || true\n\n  cache_installed_version \"mongodb\" \"$MONGO_VERSION\"\n  msg_ok \"Setup MongoDB $MONGO_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs or upgrades MySQL.\n#\n# Description:\n#   - By default uses distro repository (Debian/Ubuntu apt) for stability\n#   - Optionally uses official MySQL repository for specific versions\n#   - Detects existing MySQL installation\n#   - Purges conflicting packages before installation\n#   - Supports clean upgrade\n#   - Handles Debian Trixie libaio1t64 transition\n#\n# Variables:\n#   USE_MYSQL_REPO - Use official MySQL repository (default: true)\n#                    Set to \"false\" to use distro packages instead\n#   MYSQL_VERSION  - MySQL version to install when using official repo\n#                    (e.g. 8.0, 8.4) (default: 8.0)\n#\n# Examples:\n#   setup_mysql                              # Uses official MySQL repo, 8.0\n#   MYSQL_VERSION=\"8.4\" setup_mysql          # Specific version from MySQL repo\n#   USE_MYSQL_REPO=false setup_mysql         # Uses distro package instead\n# ------------------------------------------------------------------------------\n\nfunction setup_mysql() {\n  local MYSQL_VERSION=\"${MYSQL_VERSION:-8.0}\"\n  local USE_MYSQL_REPO=\"${USE_MYSQL_REPO:-true}\"\n  local DISTRO_ID DISTRO_CODENAME\n  DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '\"')\n  DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n\n  # Ensure non-interactive mode for all apt operations\n  export DEBIAN_FRONTEND=noninteractive\n  export NEEDRESTART_MODE=a\n  export NEEDRESTART_SUSPEND=1\n\n  # Get currently installed version\n  local CURRENT_VERSION=\"\"\n  CURRENT_VERSION=$(is_tool_installed \"mysql\" 2>/dev/null) || true\n\n  # Scenario 1: Use distro repository (default, most stable)\n  if [[ \"$USE_MYSQL_REPO\" != \"true\" && \"$USE_MYSQL_REPO\" != \"TRUE\" && \"$USE_MYSQL_REPO\" != \"1\" ]]; then\n    msg_info \"Setup MySQL (distro package)\"\n\n    # If already installed, just update\n    if [[ -n \"$CURRENT_VERSION\" ]]; then\n      msg_info \"Update MySQL $CURRENT_VERSION\"\n      ensure_apt_working || return 100\n      upgrade_packages_with_retry \"default-mysql-server\" \"default-mysql-client\" ||\n        upgrade_packages_with_retry \"mysql-server\" \"mysql-client\" ||\n        upgrade_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n        msg_error \"Failed to upgrade MySQL/MariaDB packages\"\n        return 100\n      }\n      cache_installed_version \"mysql\" \"$CURRENT_VERSION\"\n      msg_ok \"Update MySQL $CURRENT_VERSION\"\n      return 0\n    fi\n\n    # Fresh install from distro repo\n    ensure_apt_working || return 100\n\n    export DEBIAN_FRONTEND=noninteractive\n    # Try default-mysql-server first, fallback to mysql-server, then mariadb\n    if apt-cache search \"^default-mysql-server$\" 2>/dev/null | grep -q .; then\n      install_packages_with_retry \"default-mysql-server\" \"default-mysql-client\" || {\n        msg_warn \"default-mysql-server failed, trying mysql-server\"\n        install_packages_with_retry \"mysql-server\" \"mysql-client\" || {\n          msg_warn \"mysql-server failed, trying mariadb as fallback\"\n          install_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n            msg_error \"Failed to install any MySQL/MariaDB from distro repository\"\n            return 100\n          }\n        }\n      }\n    elif apt-cache search \"^mysql-server$\" 2>/dev/null | grep -q .; then\n      install_packages_with_retry \"mysql-server\" \"mysql-client\" || {\n        msg_warn \"mysql-server failed, trying mariadb as fallback\"\n        install_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n          msg_error \"Failed to install any MySQL/MariaDB from distro repository\"\n          return 100\n        }\n      }\n    else\n      # Distro doesn't have MySQL, use MariaDB\n      install_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n        msg_error \"Failed to install MariaDB from distro repository\"\n        return 100\n      }\n    fi\n\n    # Get installed version\n    local INSTALLED_VERSION=\"\"\n    INSTALLED_VERSION=$(is_tool_installed \"mysql\" 2>/dev/null) || true\n    if [[ -z \"$INSTALLED_VERSION\" ]]; then\n      INSTALLED_VERSION=$(is_tool_installed \"mariadb\" 2>/dev/null) || true\n    fi\n    cache_installed_version \"mysql\" \"${INSTALLED_VERSION:-distro}\"\n    msg_ok \"Setup MySQL/MariaDB ${INSTALLED_VERSION:-from distro}\"\n    return 0\n  fi\n\n  # Scenario 2: Use official MySQL repository (USE_MYSQL_REPO=true)\n  # Scenario 2a: Already at target version - just update packages\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" == \"$MYSQL_VERSION\" ]]; then\n    msg_info \"Update MySQL $MYSQL_VERSION\"\n\n    ensure_apt_working || return 100\n\n    # Perform upgrade with retry logic (non-fatal if fails)\n    upgrade_packages_with_retry \"mysql-server\" \"mysql-client\" || {\n      msg_warn \"MySQL package upgrade had issues, continuing with current version\"\n    }\n\n    cache_installed_version \"mysql\" \"$MYSQL_VERSION\"\n    msg_ok \"Update MySQL $MYSQL_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Different version installed - clean upgrade\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" != \"$MYSQL_VERSION\" ]]; then\n    msg_info \"Upgrade MySQL from $CURRENT_VERSION to $MYSQL_VERSION\"\n    remove_old_tool_version \"mysql\"\n  else\n    msg_info \"Setup MySQL $MYSQL_VERSION\"\n  fi\n\n  # Prepare repository (cleanup + validation)\n  prepare_repository_setup \"mysql\" || {\n    msg_error \"Failed to prepare MySQL repository\"\n    return 100\n  }\n\n  # Debian 13+ Fix: MySQL 8.0 incompatible with libaio1t64, use 8.4 LTS\n  if [[ \"$DISTRO_ID\" == \"debian\" && \"$DISTRO_CODENAME\" =~ ^(trixie|forky|sid)$ ]]; then\n    msg_info \"Debian ${DISTRO_CODENAME} detected → using MySQL 8.4 LTS (libaio1t64 compatible)\"\n\n    if ! download_gpg_key \"https://repo.mysql.com/RPM-GPG-KEY-mysql-2023\" \"/etc/apt/keyrings/mysql.gpg\" \"dearmor\"; then\n      msg_error \"Failed to import MySQL GPG key\"\n      return 7\n    fi\n\n    cat >/etc/apt/sources.list.d/mysql.sources <<EOF\nTypes: deb\nURIs: https://repo.mysql.com/apt/debian/\nSuites: bookworm\nComponents: mysql-8.4-lts\nArchitectures: $(dpkg --print-architecture)\nSigned-By: /etc/apt/keyrings/mysql.gpg\nEOF\n\n    $STD apt update || {\n      msg_error \"Failed to update APT for MySQL 8.4 LTS\"\n      return 100\n    }\n\n    # Install with retry logic\n    if ! install_packages_with_retry \"mysql-community-server\" \"mysql-community-client\"; then\n      msg_warn \"MySQL 8.4 LTS installation failed – falling back to MariaDB\"\n      cleanup_old_repo_files \"mysql\"\n      $STD apt update\n      install_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n        msg_error \"Failed to install database engine (MySQL/MariaDB fallback)\"\n        return 100\n      }\n      msg_ok \"Setup Database Engine (MariaDB fallback on Debian ${DISTRO_CODENAME})\"\n      return 0\n    fi\n\n    cache_installed_version \"mysql\" \"8.4\"\n    msg_ok \"Setup MySQL 8.4 LTS (Debian ${DISTRO_CODENAME})\"\n    return 0\n  fi\n\n  # Standard setup for other distributions\n  local SUITE\n  if [[ \"$DISTRO_ID\" == \"debian\" ]]; then\n    case \"$DISTRO_CODENAME\" in\n    bookworm | bullseye) SUITE=\"$DISTRO_CODENAME\" ;;\n    *) SUITE=\"bookworm\" ;;\n    esac\n  else\n    SUITE=$(get_fallback_suite \"$DISTRO_ID\" \"$DISTRO_CODENAME\" \"https://repo.mysql.com/apt/${DISTRO_ID}\")\n  fi\n\n  # Setup repository\n  manage_tool_repository \"mysql\" \"$MYSQL_VERSION\" \"https://repo.mysql.com/apt/${DISTRO_ID}\" \\\n    \"https://repo.mysql.com/RPM-GPG-KEY-mysql-2023\" || {\n    msg_error \"Failed to setup MySQL repository\"\n    return 100\n  }\n\n  ensure_apt_working || return 100\n\n  # Try multiple package names with retry logic\n  local mysql_install_success=false\n\n  if apt-cache search \"^mysql-server$\" 2>/dev/null | grep -q . &&\n    install_packages_with_retry \"mysql-server\" \"mysql-client\"; then\n    mysql_install_success=true\n  elif apt-cache search \"^mysql-community-server$\" 2>/dev/null | grep -q . &&\n    install_packages_with_retry \"mysql-community-server\" \"mysql-community-client\"; then\n    mysql_install_success=true\n  elif apt-cache search \"^mysql$\" 2>/dev/null | grep -q . &&\n    install_packages_with_retry \"mysql\"; then\n    mysql_install_success=true\n  fi\n\n  if [[ \"$mysql_install_success\" == false ]]; then\n    msg_error \"MySQL ${MYSQL_VERSION} package not available for suite ${SUITE}\"\n    return 100\n  fi\n\n  # Verify mysql command is accessible\n  if ! command -v mysql >/dev/null 2>&1; then\n    hash -r\n    if ! command -v mysql >/dev/null 2>&1; then\n      msg_error \"MySQL installed but mysql command still not found\"\n      return 127\n    fi\n  fi\n\n  cache_installed_version \"mysql\" \"$MYSQL_VERSION\"\n  msg_ok \"Setup MySQL $MYSQL_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs Node.js and optional global modules.\n#\n# Description:\n#   - Installs specified Node.js version using NodeSource APT repo\n#   - Optionally installs or updates global npm modules\n#\n# Variables:\n#   NODE_VERSION   - Node.js version to install (default: 24 LTS)\n#   NODE_MODULE    - Comma-separated list of global modules (e.g. \"yarn,@vue/cli@5.0.0\")\n# ------------------------------------------------------------------------------\n\nfunction setup_nodejs() {\n  local NODE_VERSION=\"${NODE_VERSION:-24}\"\n  local NODE_MODULE=\"${NODE_MODULE:-}\"\n\n  # ALWAYS clean up legacy installations first (nvm, etc.) to prevent conflicts\n  cleanup_legacy_install \"nodejs\"\n\n  # Get currently installed version\n  local CURRENT_NODE_VERSION=\"\"\n  CURRENT_NODE_VERSION=$(is_tool_installed \"nodejs\" 2>/dev/null) || true\n\n  # Ensure jq is available for JSON parsing\n  if ! command -v jq &>/dev/null; then\n    $STD apt update\n    $STD apt install -y jq || {\n      msg_error \"Failed to install jq\"\n      return 100\n    }\n  fi\n\n  # Scenario 1: Already installed at target version - upgrade to latest minor/patch + update packages/modules\n  if [[ -n \"$CURRENT_NODE_VERSION\" && \"$CURRENT_NODE_VERSION\" == \"$NODE_VERSION\" ]]; then\n    msg_info \"Update Node.js $NODE_VERSION\"\n\n    ensure_apt_working || return 100\n\n    # Upgrade to the latest minor/patch release from NodeSource\n    $STD apt-get install -y --only-upgrade nodejs 2>/dev/null || true\n\n    # Pin npm to 11.11.0 to work around Node.js 22.22.2 regression (nodejs/node#62425)\n    $STD npm install -g npm@11.11.0 2>/dev/null || true\n\n    cache_installed_version \"nodejs\" \"$NODE_VERSION\"\n    msg_ok \"Update Node.js $NODE_VERSION\"\n  else\n    # Scenario 2: Different version installed - clean upgrade\n    if [[ -n \"$CURRENT_NODE_VERSION\" && \"$CURRENT_NODE_VERSION\" != \"$NODE_VERSION\" ]]; then\n      msg_info \"Upgrade Node.js from $CURRENT_NODE_VERSION to $NODE_VERSION\"\n      remove_old_tool_version \"nodejs\"\n    else\n      msg_info \"Setup Node.js $NODE_VERSION\"\n    fi\n\n    # Remove ALL Debian nodejs packages BEFORE adding NodeSource repo\n    if dpkg -l 2>/dev/null | grep -qE \"^ii.*(nodejs|libnode|node-cjs|node-acorn|node-balanced|node-brace|node-minimatch|node-undici|node-xtend|node-corepack)\"; then\n      msg_info \"Removing Debian-packaged Node.js and dependencies\"\n      $STD apt purge -y nodejs nodejs-doc libnode* node-* 2>/dev/null || true\n      $STD apt autoremove -y 2>/dev/null || true\n      $STD apt clean 2>/dev/null || true\n    fi\n\n    # Remove any APT pinning (not needed)\n    rm -f /etc/apt/preferences.d/nodesource 2>/dev/null || true\n\n    # Prepare repository (cleanup + validation)\n    prepare_repository_setup \"nodesource\" || {\n      msg_error \"Failed to prepare Node.js repository\"\n      return 250\n    }\n\n    # Setup NodeSource repository\n    manage_tool_repository \"nodejs\" \"$NODE_VERSION\" \"https://deb.nodesource.com/node_${NODE_VERSION}.x\" \"https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key\" || {\n      msg_error \"Failed to setup Node.js repository\"\n      return 250\n    }\n\n    # Force APT cache refresh after repository setup\n    $STD apt update || {\n      msg_warn \"apt update failed after Node.js repository setup\"\n    }\n\n    ensure_dependencies curl ca-certificates gnupg\n\n    install_packages_with_retry \"nodejs\" || {\n      msg_error \"Failed to install Node.js ${NODE_VERSION} from NodeSource\"\n      return 100\n    }\n\n    # Verify Node.js was installed correctly\n    if ! command -v node >/dev/null 2>&1; then\n      msg_error \"Node.js binary not found after installation\"\n      return 127\n    fi\n\n    local INSTALLED_NODE_VERSION\n    INSTALLED_NODE_VERSION=$(node -v 2>/dev/null | grep -oP '^v\\K[0-9]+' || echo \"0\")\n    verify_tool_version \"Node.js\" \"$NODE_VERSION\" \"$INSTALLED_NODE_VERSION\" || true\n\n    # Verify npm is available (should come with NodeSource nodejs)\n    if ! command -v npm >/dev/null 2>&1; then\n      msg_error \"npm not found after Node.js installation - repository issue?\"\n      return 127\n    fi\n\n    # Pin npm to 11.11.0 to work around Node.js 22.22.2 regression (nodejs/node#62425)\n    local NPM_VERSION\n    NPM_VERSION=$(npm -v 2>/dev/null || echo \"0\")\n    if [[ \"$NPM_VERSION\" != \"0\" ]]; then\n      $STD npm install -g npm@11.11.0 2>/dev/null || {\n        msg_warn \"Failed to update npm to 11.11.0 (continuing with bundled npm $NPM_VERSION)\"\n      }\n    fi\n\n    cache_installed_version \"nodejs\" \"$NODE_VERSION\"\n    msg_ok \"Setup Node.js $NODE_VERSION\"\n  fi\n\n  # Set a safe default heap limit for Node.js builds if not explicitly provided.\n  # Priority:\n  #   1) NODE_OPTIONS (caller/user override)\n  #   2) NODE_MAX_OLD_SPACE_SIZE (explicit MB override)\n  #   3) var_ram (LXC memory setting, MB)\n  #   4) /proc/meminfo (runtime memory detection)\n  # Auto value is clamped to 1024..12288 MB.\n  if [[ -z \"${NODE_OPTIONS:-}\" ]]; then\n    local node_heap_mb=\"\"\n\n    if [[ -n \"${NODE_MAX_OLD_SPACE_SIZE:-}\" ]] && [[ \"${NODE_MAX_OLD_SPACE_SIZE}\" =~ ^[0-9]+$ ]]; then\n      node_heap_mb=\"${NODE_MAX_OLD_SPACE_SIZE}\"\n    elif [[ -n \"${var_ram:-}\" ]] && [[ \"${var_ram}\" =~ ^[0-9]+$ ]]; then\n      node_heap_mb=$((var_ram * 75 / 100))\n    else\n      local total_mem_kb=\"\"\n      total_mem_kb=$(awk '/^MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null || echo \"\")\n      if [[ \"$total_mem_kb\" =~ ^[0-9]+$ ]]; then\n        local total_mem_mb=$((total_mem_kb / 1024))\n        node_heap_mb=$((total_mem_mb * 75 / 100))\n      fi\n    fi\n\n    if [[ -z \"$node_heap_mb\" ]] || ((node_heap_mb < 1024)); then\n      node_heap_mb=1024\n    elif ((node_heap_mb > 12288)); then\n      node_heap_mb=12288\n    fi\n\n    export NODE_OPTIONS=\"--max-old-space-size=${node_heap_mb}\"\n  fi\n\n  # Ensure valid working directory for npm (avoids uv_cwd error)\n  if [[ ! -d /opt ]]; then\n    mkdir -p /opt\n  fi\n  cd /opt || {\n    msg_error \"Failed to set safe working directory before npm install\"\n    return 127\n  }\n\n  # Install global Node modules\n  if [[ -n \"$NODE_MODULE\" ]]; then\n    IFS=',' read -ra MODULES <<<\"$NODE_MODULE\"\n\n    # Pin pnpm to v10 to avoid breaking changes from newer major versions\n    for i in \"${!MODULES[@]}\"; do\n      if [[ \"${MODULES[$i]}\" =~ ^pnpm(@.*)?$ ]]; then\n        MODULES[$i]=\"pnpm@^10\"\n      fi\n    done\n\n    local failed_modules=0\n    for mod in \"${MODULES[@]}\"; do\n      local MODULE_NAME MODULE_REQ_VERSION MODULE_INSTALLED_VERSION\n      if [[ \"$mod\" == @*/*@* ]]; then\n        # Scoped package with version, e.g. @vue/cli-service@latest\n        MODULE_NAME=\"${mod%@*}\"\n        MODULE_REQ_VERSION=\"${mod##*@}\"\n      elif [[ \"$mod\" == *\"@\"* ]]; then\n        # Unscoped package with version, e.g. yarn@latest\n        MODULE_NAME=\"${mod%@*}\"\n        MODULE_REQ_VERSION=\"${mod##*@}\"\n      else\n        # No version specified\n        MODULE_NAME=\"$mod\"\n        MODULE_REQ_VERSION=\"latest\"\n      fi\n\n      # Check if the module is already installed\n      if $STD npm list -g --depth=0 \"$MODULE_NAME\" 2>&1 | grep -q \"$MODULE_NAME@\"; then\n        MODULE_INSTALLED_VERSION=\"$(npm list -g --depth=0 \"$MODULE_NAME\" 2>&1 | grep \"$MODULE_NAME@\" | awk -F@ '{print $2}' 2>/dev/null | tr -d '[:space:]' || echo '')\"\n        if [[ \"$MODULE_REQ_VERSION\" != \"latest\" && \"$MODULE_REQ_VERSION\" != \"$MODULE_INSTALLED_VERSION\" ]]; then\n          msg_info \"Updating $MODULE_NAME from v$MODULE_INSTALLED_VERSION to v$MODULE_REQ_VERSION\"\n          if ! $STD npm install -g \"${MODULE_NAME}@${MODULE_REQ_VERSION}\" 2>/dev/null; then\n            msg_warn \"Failed to update $MODULE_NAME to version $MODULE_REQ_VERSION\"\n            ((failed_modules++)) || true\n            continue\n          fi\n        elif [[ \"$MODULE_REQ_VERSION\" == \"latest\" ]]; then\n          msg_info \"Updating $MODULE_NAME to latest version\"\n          if ! $STD npm install -g \"${MODULE_NAME}@latest\" 2>/dev/null; then\n            msg_warn \"Failed to update $MODULE_NAME to latest version\"\n            ((failed_modules++)) || true\n            continue\n          fi\n        fi\n      else\n        msg_info \"Installing $MODULE_NAME@$MODULE_REQ_VERSION\"\n        if ! $STD npm install -g \"${MODULE_NAME}@${MODULE_REQ_VERSION}\" 2>/dev/null; then\n          msg_warn \"Failed to install $MODULE_NAME@$MODULE_REQ_VERSION\"\n          ((failed_modules++)) || true\n          continue\n        fi\n      fi\n    done\n    if [[ $failed_modules -eq 0 ]]; then\n      msg_ok \"Installed Node.js modules: $NODE_MODULE\"\n    else\n      msg_warn \"Installed Node.js modules with $failed_modules failure(s): $NODE_MODULE\"\n    fi\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Installs PHP with selected modules and configures Apache/FPM support.\n#\n# Description:\n#   - Adds Sury PHP repo if needed\n#   - Installs default and user-defined modules\n#   - Patches php.ini for CLI, Apache, and FPM as needed\n#   - Handles built-in modules gracefully (e.g., opcache in PHP 8.5+)\n#   - Skips unavailable packages without failing\n#\n# Variables:\n#   PHP_VERSION                - PHP version to install (default: 8.4)\n#   PHP_MODULE                 - Additional comma-separated modules\n#   PHP_APACHE                 - Set YES to enable PHP with Apache\n#   PHP_FPM                    - Set YES to enable PHP-FPM\n#   PHP_MEMORY_LIMIT           - (default: 512M)\n#   PHP_UPLOAD_MAX_FILESIZE    - (default: 128M)\n#   PHP_POST_MAX_SIZE          - (default: 128M)\n#   PHP_MAX_EXECUTION_TIME     - (default: 300)\n#\n# Notes on modules:\n#   - Base modules (always installed): bcmath, cli, curl, gd, intl, mbstring,\n#     readline, xml, zip, common\n#   - Extended modules (commonly needed): mysql, sqlite3, pgsql, redis,\n#     imagick, bz2, ldap, soap, imap, gmp, apcu\n#   - Some modules are built-in depending on PHP version:\n#     * PHP 8.5+: opcache is built-in (no separate package)\n#     * All versions: ctype, fileinfo, iconv, tokenizer, phar, posix, etc.\n#       are part of php-common\n#   - Unavailable modules are skipped with a warning, not an error\n# ------------------------------------------------------------------------------\n\nfunction setup_php() {\n  local PHP_VERSION=\"${PHP_VERSION:-8.4}\"\n  local PHP_MODULE=\"${PHP_MODULE:-}\"\n  local PHP_APACHE=\"${PHP_APACHE:-NO}\"\n  local PHP_FPM=\"${PHP_FPM:-NO}\"\n  local DISTRO_ID DISTRO_CODENAME\n  DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '\"')\n  DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n\n  # Parse version for compatibility checks\n  local PHP_MAJOR=\"${PHP_VERSION%%.*}\"\n  local PHP_MINOR=\"${PHP_VERSION#*.}\"\n  PHP_MINOR=\"${PHP_MINOR%%.*}\"\n\n  # Modules that are ALWAYS part of php-common (no separate package needed)\n  # These are either built-in or virtual packages provided by php-common\n  local BUILTIN_MODULES=\"calendar,ctype,exif,ffi,fileinfo,ftp,gettext,iconv,pdo,phar,posix,shmop,sockets,sysvmsg,sysvsem,sysvshm,tokenizer\"\n\n  # Modules that became built-in in specific PHP versions\n  # PHP 8.5+: opcache is now part of the core\n  local BUILTIN_85=\"\"\n  if [[ \"$PHP_MAJOR\" -gt 8 ]] || [[ \"$PHP_MAJOR\" -eq 8 && \"$PHP_MINOR\" -ge 5 ]]; then\n    BUILTIN_85=\"opcache\"\n  fi\n\n  # Base modules - essential for most PHP applications\n  # Note: 'common' provides many built-in extensions\n  local BASE_MODULES=\"cli,common,bcmath,curl,dom,gd,gmp,intl,mbstring,readline,xml,zip\"\n\n  # Add opcache only for PHP < 8.5 (it's built-in starting from 8.5)\n  if [[ \"$PHP_MAJOR\" -lt 8 ]] || [[ \"$PHP_MAJOR\" -eq 8 && \"$PHP_MINOR\" -lt 5 ]]; then\n    BASE_MODULES=\"${BASE_MODULES},opcache\"\n  fi\n\n  # Extended default modules - commonly needed by web applications\n  # These cover ~90% of typical use cases without bloat\n  local EXTENDED_MODULES=\"mysql,sqlite3,pgsql,redis,imagick,bz2,apcu\"\n\n  local COMBINED_MODULES=\"${BASE_MODULES},${EXTENDED_MODULES}\"\n\n  local PHP_MEMORY_LIMIT=\"${PHP_MEMORY_LIMIT:-512M}\"\n  local PHP_UPLOAD_MAX_FILESIZE=\"${PHP_UPLOAD_MAX_FILESIZE:-128M}\"\n  local PHP_POST_MAX_SIZE=\"${PHP_POST_MAX_SIZE:-128M}\"\n  local PHP_MAX_EXECUTION_TIME=\"${PHP_MAX_EXECUTION_TIME:-300}\"\n\n  # Merge with user-defined modules\n  if [[ -n \"$PHP_MODULE\" ]]; then\n    COMBINED_MODULES=\"${COMBINED_MODULES},${PHP_MODULE}\"\n  fi\n\n  # Filter out built-in modules (they don't have separate packages)\n  local FILTERED_MODULES=\"\"\n  IFS=',' read -ra ALL_MODULES <<<\"$COMBINED_MODULES\"\n  for mod in \"${ALL_MODULES[@]}\"; do\n    mod=$(echo \"$mod\" | tr -d '[:space:]')\n    [[ -z \"$mod\" ]] && continue\n\n    # Skip if it's a known built-in module\n    if echo \",$BUILTIN_MODULES,$BUILTIN_85,\" | grep -qi \",$mod,\"; then\n      continue\n    fi\n\n    # Add to filtered list\n    if [[ -z \"$FILTERED_MODULES\" ]]; then\n      FILTERED_MODULES=\"$mod\"\n    else\n      FILTERED_MODULES=\"${FILTERED_MODULES},$mod\"\n    fi\n  done\n\n  # Deduplicate\n  COMBINED_MODULES=$(echo \"$FILTERED_MODULES\" | tr ',' '\\n' | awk '!seen[$0]++' | paste -sd, -)\n\n  # Get current PHP-CLI version\n  local CURRENT_PHP=\"\"\n  CURRENT_PHP=$(is_tool_installed \"php\" 2>/dev/null) || true\n\n  # Remove conflicting PHP version before pinning\n  if [[ -n \"$CURRENT_PHP\" && \"$CURRENT_PHP\" != \"$PHP_VERSION\" ]]; then\n    msg_info \"Removing conflicting PHP ${CURRENT_PHP} (need ${PHP_VERSION})\"\n    stop_all_services \"php.*-fpm\"\n    $STD apt purge -y \"php*\" 2>/dev/null || true\n    $STD apt autoremove -y 2>/dev/null || true\n  fi\n\n  # NOW create pinning for the desired version\n  mkdir -p /etc/apt/preferences.d\n  cat <<EOF >/etc/apt/preferences.d/php-pin\nPackage: php${PHP_VERSION}*\nPin: version ${PHP_VERSION}.*\nPin-Priority: 1001\n\nPackage: php[0-9].*\nPin: release o=packages.sury.org-php\nPin-Priority: -1\nEOF\n\n  # Setup repository\n  prepare_repository_setup \"php\" \"deb.sury.org-php\" || {\n    msg_error \"Failed to prepare PHP repository\"\n    return 100\n  }\n\n  # Use different repository based on OS\n  if [[ \"$DISTRO_ID\" == \"ubuntu\" ]]; then\n    # Ubuntu: Use ondrej/php PPA\n    msg_info \"Adding ondrej/php PPA for Ubuntu\"\n    $STD apt install -y software-properties-common || {\n      msg_error \"Failed to install software-properties-common\"\n      return 100\n    }\n    # Don't use $STD for add-apt-repository as it uses background processes\n    add-apt-repository -y ppa:ondrej/php >>\"$(get_active_logfile)\" 2>&1\n  else\n    # Debian: Use Sury repository\n    manage_tool_repository \"php\" \"$PHP_VERSION\" \"\" \"https://packages.sury.org/debsuryorg-archive-keyring.deb\" || {\n      msg_error \"Failed to setup PHP repository\"\n      return 100\n    }\n  fi\n  ensure_apt_working || return 100\n  $STD apt update || {\n    msg_warn \"apt update failed after PHP repository setup\"\n  }\n\n  # Get available PHP version from repository\n  local AVAILABLE_PHP_VERSION=\"\"\n  AVAILABLE_PHP_VERSION=$(apt-cache show \"php${PHP_VERSION}\" 2>/dev/null | grep -m1 \"^Version:\" | awk '{print $2}' 2>/dev/null | cut -d- -f1 || true)\n\n  if [[ -z \"$AVAILABLE_PHP_VERSION\" ]]; then\n    msg_error \"PHP ${PHP_VERSION} not found in configured repositories\"\n    return 100\n  fi\n\n  # Build module list - verify each package exists before adding\n  local MODULE_LIST=\"php${PHP_VERSION}\"\n  local SKIPPED_MODULES=\"\"\n\n  IFS=',' read -ra MODULES <<<\"$COMBINED_MODULES\"\n  for mod in \"${MODULES[@]}\"; do\n    mod=$(echo \"$mod\" | tr -d '[:space:]')\n    [[ -z \"$mod\" ]] && continue\n\n    local pkg_name=\"php${PHP_VERSION}-${mod}\"\n\n    # Check if package exists in repository\n    if apt-cache show \"$pkg_name\" &>/dev/null; then\n      MODULE_LIST+=\" $pkg_name\"\n    else\n      # Package doesn't exist - could be built-in or renamed\n      if [[ -z \"$SKIPPED_MODULES\" ]]; then\n        SKIPPED_MODULES=\"$mod\"\n      else\n        SKIPPED_MODULES=\"${SKIPPED_MODULES}, $mod\"\n      fi\n    fi\n  done\n\n  # Log skipped modules (informational, not an error)\n  if [[ -n \"$SKIPPED_MODULES\" ]]; then\n    msg_info \"Skipping unavailable/built-in modules: $SKIPPED_MODULES\"\n  fi\n\n  if [[ \"$PHP_FPM\" == \"YES\" ]]; then\n    if apt-cache show \"php${PHP_VERSION}-fpm\" &>/dev/null; then\n      MODULE_LIST+=\" php${PHP_VERSION}-fpm\"\n    else\n      msg_warn \"php${PHP_VERSION}-fpm not available\"\n    fi\n    # Create systemd override for PHP-FPM to fix runtime directory issues in LXC containers\n    mkdir -p /etc/systemd/system/php${PHP_VERSION}-fpm.service.d/\n    cat <<EOF >/etc/systemd/system/php${PHP_VERSION}-fpm.service.d/override.conf\n[Service]\nRuntimeDirectory=php\nRuntimeDirectoryMode=0755\nEOF\n    $STD systemctl daemon-reload\n  fi\n\n  # install apache2 with PHP support if requested\n  if [[ \"$PHP_APACHE\" == \"YES\" ]]; then\n    if ! dpkg -l 2>/dev/null | grep -q \"libapache2-mod-php${PHP_VERSION}\"; then\n      msg_info \"Installing Apache with PHP ${PHP_VERSION} module\"\n      install_packages_with_retry \"apache2\" || {\n        msg_error \"Failed to install Apache\"\n        return 100\n      }\n      install_packages_with_retry \"libapache2-mod-php${PHP_VERSION}\" || {\n        msg_warn \"Failed to install libapache2-mod-php${PHP_VERSION}, continuing without Apache module\"\n      }\n    fi\n  fi\n\n  # Install PHP packages (pinning via preferences.d ensures correct version)\n  msg_info \"Installing PHP ${PHP_VERSION} packages\"\n\n  # First attempt: Install all verified packages at once\n  if ! $STD apt install -y $MODULE_LIST 2>/dev/null; then\n    msg_warn \"Bulk installation failed, attempting individual installation\"\n\n    # Install main package first (critical)\n    if ! $STD apt install -y \"php${PHP_VERSION}\" 2>/dev/null; then\n      msg_error \"Failed to install php${PHP_VERSION}\"\n      return 100\n    fi\n\n    # Try to install Apache module individually if requested\n    if [[ \"$PHP_APACHE\" == \"YES\" ]]; then\n      $STD apt install -y \"libapache2-mod-php${PHP_VERSION}\" 2>/dev/null || {\n        msg_warn \"Could not install libapache2-mod-php${PHP_VERSION}\"\n      }\n    fi\n\n    # Try to install each package individually\n    for pkg in $MODULE_LIST; do\n      [[ \"$pkg\" == \"php${PHP_VERSION}\" ]] && continue # Already installed\n      $STD apt install -y \"$pkg\" 2>/dev/null || {\n        msg_warn \"Could not install $pkg - continuing without it\"\n      }\n    done\n  fi\n  cache_installed_version \"php\" \"$PHP_VERSION\"\n\n  # Patch all relevant php.ini files\n  local PHP_INI_PATHS=(\"/etc/php/${PHP_VERSION}/cli/php.ini\")\n  [[ \"$PHP_FPM\" == \"YES\" ]] && PHP_INI_PATHS+=(\"/etc/php/${PHP_VERSION}/fpm/php.ini\")\n  [[ \"$PHP_APACHE\" == \"YES\" ]] && PHP_INI_PATHS+=(\"/etc/php/${PHP_VERSION}/apache2/php.ini\")\n  for ini in \"${PHP_INI_PATHS[@]}\"; do\n    if [[ -f \"$ini\" ]]; then\n      $STD sed -i \"s|^memory_limit = .*|memory_limit = ${PHP_MEMORY_LIMIT}|\" \"$ini\"\n      $STD sed -i \"s|^upload_max_filesize = .*|upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE}|\" \"$ini\"\n      $STD sed -i \"s|^post_max_size = .*|post_max_size = ${PHP_POST_MAX_SIZE}|\" \"$ini\"\n      $STD sed -i \"s|^max_execution_time = .*|max_execution_time = ${PHP_MAX_EXECUTION_TIME}|\" \"$ini\"\n    fi\n  done\n\n  # Patch Apache configuration if needed\n  if [[ \"$PHP_APACHE\" == \"YES\" ]]; then\n    for mod in $(ls /etc/apache2/mods-enabled/ 2>/dev/null | grep -E '^php[0-9]\\.[0-9]\\.conf$' | sed 's/\\.conf//'); do\n      if [[ \"$mod\" != \"php${PHP_VERSION}\" ]]; then\n        $STD a2dismod \"$mod\" || true\n      fi\n    done\n    $STD a2enmod mpm_prefork\n    $STD a2enmod \"php${PHP_VERSION}\"\n    safe_service_restart apache2 || true\n  fi\n\n  # Enable and restart PHP-FPM if requested\n  if [[ \"$PHP_FPM\" == \"YES\" ]]; then\n    if systemctl list-unit-files | grep -q \"php${PHP_VERSION}-fpm.service\"; then\n      $STD systemctl enable php${PHP_VERSION}-fpm\n      safe_service_restart php${PHP_VERSION}-fpm\n    fi\n  fi\n\n  # Verify PHP installation - critical check\n  if ! command -v php >/dev/null 2>&1; then\n    msg_error \"PHP installation verification failed - php command not found\"\n    return 127\n  fi\n\n  local INSTALLED_VERSION=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)\n\n  if [[ \"$INSTALLED_VERSION\" != \"$PHP_VERSION\" ]]; then\n    msg_error \"PHP version mismatch: requested ${PHP_VERSION} but got ${INSTALLED_VERSION}\"\n    msg_error \"This indicates a critical package installation issue\"\n    # Don't cache wrong version\n    return 127\n  fi\n\n  cache_installed_version \"php\" \"$INSTALLED_VERSION\"\n  msg_ok \"Setup PHP ${INSTALLED_VERSION}\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs or upgrades PostgreSQL and optional extensions/modules.\n#\n# Description:\n#   - By default uses distro repository (Debian/Ubuntu apt) for stability\n#   - Optionally uses official PGDG repository for specific versions\n#   - Detects existing PostgreSQL version\n#   - Dumps all databases before upgrade\n#   - Installs optional PG_MODULES (e.g. postgis, contrib, cron)\n#   - Restores dumped data post-upgrade\n#\n# Variables:\n#   USE_PGDG_REPO  - Use official PGDG repository (default: true)\n#                    Set to \"false\" to use distro packages instead\n#   PG_VERSION    - Major PostgreSQL version (e.g. 15, 16) (default: 16)\n#   PG_MODULES    - Comma-separated list of modules (e.g. \"postgis,contrib,cron\")\n#\n# Examples:\n#   setup_postgresql                                          # Uses PGDG repo, PG 16\n#   PG_VERSION=\"17\" setup_postgresql                          # Specific version from PGDG\n#   USE_PGDG_REPO=false setup_postgresql                      # Uses distro package instead\n#   PG_VERSION=\"17\" PG_MODULES=\"cron\" setup_postgresql        # With pg_cron module\n# ------------------------------------------------------------------------------\n\n# Internal helper: Configure shared_preload_libraries for pg_cron\n_configure_pg_cron_preload() {\n  local modules=\"${1:-}\"\n  [[ -z \"$modules\" ]] && return 0\n  if [[ \",$modules,\" == *\",cron,\"* ]]; then\n    local current_libs\n    current_libs=$(sudo -u postgres psql -tAc \"SHOW shared_preload_libraries;\" 2>/dev/null || echo \"\")\n    if [[ \"$current_libs\" != *\"pg_cron\"* ]]; then\n      local new_libs=\"${current_libs:+${current_libs},}pg_cron\"\n      $STD sudo -u postgres psql -c \"ALTER SYSTEM SET shared_preload_libraries = '${new_libs}';\"\n      $STD systemctl restart postgresql\n    fi\n  fi\n}\n\nsetup_postgresql() {\n  local PG_VERSION=\"${PG_VERSION:-16}\"\n  local PG_MODULES=\"${PG_MODULES:-}\"\n  local USE_PGDG_REPO=\"${USE_PGDG_REPO:-true}\"\n  local DISTRO_ID DISTRO_CODENAME\n  DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '\"')\n  DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n\n  # Ensure non-interactive mode for all apt operations\n  export DEBIAN_FRONTEND=noninteractive\n  export NEEDRESTART_MODE=a\n  export NEEDRESTART_SUSPEND=1\n\n  # Get currently installed version\n  local CURRENT_PG_VERSION=\"\"\n  if command -v psql >/dev/null; then\n    CURRENT_PG_VERSION=\"$(psql -V 2>/dev/null | awk '{print $3}' | cut -d. -f1)\"\n  fi\n\n  # Scenario 1: Use distro repository (default, most stable)\n  if [[ \"$USE_PGDG_REPO\" != \"true\" && \"$USE_PGDG_REPO\" != \"TRUE\" && \"$USE_PGDG_REPO\" != \"1\" ]]; then\n    msg_info \"Setup PostgreSQL (distro package)\"\n\n    # If already installed, just update\n    if [[ -n \"$CURRENT_PG_VERSION\" ]]; then\n      msg_info \"Update PostgreSQL $CURRENT_PG_VERSION\"\n      ensure_apt_working || return 100\n      upgrade_packages_with_retry \"postgresql\" \"postgresql-client\" || true\n      cache_installed_version \"postgresql\" \"$CURRENT_PG_VERSION\"\n      msg_ok \"Update PostgreSQL $CURRENT_PG_VERSION\"\n\n      # Still install modules if specified\n      if [[ -n \"$PG_MODULES\" ]]; then\n        IFS=',' read -ra MODULES <<<\"$PG_MODULES\"\n        for module in \"${MODULES[@]}\"; do\n          $STD apt install -y \"postgresql-${CURRENT_PG_VERSION}-${module}\" 2>/dev/null || true\n        done\n      fi\n      _configure_pg_cron_preload \"$PG_MODULES\"\n      return 0\n    fi\n\n    # Fresh install from distro repo\n    ensure_apt_working || return 100\n\n    export DEBIAN_FRONTEND=noninteractive\n    install_packages_with_retry \"postgresql\" \"postgresql-client\" || {\n      msg_error \"Failed to install PostgreSQL from distro repository\"\n      return 100\n    }\n\n    # Get installed version\n    local INSTALLED_VERSION=\"\"\n    if command -v psql >/dev/null; then\n      INSTALLED_VERSION=\"$(psql -V 2>/dev/null | awk '{print $3}' | cut -d. -f1)\"\n    fi\n\n    $STD systemctl enable --now postgresql 2>/dev/null || true\n\n    # Add PostgreSQL binaries to PATH\n    if [[ -n \"$INSTALLED_VERSION\" ]] && ! grep -q '/usr/lib/postgresql' /etc/environment 2>/dev/null; then\n      echo 'PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/postgresql/'\"${INSTALLED_VERSION}\"'/bin\"' >/etc/environment\n    fi\n\n    cache_installed_version \"postgresql\" \"${INSTALLED_VERSION:-distro}\"\n    msg_ok \"Setup PostgreSQL ${INSTALLED_VERSION:-from distro}\"\n\n    # Install optional modules\n    if [[ -n \"$PG_MODULES\" && -n \"$INSTALLED_VERSION\" ]]; then\n      IFS=',' read -ra MODULES <<<\"$PG_MODULES\"\n      for module in \"${MODULES[@]}\"; do\n        $STD apt install -y \"postgresql-${INSTALLED_VERSION}-${module}\" 2>/dev/null || true\n      done\n    fi\n    _configure_pg_cron_preload \"$PG_MODULES\"\n    return 0\n  fi\n\n  # Scenario 2: Use official PGDG repository (USE_PGDG_REPO=true)\n  # Scenario 2a: Already at correct version\n  if [[ \"$CURRENT_PG_VERSION\" == \"$PG_VERSION\" ]]; then\n    msg_info \"Update PostgreSQL $PG_VERSION\"\n    ensure_apt_working || return 100\n\n    # Perform upgrade with retry logic (non-fatal if fails)\n    upgrade_packages_with_retry \"postgresql-${PG_VERSION}\" \"postgresql-client-${PG_VERSION}\" 2>/dev/null || true\n    cache_installed_version \"postgresql\" \"$PG_VERSION\"\n    msg_ok \"Update PostgreSQL $PG_VERSION\"\n\n    # Still install modules if specified\n    if [[ -n \"$PG_MODULES\" ]]; then\n      IFS=',' read -ra MODULES <<<\"$PG_MODULES\"\n      for module in \"${MODULES[@]}\"; do\n        $STD apt install -y \"postgresql-${PG_VERSION}-${module}\" 2>/dev/null || true\n      done\n    fi\n    _configure_pg_cron_preload \"$PG_MODULES\"\n    return 0\n  fi\n\n  # Scenario 2: Different version - backup, remove old, install new\n  if [[ -n \"$CURRENT_PG_VERSION\" ]]; then\n    msg_info \"Upgrade PostgreSQL from $CURRENT_PG_VERSION to $PG_VERSION\"\n    msg_info \"Creating backup of PostgreSQL $CURRENT_PG_VERSION databases...\"\n    local PG_BACKUP_FILE=\"/var/lib/postgresql/backup_$(date +%F)_v${CURRENT_PG_VERSION}.sql\"\n    $STD runuser -u postgres -- pg_dumpall >\"$PG_BACKUP_FILE\" || {\n      msg_error \"Failed to backup PostgreSQL databases\"\n      return 150\n    }\n    $STD systemctl stop postgresql || true\n    $STD apt purge -y \"postgresql-${CURRENT_PG_VERSION}\" \"postgresql-client-${CURRENT_PG_VERSION}\" 2>/dev/null || true\n  else\n    msg_info \"Setup PostgreSQL $PG_VERSION\"\n  fi\n\n  # Scenario 3: Fresh install or after removal - setup repo and install\n  prepare_repository_setup \"pgdg\" \"postgresql\" || {\n    msg_error \"Failed to prepare PostgreSQL repository\"\n    return 100\n  }\n\n  local SUITE\n  case \"$DISTRO_CODENAME\" in\n  trixie | forky | sid)\n\n    if verify_repo_available \"https://apt.postgresql.org/pub/repos/apt\" \"trixie-pgdg\"; then\n      SUITE=\"trixie-pgdg\"\n\n    else\n      msg_warn \"PGDG repo not available for ${DISTRO_CODENAME}, falling back to distro packages\"\n      USE_PGDG_REPO=false setup_postgresql\n      return $?\n    fi\n\n    ;;\n  *)\n    SUITE=$(get_fallback_suite \"$DISTRO_ID\" \"$DISTRO_CODENAME\" \"https://apt.postgresql.org/pub/repos/apt\")\n    SUITE=\"${SUITE}-pgdg\"\n    ;;\n  esac\n\n  setup_deb822_repo \\\n    \"pgdg\" \\\n    \"https://www.postgresql.org/media/keys/ACCC4CF8.asc\" \\\n    \"https://apt.postgresql.org/pub/repos/apt\" \\\n    \"$SUITE\" \\\n    \"main\"\n\n  if ! $STD apt update; then\n    msg_error \"APT update failed for PostgreSQL repository\"\n    return 100\n  fi\n\n  # Install ssl-cert dependency if available\n  if apt-cache search \"^ssl-cert$\" 2>/dev/null | grep -q .; then\n    $STD apt install -y ssl-cert 2>/dev/null || true\n  fi\n\n  # Try multiple PostgreSQL package patterns with retry logic\n  local pg_install_success=false\n\n  if apt-cache search \"^postgresql-${PG_VERSION}$\" 2>/dev/null | grep -q . &&\n    install_packages_with_retry \"postgresql-${PG_VERSION}\" \"postgresql-client-${PG_VERSION}\"; then\n    pg_install_success=true\n  fi\n\n  if [[ \"$pg_install_success\" == false ]] &&\n    apt-cache search \"^postgresql-server-${PG_VERSION}$\" 2>/dev/null | grep -q . &&\n    $STD apt install -y \"postgresql-server-${PG_VERSION}\" \"postgresql-client-${PG_VERSION}\" 2>/dev/null; then\n    pg_install_success=true\n  fi\n\n  if [[ \"$pg_install_success\" == false ]] &&\n    apt-cache search \"^postgresql$\" 2>/dev/null | grep -q . &&\n    $STD apt install -y postgresql postgresql-client 2>/dev/null; then\n    pg_install_success=true\n  fi\n\n  if [[ \"$pg_install_success\" == false ]]; then\n    msg_error \"PostgreSQL package not available for suite ${SUITE}\"\n    return 100\n  fi\n\n  if ! command -v psql >/dev/null 2>&1; then\n    msg_error \"PostgreSQL installed but psql command not found\"\n    return 127\n  fi\n\n  # Restore database backup if we upgraded from previous version\n  if [[ -n \"$CURRENT_PG_VERSION\" && -n \"${PG_BACKUP_FILE:-}\" && -f \"${PG_BACKUP_FILE}\" ]]; then\n    msg_info \"Restoring PostgreSQL databases from backup...\"\n    $STD runuser -u postgres -- psql <\"$PG_BACKUP_FILE\" 2>/dev/null || {\n      msg_warn \"Failed to restore database backup - this may be expected for major version upgrades\"\n    }\n  fi\n\n  $STD systemctl enable --now postgresql 2>/dev/null || {\n    msg_warn \"Failed to enable/start PostgreSQL service\"\n  }\n\n  # Add PostgreSQL binaries to PATH\n  if ! grep -q '/usr/lib/postgresql' /etc/environment 2>/dev/null; then\n    echo 'PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/postgresql/'\"${PG_VERSION}\"'/bin\"' >/etc/environment\n  fi\n\n  cache_installed_version \"postgresql\" \"$PG_VERSION\"\n  msg_ok \"Setup PostgreSQL $PG_VERSION\"\n\n  # Install optional modules\n  if [[ -n \"$PG_MODULES\" ]]; then\n    IFS=',' read -ra MODULES <<<\"$PG_MODULES\"\n    for module in \"${MODULES[@]}\"; do\n      $STD apt install -y \"postgresql-${PG_VERSION}-${module}\" 2>/dev/null || {\n        msg_warn \"Failed to install PostgreSQL module: ${module}\"\n      }\n    done\n  fi\n  _configure_pg_cron_preload \"$PG_MODULES\"\n}\n\n# ------------------------------------------------------------------------------\n# Creates PostgreSQL database with user and optional extensions\n#\n# Description:\n#   - Creates PostgreSQL role with login and password\n#   - Creates database with UTF8 encoding and template0\n#   - Installs optional extensions (postgis, pgvector, etc.)\n#   - Configures ALTER ROLE settings for Django/Rails compatibility\n#   - Saves credentials to file\n#   - Exports variables for use in calling script\n#\n# Usage:\n#   PG_DB_NAME=\"myapp_db\" PG_DB_USER=\"myapp_user\" setup_postgresql_db\n#   PG_DB_NAME=\"immich\" PG_DB_USER=\"immich\" PG_DB_EXTENSIONS=\"pgvector\" setup_postgresql_db\n#   PG_DB_NAME=\"ghostfolio\" PG_DB_USER=\"ghostfolio\" PG_DB_GRANT_SUPERUSER=\"true\" setup_postgresql_db\n#   PG_DB_NAME=\"adventurelog\" PG_DB_USER=\"adventurelog\" PG_DB_EXTENSIONS=\"postgis\" setup_postgresql_db\n#   PG_DB_NAME=\"splitpro\" PG_DB_USER=\"splitpro\" PG_DB_EXTENSIONS=\"pg_cron\" setup_postgresql_db\n#\n# Variables:\n#   PG_DB_NAME             - Database name (required)\n#   PG_DB_USER             - Database user (required)\n#   PG_DB_PASS             - Database password (optional, auto-generated if empty)\n#   PG_DB_EXTENSIONS       - Comma-separated list of extensions (optional, e.g. \"postgis,pgvector\")\n#   PG_DB_GRANT_SUPERUSER  - Grant SUPERUSER privilege (optional, \"true\" to enable, security risk!)\n#   PG_DB_SCHEMA_PERMS     - Grant schema-level permissions (optional, \"true\" to enable)\n#   PG_DB_SKIP_ALTER_ROLE  - Skip ALTER ROLE settings (optional, \"true\" to skip)\n#   PG_DB_CREDS_FILE       - Credentials file path (optional, default: ~/${APPLICATION}.creds)\n#\n# Exports:\n#   PG_DB_NAME, PG_DB_USER, PG_DB_PASS - For use in calling script\n# ------------------------------------------------------------------------------\n\nfunction setup_postgresql_db() {\n  # Validation\n  if [[ -z \"${PG_DB_NAME:-}\" || -z \"${PG_DB_USER:-}\" ]]; then\n    msg_error \"PG_DB_NAME and PG_DB_USER must be set before calling setup_postgresql_db\"\n    return 65\n  fi\n\n  # Generate password if not provided\n  if [[ -z \"${PG_DB_PASS:-}\" ]]; then\n    PG_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n  fi\n\n  msg_info \"Setting up PostgreSQL Database\"\n  $STD sudo -u postgres psql -c \"CREATE ROLE $PG_DB_USER WITH LOGIN PASSWORD '$PG_DB_PASS';\"\n  $STD sudo -u postgres psql -c \"CREATE DATABASE $PG_DB_NAME WITH OWNER $PG_DB_USER ENCODING 'UTF8' TEMPLATE template0;\"\n\n  # Configure pg_cron database BEFORE creating the extension (must be set before pg_cron loads)\n  if [[ -n \"${PG_DB_EXTENSIONS:-}\" ]] && [[ \",${PG_DB_EXTENSIONS//[[:space:]]/},\" == *\",pg_cron,\"* ]]; then\n    $STD sudo -u postgres psql -c \"ALTER SYSTEM SET cron.database_name = '${PG_DB_NAME}';\"\n    $STD sudo -u postgres psql -c \"ALTER SYSTEM SET cron.timezone = 'UTC';\"\n    $STD systemctl restart postgresql\n  fi\n\n  # Install extensions (comma-separated)\n  if [[ -n \"${PG_DB_EXTENSIONS:-}\" ]]; then\n    IFS=',' read -ra EXT_LIST <<<\"${PG_DB_EXTENSIONS:-}\"\n    for ext in \"${EXT_LIST[@]}\"; do\n      ext=$(echo \"$ext\" | xargs) # Trim whitespace\n      $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"CREATE EXTENSION IF NOT EXISTS $ext;\"\n    done\n  fi\n\n  # Grant pg_cron schema permissions to DB user\n  if [[ -n \"${PG_DB_EXTENSIONS:-}\" ]] && [[ \",${PG_DB_EXTENSIONS//[[:space:]]/},\" == *\",pg_cron,\"* ]]; then\n    $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"GRANT USAGE ON SCHEMA cron TO ${PG_DB_USER};\"\n    $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"GRANT ALL ON ALL TABLES IN SCHEMA cron TO ${PG_DB_USER};\"\n  fi\n\n  # ALTER ROLE settings for Django/Rails compatibility (unless skipped)\n  if [[ \"${PG_DB_SKIP_ALTER_ROLE:-}\" != \"true\" ]]; then\n    $STD sudo -u postgres psql -c \"ALTER ROLE $PG_DB_USER SET client_encoding TO 'utf8';\"\n    $STD sudo -u postgres psql -c \"ALTER ROLE $PG_DB_USER SET default_transaction_isolation TO 'read committed';\"\n    $STD sudo -u postgres psql -c \"ALTER ROLE $PG_DB_USER SET timezone TO 'UTC';\"\n  fi\n\n  # Schema permissions (if requested)\n  if [[ \"${PG_DB_SCHEMA_PERMS:-}\" == \"true\" ]]; then\n    $STD sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE $PG_DB_NAME TO $PG_DB_USER;\"\n    $STD sudo -u postgres psql -c \"ALTER USER $PG_DB_USER CREATEDB;\"\n    $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"GRANT ALL ON SCHEMA public TO $PG_DB_USER;\"\n    $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"GRANT CREATE ON SCHEMA public TO $PG_DB_USER;\"\n    $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $PG_DB_USER;\"\n    $STD sudo -u postgres psql -d \"$PG_DB_NAME\" -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $PG_DB_USER;\"\n  fi\n\n  # Superuser grant (if requested - WARNING!)\n  if [[ \"${PG_DB_GRANT_SUPERUSER:-}\" == \"true\" ]]; then\n    msg_warn \"Granting SUPERUSER privilege (security risk!)\"\n    $STD sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE $PG_DB_NAME to $PG_DB_USER;\"\n    $STD sudo -u postgres psql -c \"ALTER USER $PG_DB_USER WITH SUPERUSER;\"\n  fi\n\n  # Save credentials\n  local app_name=\"${APPLICATION,,}\"\n  local CREDS_FILE=\"${PG_DB_CREDS_FILE:-${HOME}/${app_name}.creds}\"\n  {\n    echo \"PostgreSQL Credentials\"\n    echo \"Database: $PG_DB_NAME\"\n    echo \"User: $PG_DB_USER\"\n    echo \"Password: $PG_DB_PASS\"\n  } >>\"$CREDS_FILE\"\n\n  msg_ok \"Set up PostgreSQL Database\"\n\n  # Export for use in calling script\n  export PG_DB_NAME\n  export PG_DB_USER\n  export PG_DB_PASS\n}\n# ------------------------------------------------------------------------------\n# Installs rbenv and ruby-build, installs Ruby and optionally Rails.\n#\n# Description:\n#   - Downloads rbenv and ruby-build from GitHub\n#   - Compiles and installs target Ruby version\n#   - Optionally installs Rails via gem\n#\n# Variables:\n#   RUBY_VERSION         - Ruby version to install (default: 3.4.4)\n#   RUBY_INSTALL_RAILS   - true/false to install Rails (default: true)\n# ------------------------------------------------------------------------------\n\nfunction setup_ruby() {\n  local RUBY_VERSION=\"${RUBY_VERSION:-3.4.4}\"\n  local RUBY_INSTALL_RAILS=\"${RUBY_INSTALL_RAILS:-true}\"\n  local RBENV_DIR=\"$HOME/.rbenv\"\n  local RBENV_BIN=\"$RBENV_DIR/bin/rbenv\"\n  local PROFILE_FILE=\"$HOME/.profile\"\n  local TMP_DIR=$(mktemp -d)\n\n  # Get currently installed Ruby version\n  local CURRENT_RUBY_VERSION=\"\"\n  if [[ -x \"$RBENV_BIN\" ]]; then\n    CURRENT_RUBY_VERSION=$(\"$RBENV_BIN\" global 2>/dev/null || echo \"\")\n  fi\n\n  # Scenario 1: Already at correct Ruby version\n  if [[ \"$CURRENT_RUBY_VERSION\" == \"$RUBY_VERSION\" ]]; then\n    msg_info \"Update Ruby $RUBY_VERSION\"\n    cache_installed_version \"ruby\" \"$RUBY_VERSION\"\n    msg_ok \"Update Ruby $RUBY_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Different version - reinstall\n  if [[ -n \"$CURRENT_RUBY_VERSION\" ]]; then\n    msg_info \"Upgrade Ruby from $CURRENT_RUBY_VERSION to $RUBY_VERSION\"\n  else\n    msg_info \"Setup Ruby $RUBY_VERSION\"\n  fi\n\n  ensure_apt_working || return 100\n\n  # Install build dependencies with fallbacks\n  local ruby_deps=()\n  local dep_variations=(\n    \"jq\"\n    \"autoconf\"\n    \"patch\"\n    \"build-essential\"\n    \"libssl-dev\"\n    \"libyaml-dev\"\n    \"libreadline-dev|libreadline6-dev\"\n    \"zlib1g-dev\"\n    \"libgmp-dev\"\n    \"libncurses-dev|libncurses5-dev\"\n    \"libffi-dev\"\n    \"libgdbm-dev\"\n    \"libdb-dev\"\n    \"uuid-dev\"\n  )\n\n  for dep_pattern in \"${dep_variations[@]}\"; do\n    if [[ \"$dep_pattern\" == *\"|\"* ]]; then\n      IFS='|' read -ra variations <<<\"$dep_pattern\"\n      for var in \"${variations[@]}\"; do\n        if apt-cache search \"^${var}$\" 2>/dev/null | grep -q .; then\n          ruby_deps+=(\"$var\")\n          break\n        fi\n      done\n    else\n      if apt-cache search \"^${dep_pattern}$\" 2>/dev/null | grep -q .; then\n        ruby_deps+=(\"$dep_pattern\")\n      fi\n    fi\n  done\n\n  if [[ ${#ruby_deps[@]} -gt 0 ]]; then\n    $STD apt install -y \"${ruby_deps[@]}\" 2>/dev/null || true\n  else\n    msg_error \"No Ruby build dependencies available\"\n    rm -rf \"$TMP_DIR\"\n    return 100\n  fi\n\n  # Download and build rbenv if needed\n  if [[ ! -x \"$RBENV_BIN\" ]]; then\n    local RBENV_RELEASE\n    local rbenv_json\n    rbenv_json=$(curl -fsSL --max-time 15 https://api.github.com/repos/rbenv/rbenv/releases/latest 2>/dev/null || echo \"\")\n\n    if [[ -z \"$rbenv_json\" ]]; then\n      msg_error \"Failed to fetch latest rbenv version from GitHub\"\n      rm -rf \"$TMP_DIR\"\n      return 7\n    fi\n\n    RBENV_RELEASE=$(echo \"$rbenv_json\" | jq -r '.tag_name' 2>/dev/null | sed 's/^v//' || echo \"\")\n\n    if [[ -z \"$RBENV_RELEASE\" ]]; then\n      msg_error \"Could not parse rbenv version from GitHub response\"\n      rm -rf \"$TMP_DIR\"\n      return 250\n    fi\n\n    if ! curl_with_retry \"https://github.com/rbenv/rbenv/archive/refs/tags/v${RBENV_RELEASE}.tar.gz\" \"$TMP_DIR/rbenv.tar.gz\"; then\n      msg_error \"Failed to download rbenv\"\n      rm -rf \"$TMP_DIR\"\n      return 7\n    fi\n\n    tar -xzf \"$TMP_DIR/rbenv.tar.gz\" -C \"$TMP_DIR\" || {\n      msg_error \"Failed to extract rbenv\"\n      rm -rf \"$TMP_DIR\"\n      return 251\n    }\n\n    mkdir -p \"$RBENV_DIR\"\n    cp -r \"$TMP_DIR/rbenv-${RBENV_RELEASE}/.\" \"$RBENV_DIR/\"\n    (cd \"$RBENV_DIR\" && src/configure && $STD make -C src) || {\n      msg_error \"Failed to build rbenv\"\n      rm -rf \"$TMP_DIR\"\n      return 150\n    }\n\n    # Setup profile\n    if ! grep -q 'rbenv init' \"$PROFILE_FILE\" 2>/dev/null; then\n      echo 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' >>\"$PROFILE_FILE\"\n      echo 'eval \"$(rbenv init -)\"' >>\"$PROFILE_FILE\"\n    fi\n  fi\n\n  # Install ruby-build plugin\n  if [[ ! -d \"$RBENV_DIR/plugins/ruby-build\" ]]; then\n    local RUBY_BUILD_RELEASE\n    local ruby_build_json\n    ruby_build_json=$(curl -fsSL --max-time 15 https://api.github.com/repos/rbenv/ruby-build/releases/latest 2>/dev/null || echo \"\")\n\n    if [[ -z \"$ruby_build_json\" ]]; then\n      msg_error \"Failed to fetch latest ruby-build version from GitHub\"\n      rm -rf \"$TMP_DIR\"\n      return 7\n    fi\n\n    RUBY_BUILD_RELEASE=$(echo \"$ruby_build_json\" | jq -r '.tag_name' 2>/dev/null | sed 's/^v//' || echo \"\")\n\n    if [[ -z \"$RUBY_BUILD_RELEASE\" ]]; then\n      msg_error \"Could not parse ruby-build version from GitHub response\"\n      rm -rf \"$TMP_DIR\"\n      return 250\n    fi\n\n    if ! curl_with_retry \"https://github.com/rbenv/ruby-build/archive/refs/tags/v${RUBY_BUILD_RELEASE}.tar.gz\" \"$TMP_DIR/ruby-build.tar.gz\"; then\n      msg_error \"Failed to download ruby-build\"\n      rm -rf \"$TMP_DIR\"\n      return 7\n    fi\n\n    tar -xzf \"$TMP_DIR/ruby-build.tar.gz\" -C \"$TMP_DIR\" || {\n      msg_error \"Failed to extract ruby-build\"\n      rm -rf \"$TMP_DIR\"\n      return 251\n    }\n\n    mkdir -p \"$RBENV_DIR/plugins/ruby-build\"\n    cp -r \"$TMP_DIR/ruby-build-${RUBY_BUILD_RELEASE}/.\" \"$RBENV_DIR/plugins/ruby-build/\"\n  fi\n\n  # Setup PATH and install Ruby version\n  export PATH=\"$RBENV_DIR/bin:$PATH\"\n  eval \"$(\"$RBENV_BIN\" init - bash)\" 2>/dev/null || true\n\n  if ! \"$RBENV_BIN\" versions --bare 2>/dev/null | grep -qx \"$RUBY_VERSION\"; then\n    $STD \"$RBENV_BIN\" install \"$RUBY_VERSION\" || {\n      msg_error \"Failed to install Ruby $RUBY_VERSION\"\n      rm -rf \"$TMP_DIR\"\n      return 150\n    }\n  fi\n\n  \"$RBENV_BIN\" global \"$RUBY_VERSION\" || {\n    msg_error \"Failed to set Ruby $RUBY_VERSION as global version\"\n    rm -rf \"$TMP_DIR\"\n    return 150\n  }\n\n  hash -r\n\n  # Install Rails if requested\n  if [[ \"$RUBY_INSTALL_RAILS\" == \"true\" ]]; then\n    $STD gem install rails || {\n      msg_warn \"Failed to install Rails - Ruby installation successful\"\n    }\n  fi\n\n  rm -rf \"$TMP_DIR\"\n  cache_installed_version \"ruby\" \"$RUBY_VERSION\"\n  msg_ok \"Setup Ruby $RUBY_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs or updates MeiliSearch search engine.\n#\n# Description:\n#   - Fresh install: Downloads binary, creates config/service, starts\n#   - Update: Checks for new release, updates binary if available\n#   - Waits for service to be ready before returning\n#   - Exports API keys for use by caller\n#\n# Variables:\n#   MEILISEARCH_BIND     - Bind address (default: 127.0.0.1:7700)\n#   MEILISEARCH_ENV      - Environment: production/development (default: production)\n#   MEILISEARCH_DB_PATH  - Database path (default: /var/lib/meilisearch/data)\n#\n# Exports:\n#   MEILISEARCH_MASTER_KEY  - The master key for admin access\n#   MEILISEARCH_API_KEY     - The default search API key\n#   MEILISEARCH_API_KEY_UID - The UID of the default API key\n#\n# Example (install script):\n#   setup_meilisearch\n#\n# Example (CT update_script):\n#   setup_meilisearch\n# ------------------------------------------------------------------------------\n\nfunction setup_meilisearch() {\n  local MEILISEARCH_BIND=\"${MEILISEARCH_BIND:-127.0.0.1:7700}\"\n  local MEILISEARCH_ENV=\"${MEILISEARCH_ENV:-production}\"\n  local MEILISEARCH_DB_PATH=\"${MEILISEARCH_DB_PATH:-/var/lib/meilisearch/data}\"\n  local MEILISEARCH_DUMP_DIR=\"${MEILISEARCH_DUMP_DIR:-/var/lib/meilisearch/dumps}\"\n  local MEILISEARCH_SNAPSHOT_DIR=\"${MEILISEARCH_SNAPSHOT_DIR:-/var/lib/meilisearch/snapshots}\"\n\n  # Get bind address for health checks\n  local MEILISEARCH_HOST=\"${MEILISEARCH_BIND%%:*}\"\n  local MEILISEARCH_PORT=\"${MEILISEARCH_BIND##*:}\"\n  [[ \"$MEILISEARCH_HOST\" == \"0.0.0.0\" ]] && MEILISEARCH_HOST=\"127.0.0.1\"\n\n  # Update mode: MeiliSearch already installed\n  if [[ -f /usr/bin/meilisearch ]]; then\n    if check_for_gh_release \"meilisearch\" \"meilisearch/meilisearch\"; then\n      msg_info \"Updating MeiliSearch\"\n\n      # Get current and new version for compatibility check\n      local CURRENT_VERSION NEW_VERSION\n      CURRENT_VERSION=$(/usr/bin/meilisearch --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -1) || CURRENT_VERSION=\"0.0.0\"\n      NEW_VERSION=\"${CHECK_UPDATE_RELEASE#v}\"\n\n      # Extract major.minor for comparison (Meilisearch requires dump/restore between minor versions)\n      local CURRENT_MAJOR_MINOR NEW_MAJOR_MINOR\n      CURRENT_MAJOR_MINOR=$(echo \"$CURRENT_VERSION\" | cut -d. -f1,2)\n      NEW_MAJOR_MINOR=$(echo \"$NEW_VERSION\" | cut -d. -f1,2)\n\n      # Determine if migration is needed (different major.minor = incompatible DB format)\n      local NEEDS_MIGRATION=false\n      if [[ \"$CURRENT_MAJOR_MINOR\" != \"$NEW_MAJOR_MINOR\" ]]; then\n        NEEDS_MIGRATION=true\n        msg_info \"MeiliSearch version change detected (${CURRENT_VERSION} → ${NEW_VERSION}), preparing data migration\"\n      fi\n\n      # Read config values for dump/restore\n      local MEILI_HOST MEILI_PORT MEILI_MASTER_KEY MEILI_DUMP_DIR\n      MEILI_HOST=\"${MEILISEARCH_HOST:-127.0.0.1}\"\n      MEILI_PORT=\"${MEILISEARCH_PORT:-7700}\"\n      MEILI_DUMP_DIR=\"${MEILISEARCH_DUMP_DIR:-/var/lib/meilisearch/dumps}\"\n      MEILI_MASTER_KEY=$(grep -E \"^master_key\\s*=\" /etc/meilisearch.toml 2>/dev/null | sed 's/.*=\\s*\"\\(.*\\)\"/\\1/' | tr -d ' ' || true)\n\n      # Create dump before update if migration is needed\n      local DUMP_UID=\"\"\n      if [[ \"$NEEDS_MIGRATION\" == \"true\" ]] && [[ -n \"$MEILI_MASTER_KEY\" ]]; then\n        msg_info \"Creating MeiliSearch data dump before upgrade\"\n\n        # Trigger dump creation\n        local DUMP_RESPONSE\n        DUMP_RESPONSE=$(curl -s -X POST \"http://${MEILI_HOST}:${MEILI_PORT}/dumps\" \\\n          -H \"Authorization: Bearer ${MEILI_MASTER_KEY}\" \\\n          -H \"Content-Type: application/json\" 2>/dev/null) || true\n\n        # The initial response only contains taskUid, not dumpUid\n        # dumpUid is only available after the task completes\n        local TASK_UID\n        TASK_UID=$(echo \"$DUMP_RESPONSE\" | grep -oP '\"taskUid\":\\s*\\K[0-9]+' || true)\n\n        if [[ -n \"$TASK_UID\" ]]; then\n          msg_info \"Waiting for dump task ${TASK_UID} to complete...\"\n          local MAX_WAIT=120\n          local WAITED=0\n          local TASK_RESULT=\"\"\n\n          while [[ $WAITED -lt $MAX_WAIT ]]; do\n            TASK_RESULT=$(curl -s \"http://${MEILI_HOST}:${MEILI_PORT}/tasks/${TASK_UID}\" \\\n              -H \"Authorization: Bearer ${MEILI_MASTER_KEY}\" 2>/dev/null) || true\n\n            local TASK_STATUS\n            TASK_STATUS=$(echo \"$TASK_RESULT\" | grep -oP '\"status\":\\s*\"\\K[^\"]+' || true)\n\n            if [[ \"$TASK_STATUS\" == \"succeeded\" ]]; then\n              # Extract dumpUid from the completed task details\n              DUMP_UID=$(echo \"$TASK_RESULT\" | grep -oP '\"dumpUid\":\\s*\"\\K[^\"]+' || true)\n              if [[ -n \"$DUMP_UID\" ]]; then\n                msg_ok \"MeiliSearch dump created successfully: ${DUMP_UID}\"\n              else\n                msg_warn \"Dump task succeeded but could not extract dumpUid\"\n              fi\n              break\n            elif [[ \"$TASK_STATUS\" == \"failed\" ]]; then\n              local ERROR_MSG\n              ERROR_MSG=$(echo \"$TASK_RESULT\" | grep -oP '\"message\":\\s*\"\\K[^\"]+' || echo \"Unknown error\")\n              msg_warn \"MeiliSearch dump failed: ${ERROR_MSG}\"\n              break\n            fi\n            sleep 2\n            WAITED=$((WAITED + 2))\n          done\n\n          if [[ $WAITED -ge $MAX_WAIT ]]; then\n            msg_warn \"MeiliSearch dump timed out after ${MAX_WAIT}s\"\n          fi\n        else\n          msg_warn \"Could not trigger MeiliSearch dump (no taskUid in response)\"\n          msg_info \"Response was: ${DUMP_RESPONSE:-empty}\"\n        fi\n      fi\n\n      # If migration is needed but dump failed, we have options:\n      # 1. Abort the update (safest, but annoying)\n      # 2. Backup data directory and proceed (allows manual recovery)\n      # 3. Just proceed and hope for the best (dangerous)\n      # We choose option 2: backup and proceed with warning\n      if [[ \"$NEEDS_MIGRATION\" == \"true\" ]] && [[ -z \"$DUMP_UID\" ]]; then\n        local MEILI_DB_PATH\n        MEILI_DB_PATH=$(grep -E \"^db_path\\s*=\" /etc/meilisearch.toml 2>/dev/null | sed 's/.*=\\s*\"\\(.*\\)\"/\\1/' | tr -d ' ' || true)\n        MEILI_DB_PATH=\"${MEILI_DB_PATH:-/var/lib/meilisearch/data}\"\n\n        if [[ -d \"$MEILI_DB_PATH\" ]] && [[ -n \"$(ls -A \"$MEILI_DB_PATH\" 2>/dev/null)\" ]]; then\n          local BACKUP_PATH=\"${MEILI_DB_PATH}.backup.$(date +%Y%m%d%H%M%S)\"\n          msg_warn \"Backing up MeiliSearch data to ${BACKUP_PATH}\"\n          mv \"$MEILI_DB_PATH\" \"$BACKUP_PATH\"\n          mkdir -p \"$MEILI_DB_PATH\"\n          msg_info \"Data backed up. After update, you may need to reindex your data.\"\n          msg_info \"Old data is preserved at: ${BACKUP_PATH}\"\n        fi\n      fi\n\n      # Stop service and update binary\n      systemctl stop meilisearch\n      fetch_and_deploy_gh_release \"meilisearch\" \"meilisearch/meilisearch\" \"binary\"\n\n      # If migration needed and dump was created, remove old data and import dump\n      if [[ \"$NEEDS_MIGRATION\" == \"true\" ]] && [[ -n \"$DUMP_UID\" ]]; then\n        local MEILI_DB_PATH\n        MEILI_DB_PATH=$(grep -E \"^db_path\\s*=\" /etc/meilisearch.toml 2>/dev/null | sed 's/.*=\\s*\"\\(.*\\)\"/\\1/' | tr -d ' ' || true)\n        MEILI_DB_PATH=\"${MEILI_DB_PATH:-/var/lib/meilisearch/data}\"\n\n        msg_info \"Removing old MeiliSearch database for migration\"\n        rm -rf \"${MEILI_DB_PATH:?}\"/*\n\n        # Import dump using CLI flag (this is the supported method)\n        local DUMP_FILE=\"${MEILI_DUMP_DIR}/${DUMP_UID}.dump\"\n        if [[ -f \"$DUMP_FILE\" ]]; then\n          msg_info \"Importing dump: ${DUMP_FILE}\"\n\n          # Start meilisearch with --import-dump flag\n          # This is a one-time import that happens during startup\n          /usr/bin/meilisearch --config-file-path /etc/meilisearch.toml --import-dump \"$DUMP_FILE\" >/dev/null 2>&1 &\n          local MEILI_PID=$!\n\n          # Wait for meilisearch to become healthy (import happens during startup)\n          msg_info \"Waiting for MeiliSearch to import and start...\"\n          local MAX_WAIT=300\n          local WAITED=0\n          while [[ $WAITED -lt $MAX_WAIT ]]; do\n            if curl -sf \"http://${MEILI_HOST}:${MEILI_PORT}/health\" &>/dev/null; then\n              msg_ok \"MeiliSearch is healthy after import\"\n              break\n            fi\n            # Check if process is still running\n            if ! kill -0 $MEILI_PID 2>/dev/null; then\n              msg_warn \"MeiliSearch process exited during import\"\n              break\n            fi\n            sleep 3\n            WAITED=$((WAITED + 3))\n          done\n\n          # Stop the manual process\n          kill $MEILI_PID 2>/dev/null || true\n          wait $MEILI_PID 2>/dev/null || true\n          sleep 2\n\n          # Start via systemd for proper management\n          systemctl start meilisearch\n\n          if systemctl is-active --quiet meilisearch; then\n            msg_ok \"MeiliSearch migrated successfully\"\n          else\n            msg_warn \"MeiliSearch failed to start after migration - check logs with: journalctl -u meilisearch\"\n          fi\n        else\n          msg_warn \"Dump file not found: ${DUMP_FILE}\"\n          systemctl start meilisearch\n        fi\n      else\n        systemctl start meilisearch\n      fi\n\n      msg_ok \"Updated MeiliSearch\"\n    fi\n    return 0\n  fi\n\n  # Fresh install\n  msg_info \"Setup MeiliSearch\"\n\n  # Install binary\n  fetch_and_deploy_gh_release \"meilisearch\" \"meilisearch/meilisearch\" \"binary\" || {\n    msg_error \"Failed to install MeiliSearch binary\"\n    return 250\n  }\n\n  # Download default config\n  curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml -o /etc/meilisearch.toml || {\n    msg_error \"Failed to download MeiliSearch config\"\n    return 7\n  }\n\n  # Generate master key\n  MEILISEARCH_MASTER_KEY=$(openssl rand -base64 12)\n  export MEILISEARCH_MASTER_KEY\n\n  # Configure\n  sed -i \\\n    -e \"s|^env =.*|env = \\\"${MEILISEARCH_ENV}\\\"|\" \\\n    -e \"s|^# master_key =.*|master_key = \\\"${MEILISEARCH_MASTER_KEY}\\\"|\" \\\n    -e \"s|^db_path =.*|db_path = \\\"${MEILISEARCH_DB_PATH}\\\"|\" \\\n    -e \"s|^dump_dir =.*|dump_dir = \\\"${MEILISEARCH_DUMP_DIR}\\\"|\" \\\n    -e \"s|^snapshot_dir =.*|snapshot_dir = \\\"${MEILISEARCH_SNAPSHOT_DIR}\\\"|\" \\\n    -e 's|^# no_analytics = true|no_analytics = true|' \\\n    -e \"s|^http_addr =.*|http_addr = \\\"${MEILISEARCH_BIND}\\\"|\" \\\n    /etc/meilisearch.toml\n\n  # Create data directories\n  mkdir -p \"${MEILISEARCH_DB_PATH}\" \"${MEILISEARCH_DUMP_DIR}\" \"${MEILISEARCH_SNAPSHOT_DIR}\"\n\n  # Create systemd service\n  cat <<EOF >/etc/systemd/system/meilisearch.service\n[Unit]\nDescription=Meilisearch\nAfter=network.target\n\n[Service]\nExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n  # Enable and start service\n  systemctl daemon-reload\n  systemctl enable -q --now meilisearch\n\n  # Wait for MeiliSearch to be ready (up to 30 seconds)\n  for i in {1..30}; do\n    if curl -s -o /dev/null -w \"%{http_code}\" \"http://${MEILISEARCH_HOST}:${MEILISEARCH_PORT}/health\" 2>/dev/null | grep -q \"200\"; then\n      break\n    fi\n    sleep 1\n  done\n\n  # Verify service is running\n  if ! systemctl is-active --quiet meilisearch; then\n    msg_error \"MeiliSearch service failed to start\"\n    return 150\n  fi\n\n  # Get API keys with retry logic\n  MEILISEARCH_API_KEY=\"\"\n  for i in {1..10}; do\n    MEILISEARCH_API_KEY=$(curl -s -X GET \"http://${MEILISEARCH_HOST}:${MEILISEARCH_PORT}/keys\" \\\n      -H \"Authorization: Bearer ${MEILISEARCH_MASTER_KEY}\" 2>/dev/null |\n      grep -o '\"key\":\"[^\"]*\"' | head -n 1 | sed 's/\"key\":\"//;s/\"//') || true\n    [[ -n \"$MEILISEARCH_API_KEY\" ]] && break\n    sleep 2\n  done\n\n  MEILISEARCH_API_KEY_UID=$(curl -s -X GET \"http://${MEILISEARCH_HOST}:${MEILISEARCH_PORT}/keys\" \\\n    -H \"Authorization: Bearer ${MEILISEARCH_MASTER_KEY}\" 2>/dev/null |\n    grep -o '\"uid\":\"[^\"]*\"' | head -n 1 | sed 's/\"uid\":\"//;s/\"//') || true\n\n  export MEILISEARCH_API_KEY\n  export MEILISEARCH_API_KEY_UID\n\n  # Cache version\n  local MEILISEARCH_VERSION\n  MEILISEARCH_VERSION=$(/usr/bin/meilisearch --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -1) || true\n  cache_installed_version \"meilisearch\" \"${MEILISEARCH_VERSION:-unknown}\"\n\n  msg_ok \"Setup MeiliSearch ${MEILISEARCH_VERSION:-}\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs or upgrades ClickHouse database server.\n#\n# Description:\n#   - Adds ClickHouse official repository\n#   - Installs specified version\n#   - Configures systemd service\n#   - Supports Debian/Ubuntu with fallback mechanism\n#\n# Variables:\n#   CLICKHOUSE_VERSION  - ClickHouse version to install (default: latest)\n# ------------------------------------------------------------------------------\n\nfunction setup_clickhouse() {\n  local CLICKHOUSE_VERSION=\"${CLICKHOUSE_VERSION:-latest}\"\n  local DISTRO_ID DISTRO_CODENAME\n  DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '\"')\n  DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)\n\n  # Ensure non-interactive mode for all apt operations\n  export DEBIAN_FRONTEND=noninteractive\n  export NEEDRESTART_MODE=a\n  export NEEDRESTART_SUSPEND=1\n\n  # Resolve \"latest\" version\n  if [[ \"$CLICKHOUSE_VERSION\" == \"latest\" ]]; then\n    CLICKHOUSE_VERSION=$(curl -fsSL --max-time 15 https://packages.clickhouse.com/tgz/stable/ 2>/dev/null |\n      grep -oP 'clickhouse-common-static-\\K[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' |\n      sort -V | tail -n1 || echo \"\")\n\n    # Fallback to GitHub API if package server failed\n    if [[ -z \"$CLICKHOUSE_VERSION\" ]]; then\n      CLICKHOUSE_VERSION=$(curl -fsSL --max-time 15 https://api.github.com/repos/ClickHouse/ClickHouse/releases/latest 2>/dev/null |\n        grep -oP '\"tag_name\":\\s*\"v\\K[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' | head -n1 || echo \"\")\n    fi\n\n    [[ -z \"$CLICKHOUSE_VERSION\" ]] && {\n      msg_error \"Could not determine latest ClickHouse version from any source\"\n      return 250\n    }\n  fi\n\n  # Get currently installed version\n  local CURRENT_VERSION=\"\"\n  if command -v clickhouse-server >/dev/null 2>&1; then\n    CURRENT_VERSION=$(clickhouse-server --version 2>/dev/null | grep -oP 'version \\K[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' | head -n1)\n  fi\n\n  # Scenario 1: Already at target version - just update packages\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" == \"$CLICKHOUSE_VERSION\" ]]; then\n    msg_info \"Update ClickHouse $CLICKHOUSE_VERSION\"\n    ensure_apt_working || return 100\n\n    # Perform upgrade with retry logic (non-fatal if fails)\n    upgrade_packages_with_retry \"clickhouse-server\" \"clickhouse-client\" || {\n      msg_warn \"ClickHouse package upgrade had issues, continuing with current version\"\n    }\n    cache_installed_version \"clickhouse\" \"$CLICKHOUSE_VERSION\"\n    msg_ok \"Update ClickHouse $CLICKHOUSE_VERSION\"\n    return 0\n  fi\n\n  # Scenario 2: Different version - clean upgrade\n  if [[ -n \"$CURRENT_VERSION\" && \"$CURRENT_VERSION\" != \"$CLICKHOUSE_VERSION\" ]]; then\n    msg_info \"Upgrade ClickHouse from $CURRENT_VERSION to $CLICKHOUSE_VERSION\"\n    stop_all_services \"clickhouse-server\"\n    remove_old_tool_version \"clickhouse\"\n  else\n    msg_info \"Setup ClickHouse $CLICKHOUSE_VERSION\"\n  fi\n\n  ensure_dependencies apt-transport-https ca-certificates dirmngr gnupg\n\n  # Prepare repository (cleanup + validation)\n  prepare_repository_setup \"clickhouse\" || {\n    msg_error \"Failed to prepare ClickHouse repository\"\n    return 100\n  }\n\n  # Setup repository (ClickHouse uses 'stable' suite)\n  setup_deb822_repo \\\n    \"clickhouse\" \\\n    \"https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key\" \\\n    \"https://packages.clickhouse.com/deb\" \\\n    \"stable\" \\\n    \"main\"\n\n  # Install packages with retry logic\n  $STD apt update || {\n    msg_error \"APT update failed for ClickHouse repository\"\n    return 100\n  }\n\n  install_packages_with_retry \"clickhouse-server\" \"clickhouse-client\" || {\n    msg_error \"Failed to install ClickHouse packages\"\n    return 100\n  }\n\n  # Verify installation\n  if ! command -v clickhouse-server >/dev/null 2>&1; then\n    msg_error \"ClickHouse installation completed but clickhouse-server command not found\"\n    return 127\n  fi\n\n  # Setup data directory\n  mkdir -p /var/lib/clickhouse\n  if id clickhouse >/dev/null 2>&1; then\n    chown -R clickhouse:clickhouse /var/lib/clickhouse\n  fi\n\n  # Enable and start service\n  $STD systemctl enable clickhouse-server || {\n    msg_warn \"Failed to enable clickhouse-server service\"\n  }\n  safe_service_restart clickhouse-server || true\n\n  cache_installed_version \"clickhouse\" \"$CLICKHOUSE_VERSION\"\n  msg_ok \"Setup ClickHouse $CLICKHOUSE_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Installs Rust toolchain and optional global crates via cargo.\n#\n# Description:\n#   - Installs rustup (if missing)\n#   - Installs or updates desired Rust toolchain (stable, nightly, or versioned)\n#   - Installs or updates specified global crates using `cargo install`\n#\n# Notes:\n#   - Skips crate install if exact version is already present\n#   - Updates crate if newer version or different version is requested\n#\n# Variables:\n#   RUST_TOOLCHAIN  - Rust toolchain to install (default: stable)\n#   RUST_CRATES     - Comma-separated list of crates (e.g. \"cargo-edit,wasm-pack@0.12.1\")\n# ------------------------------------------------------------------------------\n\nfunction setup_rust() {\n  local RUST_TOOLCHAIN=\"${RUST_TOOLCHAIN:-stable}\"\n  local RUST_CRATES=\"${RUST_CRATES:-}\"\n  local CARGO_BIN=\"${HOME}/.cargo/bin\"\n\n  # Get currently installed version\n  local CURRENT_VERSION=\"\"\n  if command -v rustc &>/dev/null; then\n    CURRENT_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true\n  fi\n\n  # Scenario 1: Rustup not installed - fresh install\n  if ! command -v rustup &>/dev/null; then\n    msg_info \"Setup Rust ($RUST_TOOLCHAIN)\"\n    curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --default-toolchain \"$RUST_TOOLCHAIN\" || {\n      msg_error \"Failed to install Rust\"\n      return 7\n    }\n    export PATH=\"$CARGO_BIN:$PATH\"\n    echo 'export PATH=\"$HOME/.cargo/bin:$PATH\"' >>\"$HOME/.profile\"\n\n    # Verify installation\n    if ! command -v rustc >/dev/null 2>&1; then\n      msg_error \"Rust binary not found after installation\"\n      return 127\n    fi\n\n    local RUST_VERSION\n    RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true\n    if [[ -z \"$RUST_VERSION\" ]]; then\n      msg_error \"Failed to determine Rust version\"\n      return 250\n    fi\n\n    cache_installed_version \"rust\" \"$RUST_VERSION\"\n    msg_ok \"Setup Rust $RUST_VERSION\"\n  else\n    # Scenario 2: Rustup already installed - update/maintain\n    msg_info \"Update Rust ($RUST_TOOLCHAIN)\"\n\n    # Ensure default toolchain is set\n    $STD rustup default \"$RUST_TOOLCHAIN\" 2>/dev/null || {\n      # If default fails, install the toolchain first\n      $STD rustup install \"$RUST_TOOLCHAIN\" || {\n        msg_error \"Failed to install Rust toolchain $RUST_TOOLCHAIN\"\n        return 150\n      }\n      $STD rustup default \"$RUST_TOOLCHAIN\" || {\n        msg_error \"Failed to set default Rust toolchain\"\n        return 150\n      }\n    }\n\n    # Update to latest patch version\n    $STD rustup update \"$RUST_TOOLCHAIN\" </dev/null || {\n      msg_warn \"Rust toolchain update had issues\"\n    }\n\n    # Ensure PATH is updated for current shell session\n    export PATH=\"$CARGO_BIN:$PATH\"\n\n    local RUST_VERSION\n    RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true\n    if [[ -z \"$RUST_VERSION\" ]]; then\n      msg_error \"Failed to determine Rust version after update\"\n      return 250\n    fi\n\n    cache_installed_version \"rust\" \"$RUST_VERSION\"\n    msg_ok \"Update Rust $RUST_VERSION\"\n  fi\n\n  # Install global crates\n  if [[ -n \"$RUST_CRATES\" ]]; then\n    msg_info \"Processing Rust crates: $RUST_CRATES\"\n    IFS=',' read -ra CRATES <<<\"$RUST_CRATES\"\n    for crate in \"${CRATES[@]}\"; do\n      crate=$(echo \"$crate\" | xargs) # trim whitespace\n      [[ -z \"$crate\" ]] && continue  # skip empty entries\n\n      local NAME VER INSTALLED_VER CRATE_LIST\n      if [[ \"$crate\" == *\"@\"* ]]; then\n        NAME=\"${crate%@*}\"\n        VER=\"${crate##*@}\"\n      else\n        NAME=\"$crate\"\n        VER=\"\"\n      fi\n\n      # Get list of installed crates once\n      CRATE_LIST=$(cargo install --list 2>/dev/null || echo \"\")\n\n      # Check if already installed\n      if echo \"$CRATE_LIST\" | grep -q \"^${NAME} \"; then\n        INSTALLED_VER=$(echo \"$CRATE_LIST\" | grep \"^${NAME} \" | head -1 | awk '{print $2}' 2>/dev/null | tr -d 'v:' || echo '')\n\n        if [[ -n \"$VER\" && \"$VER\" != \"$INSTALLED_VER\" ]]; then\n          msg_info \"Upgrading $NAME from v$INSTALLED_VER to v$VER\"\n          $STD cargo install \"$NAME\" --version \"$VER\" --force || {\n            msg_error \"Failed to install $NAME@$VER\"\n            return 150\n          }\n          msg_ok \"Upgraded $NAME to v$VER\"\n        elif [[ -z \"$VER\" ]]; then\n          msg_info \"Upgrading $NAME to latest\"\n          $STD cargo install \"$NAME\" --force || {\n            msg_error \"Failed to upgrade $NAME\"\n            return 150\n          }\n          local NEW_VER=$(cargo install --list 2>/dev/null | grep \"^${NAME} \" | head -1 | awk '{print $2}' 2>/dev/null | tr -d 'v:' || echo 'unknown')\n          msg_ok \"Upgraded $NAME to v$NEW_VER\"\n        else\n          msg_ok \"$NAME v$INSTALLED_VER already installed\"\n        fi\n      else\n        msg_info \"Installing $NAME${VER:+@$VER}\"\n        if [[ -n \"$VER\" ]]; then\n          $STD cargo install \"$NAME\" --version \"$VER\" || {\n            msg_error \"Failed to install $NAME@$VER\"\n            return 150\n          }\n          msg_ok \"Installed $NAME v$VER\"\n        else\n          $STD cargo install \"$NAME\" || {\n            msg_error \"Failed to install $NAME\"\n            return 150\n          }\n          local NEW_VER=$(cargo install --list 2>/dev/null | grep \"^${NAME} \" | head -1 | awk '{print $2}' 2>/dev/null | tr -d 'v:' || echo 'unknown')\n          msg_ok \"Installed $NAME v$NEW_VER\"\n        fi\n      fi\n    done\n    msg_ok \"Processed Rust crates\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Installs or upgrades uv (Python package manager) from GitHub releases.\n#   - Downloads platform-specific tarball (no install.sh!)\n#   - Extracts uv binary\n#   - Places it in /usr/local/bin\n#   - Optionally installs a specific Python version via uv\n# ------------------------------------------------------------------------------\n\nfunction setup_uv() {\n  local UV_BIN=\"/usr/local/bin/uv\"\n  local UVX_BIN=\"/usr/local/bin/uvx\"\n  local TMP_DIR=$(mktemp -d)\n  local CACHED_VERSION\n\n  # trap for TMP Cleanup\n  trap \"rm -rf '$TMP_DIR'\" EXIT\n\n  CACHED_VERSION=$(get_cached_version \"uv\")\n\n  # Architecture Detection\n  local ARCH=$(uname -m)\n  local OS_TYPE=\"\"\n  local UV_TAR=\"\"\n\n  if grep -qi \"alpine\" /etc/os-release; then\n    OS_TYPE=\"musl\"\n  else\n    OS_TYPE=\"gnu\"\n  fi\n\n  case \"$ARCH\" in\n  x86_64)\n    UV_TAR=\"uv-x86_64-unknown-linux-${OS_TYPE}.tar.gz\"\n    ;;\n  aarch64)\n    UV_TAR=\"uv-aarch64-unknown-linux-${OS_TYPE}.tar.gz\"\n    ;;\n  i686)\n    UV_TAR=\"uv-i686-unknown-linux-${OS_TYPE}.tar.gz\"\n    ;;\n  *)\n    msg_error \"Unsupported architecture: $ARCH (supported: x86_64, aarch64, i686)\"\n    return 236\n    ;;\n  esac\n\n  ensure_dependencies jq\n\n  # Fetch latest version\n  local releases_json\n  releases_json=$(curl -fsSL --max-time 15 \\\n    \"https://api.github.com/repos/astral-sh/uv/releases/latest\" 2>/dev/null || echo \"\")\n\n  if [[ -z \"$releases_json\" ]]; then\n    msg_error \"Could not fetch latest uv version from GitHub API\"\n    return 7\n  fi\n\n  local LATEST_VERSION\n  LATEST_VERSION=$(echo \"$releases_json\" | jq -r '.tag_name' 2>/dev/null | sed 's/^v//')\n\n  if [[ -z \"$LATEST_VERSION\" ]]; then\n    msg_error \"Could not parse uv version from GitHub API response\"\n    return 250\n  fi\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  if [[ -x \"$UV_BIN\" ]]; then\n    INSTALLED_VERSION=$(\"$UV_BIN\" --version 2>/dev/null | awk '{print $2}')\n  fi\n\n  # Scenario 1: Already at latest version\n  if [[ -n \"$INSTALLED_VERSION\" && \"$INSTALLED_VERSION\" == \"$LATEST_VERSION\" ]]; then\n    cache_installed_version \"uv\" \"$LATEST_VERSION\"\n\n    # Check if uvx is needed and missing\n    if [[ \"${USE_UVX:-NO}\" == \"YES\" ]] && [[ ! -x \"$UVX_BIN\" ]]; then\n      msg_info \"Installing uvx wrapper\"\n      _install_uvx_wrapper || return 252\n      msg_ok \"uvx wrapper installed\"\n    fi\n\n    return 0\n  fi\n\n  # Scenario 2: New install or upgrade\n  if [[ -n \"$INSTALLED_VERSION\" && \"$INSTALLED_VERSION\" != \"$LATEST_VERSION\" ]]; then\n    msg_info \"Upgrade uv from $INSTALLED_VERSION to $LATEST_VERSION\"\n  else\n    msg_info \"Setup uv $LATEST_VERSION\"\n  fi\n\n  local UV_URL=\"https://github.com/astral-sh/uv/releases/download/${LATEST_VERSION}/${UV_TAR}\"\n\n  if ! curl_with_retry \"$UV_URL\" \"$TMP_DIR/uv.tar.gz\"; then\n    msg_error \"Failed to download uv from $UV_URL\"\n    return 7\n  fi\n\n  # Extract\n  $STD tar -xzf \"$TMP_DIR/uv.tar.gz\" -C \"$TMP_DIR\" || {\n    msg_error \"Failed to extract uv\"\n    return 251\n  }\n\n  # Find and install uv binary (tarball extracts to uv-VERSION-ARCH/ directory)\n  local UV_BINARY=$(find \"$TMP_DIR\" -name \"uv\" -type f -executable | head -n1)\n  if [[ ! -f \"$UV_BINARY\" ]]; then\n    msg_error \"Could not find uv binary in extracted tarball\"\n    return 127\n  fi\n\n  $STD /usr/bin/install -m 755 \"$UV_BINARY\" \"$UV_BIN\" || {\n    msg_error \"Failed to install uv binary\"\n    return 252\n  }\n\n  ensure_usr_local_bin_persist\n  export PATH=\"/usr/local/bin:$PATH\"\n\n  # Optional: Install uvx wrapper\n  if [[ \"${USE_UVX:-NO}\" == \"YES\" ]]; then\n    msg_info \"Installing uvx wrapper\"\n    _install_uvx_wrapper || {\n      msg_error \"Failed to install uvx wrapper\"\n      return 252\n    }\n    msg_ok \"uvx wrapper installed\"\n  fi\n\n  # Optional: Generate shell completions\n  $STD uv generate-shell-completion bash >/etc/bash_completion.d/uv 2>/dev/null || true\n  if [[ -d /usr/share/zsh/site-functions ]]; then\n    $STD uv generate-shell-completion zsh >/usr/share/zsh/site-functions/_uv 2>/dev/null || true\n  fi\n\n  # Optional: Install specific Python version if requested\n  if [[ -n \"${PYTHON_VERSION:-}\" ]]; then\n    msg_info \"Installing Python $PYTHON_VERSION via uv\"\n    $STD uv python install \"$PYTHON_VERSION\" || {\n      msg_error \"Failed to install Python $PYTHON_VERSION\"\n      return 150\n    }\n    msg_ok \"Python $PYTHON_VERSION installed\"\n  fi\n\n  cache_installed_version \"uv\" \"$LATEST_VERSION\"\n  msg_ok \"Setup uv $LATEST_VERSION\"\n}\n\n# Helper function to install uvx wrapper\n_install_uvx_wrapper() {\n  local UVX_BIN=\"/usr/local/bin/uvx\"\n\n  cat >\"$UVX_BIN\" <<'EOF'\n#!/bin/bash\n# uvx - Run Python applications from PyPI as command-line tools\n# Wrapper for: uv tool run\nexec /usr/local/bin/uv tool run \"$@\"\nEOF\n\n  chmod +x \"$UVX_BIN\"\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Installs or updates yq (mikefarah/yq - Go version).\n#\n# Description:\n#   - Checks if yq is installed and from correct source\n#   - Compares with latest release on GitHub\n#   - Updates if outdated or wrong implementation\n# ------------------------------------------------------------------------------\n\nfunction setup_yq() {\n  local TMP_DIR=$(mktemp -d)\n  local BINARY_PATH=\"/usr/local/bin/yq\"\n  local GITHUB_REPO=\"mikefarah/yq\"\n\n  ensure_dependencies jq\n  ensure_usr_local_bin_persist\n\n  # Remove non-mikefarah implementations\n  if command -v yq &>/dev/null; then\n    if ! yq --version 2>&1 | grep -q 'mikefarah'; then\n      rm -f \"$(command -v yq)\"\n    fi\n  fi\n\n  local LATEST_VERSION\n  local releases_json\n  releases_json=$(curl -fsSL --max-time 15 \"https://api.github.com/repos/${GITHUB_REPO}/releases/latest\" 2>/dev/null || echo \"\")\n\n  if [[ -z \"$releases_json\" ]]; then\n    msg_error \"Could not fetch latest yq version from GitHub API\"\n    rm -rf \"$TMP_DIR\"\n    return 250\n  fi\n\n  LATEST_VERSION=$(echo \"$releases_json\" | jq -r '.tag_name' 2>/dev/null | sed 's/^v//' || echo \"\")\n\n  if [[ -z \"$LATEST_VERSION\" ]]; then\n    msg_error \"Could not parse yq version from GitHub API response\"\n    rm -rf \"$TMP_DIR\"\n    return 250\n  fi\n\n  # Get currently installed version\n  local INSTALLED_VERSION=\"\"\n  if command -v yq &>/dev/null && yq --version 2>&1 | grep -q 'mikefarah'; then\n    INSTALLED_VERSION=$(yq --version 2>/dev/null | awk '{print $NF}' | sed 's/^v//')\n  fi\n\n  # Scenario 1: Already at latest version\n  if [[ -n \"$INSTALLED_VERSION\" && \"$INSTALLED_VERSION\" == \"$LATEST_VERSION\" ]]; then\n    cache_installed_version \"yq\" \"$LATEST_VERSION\"\n    rm -rf \"$TMP_DIR\"\n    return 0\n  fi\n\n  # Scenario 2: New install or upgrade\n  if [[ -n \"$INSTALLED_VERSION\" && \"$INSTALLED_VERSION\" != \"$LATEST_VERSION\" ]]; then\n    msg_info \"Upgrade yq from $INSTALLED_VERSION to $LATEST_VERSION\"\n  else\n    msg_info \"Setup yq $LATEST_VERSION\"\n  fi\n\n  if ! curl_with_retry \"https://github.com/${GITHUB_REPO}/releases/download/v${LATEST_VERSION}/yq_linux_amd64\" \"$TMP_DIR/yq\"; then\n    msg_error \"Failed to download yq\"\n    rm -rf \"$TMP_DIR\"\n    return 250\n  fi\n\n  chmod +x \"$TMP_DIR/yq\"\n  mv \"$TMP_DIR/yq\" \"$BINARY_PATH\" || {\n    msg_error \"Failed to install yq\"\n    rm -rf \"$TMP_DIR\"\n    return 252\n  }\n\n  rm -rf \"$TMP_DIR\"\n  hash -r\n\n  local FINAL_VERSION\n  FINAL_VERSION=$(\"$BINARY_PATH\" --version 2>/dev/null | awk '{print $NF}' | sed 's/^v//')\n  cache_installed_version \"yq\" \"$FINAL_VERSION\"\n  msg_ok \"Setup yq $FINAL_VERSION\"\n}\n\n# ------------------------------------------------------------------------------\n# Docker Engine Installation and Management (All-In-One)\n#\n# Description:\n#   - By default uses distro repository (docker.io) for stability\n#   - Optionally uses official Docker repository for latest features\n#   - Detects and migrates old Docker installations\n#   - Optional: Installs/Updates Portainer CE\n#   - Updates running containers interactively\n#   - Cleans up legacy repository files\n#\n# Usage:\n#   setup_docker                              # Uses distro package (recommended)\n#   USE_DOCKER_REPO=true setup_docker         # Uses official Docker repo\n#   DOCKER_PORTAINER=\"true\" setup_docker\n#   DOCKER_LOG_DRIVER=\"json-file\" setup_docker\n#\n# Variables:\n#   USE_DOCKER_REPO        - Set to \"true\" to use official Docker repository\n#                            (default: false, uses distro docker.io package)\n#   DOCKER_PORTAINER       - Install Portainer CE (optional, \"true\" to enable)\n#   DOCKER_LOG_DRIVER      - Log driver (optional, default: \"journald\")\n#   DOCKER_SKIP_UPDATES    - Skip container update check (optional, \"true\" to skip)\n#\n# Features:\n#   - Uses stable distro packages by default\n#   - Migrates from get.docker.com to repository-based installation\n#   - Updates Docker Engine if newer version available\n#   - Interactive container update with multi-select\n#   - Portainer installation and update support\n# ------------------------------------------------------------------------------\nfunction setup_docker() {\n  local docker_installed=false\n  local portainer_installed=false\n  local USE_DOCKER_REPO=\"${USE_DOCKER_REPO:-false}\"\n\n  # Check if Docker is already installed\n  if command -v docker &>/dev/null; then\n    docker_installed=true\n    DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\\d+\\.\\d+\\.\\d+' | head -1)\n    msg_info \"Docker $DOCKER_CURRENT_VERSION detected\"\n  fi\n\n  # Check if Portainer is running\n  if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^portainer$'; then\n    portainer_installed=true\n    msg_info \"Portainer container detected\"\n  fi\n\n  # Scenario 1: Use distro repository (default, most stable)\n  if [[ \"$USE_DOCKER_REPO\" != \"true\" && \"$USE_DOCKER_REPO\" != \"TRUE\" && \"$USE_DOCKER_REPO\" != \"1\" ]]; then\n\n    # Install or upgrade Docker from distro repo\n    if [ \"$docker_installed\" = true ]; then\n      msg_info \"Checking for Docker updates (distro package)\"\n      ensure_apt_working || return 100\n      upgrade_packages_with_retry \"docker.io\" \"docker-compose\" || true\n      DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\\d+\\.\\d+\\.\\d+' | head -1)\n      msg_ok \"Docker is up-to-date ($DOCKER_CURRENT_VERSION)\"\n    else\n      msg_info \"Installing Docker (distro package)\"\n      ensure_apt_working || return 100\n\n      # Install docker.io and docker-compose from distro\n      if ! install_packages_with_retry \"docker.io\"; then\n        msg_error \"Failed to install docker.io from distro repository\"\n        return 100\n      fi\n      # docker-compose is optional\n      $STD apt install -y docker-compose 2>/dev/null || true\n\n      DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\\d+\\.\\d+\\.\\d+' | head -1)\n      msg_ok \"Installed Docker $DOCKER_CURRENT_VERSION (distro package)\"\n    fi\n\n    # Configure daemon.json\n    local log_driver=\"${DOCKER_LOG_DRIVER:-journald}\"\n    mkdir -p /etc/docker\n    if [ ! -f /etc/docker/daemon.json ]; then\n      cat <<EOF >/etc/docker/daemon.json\n{\n  \"log-driver\": \"$log_driver\"\n}\nEOF\n    fi\n\n    # Enable and start Docker\n    systemctl enable -q --now docker\n\n    # Continue to Portainer section below\n  else\n    # Scenario 2: Use official Docker repository (USE_DOCKER_REPO=true)\n\n    # Cleanup old repository configurations\n    if [ -f /etc/apt/sources.list.d/docker.list ]; then\n      msg_info \"Migrating from old Docker repository format\"\n      rm -f /etc/apt/sources.list.d/docker.list\n      rm -f /etc/apt/keyrings/docker.asc\n    fi\n\n    # Setup/Update Docker repository\n    msg_info \"Setting up Docker Repository\"\n    setup_deb822_repo \\\n      \"docker\" \\\n      \"https://download.docker.com/linux/$(get_os_info id)/gpg\" \\\n      \"https://download.docker.com/linux/$(get_os_info id)\" \\\n      \"$(get_os_info codename)\" \\\n      \"stable\" \\\n      \"$(dpkg --print-architecture)\"\n\n    # Install or upgrade Docker\n    if [ \"$docker_installed\" = true ]; then\n      msg_info \"Checking for Docker updates\"\n      DOCKER_LATEST_VERSION=$(apt-cache policy docker-ce | grep Candidate | awk '{print $2}' 2>/dev/null | cut -d':' -f2 | cut -d'-' -f1 || echo '')\n\n      if [ \"$DOCKER_CURRENT_VERSION\" != \"$DOCKER_LATEST_VERSION\" ]; then\n        msg_info \"Updating Docker $DOCKER_CURRENT_VERSION → $DOCKER_LATEST_VERSION\"\n        $STD apt install -y --only-upgrade \\\n          docker-ce \\\n          docker-ce-cli \\\n          containerd.io \\\n          docker-buildx-plugin \\\n          docker-compose-plugin || {\n          msg_error \"Failed to update Docker packages\"\n          return 100\n        }\n        msg_ok \"Updated Docker to $DOCKER_LATEST_VERSION\"\n      else\n        msg_ok \"Docker is up-to-date ($DOCKER_CURRENT_VERSION)\"\n      fi\n    else\n      msg_info \"Installing Docker\"\n      $STD apt install -y \\\n        docker-ce \\\n        docker-ce-cli \\\n        containerd.io \\\n        docker-buildx-plugin \\\n        docker-compose-plugin || {\n        msg_error \"Failed to install Docker packages\"\n        return 100\n      }\n\n      DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\\d+\\.\\d+\\.\\d+' | head -1)\n      msg_ok \"Installed Docker $DOCKER_CURRENT_VERSION\"\n    fi\n\n    # Configure daemon.json\n    local log_driver=\"${DOCKER_LOG_DRIVER:-journald}\"\n    mkdir -p /etc/docker\n    if [ ! -f /etc/docker/daemon.json ]; then\n      cat <<EOF >/etc/docker/daemon.json\n{\n  \"log-driver\": \"$log_driver\"\n}\nEOF\n    fi\n\n    # Enable and start Docker\n    systemctl enable -q --now docker\n  fi\n\n  # Portainer Management (common for both modes)\n  if [[ \"${DOCKER_PORTAINER:-}\" == \"true\" ]]; then\n    if [ \"$portainer_installed\" = true ]; then\n      msg_info \"Checking for Portainer updates\"\n      PORTAINER_CURRENT=$(docker inspect portainer --format='{{.Config.Image}}' 2>/dev/null | cut -d':' -f2)\n      PORTAINER_LATEST=$(curl -fsSL https://registry.hub.docker.com/v2/repositories/portainer/portainer-ce/tags?page_size=100 | grep -oP '\"name\":\"\\K[0-9]+\\.[0-9]+\\.[0-9]+\"' | head -1 | tr -d '\"')\n\n      if [ \"$PORTAINER_CURRENT\" != \"$PORTAINER_LATEST\" ]; then\n        read -r -p \"${TAB3}Update Portainer $PORTAINER_CURRENT → $PORTAINER_LATEST? <y/N> \" prompt\n        if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n          msg_info \"Updating Portainer\"\n          docker stop portainer\n          docker rm portainer\n          docker pull portainer/portainer-ce:latest\n          docker run -d \\\n            -p 9000:9000 \\\n            -p 9443:9443 \\\n            --name=portainer \\\n            --restart=always \\\n            -v /var/run/docker.sock:/var/run/docker.sock \\\n            -v portainer_data:/data \\\n            portainer/portainer-ce:latest\n          msg_ok \"Updated Portainer to $PORTAINER_LATEST\"\n        fi\n      else\n        msg_ok \"Portainer is up-to-date ($PORTAINER_CURRENT)\"\n      fi\n    else\n      msg_info \"Installing Portainer\"\n      docker volume create portainer_data\n      docker run -d \\\n        -p 9000:9000 \\\n        -p 9443:9443 \\\n        --name=portainer \\\n        --restart=always \\\n        -v /var/run/docker.sock:/var/run/docker.sock \\\n        -v portainer_data:/data \\\n        portainer/portainer-ce:latest\n\n      LOCAL_IP=$(hostname -I | awk '{print $1}')\n      msg_ok \"Installed Portainer (http://${LOCAL_IP}:9000)\"\n    fi\n  fi\n\n  # Interactive Container Update Check\n  if [[ \"${DOCKER_SKIP_UPDATES:-}\" != \"true\" ]] && [ \"$docker_installed\" = true ]; then\n    msg_info \"Checking for container updates\"\n\n    # Get list of running containers with update status\n    local containers_with_updates=()\n    local container_info=()\n    local index=1\n\n    while IFS= read -r container; do\n      local name=$(echo \"$container\" | awk '{print $1}')\n      local image=$(echo \"$container\" | awk '{print $2}')\n      local current_digest=$(docker inspect \"$name\" --format='{{.Image}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12)\n\n      # Pull latest image digest\n      docker pull \"$image\" >/dev/null 2>&1\n      local latest_digest=$(docker inspect \"$image\" --format='{{.Id}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12)\n\n      if [ \"$current_digest\" != \"$latest_digest\" ]; then\n        containers_with_updates+=(\"$name\")\n        container_info+=(\"${index}) ${name} (${image})\")\n        ((index++))\n      fi\n    done < <(docker ps --format '{{.Names}} {{.Image}}')\n\n    if [ ${#containers_with_updates[@]} -gt 0 ]; then\n      echo \"\"\n      echo \"${TAB3}Container updates available:\"\n      for info in \"${container_info[@]}\"; do\n        echo \"${TAB3}  $info\"\n      done\n      echo \"\"\n      read -r -p \"${TAB3}Select containers to update (e.g., 1,3,5 or 'all' or 'none'): \" selection\n\n      if [[ ${selection,,} == \"all\" ]]; then\n        for container in \"${containers_with_updates[@]}\"; do\n          msg_info \"Updating container: $container\"\n          docker stop \"$container\"\n          docker rm \"$container\"\n          # Note: This requires the original docker run command - best to recreate via compose\n          msg_ok \"Stopped and removed $container (please recreate with updated image)\"\n        done\n      elif [[ ${selection,,} != \"none\" ]]; then\n        IFS=',' read -ra SELECTED <<<\"$selection\"\n        for num in \"${SELECTED[@]}\"; do\n          num=$(echo \"$num\" | xargs) # trim whitespace\n          if [[ \"$num\" =~ ^[0-9]+$ ]] && [ \"$num\" -ge 1 ] && [ \"$num\" -le \"${#containers_with_updates[@]}\" ]; then\n            container=\"${containers_with_updates[$((num - 1))]}\"\n            msg_info \"Updating container: $container\"\n            docker stop \"$container\"\n            docker rm \"$container\"\n            msg_ok \"Stopped and removed $container (please recreate with updated image)\"\n          fi\n        done\n      fi\n    else\n      msg_ok \"All containers are up-to-date\"\n    fi\n  fi\n\n  msg_ok \"Docker setup completed\"\n}\n\n# ------------------------------------------------------------------------------\n# Fetch and deploy from URL\n# Downloads an archive (zip, tar.gz, or .deb) from a URL and extracts/installs it\n#\n# Usage: fetch_and_deploy_from_url \"url\" \"directory\"\n#   url       - URL to the archive (zip, tar.gz, or .deb)\n#   directory - Destination path where the archive will be extracted\n#               (not used for .deb packages)\n#\n# Examples:\n#   fetch_and_deploy_from_url \"https://example.com/app.tar.gz\" \"/opt/myapp\"\n#   fetch_and_deploy_from_url \"https://example.com/app.zip\" \"/opt/myapp\"\n#   fetch_and_deploy_from_url \"https://example.com/package.deb\" \"\"\n# ------------------------------------------------------------------------------\nfunction fetch_and_deploy_from_url() {\n  local url=\"$1\"\n  local directory=\"${2:-}\"\n\n  if [[ -z \"$url\" ]]; then\n    msg_error \"URL parameter is required\"\n    return 65\n  fi\n\n  local filename=\"${url##*/}\"\n\n  msg_info \"Downloading from $url\"\n\n  local tmpdir\n  tmpdir=$(mktemp -d) || {\n    msg_error \"Failed to create temporary directory\"\n    return 252\n  }\n\n  curl -fsSL -o \"$tmpdir/$filename\" \"$url\" || {\n    msg_error \"Download failed: $url\"\n    rm -rf \"$tmpdir\"\n    return 250\n  }\n\n  # Auto-detect archive type using file description\n  local file_desc\n  file_desc=$(file -b \"$tmpdir/$filename\")\n\n  local archive_type=\"unknown\"\n\n  if [[ \"$file_desc\" =~ gzip.*compressed|gzip\\ compressed\\ data ]]; then\n    archive_type=\"tar\"\n  elif [[ \"$file_desc\" =~ Zip.*archive|ZIP\\ archive ]]; then\n    archive_type=\"zip\"\n  elif [[ \"$file_desc\" =~ Debian.*package|Debian\\ binary\\ package ]]; then\n    archive_type=\"deb\"\n  elif [[ \"$file_desc\" =~ POSIX.*tar.*archive|tar\\ archive ]]; then\n    archive_type=\"tar\"\n  else\n    msg_error \"Unsupported or unknown archive type: $file_desc\"\n    rm -rf \"$tmpdir\"\n    return 65\n  fi\n\n  msg_info \"Detected archive type: $archive_type (file type: $file_desc)\"\n\n  if [[ \"$archive_type\" == \"deb\" ]]; then\n    msg_info \"Installing .deb package\"\n\n    chmod 644 \"$tmpdir/$filename\"\n    $STD apt install -y \"$tmpdir/$filename\" || {\n      $STD dpkg -i \"$tmpdir/$filename\" || {\n        msg_error \"Both apt and dpkg installation failed\"\n        rm -rf \"$tmpdir\"\n        return 100\n      }\n    }\n\n    rm -rf \"$tmpdir\"\n    msg_ok \"Successfully installed .deb package\"\n    return 0\n  fi\n\n  if [[ -z \"$directory\" ]]; then\n    msg_error \"Directory parameter is required for archive extraction\"\n    rm -rf \"$tmpdir\"\n    return 65\n  fi\n\n  msg_info \"Extracting archive to $directory\"\n\n  mkdir -p \"$directory\"\n\n  if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n    rm -rf \"${directory:?}/\"*\n  fi\n\n  local unpack_tmp\n  unpack_tmp=$(mktemp -d)\n\n  if [[ \"$archive_type\" == \"zip\" ]]; then\n    ensure_dependencies unzip\n    unzip -q \"$tmpdir/$filename\" -d \"$unpack_tmp\" || {\n      msg_error \"Failed to extract ZIP archive\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 251\n    }\n  elif [[ \"$archive_type\" == \"tar\" ]]; then\n    tar --no-same-owner -xf \"$tmpdir/$filename\" -C \"$unpack_tmp\" || {\n      msg_error \"Failed to extract TAR archive\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 251\n    }\n  fi\n\n  local top_entries\n  top_entries=$(find \"$unpack_tmp\" -mindepth 1 -maxdepth 1)\n\n  if [[ \"$(echo \"$top_entries\" | wc -l)\" -eq 1 && -d \"$top_entries\" ]]; then\n    local inner_dir=\"$top_entries\"\n    shopt -s dotglob nullglob\n    if compgen -G \"$inner_dir/*\" >/dev/null; then\n      cp -r \"$inner_dir\"/* \"$directory/\" || {\n        msg_error \"Failed to copy contents from $inner_dir to $directory\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 252\n      }\n    else\n      msg_error \"Inner directory is empty: $inner_dir\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 252\n    fi\n    shopt -u dotglob nullglob\n  else\n    shopt -s dotglob nullglob\n    if compgen -G \"$unpack_tmp/*\" >/dev/null; then\n      cp -r \"$unpack_tmp\"/* \"$directory/\" || {\n        msg_error \"Failed to copy contents to $directory\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 252\n      }\n    else\n      msg_error \"Unpacked archive is empty\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 252\n    fi\n    shopt -u dotglob nullglob\n  fi\n\n  rm -rf \"$tmpdir\" \"$unpack_tmp\"\n  msg_ok \"Successfully deployed archive to $directory\"\n  return 0\n}\n\nsetup_nonfree() {\n  local sources_file=\"/etc/apt/sources.list.d/debian-nonfree.sources\"\n\n  if [ ! -f \"$sources_file\" ]; then\n    cat <<EOF >$sources_file\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie trixie-updates\nComponents: main contrib non-free non-free-firmware\n\nTypes: deb\nURIs: http://security.debian.org/debian-security\nSuites: trixie-security\nComponents: main contrib non-free non-free-firmware\nEOF\n  fi\n  $STD apt update\n  return 0\n}\n\n# ------------------------------------------------------------------------------\n# Get latest GitLab release version.\n# Usage: get_latest_gitlab_release \"owner/repo\" [strip_v]\n# ------------------------------------------------------------------------------\nget_latest_gitlab_release() {\n  local repo=\"$1\"\n  local strip_v=\"${2:-true}\"\n\n  local repo_encoded\n  repo_encoded=$(printf '%s' \"$repo\" | sed 's|/|%2F|g')\n\n  local header=()\n  [[ -n \"${GITLAB_TOKEN:-}\" ]] && header=(-H \"PRIVATE-TOKEN: $GITLAB_TOKEN\")\n\n  local temp_file\n  temp_file=$(mktemp)\n\n  local http_code\n  http_code=$(curl --connect-timeout 10 --max-time 30 -sSL \\\n    -w \"%{http_code}\" -o \"$temp_file\" \\\n    \"${header[@]}\" \\\n    \"https://gitlab.com/api/v4/projects/$repo_encoded/releases?per_page=1&order_by=released_at&sort=desc\" 2>/dev/null) || true\n\n  if [[ \"$http_code\" != \"200\" ]]; then\n    rm -f \"$temp_file\"\n    msg_warn \"GitLab API call failed for ${repo} (HTTP ${http_code})\"\n    return 22\n  fi\n\n  local version\n  version=$(jq -r '.[0].tag_name // empty' \"$temp_file\")\n  rm -f \"$temp_file\"\n\n  if [[ -z \"$version\" ]]; then\n    msg_error \"Could not determine latest version for ${repo}\"\n    return 250\n  fi\n\n  if [[ \"$strip_v\" == \"true\" ]]; then\n    [[ \"$version\" =~ ^v[0-9] ]] && version=\"${version:1}\"\n  fi\n\n  echo \"$version\"\n}\n\n# ------------------------------------------------------------------------------\n# Checks for new GitLab release (latest tag).\n#\n# Description:\n#   - Queries the GitLab API for the latest release tag\n#   - Compares it to a local cached version (~/.<app>)\n#   - If newer, sets global CHECK_UPDATE_RELEASE and returns 0\n#\n# Usage:\n#     if check_for_gl_release \"myapp\" \"owner/repo\" [optional] \"v1.2.3\"; then\n#       # trigger update...\n#     fi\n#     exit 0\n#     } (end of update_script not from the function)\n#\n# Notes:\n#   - Requires `jq` (auto-installed if missing)\n#   - Supports GITLAB_TOKEN env var for private/rate-limited repos\n#   - Does not modify anything, only checks version state\n# ------------------------------------------------------------------------------\ncheck_for_gl_release() {\n  local app=\"$1\"\n  local source=\"$2\"\n  local pinned_version_in=\"${3:-}\" # optional\n  local pin_reason=\"${4:-}\"        # optional reason shown to user\n  local app_lc=\"${app,,}\"\n  local current_file=\"$HOME/.${app_lc}\"\n\n  msg_info \"Checking for update: ${app}\"\n\n  # DNS check\n  if ! getent hosts gitlab.com >/dev/null 2>&1; then\n    msg_error \"Network error: cannot resolve gitlab.com\"\n    return 6\n  fi\n\n  ensure_dependencies jq\n\n  local repo_encoded\n  repo_encoded=$(printf '%s' \"$source\" | sed 's|/|%2F|g')\n\n  local header=()\n  [[ -n \"${GITLAB_TOKEN:-}\" ]] && header=(-H \"PRIVATE-TOKEN: $GITLAB_TOKEN\")\n\n  local releases_json=\"\" http_code=\"\"\n\n  # For pinned versions, try to fetch the specific release tag first\n  if [[ -n \"$pinned_version_in\" ]]; then\n    local pinned_encoded=\"${pinned_version_in//\\//%2F}\"\n    http_code=$(curl -sSL --max-time 20 -w \"%{http_code}\" -o /tmp/gl_check.json \\\n      \"${header[@]}\" \\\n      \"https://gitlab.com/api/v4/projects/$repo_encoded/releases/$pinned_encoded\" 2>/dev/null) || true\n    if [[ \"$http_code\" == \"200\" ]] && [[ -s /tmp/gl_check.json ]]; then\n      releases_json=\"[$(</tmp/gl_check.json)]\"\n    fi\n    rm -f /tmp/gl_check.json\n  fi\n\n  # Fetch full releases list if needed\n  if [[ -z \"$releases_json\" ]]; then\n    http_code=$(curl -sSL --max-time 20 -w \"%{http_code}\" -o /tmp/gl_check.json \\\n      \"${header[@]}\" \\\n      \"https://gitlab.com/api/v4/projects/$repo_encoded/releases?per_page=100&order_by=released_at&sort=desc\" 2>/dev/null) || true\n\n    if [[ \"$http_code\" == \"200\" ]] && [[ -s /tmp/gl_check.json ]]; then\n      releases_json=$(</tmp/gl_check.json)\n    elif [[ \"$http_code\" == \"401\" ]]; then\n      msg_error \"GitLab API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITLAB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITLAB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication. Try: export GITLAB_TOKEN=\\\"glpat-your_token\\\"\"\n      fi\n      rm -f /tmp/gl_check.json\n      return 22\n    elif [[ \"$http_code\" == \"404\" ]]; then\n      msg_error \"GitLab project not found (HTTP 404). Ensure '${source}' is correct and publicly accessible.\"\n      rm -f /tmp/gl_check.json\n      return 22\n    elif [[ \"$http_code\" == \"429\" ]]; then\n      msg_error \"GitLab API rate limit exceeded (HTTP 429).\"\n      msg_error \"To increase the limit, export a GitLab token: export GITLAB_TOKEN=\\\"glpat-your_token_here\\\"\"\n      rm -f /tmp/gl_check.json\n      return 22\n    elif [[ \"$http_code\" == \"000\" || -z \"$http_code\" ]]; then\n      msg_error \"GitLab API connection failed (no response).\"\n      msg_error \"Check your network/DNS: curl -sSL https://gitlab.com/api/v4/version\"\n      rm -f /tmp/gl_check.json\n      return 7\n    else\n      msg_error \"Unable to fetch releases for ${app} (HTTP ${http_code})\"\n      rm -f /tmp/gl_check.json\n      return 22\n    fi\n    rm -f /tmp/gl_check.json\n  fi\n\n  mapfile -t raw_tags < <(jq -r '.[] | .tag_name' <<<\"$releases_json\")\n  if ((${#raw_tags[@]} == 0)); then\n    msg_error \"No releases found for ${app} on GitLab\"\n    return 250\n  fi\n\n  local clean_tags=()\n  for t in \"${raw_tags[@]}\"; do\n    # Only strip leading 'v' when followed by a digit (e.g. v1.2.3)\n    if [[ \"$t\" =~ ^v[0-9] ]]; then\n      clean_tags+=(\"${t:1}\")\n    else\n      clean_tags+=(\"$t\")\n    fi\n  done\n\n  local latest_raw=\"${raw_tags[0]}\"\n  local latest_clean=\"${clean_tags[0]}\"\n\n  # current installed (stored without v)\n  local current=\"\"\n  if [[ -f \"$current_file\" ]]; then\n    current=\"$(<\"$current_file\")\"\n  else\n    # Migration: search for any /opt/*_version.txt\n    local legacy_files\n    mapfile -t legacy_files < <(find /opt -maxdepth 1 -type f -name \"*_version.txt\" 2>/dev/null)\n    if ((${#legacy_files[@]} == 1)); then\n      current=\"$(<\"${legacy_files[0]}\")\"\n      echo \"${current#v}\" >\"$current_file\"\n      rm -f \"${legacy_files[0]}\"\n    fi\n  fi\n  if [[ \"$current\" =~ ^v[0-9] ]]; then\n    current=\"${current:1}\"\n  fi\n\n  # Pinned version handling\n  if [[ -n \"$pinned_version_in\" ]]; then\n    local pin_clean\n    if [[ \"$pinned_version_in\" =~ ^v[0-9] ]]; then\n      pin_clean=\"${pinned_version_in:1}\"\n    else\n      pin_clean=\"$pinned_version_in\"\n    fi\n    local match_raw=\"\"\n    for i in \"${!clean_tags[@]}\"; do\n      if [[ \"${clean_tags[$i]}\" == \"$pin_clean\" ]]; then\n        match_raw=\"${raw_tags[$i]}\"\n        break\n      fi\n    done\n\n    if [[ -z \"$match_raw\" ]]; then\n      msg_error \"Pinned version ${pinned_version_in} not found upstream\"\n      return 250\n    fi\n\n    if [[ \"$current\" != \"$pin_clean\" ]]; then\n      CHECK_UPDATE_RELEASE=\"$match_raw\"\n      msg_ok \"Update available: ${app} ${current:-not installed} → ${pin_clean}\"\n      return 0\n    fi\n\n    if [[ -n \"$pin_reason\" ]]; then\n      msg_ok \"No update available: ${app} (${current}) - update held back: ${pin_reason}\"\n    else\n      msg_ok \"No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases\"\n    fi\n    return 1\n  fi\n\n  # No pinning → use latest\n  if [[ -z \"$current\" || \"$current\" != \"$latest_clean\" ]]; then\n    CHECK_UPDATE_RELEASE=\"$latest_raw\"\n    msg_ok \"Update available: ${app} ${current:-not installed} → ${latest_clean}\"\n    return 0\n  fi\n\n  msg_ok \"No update available: ${app} (${latest_clean})\"\n  return 1\n}\n\n# ------------------------------------------------------------------------------\n# Scan older GitLab releases for a matching asset (fallback helper).\n#\n# Description:\n#   When the latest release does not contain the expected asset\n#   (e.g. .deb for the current arch, or a custom pattern), walks back\n#   through up to 15 recent releases and returns the first release JSON\n#   that has a matching asset. Used internally by fetch_and_deploy_gl_release.\n#\n# Usage (internal):\n#   _gl_scan_older_releases \"owner/repo\" \"owner%2Frepo\" \"https://gitlab.com\" \\\n#     \"binary|prebuild|singlefile\" \"$asset_pattern\" \"$skip_tag\"\n#\n# Returns:\n#   - stdout: JSON of the matching release (single object) on success\n#   - 0 on success, 22 on API error, 250 if no match found\n# ------------------------------------------------------------------------------\n_gl_scan_older_releases() {\n  local repo=\"$1\"\n  local repo_encoded=\"$2\"\n  local base_url=\"${3:-https://gitlab.com}\"\n  local mode=\"$4\"\n  local asset_pattern=\"$5\"\n  local skip_tag=\"$6\"\n\n  local header=()\n  [[ -n \"${GITLAB_TOKEN:-}\" ]] && header=(-H \"PRIVATE-TOKEN: $GITLAB_TOKEN\")\n\n  local releases_list\n  releases_list=$(curl --connect-timeout 10 --max-time 30 -fsSL \\\n    \"${header[@]}\" \\\n    \"${base_url}/api/v4/projects/${repo_encoded}/releases?per_page=15&order_by=released_at&sort=desc\" 2>/dev/null) || {\n    msg_warn \"Failed to fetch older releases for ${repo}\"\n    return 22\n  }\n\n  local count\n  count=$(echo \"$releases_list\" | jq 'length' 2>/dev/null || echo 0)\n  [[ \"$count\" -eq 0 ]] && return 250\n\n  for ((i = 0; i < count; i++)); do\n    local rel_tag\n    rel_tag=$(echo \"$releases_list\" | jq -r \".[$i].tag_name\")\n\n    # Skip the tag we already checked\n    [[ \"$rel_tag\" == \"$skip_tag\" ]] && continue\n\n    # Asset URLs for this release (direct_asset_url preferred, fallback to url)\n    local asset_urls\n    asset_urls=$(echo \"$releases_list\" | jq -r \".[$i].assets.links // [] | .[] | .direct_asset_url // .url\")\n    [[ -z \"$asset_urls\" ]] && continue\n\n    local has_match=false\n\n    if [[ \"$mode\" == \"binary\" ]]; then\n      local arch\n      arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n      [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n      [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n\n      # Check with explicit pattern first, then arch heuristic, then any .deb\n      if [[ -n \"$asset_pattern\" ]]; then\n        while read -r u; do\n          case \"${u##*/}\" in $asset_pattern)\n            has_match=true\n            break\n            ;;\n          esac\n        done <<<\"$asset_urls\"\n      fi\n      if [[ \"$has_match\" != \"true\" ]]; then\n        echo \"$asset_urls\" | grep -qE \"($arch|amd64|x86_64|aarch64|arm64).*\\.deb$\" && has_match=true\n      fi\n      if [[ \"$has_match\" != \"true\" ]]; then\n        echo \"$asset_urls\" | grep -qE '\\.deb$' && has_match=true\n      fi\n\n    elif [[ \"$mode\" == \"prebuild\" || \"$mode\" == \"singlefile\" ]]; then\n      while read -r u; do\n        case \"${u##*/}\" in $asset_pattern)\n          has_match=true\n          break\n          ;;\n        esac\n      done <<<\"$asset_urls\"\n    fi\n\n    if [[ \"$has_match\" == \"true\" ]]; then\n      local use_fallback=\"y\"\n      if [[ -t 0 ]]; then\n        msg_warn \"Release ${skip_tag} has no matching asset. Previous release ${rel_tag} has a compatible asset.\"\n        read -rp \"Use version ${rel_tag} instead? [Y/n] (auto-yes in 60s): \" -t 60 use_fallback || use_fallback=\"y\"\n        use_fallback=\"${use_fallback:-y}\"\n      fi\n\n      if [[ \"${use_fallback,,}\" == \"y\" || \"${use_fallback,,}\" == \"yes\" ]]; then\n        echo \"$releases_list\" | jq \".[$i]\"\n        return 0\n      else\n        return 250\n      fi\n    fi\n  done\n\n  return 250\n}\n\nfunction fetch_and_deploy_gl_release() {\n  local app=\"$1\"\n  local repo=\"$2\"\n  local mode=\"${3:-tarball}\"\n  local version=\"${var_appversion:-${4:-latest}}\"\n  local target=\"${5:-/opt/$app}\"\n  local asset_pattern=\"${6:-}\"\n\n  if [[ -z \"$app\" ]]; then\n    app=\"${repo##*/}\"\n    if [[ -z \"$app\" ]]; then\n      msg_error \"fetch_and_deploy_gl_release requires app name or valid repo\"\n      return 1\n    fi\n  fi\n\n  local app_lc=$(echo \"${app,,}\" | tr -d ' ')\n  local version_file=\"$HOME/.${app_lc}\"\n\n  local api_timeout=\"--connect-timeout 10 --max-time 60\"\n  local download_timeout=\"--connect-timeout 15 --max-time 900\"\n\n  local current_version=\"\"\n  [[ -f \"$version_file\" ]] && current_version=$(<\"$version_file\")\n\n  ensure_dependencies jq\n\n  local repo_encoded\n  repo_encoded=$(printf '%s' \"$repo\" | sed 's|/|%2F|g')\n\n  local api_base=\"https://gitlab.com/api/v4/projects/$repo_encoded/releases\"\n  local api_url\n  if [[ \"$version\" != \"latest\" ]]; then\n    api_url=\"$api_base/$version\"\n  else\n    api_url=\"$api_base?per_page=1&order_by=released_at&sort=desc\"\n  fi\n\n  local header=()\n  [[ -n \"${GITLAB_TOKEN:-}\" ]] && header=(-H \"PRIVATE-TOKEN: $GITLAB_TOKEN\")\n\n  local max_retries=3 retry_delay=2 attempt=1 success=false http_code\n\n  while ((attempt <= max_retries)); do\n    http_code=$(curl $api_timeout -sSL -w \"%{http_code}\" -o /tmp/gl_rel.json \"${header[@]}\" \"$api_url\" 2>/dev/null) || true\n    if [[ \"$http_code\" == \"200\" ]]; then\n      success=true\n      break\n    elif [[ \"$http_code\" == \"429\" ]]; then\n      if ((attempt < max_retries)); then\n        msg_warn \"GitLab API rate limit hit, retrying in ${retry_delay}s... (attempt $attempt/$max_retries)\"\n        sleep \"$retry_delay\"\n        retry_delay=$((retry_delay * 2))\n      fi\n    else\n      sleep \"$retry_delay\"\n    fi\n    ((attempt++))\n  done\n\n  if ! $success; then\n    if [[ \"$http_code\" == \"401\" ]]; then\n      msg_error \"GitLab API authentication failed (HTTP 401).\"\n      if [[ -n \"${GITLAB_TOKEN:-}\" ]]; then\n        msg_error \"Your GITLAB_TOKEN appears to be invalid or expired.\"\n      else\n        msg_error \"The repository may require authentication. Try: export GITLAB_TOKEN=\\\"glpat-your_token\\\"\"\n      fi\n    elif [[ \"$http_code\" == \"404\" ]]; then\n      msg_error \"GitLab project or release not found (HTTP 404).\"\n      msg_error \"Ensure '$repo' is correct and the project is accessible.\"\n    elif [[ \"$http_code\" == \"429\" ]]; then\n      msg_error \"GitLab API rate limit exceeded (HTTP 429).\"\n      msg_error \"To increase the limit, export a GitLab token before running the script:\"\n      msg_error \"  export GITLAB_TOKEN=\\\"glpat-your_token_here\\\"\"\n    elif [[ \"$http_code\" == \"000\" || -z \"$http_code\" ]]; then\n      msg_error \"GitLab API connection failed (no response).\"\n      msg_error \"Check your network/DNS: curl -sSL https://gitlab.com/api/v4/version\"\n    else\n      msg_error \"Failed to fetch release metadata (HTTP $http_code)\"\n    fi\n    return 1\n  fi\n\n  local json tag_name\n  json=$(</tmp/gl_rel.json)\n\n  if [[ \"$version\" == \"latest\" ]]; then\n    json=$(echo \"$json\" | jq '.[0] // empty')\n    if [[ -z \"$json\" || \"$json\" == \"null\" ]]; then\n      msg_error \"No releases found for $repo on GitLab\"\n      return 1\n    fi\n  fi\n\n  tag_name=$(echo \"$json\" | jq -r '.tag_name // empty')\n  if [[ -z \"$tag_name\" ]]; then\n    msg_error \"Could not determine tag name from release metadata\"\n    return 1\n  fi\n  [[ \"$tag_name\" =~ ^v[0-9] ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n  local version_safe=\"${version//\\//-}\"\n\n  if [[ \"$current_version\" == \"$version\" ]]; then\n    $STD msg_ok \"$app is already up-to-date (v$version)\"\n    return 0\n  fi\n\n  local tmpdir\n  tmpdir=$(mktemp -d) || return 1\n  local filename=\"\"\n\n  msg_info \"Fetching GitLab release: $app ($version)\"\n\n  _gl_asset_urls() {\n    local release_json=\"$1\"\n    echo \"$release_json\" | jq -r '\n      (.assets.links // [])[] | .direct_asset_url // .url\n    '\n  }\n\n  ### Tarball Mode ###\n  if [[ \"$mode\" == \"tarball\" || \"$mode\" == \"source\" ]]; then\n    local direct_tarball_url=\"https://gitlab.com/$repo/-/archive/$tag_name/${app_lc}-${version_safe}.tar.gz\"\n    filename=\"${app_lc}-${version_safe}.tar.gz\"\n\n    curl $download_timeout -fsSL \"${header[@]}\" -o \"$tmpdir/$filename\" \"$direct_tarball_url\" || {\n      msg_error \"Download failed: $direct_tarball_url\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    tar --no-same-owner -xzf \"$tmpdir/$filename\" -C \"$tmpdir\" || {\n      msg_error \"Failed to extract tarball\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n    local unpack_dir\n    unpack_dir=$(find \"$tmpdir\" -mindepth 1 -maxdepth 1 -type d | head -n1)\n\n    shopt -s dotglob nullglob\n    cp -r \"$unpack_dir\"/* \"$target/\"\n    shopt -u dotglob nullglob\n\n  ### Binary Mode ###\n  elif [[ \"$mode\" == \"binary\" ]]; then\n    local arch\n    arch=$(dpkg --print-architecture 2>/dev/null || uname -m)\n    [[ \"$arch\" == \"x86_64\" ]] && arch=\"amd64\"\n    [[ \"$arch\" == \"aarch64\" ]] && arch=\"arm64\"\n\n    local assets url_match=\"\"\n    assets=$(_gl_asset_urls \"$json\")\n\n    if [[ -n \"$asset_pattern\" ]]; then\n      for u in $assets; do\n        case \"${u##*/}\" in\n        $asset_pattern)\n          url_match=\"$u\"\n          break\n          ;;\n        esac\n      done\n    fi\n\n    if [[ -z \"$url_match\" ]]; then\n      for u in $assets; do\n        if [[ \"$u\" =~ ($arch|amd64|x86_64|aarch64|arm64).*\\.deb$ ]]; then\n          url_match=\"$u\"\n          break\n        fi\n      done\n    fi\n\n    if [[ -z \"$url_match\" ]]; then\n      for u in $assets; do\n        [[ \"$u\" =~ \\.deb$ ]] && url_match=\"$u\" && break\n      done\n    fi\n\n    if [[ -z \"$url_match\" ]]; then\n      local fallback_json\n      if fallback_json=$(_gl_scan_older_releases \"$repo\" \"$repo_encoded\" \"https://gitlab.com\" \"binary\" \"$asset_pattern\" \"$tag_name\"); then\n        json=\"$fallback_json\"\n        tag_name=$(echo \"$json\" | jq -r '.tag_name // empty')\n        [[ \"$tag_name\" =~ ^v[0-9] ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n        msg_info \"Fetching GitLab release: $app ($version)\"\n        assets=$(_gl_asset_urls \"$json\")\n        if [[ -n \"$asset_pattern\" ]]; then\n          for u in $assets; do\n            case \"${u##*/}\" in $asset_pattern)\n              url_match=\"$u\"\n              break\n              ;;\n            esac\n          done\n        fi\n        if [[ -z \"$url_match\" ]]; then\n          for u in $assets; do\n            [[ \"$u\" =~ ($arch|amd64|x86_64|aarch64|arm64).*\\.deb$ ]] && url_match=\"$u\" && break\n          done\n        fi\n        if [[ -z \"$url_match\" ]]; then\n          for u in $assets; do\n            [[ \"$u\" =~ \\.deb$ ]] && url_match=\"$u\" && break\n          done\n        fi\n      fi\n    fi\n\n    if [[ -z \"$url_match\" ]]; then\n      msg_error \"No suitable .deb asset found for $app\"\n      rm -rf \"$tmpdir\"\n      return 1\n    fi\n\n    filename=\"${url_match##*/}\"\n    curl $download_timeout -fsSL \"${header[@]}\" -o \"$tmpdir/$filename\" \"$url_match\" || {\n      msg_error \"Download failed: $url_match\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    chmod 644 \"$tmpdir/$filename\"\n    local dpkg_opts=\"\"\n    [[ \"${DPKG_FORCE_CONFOLD:-}\" == \"1\" ]] && dpkg_opts=\"-o Dpkg::Options::=--force-confold\"\n    [[ \"${DPKG_FORCE_CONFNEW:-}\" == \"1\" ]] && dpkg_opts=\"-o Dpkg::Options::=--force-confnew\"\n    DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts \"$tmpdir/$filename\" || {\n      SYSTEMD_OFFLINE=1 $STD dpkg -i \"$tmpdir/$filename\" || {\n        msg_error \"Both apt and dpkg installation failed\"\n        rm -rf \"$tmpdir\"\n        return 1\n      }\n    }\n\n  ### Prebuild Mode ###\n  elif [[ \"$mode\" == \"prebuild\" ]]; then\n    local pattern=\"${6%\\\"}\"\n    pattern=\"${pattern#\\\"}\"\n    [[ -z \"$pattern\" ]] && {\n      msg_error \"Mode 'prebuild' requires 6th parameter (asset filename pattern)\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    local asset_url=\"\"\n    for u in $(_gl_asset_urls \"$json\"); do\n      filename_candidate=\"${u##*/}\"\n      case \"$filename_candidate\" in\n      $pattern)\n        asset_url=\"$u\"\n        break\n        ;;\n      esac\n    done\n\n    if [[ -z \"$asset_url\" ]]; then\n      local fallback_json\n      if fallback_json=$(_gl_scan_older_releases \"$repo\" \"$repo_encoded\" \"https://gitlab.com\" \"prebuild\" \"$pattern\" \"$tag_name\"); then\n        json=\"$fallback_json\"\n        tag_name=$(echo \"$json\" | jq -r '.tag_name // empty')\n        [[ \"$tag_name\" =~ ^v[0-9] ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n        msg_info \"Fetching GitLab release: $app ($version)\"\n        for u in $(_gl_asset_urls \"$json\"); do\n          filename_candidate=\"${u##*/}\"\n          case \"$filename_candidate\" in $pattern)\n            asset_url=\"$u\"\n            break\n            ;;\n          esac\n        done\n      fi\n    fi\n\n    [[ -z \"$asset_url\" ]] && {\n      msg_error \"No asset matching '$pattern' found\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    filename=\"${asset_url##*/}\"\n    curl $download_timeout -fsSL \"${header[@]}\" -o \"$tmpdir/$filename\" \"$asset_url\" || {\n      msg_error \"Download failed: $asset_url\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    local unpack_tmp\n    unpack_tmp=$(mktemp -d)\n    mkdir -p \"$target\"\n    if [[ \"${CLEAN_INSTALL:-0}\" == \"1\" ]]; then\n      rm -rf \"${target:?}/\"*\n    fi\n\n    if [[ \"$filename\" == *.zip ]]; then\n      ensure_dependencies unzip\n      unzip -q \"$tmpdir/$filename\" -d \"$unpack_tmp\" || {\n        msg_error \"Failed to extract ZIP archive\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 1\n      }\n    elif [[ \"$filename\" == *.tar.* || \"$filename\" == *.tgz || \"$filename\" == *.txz ]]; then\n      tar --no-same-owner -xf \"$tmpdir/$filename\" -C \"$unpack_tmp\" || {\n        msg_error \"Failed to extract TAR archive\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 1\n      }\n    else\n      msg_error \"Unsupported archive format: $filename\"\n      rm -rf \"$tmpdir\" \"$unpack_tmp\"\n      return 1\n    fi\n\n    local top_entries inner_dir\n    top_entries=$(find \"$unpack_tmp\" -mindepth 1 -maxdepth 1)\n    if [[ \"$(echo \"$top_entries\" | wc -l)\" -eq 1 && -d \"$top_entries\" ]]; then\n      inner_dir=\"$top_entries\"\n      shopt -s dotglob nullglob\n      if compgen -G \"$inner_dir/*\" >/dev/null; then\n        cp -r \"$inner_dir\"/* \"$target/\" || {\n          msg_error \"Failed to copy contents from $inner_dir to $target\"\n          rm -rf \"$tmpdir\" \"$unpack_tmp\"\n          return 1\n        }\n      else\n        msg_error \"Inner directory is empty: $inner_dir\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 1\n      fi\n      shopt -u dotglob nullglob\n    else\n      shopt -s dotglob nullglob\n      if compgen -G \"$unpack_tmp/*\" >/dev/null; then\n        cp -r \"$unpack_tmp\"/* \"$target/\" || {\n          msg_error \"Failed to copy contents to $target\"\n          rm -rf \"$tmpdir\" \"$unpack_tmp\"\n          return 1\n        }\n      else\n        msg_error \"Unpacked archive is empty\"\n        rm -rf \"$tmpdir\" \"$unpack_tmp\"\n        return 1\n      fi\n      shopt -u dotglob nullglob\n    fi\n\n  ### Singlefile Mode ###\n  elif [[ \"$mode\" == \"singlefile\" ]]; then\n    local pattern=\"${6%\\\"}\"\n    pattern=\"${pattern#\\\"}\"\n    [[ -z \"$pattern\" ]] && {\n      msg_error \"Mode 'singlefile' requires 6th parameter (asset filename pattern)\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    local asset_url=\"\"\n    for u in $(_gl_asset_urls \"$json\"); do\n      filename_candidate=\"${u##*/}\"\n      case \"$filename_candidate\" in\n      $pattern)\n        asset_url=\"$u\"\n        break\n        ;;\n      esac\n    done\n\n    if [[ -z \"$asset_url\" ]]; then\n      local fallback_json\n      if fallback_json=$(_gl_scan_older_releases \"$repo\" \"$repo_encoded\" \"https://gitlab.com\" \"singlefile\" \"$pattern\" \"$tag_name\"); then\n        json=\"$fallback_json\"\n        tag_name=$(echo \"$json\" | jq -r '.tag_name // empty')\n        [[ \"$tag_name\" =~ ^v[0-9] ]] && version=\"${tag_name:1}\" || version=\"$tag_name\"\n        msg_info \"Fetching GitLab release: $app ($version)\"\n        for u in $(_gl_asset_urls \"$json\"); do\n          filename_candidate=\"${u##*/}\"\n          case \"$filename_candidate\" in $pattern)\n            asset_url=\"$u\"\n            break\n            ;;\n          esac\n        done\n      fi\n    fi\n\n    [[ -z \"$asset_url\" ]] && {\n      msg_error \"No asset matching '$pattern' found\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    filename=\"${asset_url##*/}\"\n    mkdir -p \"$target\"\n\n    local use_filename=\"${USE_ORIGINAL_FILENAME:-false}\"\n    local target_file=\"$app\"\n    [[ \"$use_filename\" == \"true\" ]] && target_file=\"$filename\"\n\n    curl $download_timeout -fsSL \"${header[@]}\" -o \"$target/$target_file\" \"$asset_url\" || {\n      msg_error \"Download failed: $asset_url\"\n      rm -rf \"$tmpdir\"\n      return 1\n    }\n\n    if [[ \"$target_file\" != *.jar && -f \"$target/$target_file\" ]]; then\n      chmod +x \"$target/$target_file\"\n    fi\n\n  else\n    msg_error \"Unknown mode: $mode\"\n    rm -rf \"$tmpdir\"\n    return 1\n  fi\n\n  echo \"$version\" >\"$version_file\"\n  msg_ok \"Deployed: $app ($version)\"\n  rm -rf \"$tmpdir\"\n}\n\n# ------------------------------------------------------------------------------\n# Download NLTK data packages directly from GitHub, bypassing Python.\n# Avoids CPU-instruction failures (SIGILL) on older hardware lacking AVX.\n#\n# Usage:\n#   setup_nltk \"averaged_perceptron_tagger_eng\" \"/nltk_data\"\n#   setup_nltk \"snowball_data stopwords punkt_tab\" \"/usr/share/nltk_data\"\n#\n# Parameters:\n#   $1 - Space-separated list of NLTK package IDs\n#   $2 - Target directory (default: /usr/share/nltk_data)\n#\n# Returns: 0 on success, non-zero if any package failed\n# ------------------------------------------------------------------------------\nfunction setup_nltk() {\n  local packages=\"${1:?setup_nltk requires at least one package name}\"\n  local target_dir=\"${2:-/usr/share/nltk_data}\"\n  local NLTK_INDEX_URL=\"https://raw.githubusercontent.com/nltk/nltk_data/gh-pages/index.xml\"\n  local index_xml rc=0\n\n  ensure_dependencies unzip\n\n  index_xml=$(curl_with_retry \"$NLTK_INDEX_URL\" \"-\") || {\n    msg_error \"Failed to fetch NLTK package index\"\n    return 1\n  }\n\n  local pkg\n  for pkg in $packages; do\n    msg_info \"Downloading NLTK: $pkg\"\n    local pkg_line subdir pkg_url do_unzip tmp_zip\n\n    pkg_line=$(echo \"$index_xml\" | grep \"id=\\\"${pkg}\\\"\" | head -1)\n    if [[ -z \"$pkg_line\" ]]; then\n      msg_error \"NLTK package not found in index: $pkg\"\n      rc=1\n      continue\n    fi\n\n    subdir=$(echo \"$pkg_line\" | grep -oP 'subdir=\"\\K[^\"]+')\n    pkg_url=$(echo \"$pkg_line\" | grep -oP 'url=\"\\K[^\"]+')\n    do_unzip=$(echo \"$pkg_line\" | grep -oP 'unzip=\"\\K[^\"]+')\n\n    if [[ -z \"$subdir\" || -z \"$pkg_url\" ]]; then\n      msg_error \"Could not parse NLTK index entry for: $pkg\"\n      rc=1\n      continue\n    fi\n\n    mkdir -p \"${target_dir}/${subdir}\"\n    tmp_zip=$(mktemp --suffix=.zip)\n\n    if CURL_TIMEOUT=120 curl_with_retry \"$pkg_url\" \"$tmp_zip\"; then\n      if [[ \"$do_unzip\" == \"1\" ]]; then\n        $STD unzip -q -o \"$tmp_zip\" -d \"${target_dir}/${subdir}/\"\n        rm -f \"$tmp_zip\"\n      else\n        mv \"$tmp_zip\" \"${target_dir}/${subdir}/${pkg}.zip\"\n      fi\n      msg_ok \"Downloaded NLTK: $pkg\"\n    else\n      msg_error \"Failed to download NLTK package: $pkg\"\n      rm -f \"$tmp_zip\"\n      rc=1\n    fi\n  done\n\n  return $rc\n}\n"
  },
  {
    "path": "misc/vm-core.func",
    "content": "# Copyright (c) 2021-2026 community-scripts ORG\n# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/LICENSE\n\nset -euo pipefail\nSPINNER_PID=\"\"\nSPINNER_ACTIVE=0\nSPINNER_MSG=\"\"\ndeclare -A MSG_INFO_SHOWN\n\n# ------------------------------------------------------------------------------\n# Loads core utility groups once (colors, formatting, icons, defaults).\n# ------------------------------------------------------------------------------\n\n[[ -n \"${_CORE_FUNC_LOADED:-}\" ]] && return\n_CORE_FUNC_LOADED=1\n\nload_functions() {\n  [[ -n \"${__FUNCTIONS_LOADED:-}\" ]] && return\n  __FUNCTIONS_LOADED=1\n  color\n  formatting\n  icons\n  default_vars\n  set_std_mode\n  shell_check\n  get_valid_nextid\n  cleanup_vmid\n  cleanup\n  check_root\n  pve_check\n  arch_check\n}\n\n# Function to download & save header files\nget_header() {\n  local app_name=$(echo \"${APP,,}\" | tr ' ' '-')\n  local app_type=${APP_TYPE:-vm}\n  local header_url=\"https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/${app_type}/headers/${app_name}\"\n  local local_header_path=\"/usr/local/community-scripts/headers/${app_type}/${app_name}\"\n\n  mkdir -p \"$(dirname \"$local_header_path\")\"\n\n  if [ ! -s \"$local_header_path\" ]; then\n    if ! curl -fsSL \"$header_url\" -o \"$local_header_path\"; then\n      return 250\n    fi\n  fi\n\n  cat \"$local_header_path\" 2>/dev/null || true\n}\n\nheader_info() {\n  local app_name=$(echo \"${APP,,}\" | tr ' ' '-')\n  local header_content\n\n  header_content=$(get_header \"$app_name\") || header_content=\"\"\n\n  clear\n  local term_width\n  term_width=$(tput cols 2>/dev/null || echo 120)\n\n  if [ -n \"$header_content\" ]; then\n    echo \"$header_content\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# Sets ANSI color codes used for styled terminal output.\n# ------------------------------------------------------------------------------\ncolor() {\n  YW=$(echo \"\\033[33m\")\n  YWB=$(echo \"\\033[93m\")\n  BL=$(echo \"\\033[36m\")\n  RD=$(echo \"\\033[01;31m\")\n  BGN=$(echo \"\\033[4;92m\")\n  GN=$(echo \"\\033[1;92m\")\n  DGN=$(echo \"\\033[32m\")\n  CL=$(echo \"\\033[m\")\n}\n\n# ------------------------------------------------------------------------------\n# Defines formatting helpers like tab, bold, and line reset sequences.\n# ------------------------------------------------------------------------------\nformatting() {\n  BFR=\"\\\\r\\\\033[K\"\n  BOLD=$(echo \"\\033[1m\")\n  HOLD=\" \"\n  TAB=\"  \"\n  TAB3=\"      \"\n}\n\n# ------------------------------------------------------------------------------\n# Sets symbolic icons used throughout user feedback and prompts.\n# ------------------------------------------------------------------------------\nicons() {\n  CM=\"${TAB}✔️${TAB}\"\n  CROSS=\"${TAB}✖️${TAB}\"\n  DNSOK=\"✔️ \"\n  DNSFAIL=\"${TAB}✖️${TAB}\"\n  INFO=\"${TAB}💡${TAB}${CL}\"\n  OS=\"${TAB}🖥️${TAB}${CL}\"\n  OSVERSION=\"${TAB}🌟${TAB}${CL}\"\n  CONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\n  DISKSIZE=\"${TAB}💾${TAB}${CL}\"\n  CPUCORE=\"${TAB}🧠${TAB}${CL}\"\n  RAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\n  SEARCH=\"${TAB}🔍${TAB}${CL}\"\n  VERBOSE_CROPPED=\"🔍${TAB}\"\n  VERIFYPW=\"${TAB}🔐${TAB}${CL}\"\n  CONTAINERID=\"${TAB}🆔${TAB}${CL}\"\n  HOSTNAME=\"${TAB}🏠${TAB}${CL}\"\n  BRIDGE=\"${TAB}🌉${TAB}${CL}\"\n  NETWORK=\"${TAB}📡${TAB}${CL}\"\n  GATEWAY=\"${TAB}🌐${TAB}${CL}\"\n  DISABLEIPV6=\"${TAB}🚫${TAB}${CL}\"\n  ICON_DISABLEIPV6=\"${TAB}🚫${TAB}${CL}\"\n  DEFAULT=\"${TAB}⚙️${TAB}${CL}\"\n  MACADDRESS=\"${TAB}🔗${TAB}${CL}\"\n  VLANTAG=\"${TAB}🏷️${TAB}${CL}\"\n  ROOTSSH=\"${TAB}🔑${TAB}${CL}\"\n  CREATING=\"${TAB}🚀${TAB}${CL}\"\n  ADVANCED=\"${TAB}🧩${TAB}${CL}\"\n  FUSE=\"${TAB}🗂️${TAB}${CL}\"\n  GPU=\"${TAB}🎮${TAB}${CL}\"\n  HOURGLASS=\"${TAB}⏳${TAB}\"\n}\n\n# ------------------------------------------------------------------------------\n# Sets default verbose mode for script and os execution.\n# ------------------------------------------------------------------------------\nset_std_mode() {\n  if [ \"${VERBOSE:-no}\" = \"yes\" ]; then\n    STD=\"\"\n  else\n    STD=\"silent\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# default_vars()\n#\n# - Sets default retry and wait variables used for system actions\n# - RETRY_NUM: Maximum number of retry attempts (default: 10)\n# - RETRY_EVERY: Seconds to wait between retries (default: 3)\n# ------------------------------------------------------------------------------\ndefault_vars() {\n  RETRY_NUM=10\n  RETRY_EVERY=3\n  i=$RETRY_NUM\n}\n\n# ------------------------------------------------------------------------------\n# get_active_logfile()\n#\n# - Returns the appropriate log file based on execution context\n# - BUILD_LOG: Host operations (VM creation)\n# - Fallback to /tmp/build-<timestamp>.log if not set\n# ------------------------------------------------------------------------------\nget_active_logfile() {\n  if [[ -n \"${BUILD_LOG:-}\" ]]; then\n    echo \"$BUILD_LOG\"\n  else\n    # Fallback for legacy scripts\n    echo \"/tmp/build-$(date +%Y%m%d_%H%M%S).log\"\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# silent()\n#\n# - Executes command with output redirected to active log file\n# - On error: displays last 20 lines of log and exits with original exit code\n# - Temporarily disables error trap to capture exit code correctly\n# - Sources explain_exit_code() for detailed error messages\n# ------------------------------------------------------------------------------\nsilent() {\n  local cmd=\"$*\"\n  local caller_line=\"${BASH_LINENO[0]:-unknown}\"\n  local logfile=\"$(get_active_logfile)\"\n\n  set +Eeuo pipefail\n  trap - ERR\n\n  \"$@\" >>\"$logfile\" 2>&1\n  local rc=$?\n\n  set -Eeuo pipefail\n  trap 'error_handler' ERR\n\n  if [[ $rc -ne 0 ]]; then\n    # Return instead of exit so that callers can use `$STD cmd || true`\n    # When no || is used, set -e + ERR trap catches it via error_handler()\n    export _SILENT_FAILED_RC=\"$rc\"\n    export _SILENT_FAILED_CMD=\"$cmd\"\n    export _SILENT_FAILED_LINE=\"$caller_line\"\n    export _SILENT_FAILED_LOG=\"$logfile\"\n\n    return \"$rc\"\n  fi\n\n  # Clear stale flags on success\n  unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE _SILENT_FAILED_LOG 2>/dev/null || true\n}\n\n# ------------------------------------------------------------------------------\n# Performs a curl request with retry logic and inline feedback.\n# ------------------------------------------------------------------------------\n\nrun_curl() {\n  if [ \"$VERB\" = \"no\" ]; then\n    curl \"$@\" >/dev/null 2>>/tmp/curl_error.log\n  else\n    curl \"$@\" 2>>/tmp/curl_error.log\n  fi\n}\n\ncurl_handler() {\n  local args=()\n  local url=\"\"\n  local max_retries=0 delay=2 attempt=1\n  local exit_code has_output_file=false\n\n  for arg in \"$@\"; do\n    if [[ \"$arg\" != -* && -z \"$url\" ]]; then\n      url=\"$arg\"\n    fi\n    [[ \"$arg\" == \"-o\" || \"$arg\" == --output ]] && has_output_file=true\n    args+=(\"$arg\")\n  done\n\n  if [[ -z \"$url\" ]]; then\n    msg_error \"no valid url or option entered for curl_handler\"\n    exit 64\n  fi\n\n  $STD msg_info \"Fetching: $url\"\n\n  while :; do\n    if $has_output_file; then\n      $STD run_curl \"${args[@]}\"\n      exit_code=$?\n    else\n      $STD result=$(run_curl \"${args[@]}\")\n      exit_code=$?\n    fi\n\n    if [[ $exit_code -eq 0 ]]; then\n      stop_spinner\n      msg_ok \"Fetched: $url\"\n      $has_output_file || printf '%s' \"$result\"\n      return 0\n    fi\n\n    if ((attempt >= max_retries)); then\n      stop_spinner\n      if [ -s /tmp/curl_error.log ]; then\n        local curl_stderr\n        curl_stderr=$(</tmp/curl_error.log)\n        rm -f /tmp/curl_error.log\n      fi\n      __curl_err_handler \"$exit_code\" \"$url\" \"$curl_stderr\"\n      exit \"$exit_code\"\n    fi\n\n    $STD printf \"\\r\\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}\" >&2\n    sleep \"$delay\"\n    ((attempt++))\n  done\n}\n\n# ------------------------------------------------------------------------------\n# Handles specific curl error codes and displays descriptive messages.\n# ------------------------------------------------------------------------------\n__curl_err_handler() {\n  local exit_code=\"$1\"\n  local target=\"$2\"\n  local curl_msg=\"$3\"\n\n  case $exit_code in\n  1) msg_error \"Unsupported protocol: $target\" ;;\n  2) msg_error \"Curl init failed: $target\" ;;\n  3) msg_error \"Malformed URL: $target\" ;;\n  5) msg_error \"Proxy resolution failed: $target\" ;;\n  6) msg_error \"Host resolution failed: $target\" ;;\n  7) msg_error \"Connection failed: $target\" ;;\n  9) msg_error \"Access denied: $target\" ;;\n  18) msg_error \"Partial file transfer: $target\" ;;\n  22) msg_error \"HTTP error (e.g. 400/404): $target\" ;;\n  23) msg_error \"Write error on local system: $target\" ;;\n  26) msg_error \"Read error from local file: $target\" ;;\n  28) msg_error \"Timeout: $target\" ;;\n  35) msg_error \"SSL connect error: $target\" ;;\n  47) msg_error \"Too many redirects: $target\" ;;\n  51) msg_error \"SSL cert verify failed: $target\" ;;\n  52) msg_error \"Empty server response: $target\" ;;\n  55) msg_error \"Send error: $target\" ;;\n  56) msg_error \"Receive error: $target\" ;;\n  60) msg_error \"SSL CA not trusted: $target\" ;;\n  67) msg_error \"Login denied by server: $target\" ;;\n  78) msg_error \"Remote file not found (404): $target\" ;;\n  *) msg_error \"Curl failed with code $exit_code: $target\" ;;\n  esac\n\n  [[ -n \"$curl_msg\" ]] && printf \"%s\\n\" \"$curl_msg\" >&2\n  exit \"$exit_code\"\n}\n\n# ------------------------------------------------------------------------------\n# shell_check()\n#\n# - Verifies that the script is running under Bash shell\n# - Exits with error message if different shell is detected\n# ------------------------------------------------------------------------------\nshell_check() {\n  if [[ \"$(ps -p $$ -o comm=)\" != \"bash\" ]]; then\n    clear\n    msg_error \"Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit 103\n  fi\n}\n\n# ------------------------------------------------------------------------------\n# clear_line()\n#\n# - Clears current terminal line using tput or ANSI escape codes\n# - Moves cursor to beginning of line (carriage return)\n# - Fallback to ANSI codes if tput not available\n# ------------------------------------------------------------------------------\nclear_line() {\n  tput cr 2>/dev/null || echo -en \"\\r\"\n  tput el 2>/dev/null || echo -en \"\\033[K\"\n}\n\n# ------------------------------------------------------------------------------\n# is_verbose_mode()\n#\n# - Determines if script should run in verbose mode\n# - Checks VERBOSE and var_verbose variables\n# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info()\n# ------------------------------------------------------------------------------\nis_verbose_mode() {\n  local verbose=\"${VERBOSE:-${var_verbose:-no}}\"\n  [[ \"$verbose\" != \"no\" ]]\n}\n\n### dev spinner ###\nSPINNER_ACTIVE=0\nSPINNER_PID=\"\"\nSPINNER_MSG=\"\"\ndeclare -A MSG_INFO_SHOWN=()\n\n# Trap cleanup on various signals\ntrap 'cleanup_spinner' EXIT INT TERM HUP\n\n# Cleans up spinner process on exit\ncleanup_spinner() {\n  stop_spinner\n  # Additional cleanup if needed\n}\n\nstart_spinner() {\n  local msg=\"${1:-Processing...}\"\n  local frames=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)\n  local spin_i=0\n  local interval=0.1\n\n  # Set message and clear current line\n  SPINNER_MSG=\"$msg\"\n  printf \"\\r\\e[2K\" >&2\n\n  # Stop any existing spinner\n  stop_spinner\n\n  # Set active flag\n  SPINNER_ACTIVE=1\n\n  # Start spinner in background\n  {\n    while [[ \"$SPINNER_ACTIVE\" -eq 1 ]]; do\n      printf \"\\r\\e[2K%s %b\" \"${TAB}${frames[spin_i]}${TAB}\" \"${YW}${SPINNER_MSG}${CL}\" >&2\n      spin_i=$(((spin_i + 1) % ${#frames[@]}))\n      sleep \"$interval\"\n    done\n  } &\n\n  SPINNER_PID=$!\n\n  # Disown to prevent getting \"Terminated\" messages\n  disown \"$SPINNER_PID\" 2>/dev/null || true\n}\n\nstop_spinner() {\n  # Check if spinner is active and PID exists\n  if [[ \"$SPINNER_ACTIVE\" -eq 1 ]] && [[ -n \"${SPINNER_PID}\" ]]; then\n    SPINNER_ACTIVE=0\n\n    if kill -0 \"$SPINNER_PID\" 2>/dev/null; then\n      kill \"$SPINNER_PID\" 2>/dev/null\n      # Give it a moment to terminate\n      sleep 0.1\n      # Force kill if still running\n      if kill -0 \"$SPINNER_PID\" 2>/dev/null; then\n        kill -9 \"$SPINNER_PID\" 2>/dev/null\n      fi\n      # Wait for process but ignore errors\n      wait \"$SPINNER_PID\" 2>/dev/null || true\n    fi\n\n    # Clear spinner line\n    printf \"\\r\\e[2K\" >&2\n    SPINNER_PID=\"\"\n  fi\n}\n\nspinner_guard() {\n  # Safely stop spinner if it's running\n  if [[ \"$SPINNER_ACTIVE\" -eq 1 ]] && [[ -n \"${SPINNER_PID}\" ]]; then\n    stop_spinner\n  fi\n}\n\nmsg_info() {\n  local msg=\"${1:-Information message}\"\n\n  # Only show each message once unless reset\n  if [[ -n \"${MSG_INFO_SHOWN[\"$msg\"]+x}\" ]]; then\n    return\n  fi\n  MSG_INFO_SHOWN[\"$msg\"]=1\n\n  spinner_guard\n  start_spinner \"$msg\"\n}\n\nmsg_ok() {\n  local msg=\"${1:-Operation completed successfully}\"\n  stop_spinner\n  printf \"\\r\\e[2K%s %b\\n\" \"${CM}\" \"${GN}${msg}${CL}\" >&2\n\n  # Remove from shown messages to allow it to be shown again\n  local sanitized_msg\n  sanitized_msg=$(printf '%s' \"$msg\" | sed 's/\\x1b\\[[0-9;]*m//g; s/[^a-zA-Z0-9_]/_/g')\n  unset 'MSG_INFO_SHOWN['\"$sanitized_msg\"']' 2>/dev/null || true\n}\n\nmsg_error() {\n  local msg=\"${1:-An error occurred}\"\n  stop_spinner\n  printf \"\\r\\e[2K%s %b\\n\" \"${CROSS}\" \"${RD}${msg}${CL}\" >&2\n}\n\nmsg_warn() {\n  stop_spinner\n  local msg=\"$1\"\n  echo -e \"${BFR:-}${INFO:-ℹ️} ${YWB}${msg}${CL}\" >&2\n}\n\n# Helper function to display a message with custom symbol and color\nmsg_custom() {\n  local symbol=\"${1:-*}\"\n  local color=\"${2:-$CL}\"\n  local msg=\"${3:-Custom message}\"\n  [[ -z \"$msg\" ]] && return\n  stop_spinner\n  printf \"\\r\\e[2K%s %b\\n\" \"$symbol\" \"${color}${msg}${CL}\" >&2\n}\n\n# ------------------------------------------------------------------------------\n# msg_debug()\n#\n# - Displays debug message with timestamp when var_full_verbose=1\n# - Automatically enables var_verbose if not already set\n# - Uses bright yellow color for debug output\n# ------------------------------------------------------------------------------\nmsg_debug() {\n  if [[ \"${var_full_verbose:-0}\" == \"1\" ]]; then\n    [[ \"${var_verbose:-0}\" != \"1\" ]] && var_verbose=1\n    echo -e \"${YWB}[$(date '+%F %T')] [DEBUG]${CL} $*\"\n  fi\n}\n\n# Displays error message and immediately terminates script\nfatal() {\n  msg_error \"$1\"\n  kill -INT $$\n}\n\nget_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\ncleanup_vmid() {\n  if [[ -z \"${VMID:-}\" ]]; then\n    return\n  fi\n  if qm status \"$VMID\" &>/dev/null; then\n    qm stop \"$VMID\" &>/dev/null\n    qm destroy \"$VMID\" &>/dev/null\n  fi\n}\n\ncleanup() {\n  local exit_code=$?\n  if [[ \"$(dirs -p | wc -l)\" -gt 1 ]]; then\n    popd >/dev/null || true\n  fi\n  # Report final telemetry status if post_to_api_vm was called but no update was sent\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if declare -f post_update_to_api >/dev/null 2>&1; then\n      if [[ $exit_code -ne 0 ]]; then\n        post_update_to_api \"failed\" \"$exit_code\"\n      else\n        # Exited cleanly but description()/success was never called — shouldn't happen\n        post_update_to_api \"failed\" \"1\"\n      fi\n    fi\n  fi\n}\n\ncheck_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit 104\n  fi\n}\n\npve_check() {\n  if ! pveversion | grep -Eq \"pve-manager/(8\\.[1-4]|9\\.[0-1])(\\.[0-9]+)*\"; then\n    msg_error \"This version of Proxmox Virtual Environment is not supported\"\n    echo -e \"Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.1.\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit 105\n  fi\n}\n\narch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit 106\n  fi\n}\n\nexit_script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit 0\n}\n\ncheck_hostname_conflict() {\n  local hostname=\"$1\"\n  if qm list | awk '{print $2}' | grep -qx \"$hostname\"; then\n    msg_error \"Hostname $hostname already in use by another VM.\"\n    exit 206\n  fi\n}\n\nset_description() {\n  local app_name script_slug script_url donate_url\n  app_name=$(echo \"${APP,,}\" | tr ' ' '-')\n  script_slug=\"${SCRIPT_SLUG:-${app_name}}\"\n  script_slug=\"$(echo \"$script_slug\" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')\"\n  script_url=\"https://community-scripts.org/scripts/${script_slug}\"\n  donate_url=\"https://community-scripts.org/donate\"\n\n  DESCRIPTION=$(\n    cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>${NSAPP} VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='${donate_url}' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/❤️-Sponsoring%20%26%20Donations-FF5E5B' alt='Sponsoring and donations' />\n    </a>\n  </p>\n\n  <p style='margin: 12px 0;'>\n    <a href='${script_url}' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/📦-Open%20Script%20Page-00617f' alt='Open script page' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n  )\n  qm set \"$VMID\" -description \"$DESCRIPTION\" >/dev/null\n\n}\n"
  },
  {
    "path": "tools/addon/add-netbird-lxc.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://netbird.io/ | Github: https://github.com/netbirdio/netbird\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    _   __     __  ____  _          __\n   / | / /__  / /_/ __ )(_)________/ /\n  /  |/ / _ \\/ __/ __  / / ___/ __  / \n / /|  /  __/ /_/ /_/ / / /  / /_/ / \n/_/ |_/\\___/\\__/_____/_/_/   \\__,_/  \n\nEOF\n}\nheader_info\nset -e\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"add-netbird-lxc\" \"addon\"\n\nwhile true; do\n  read -p \"This will add NetBird to an existing LXC Container ONLY. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nheader_info\necho \"Loading...\"\n\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\n\nNODE=$(hostname)\nMSG_MAX_LENGTH=0\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\n\nwhile [ -z \"${CTID:+x}\" ]; do\n  CTID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --radiolist \\\n    \"\\nSelect a container to add NetBird to:\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\n\nLXC_STATUS=$(pct status \"$CTID\" | awk '{print $2}')\nif [[ \"$LXC_STATUS\" != \"running\" ]]; then\n  msg \"\\e[1;33m The container $CTID is not running. Starting it now...\\e[0m\"\n  pct start \"$CTID\"\n  while [[ \"$(pct status \"$CTID\" | awk '{print $2}')\" != \"running\" ]]; do\n    msg \"\\e[1;33m Waiting for the container to start...\\e[0m\"\n    sleep 2\n  done\n  msg \"\\e[1;32m Container $CTID is now running.\\e[0m\"\nfi\n\nDISTRO=$(pct exec \"$CTID\" -- cat /etc/os-release | grep -w \"ID\" | cut -d'=' -f2 | tr -d '\"')\nif [[ \"$DISTRO\" != \"debian\" && \"$DISTRO\" != \"ubuntu\" ]]; then\n  msg \"\\e[1;31m Error: This script only supports Debian or Ubuntu LXC containers. Detected: $DISTRO. Aborting...\\e[0m\"\n  exit 238\nfi\n\nCTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf\ncat <<EOF >>$CTID_CONFIG_PATH\nlxc.cgroup2.devices.allow: c 10:200 rwm\nlxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file\nEOF\nheader_info\nmsg \"Installing NetBird...\"\npct exec \"$CTID\" -- bash -c '\nif ! command -v curl &>/dev/null; then\n  apt-get update -qq\n  apt-get install -y curl >/dev/null\nfi\napt install -y ca-certificates gpg &>/dev/null\ncurl -fsSL \"https://pkgs.netbird.io/debian/public.key\" | gpg --dearmor >/usr/share/keyrings/netbird-archive-keyring.gpg\necho \"deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main\" >/etc/apt/sources.list.d/netbird.list\napt-get update &>/dev/null\napt-get install -y netbird-ui &>/dev/null\nif systemctl list-unit-files docker.service &>/dev/null; then\n  mkdir -p /etc/systemd/system/netbird.service.d\n  cat <<OVERRIDE >/etc/systemd/system/netbird.service.d/after-docker.conf\n[Unit]\nAfter=docker.service\nWants=docker.service\nOVERRIDE\n  systemctl daemon-reload\nfi\n'\nmsg \"\\e[1;32m ✔ Installed NetBird.\\e[0m\"\nsleep 2\nmsg \"\\e[1;31m Reboot ${CTID} LXC to apply the changes, then run netbird up in the LXC console\\e[0m\"\n"
  },
  {
    "path": "tools/addon/add-tailscale-lxc.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nset -Eeuo pipefail\ntrap 'echo -e \"\\n[ERROR] in line $LINENO: exit code $?\"' ERR\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n  ______      _ __                __\n /_  __/___ _(_) /_____________ _/ /__\n  / / / __ `/ / / ___/ ___/ __ `/ / _ \\\n / / / /_/ / / (__  ) /__/ /_/ / /  __/\n/_/  \\__,_/_/_/____/\\___/\\__,_/_/\\___/\n\nEOF\n}\n\nfunction msg_info() { echo -e \" \\e[1;36m➤\\e[0m $1\"; }\nfunction msg_ok() { echo -e \" \\e[1;32m✔\\e[0m $1\"; }\nfunction msg_error() { echo -e \" \\e[1;31m✖\\e[0m $1\"; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"add-tailscale-lxc\" \"addon\"\n\nheader_info\n\nif ! command -v pveversion &>/dev/null; then\n  msg_error \"This script must be run on the Proxmox VE host (not inside an LXC container)\"\n  exit 232\nfi\n\nwhile true; do\n  read -rp \"This will add Tailscale to an existing LXC Container ONLY. Proceed (y/n)? \" yn\n  case \"$yn\" in\n  [Yy]*) break ;;\n  [Nn]*) exit 0 ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nheader_info\nmsg_info \"Loading container list...\"\n\nNODE=$(hostname)\nMSG_MAX_LENGTH=0\nCTID_MENU=()\n\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))\n  CTID_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pct list | awk 'NR>1')\n\nCTID=\"\"\nwhile [[ -z \"${CTID}\" ]]; do\n  CTID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --radiolist \\\n    \"\\nSelect a container to add Tailscale to:\\n\" \\\n    16 $((MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3) || exit 0\ndone\n\nCTID_CONFIG_PATH=\"/etc/pve/lxc/${CTID}.conf\"\n\n# Skip if already configured\ngrep -q \"lxc.cgroup2.devices.allow: c 10:200 rwm\" \"$CTID_CONFIG_PATH\" || echo \"lxc.cgroup2.devices.allow: c 10:200 rwm\" >>\"$CTID_CONFIG_PATH\"\ngrep -q \"lxc.mount.entry: /dev/net/tun\" \"$CTID_CONFIG_PATH\" || echo \"lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file\" >>\"$CTID_CONFIG_PATH\"\n\nheader_info\nmsg_info \"Installing Tailscale in CT $CTID\"\n\npct exec \"$CTID\" -- sh -c '\nset -e\n\n# Detect OS inside container\nif [ -f /etc/alpine-release ]; then\n  # ── Alpine Linux ──\n  echo \"[INFO] Alpine Linux detected, installing Tailscale via apk...\"\n\n  # Enable community repo if not already enabled\n  if ! grep -q \"^[^#].*community\" /etc/apk/repositories 2>/dev/null; then\n    ALPINE_VERSION=$(cat /etc/alpine-release | cut -d. -f1,2)\n    echo \"https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/community\" >> /etc/apk/repositories\n  fi\n\n  apk update\n  apk add --no-cache tailscale\n\n  # Enable and start Tailscale service\n  rc-update add tailscale default 2>/dev/null || true\n  rc-service tailscale start 2>/dev/null || true\n\nelse\n  # ── Debian / Ubuntu ──\n  export DEBIAN_FRONTEND=noninteractive\n\n  # Source os-release properly (handles quoted values)\n  . /etc/os-release\n\n  # Fallback if DNS is poisoned or blocked\n  ORIG_RESOLV=\"/etc/resolv.conf\"\n  BACKUP_RESOLV=\"/tmp/resolv.conf.backup\"\n\n  # Check DNS resolution using multiple methods (dig may not be installed)\n  dns_check_failed=true\n  if command -v dig >/dev/null 2>&1; then\n    if dig +short pkgs.tailscale.com 2>/dev/null | grep -qvE \"^127\\.|^0\\.0\\.0\\.0$|^$\"; then\n      dns_check_failed=false\n    fi\n  elif command -v host >/dev/null 2>&1; then\n    if host pkgs.tailscale.com 2>/dev/null | grep -q \"has address\"; then\n      dns_check_failed=false\n    fi\n  elif command -v nslookup >/dev/null 2>&1; then\n    if nslookup pkgs.tailscale.com 2>/dev/null | grep -q \"Address:\"; then\n      dns_check_failed=false\n    fi\n  elif command -v getent >/dev/null 2>&1; then\n    if getent hosts pkgs.tailscale.com >/dev/null 2>&1; then\n      dns_check_failed=false\n    fi\n  else\n    # No DNS tools available, try curl directly and assume DNS works\n    dns_check_failed=false\n  fi\n\n  if $dns_check_failed; then\n    echo \"[INFO] DNS resolution for pkgs.tailscale.com failed (blocked or redirected).\"\n    echo \"[INFO] Temporarily overriding /etc/resolv.conf with Cloudflare DNS (1.1.1.1)\"\n    cp \"$ORIG_RESOLV\" \"$BACKUP_RESOLV\"\n    echo \"nameserver 1.1.1.1\" >\"$ORIG_RESOLV\"\n  fi\n\n  if ! command -v curl >/dev/null 2>&1; then\n    echo \"[INFO] curl not found, installing...\"\n    apt-get update -qq\n    apt-get install -y curl >/dev/null\n  fi\n\n  # Ensure keyrings directory exists\n  mkdir -p /usr/share/keyrings\n\n  curl -fsSL \"https://pkgs.tailscale.com/stable/${ID}/${VERSION_CODENAME}.noarmor.gpg\" \\\n    | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null\n\n  echo \"deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/${ID} ${VERSION_CODENAME} main\" \\\n    >/etc/apt/sources.list.d/tailscale.list\n\n  apt-get update -qq\n  apt-get install -y tailscale >/dev/null\n\n  if [ -f /tmp/resolv.conf.backup ]; then\n    echo \"[INFO] Restoring original /etc/resolv.conf\"\n    mv /tmp/resolv.conf.backup /etc/resolv.conf\n  fi\nfi\n'\n\nTAGS=$(awk -F': ' '/^tags:/ {print $2}' \"$CTID_CONFIG_PATH\")\nTAGS=\"${TAGS:+$TAGS; }tailscale\"\npct set \"$CTID\" -tags \"$TAGS\"\n\nmsg_ok \"Tailscale installed on CT $CTID\"\nmsg_info \"Reboot the container, then run 'tailscale up' inside the container to activate.\"\n"
  },
  {
    "path": "tools/addon/adguardhome-sync.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/bakito/adguardhome-sync\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  if [[ -f \"/etc/alpine-release\" ]]; then\n    apk -U add curl >/dev/null 2>&1\n  else\n    apt-get update >/dev/null 2>&1\n    apt-get install -y curl >/dev/null 2>&1\n  fi\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func) 2>/dev/null || true\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"AdGuardHome-Sync\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/adguardhome-sync\"\nCONFIG_PATH=\"/opt/adguardhome-sync/adguardhome-sync.yaml\"\nDEFAULT_PORT=8080\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# HEADER\n# ==============================================================================\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ___       __                          ____  __                        _____\n   /   | ____/ /___ ___  ______ __________/ / / / /___  ____ ___  ___     / ___/__  ______  _____\n  / /| |/ __  / __ `/ / / / __ `/ ___/ __  / /_/ / __ \\/ __ `__ \\/ _ \\    \\__ \\/ / / / __ \\/ ___/\n / ___ / /_/ / /_/ / /_/ / /_/ / /  / /_/ / __  / /_/ / / / / / /  __/   ___/ / /_/ / / / / /__\n/_/  |_\\__,_/\\__, /\\__,_/\\__,_/_/   \\__,_/_/ /_/\\____/_/ /_/ /_/\\___/   /____/\\__, /_/ /_/\\___/\n            /____/                                                           /____/\nEOF\n}\n\n# ==============================================================================\n# HELPER FUNCTIONS\n# ==============================================================================\nget_ip() {\n  hostname -I 2>/dev/null | awk '{print $1}' || ip -4 addr show scope global 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1 || echo \"127.0.0.1\"\n}\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  SERVICE_PATH=\"/etc/init.d/adguardhome-sync\"\nelif [[ -f \"/etc/debian_version\" ]]; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/adguardhome-sync.service\"\nelse\n  msg_error \"Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\n# ==============================================================================\n# DEPENDENCY CHECK\n# ==============================================================================\nif ! command -v jq &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Installing jq \\033[m' >&2\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    apk -U add jq >/dev/null 2>&1\n  fi\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    rc-service adguardhome-sync stop &>/dev/null || true\n    rc-update del adguardhome-sync &>/dev/null || true\n    rm -f \"$SERVICE_PATH\"\n  else\n    systemctl disable --now adguardhome-sync.service &>/dev/null || true\n    rm -f \"$SERVICE_PATH\"\n  fi\n  rm -rf \"$INSTALL_PATH\"\n  rm -f \"/usr/local/bin/update_adguardhome-sync\"\n  rm -f \"$HOME/.adguardhome-sync\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"adguardhome-sync\" \"bakito/adguardhome-sync\"; then\n    msg_info \"Stopping service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service adguardhome-sync stop &>/dev/null || true\n    else\n      systemctl stop adguardhome-sync.service &>/dev/null || true\n    fi\n    msg_ok \"Stopped service\"\n\n    msg_info \"Backing up configuration\"\n    cp \"$CONFIG_PATH\" /tmp/adguardhome-sync.yaml.bak 2>/dev/null || true\n    msg_ok \"Backed up configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"adguardhome-sync\" \"bakito/adguardhome-sync\" \"prebuild\" \"latest\" \"$INSTALL_PATH\" \"adguardhome-sync_*_linux_arm64.tar.gz\"\n\n    msg_info \"Restoring configuration\"\n    cp /tmp/adguardhome-sync.yaml.bak \"$CONFIG_PATH\" 2>/dev/null || true\n    rm -f /tmp/adguardhome-sync.yaml.bak\n    msg_ok \"Restored configuration\"\n\n    msg_info \"Starting service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service adguardhome-sync start\n    else\n      systemctl start adguardhome-sync.service\n    fi\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  local ip\n  ip=$(get_ip)\n\n  fetch_and_deploy_gh_release \"adguardhome-sync\" \"bakito/adguardhome-sync\" \"prebuild\" \"latest\" \"$INSTALL_PATH\" \"adguardhome-sync_*_linux_arm64.tar.gz\"\n\n  # Gather configuration from user\n  echo \"\"\n  echo -e \"${TAB}Enter details for your AdGuard Home instances.\"\n  echo -e \"${TAB}The Origin is your primary instance, Replica will sync from it.\"\n  echo \"\"\n\n  # Origin instance\n  echo -e \"${YW}── Origin (Primary) Instance ──${CL}\"\n  local origin_url origin_user origin_pass\n  read -rp \"  Origin URL (e.g., http://192.168.1.1): \" origin_url\n  origin_url=\"${origin_url:-http://192.168.1.1}\"\n  # Add http:// if no protocol specified\n  [[ ! \"$origin_url\" =~ ^https?:// ]] && origin_url=\"http://${origin_url}\"\n  read -rp \"  Origin Username [admin]: \" origin_user\n  origin_user=\"${origin_user:-admin}\"\n  read -rsp \"  Origin Password: \" origin_pass\n  echo \"\"\n  origin_pass=\"${origin_pass:-changeme}\"\n\n  # Replica instance\n  echo \"\"\n  echo -e \"${YW}── Replica Instance ──${CL}\"\n  local replica_url replica_user replica_pass\n  read -rp \"  Replica URL (e.g., http://192.168.1.2): \" replica_url\n  replica_url=\"${replica_url:-http://192.168.1.2}\"\n  # Add http:// if no protocol specified\n  [[ ! \"$replica_url\" =~ ^https?:// ]] && replica_url=\"http://${replica_url}\"\n  read -rp \"  Replica Username [admin]: \" replica_user\n  replica_user=\"${replica_user:-admin}\"\n  read -rsp \"  Replica Password: \" replica_pass\n  echo \"\"\n  replica_pass=\"${replica_pass:-changeme}\"\n  echo \"\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"\n# AdGuardHome-Sync Configuration\n# Documentation: https://github.com/bakito/adguardhome-sync\n\n# Cron expression for sync interval (e.g., every 2 hours: \"0 */2 * * *\")\ncron: \"0 */2 * * *\"\n\n# Run sync on startup\nrunOnStart: true\n\n# Continue sync on errors\ncontinueOnError: false\n\n# Origin AdGuardHome instance (primary)\norigin:\n  url: \"${origin_url}\"\n  username: \"${origin_user}\"\n  password: \"${origin_pass}\"\n  insecureSkipVerify: false\n\n# Replica instances (one or more)\nreplicas:\n  - url: \"${replica_url}\"\n    username: \"${replica_user}\"\n    password: \"${replica_pass}\"\n    insecureSkipVerify: false\n  # Add more replicas as needed:\n  # - url: \"http://192.168.1.3\"\n  #   username: \"admin\"\n  #   password: \"changeme\"\n\n# API settings (web UI)\napi:\n  port: ${DEFAULT_PORT}\n  darkMode: true\n  metrics:\n    enabled: false\n\n# Sync features (all enabled by default)\nfeatures:\n  dns:\n    accessLists: true\n    serverConfig: true\n    rewrites: true\n  dhcp:\n    serverConfig: true\n    staticLeases: true\n  generalSettings: true\n  queryLogConfig: true\n  statsConfig: true\n  clientSettings: true\n  services: true\n  filters: true\n  theme: true\nEOF\n  chmod 600 \"$CONFIG_PATH\"\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    cat <<EOF >\"$SERVICE_PATH\"\n#!/sbin/openrc-run\n\nname=\"adguardhome-sync\"\ndescription=\"AdGuardHome Sync\"\ncommand=\"${INSTALL_PATH}/adguardhome-sync\"\ncommand_args=\"run --config ${CONFIG_PATH}\"\ncommand_background=true\npidfile=\"/run/\\${RC_SVCNAME}.pid\"\noutput_log=\"/var/log/adguardhome-sync.log\"\nerror_log=\"/var/log/adguardhome-sync.log\"\n\ndepend() {\n    need net\n    after firewall\n}\nEOF\n    chmod +x \"$SERVICE_PATH\"\n    rc-update add adguardhome-sync default\n    rc-service adguardhome-sync start\n  else\n    cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=AdGuardHome Sync\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${INSTALL_PATH}/adguardhome-sync run --config ${CONFIG_PATH}\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    systemctl enable --now adguardhome-sync &>/dev/null\n  fi\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  cat <<'UPDATEEOF' >/usr/local/bin/update_adguardhome-sync\n#!/usr/bin/env bash\n# AdGuardHome-Sync Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/adguardhome-sync.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_adguardhome-sync\n  msg_ok \"Created update script (/usr/local/bin/update_adguardhome-sync)\"\n\n  echo \"\"\n  msg_ok \"${APP} installed successfully\"\n  msg_ok \"Web UI: ${BL}http://${ip}:${DEFAULT_PORT}${CL}\"\n  msg_ok \"Config: ${BL}${CONFIG_PATH}${CL}\"\n  echo \"\"\n  msg_warn \"Edit the config file to add your AdGuardHome instances!\"\n  msg_warn \"  Origin: Your primary AdGuardHome instance\"\n  msg_warn \"  Replicas: One or more replica instances to sync to\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/adguardhome-sync\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\n\nIP=$(get_ip)\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/adguardhome-sync\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - AdGuardHome-Sync (Go binary)\"\necho -e \"${TAB}  - Systemd/OpenRC service\"\necho -e \"${TAB}  - Web UI on port ${DEFAULT_PORT}\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/all-templates.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ___   ____  ______               __     __\n  / _ | / / / /_  __/__ __ _  ___  / /__ _/ /____ ___\n / __ |/ / /   / / / -_)  ' \\/ _ \\/ / _ `/ __/ -_|_-<\n/_/ |_/_/_/   /_/  \\__/_/_/_/ .__/_/\\_,_/\\__/\\__/___/\n                           /_/\nEOF\n}\n\nset -eEuo pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\" 1>&2\n  [ ! -z ${CTID-} ] && cleanup_ctid\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"all-templates\" \"addon\"\n\nfunction validate_container_id() {\n  local ctid=\"$1\"\n  # Check if ID is numeric\n  if ! [[ \"$ctid\" =~ ^[0-9]+$ ]]; then\n    return 1\n  fi\n  # Check if config file exists for VM or LXC\n  if [[ -f \"/etc/pve/qemu-server/${ctid}.conf\" ]] || [[ -f \"/etc/pve/lxc/${ctid}.conf\" ]]; then\n    return 1\n  fi\n  # Check if ID is used in LVM logical volumes\n  if lvs --noheadings -o lv_name 2>/dev/null | grep -qE \"(^|[-_])${ctid}($|[-_])\"; then\n    return 1\n  fi\n  return 0\n}\nfunction get_valid_container_id() {\n  local suggested_id=\"${1:-$(pvesh get /cluster/nextid)}\"\n  while ! validate_container_id \"$suggested_id\"; do\n    suggested_id=$((suggested_id + 1))\n  done\n  echo \"$suggested_id\"\n}\nfunction cleanup_ctid() {\n  if pct status $CTID &>/dev/null; then\n    if [ \"$(pct status $CTID | awk '{print $2}')\" == \"running\" ]; then\n      pct stop $CTID\n    fi\n    pct destroy $CTID\n  fi\n}\n\n# Stop Proxmox VE Monitor-All if running\nif systemctl is-active -q ping-instances.service; then\n  systemctl stop ping-instances.service\nfi\nheader_info\necho \"Loading...\"\npveam update >/dev/null 2>&1\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"All Templates\" --yesno \"This will allow for the creation of one of the many Template LXC Containers. Proceed?\" 10 68\nTEMPLATE_MENU=()\nMSG_MAX_LENGTH=0\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  TEMPLATE_MENU+=(\"$ITEM\" \"$TAG \" \"OFF\")\ndone < <(pveam available)\nTEMPLATE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"All Template LXCs\" --radiolist \"\\nSelect a Template LXC to create:\\n\" 16 $((MSG_MAX_LENGTH + 58)) 10 \"${TEMPLATE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n[ -z \"$TEMPLATE\" ] && {\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No Template LXC Selected\" --msgbox \"It appears that no Template LXC container was selected\" 10 68\n  msg \"Done\"\n  exit\n}\n\n# Setup script environment\nNAME=$(echo \"$TEMPLATE\" | grep -oE '^[^-]+-[^-]+')\nPASS=\"$(openssl rand -base64 8)\"\n\n# Get valid Container ID\nCTID=$(pvesh get /cluster/nextid)\nif ! validate_container_id \"$CTID\"; then\n  warn \"Container ID $CTID is already in use.\"\n  CTID=$(get_valid_container_id \"$CTID\")\n  info \"Using next available ID: $CTID\"\nfi\n\nPCT_OPTIONS=\"\n    -features keyctl=1,nesting=1\n    -hostname $NAME\n    -tags community-script\n    -onboot 0\n    -cores 2\n    -memory 2048\n    -password $PASS\n    -net0 name=eth0,bridge=vmbr0,ip=dhcp\n    -unprivileged 1\n  \"\nDEFAULT_PCT_OPTIONS=(\n  -arch $(dpkg --print-architecture)\n)\n\n# Set the CONTENT and CONTENT_LABEL variables\nfunction select_storage() {\n  local CLASS=$1\n  local CONTENT\n  local CONTENT_LABEL\n  case $CLASS in\n  container)\n    CONTENT='rootdir'\n    CONTENT_LABEL='Container'\n    ;;\n  template)\n    CONTENT='vztmpl'\n    CONTENT_LABEL='Container template'\n    ;;\n  *) false || die \"Invalid storage class.\" ;;\n  esac\n\n  # Query all storage locations\n  local -a MENU\n  while read -r line; do\n    local TAG=$(echo $line | awk '{print $1}')\n    local TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n    local FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n    local ITEM=\"  Type: $TYPE Free: $FREE \"\n    local OFFSET=2\n    if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n      local MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n    fi\n    MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\n  done < <(pvesm status -content $CONTENT | awk 'NR>1')\n\n  # Select storage location\n  if [ $((${#MENU[@]} / 3)) -eq 0 ]; then\n    warn \"'$CONTENT_LABEL' needs to be selected for at least one storage location.\"\n    die \"Unable to detect valid storage location.\"\n  elif [ $((${#MENU[@]} / 3)) -eq 1 ]; then\n    printf ${MENU[0]}\n  else\n    local STORAGE\n    while [ -z \"${STORAGE:+x}\" ]; do\n      STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n        \"Which storage pool would you like to use for the ${CONTENT_LABEL,,}?\\n\\n\" \\\n        16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n        \"${MENU[@]}\" 3>&1 1>&2 2>&3) || die \"Menu aborted.\"\n    done\n    printf $STORAGE\n  fi\n}\nheader_info\n# Get template storage\nTEMPLATE_STORAGE=$(select_storage template)\ninfo \"Using '$TEMPLATE_STORAGE' for template storage.\"\n\n# Get container storage\nCONTAINER_STORAGE=$(select_storage container)\ninfo \"Using '$CONTAINER_STORAGE' for container storage.\"\n\n# Download template\nmsg \"Downloading LXC template (Patience)...\"\npveam download $TEMPLATE_STORAGE $TEMPLATE >/dev/null || die \"A problem occured while downloading the LXC template.\"\n\n# Create variable for 'pct' options\nPCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})\n[[ \" ${PCT_OPTIONS[@]} \" =~ \" -rootfs \" ]] || PCT_OPTIONS+=(-rootfs $CONTAINER_STORAGE:${PCT_DISK_SIZE:-8})\n\n# Create LXC\nmsg \"Creating LXC container...\"\npct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[@]} >/dev/null ||\n  die \"A problem occured while trying to create container.\"\n\n# Save password\necho \"$NAME password: ${PASS}\" >>~/$NAME.creds # file is located in the Proxmox root directory\n\n# Start container\nmsg \"Starting LXC Container...\"\npct start \"$CTID\"\nsleep 5\n\n# Get container IP\nset +eEuo pipefail\nmax_attempts=5\nattempt=1\nIP=\"\"\nwhile [[ $attempt -le $max_attempts ]]; do\n  IP=$(pct exec $CTID ip a show dev eth0 | grep -oP 'inet \\K[^/]+')\n  if [[ -n $IP ]]; then\n    break\n  else\n    warn \"Attempt $attempt: IP address not found. Pausing for 5 seconds...\"\n    sleep 5\n    ((attempt++))\n  fi\ndone\n\nif [[ -z $IP ]]; then\n  warn \"Maximum number of attempts reached. IP address not found.\"\n  IP=\"NOT FOUND\"\nfi\n\nset -eEuo pipefail\n# Start Proxmox VE Monitor-All if available\nif [[ -f /etc/systemd/system/ping-instances.service ]]; then\n  systemctl start ping-instances.service\nfi\n\n# Success message\nheader_info\necho\ninfo \"LXC container '$CTID' was successfully created, and its IP address is ${IP}.\"\necho\ninfo \"Proceed to the LXC console to complete the setup.\"\necho\ninfo \"login: root\"\ninfo \"password: $PASS\"\necho\n"
  },
  {
    "path": "tools/addon/arcane.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: summoningpixels\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/getarcaneapp/arcane\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"arcane\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Arcane\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/arcane\"\nCOMPOSE_FILE=\"${INSTALL_PATH}/compose.yaml\"\nENV_FILE=\"${INSTALL_PATH}/.env\"\nDEFAULT_PORT=3552\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# HEADER\n# ==============================================================================\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ___    ____  _________    _   ________\n   /   |  / __ \\/ ____/   |  / | / / ____/\n  / /| | / /_/ / /   / /| | /  |/ / __/\n / ___ |/ _, _/ /___/ ___ |/ /|  / /___\n/_/  |_/_/ |_|\\____/_/  |_/_/ |_/_____/\n\nEOF\n}\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n\n  if [[ -f \"$COMPOSE_FILE\" ]]; then\n    msg_info \"Stopping and removing Docker containers\"\n    cd \"$INSTALL_PATH\"\n    $STD docker compose down --volumes --remove-orphans\n    msg_ok \"Stopped and removed Docker containers\"\n  fi\n\n  rm -rf \"$INSTALL_PATH\"\n  rm -f \"/usr/local/bin/update_arcane\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  msg_info \"Pulling latest ${APP} image\"\n  cd \"$INSTALL_PATH\"\n  $STD docker compose pull\n  msg_ok \"Pulled latest image\"\n\n  msg_info \"Restarting ${APP}\"\n  $STD docker compose up -d --remove-orphans\n  msg_ok \"Restarted ${APP}\"\n\n  msg_ok \"Updated successfully\"\n  exit\n}\n\n# ==============================================================================\n# CHECK DOCKER\n# ==============================================================================\nfunction check_docker() {\n  if ! command -v docker &>/dev/null; then\n    msg_error \"Docker is not installed. This script requires an existing Docker LXC. Exiting.\"\n    exit 10\n  fi\n  if ! docker compose version &>/dev/null; then\n    msg_error \"Docker Compose plugin is not available. Please install it before running this script. Exiting.\"\n    exit 10\n  fi\n  msg_ok \"Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available\"\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  check_docker\n\n  msg_info \"Creating install directory\"\n  mkdir -p \"$INSTALL_PATH\"\n  msg_ok \"Created ${INSTALL_PATH}\"\n\n  # Generate secrets and config values\n  local ENCRYPTION_KEY JWT_SECRET PROJ_DIR\n  ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)\n  JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)\n  PROJ_DIR=\"/etc/arcane/projects\"\n\n  msg_info \"Creating stacks directory\"\n  mkdir -p \"$PROJ_DIR\"\n  msg_ok \"Created ${PROJ_DIR}\"\n\n  msg_info \"Downloading Docker Compose file\"\n  curl -fsSL \"https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/docker/examples/compose.basic.yaml\" -o \"$COMPOSE_FILE\"\n  msg_ok \"Downloaded Docker Compose file\"\n\n  msg_info \"Downloading .env file\"\n  curl -fsSL \"https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/.env.example\" -o \"$ENV_FILE\"\n  chmod 600 \"$ENV_FILE\"\n  msg_ok \"Downloaded .env file\"\n\n  msg_info \"Configuring compose and env files\"\n  sed -i '/^[[:space:]]*#/!s|/host/path/to/projects|'\"$PROJ_DIR\"'|g' \"$COMPOSE_FILE\"\n  sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|ENCRYPTION_KEY='\"$ENCRYPTION_KEY\"'|g' \"$COMPOSE_FILE\"\n  sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|JWT_SECRET='\"$JWT_SECRET\"'|g' \"$COMPOSE_FILE\"\n  sed -i '/^[[:space:]]*#/!s|APP_URL=.*|APP_URL=http://localhost:'\"$DEFAULT_PORT\"'|g' \"$ENV_FILE\"\n  sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|#&|g' \"$ENV_FILE\"\n  sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|#&|g' \"$ENV_FILE\"\n  msg_ok \"Configured compose and env files\"\n\n  msg_info \"Starting ${APP}\"\n  cd \"$INSTALL_PATH\"\n  $STD docker compose up -d\n  msg_ok \"Started ${APP}\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  cat <<'UPDATEEOF' >/usr/local/bin/update_arcane\n#!/usr/bin/env bash\n# Arcane Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/arcane.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_arcane\n  msg_ok \"Created update script (/usr/local/bin/update_arcane)\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n  echo \"\"\n  echo -e \"Arcane Credentials\"\n  echo -e \"==================\"\n  echo -e \"User: arcane\"\n  echo -e \"Password: arcane-admin\"\n  echo \"\"\n  msg_warn \"On first access, you'll be prompted to change your password.\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -f \"$COMPOSE_FILE\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\nget_lxc_ip\n\n# Check if already installed\nif [[ -f \"$COMPOSE_FILE\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Arcane (via Docker Compose)\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/coder-code-server.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://coder.com/ | Github: https://github.com/coder/code-server\n\nfunction header_info {\n  cat <<\"EOF\"\n   ______          __        _____                          \n  / ____/___  ____/ /__     / ___/___  ______   _____  _____\n / /   / __ \\/ __  / _ \\    \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n/ /___/ /_/ / /_/ /  __/   ___/ /  __/ /   | |/ /  __/ /    \n\\____/\\____/\\__,_/\\___/   /____/\\___/_/    |___/\\___/_/     \n \nEOF\n}\nIP=$(hostname -I | awk '{print $1}')\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nAPP=\"Coder Code Server\"\nhostname=\"$(hostname)\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"coder-code-server\" \"addon\"\n\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\n\nfunction error_exit() {\n  trap - ERR\n  local reason=\"Unknown failure occured.\"\n  local msg=\"${1:-$reason}\"\n  local flag=\"${RD}‼ ERROR ${CL}$EXIT@$LINE\"\n  echo -e \"$flag $msg\" 1>&2\n  exit \"$EXIT\"\n}\nclear\nheader_info\nif command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Can't Install on Proxmox \"\n  exit\nfi\nif [ -e /etc/alpine-release ]; then\n  echo -e \"⚠️  Can't Install on Alpine\"\n  exit\nfi\nwhile true; do\n  read -p \"This will Install ${APP} on $hostname. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_info \"Installing Dependencies\"\napt-get update &>/dev/null\napt-get install -y curl &>/dev/null\napt-get install -y git &>/dev/null\nmsg_ok \"Installed Dependencies\"\n\nVERSION=$(curl -fsSL https://api.github.com/repos/coder/code-server/releases/latest |\n  grep \"tag_name\" |\n  awk '{print substr($2, 3, length($2)-4) }')\n\nmsg_info \"Installing Code-Server v${VERSION}\"\ncurl -fOL https://github.com/coder/code-server/releases/download/v\"$VERSION\"/code-server_\"${VERSION}\"_arm64.deb &>/dev/null\ndpkg -i code-server_\"${VERSION}\"_arm64.deb &>/dev/null\nrm -rf code-server_\"${VERSION}\"_arm64.deb\nmkdir -p ~/.config/code-server/\ncat <<EOF >~/.config/code-server/config.yaml\nbind-addr: 0.0.0.0:8680\nauth: none\npassword: \ncert: false\nEOF\nsystemctl enable -q --now code-server@\"$USER\"\nsystemctl restart code-server@\"$USER\"\nif ! systemctl is-active --quiet code-server@\"$USER\"; then\n  error_exit \"code-server service failed to start.\"\nfi\nmsg_ok \"Installed Code-Server v${VERSION} on $hostname\"\n\necho -e \"${APP} should be reachable by going to the following URL.\n         ${BL}http://$IP:8680${CL} \\n\"\n"
  },
  {
    "path": "tools/addon/coolify.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://coolify.io/ | Github: https://github.com/coollabsio/coolify\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  if [[ -f /etc/alpine-release ]]; then\n    apk update >/dev/null 2>&1\n    apk add --no-cache curl >/dev/null 2>&1\n  else\n    apt-get update >/dev/null 2>&1\n    apt-get install -y curl >/dev/null 2>&1\n  fi\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"coolify\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Coolify\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/data/coolify\"\nDEFAULT_PORT=8000\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n\n  if command -v docker &>/dev/null; then\n    msg_info \"Stopping and removing Docker containers\"\n    cd /data/coolify/source 2>/dev/null && docker compose down --remove-orphans 2>/dev/null || true\n    $STD docker stop $(docker ps -aq) 2>/dev/null || true\n    $STD docker rm $(docker ps -aq) 2>/dev/null || true\n    $STD docker network prune -f 2>/dev/null || true\n    msg_ok \"Stopped and removed Docker containers\"\n  fi\n\n  rm -rf \"$INSTALL_PATH\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  msg_info \"Updating ${APP}\"\n  $STD bash <(curl -fsSL https://cdn.coollabs.io/coolify/install.sh)\n  msg_ok \"Updated ${APP}\"\n\n  msg_ok \"Updated successfully\"\n  exit\n}\n\n# ==============================================================================\n# PROXMOX HOST CHECK\n# ==============================================================================\nfunction check_proxmox_host() {\n  if command -v pveversion &>/dev/null; then\n    msg_error \"Running on the Proxmox host is NOT recommended!\"\n    msg_error \"This should be executed inside an LXC container.\"\n    echo \"\"\n    echo -n \"${TAB}Continue anyway? (y/N): \"\n    read -r confirm\n    if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n      msg_warn \"Aborted. Please run this inside an LXC container.\"\n      exit 0\n    fi\n    msg_warn \"Proceeding on Proxmox host at your own risk!\"\n  fi\n}\n\n# ==============================================================================\n# CHECK / INSTALL DOCKER\n# ==============================================================================\nfunction check_or_install_docker() {\n  if command -v docker &>/dev/null; then\n    msg_ok \"Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available\"\n    if docker compose version &>/dev/null; then\n      msg_ok \"Docker Compose is available\"\n    else\n      msg_error \"Docker Compose plugin is not available. Please install it.\"\n      exit 10\n    fi\n    return\n  fi\n\n  msg_warn \"Docker is not installed.\"\n  echo -n \"${TAB}Install Docker now? (y/N): \"\n  read -r install_docker_prompt\n  if [[ ! \"${install_docker_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_error \"Docker is required for ${APP}. Exiting.\"\n    exit 10\n  fi\n\n  msg_info \"Installing Docker\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add docker docker-cli-compose\n    $STD rc-service docker start\n    $STD rc-update add docker default\n  else\n    DOCKER_CONFIG_PATH='/etc/docker/daemon.json'\n    mkdir -p \"$(dirname \"$DOCKER_CONFIG_PATH\")\"\n    echo -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n    $STD sh <(curl -fsSL https://get.docker.com)\n  fi\n  msg_ok \"Installed Docker\"\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  check_or_install_docker\n\n  msg_info \"Installing dependencies\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add --no-cache git openssl\n  else\n    $STD apt-get update\n    $STD apt-get install -y git openssl\n  fi\n  msg_ok \"Installed dependencies\"\n\n  msg_warn \"WARNING: This will run an external installer from https://coolify.io/\"\n  msg_warn \"The following code is NOT maintained or audited by our repository.\"\n  msg_warn \"Review: https://cdn.coollabs.io/coolify/install.sh\"\n  echo \"\"\n  echo -n \"${TAB}Do you want to continue? (y/N): \"\n  read -r confirm\n  if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Installation cancelled. Exiting.\"\n    exit 0\n  fi\n\n  msg_info \"Installing ${APP} (this installs Docker and pulls containers)\"\n  $STD bash <(curl -fsSL https://cdn.coollabs.io/coolify/install.sh)\n  msg_ok \"Installed ${APP}\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -d \"$INSTALL_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\ncheck_proxmox_host\nget_lxc_ip\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Coolify (via external installer)\"\necho -e \"${TAB}  - Docker (if not already installed)\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/copyparty.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/9001/copyparty\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"copyparty\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nVERBOSE=${var_verbose:-no}\nAPP=\"CopyParty\"\nAPP_TYPE=\"addon\"\nBIN_PATH=\"/usr/local/bin/copyparty-sfx.py\"\nCONF_PATH=\"/etc/copyparty.conf\"\nLOG_PATH=\"/var/log/copyparty\"\nDATA_PATH=\"/var/lib/copyparty\"\nSVC_USER=\"copyparty\"\nSVC_GROUP=\"copyparty\"\nSRC_URL=\"https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py\"\nDEFAULT_PORT=3923\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  PKG_MANAGER=\"apk add --no-cache\"\n  SERVICE_PATH=\"/etc/init.d/copyparty\"\nelif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n  OS=\"Debian\"\n  PKG_MANAGER=\"apt-get install -y\"\n  SERVICE_PATH=\"/etc/systemd/system/copyparty.service\"\nelse\n  msg_error \"Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\n# ==============================================================================\n# HEADER\n# ==============================================================================\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n   ______                  ____             __\n  / ____/___  ____  __  __/ __ \\____ ______/ /___  __\n / /   / __ \\/ __ \\/ / / / /_/ / __ `/ ___/ __/ / / /\n/ /___/ /_/ / /_/ / /_/ / ____/ /_/ / /  / /_/ /_/ /\n\\____/\\____/ .___/\\__, /_/    \\__,_/_/   \\__/\\__, /\n          /_/    /____/                     /____/\nEOF\n}\n\n# ==============================================================================\n# HELPER FUNCTIONS\n# ==============================================================================\n\n# ==============================================================================\n# HELPER FUNCTIONS\n# ==============================================================================\nfunction setup_user_and_dirs() {\n  msg_info \"Creating $SVC_USER user and directories\"\n  if ! id \"$SVC_USER\" &>/dev/null; then\n    if [[ \"$OS\" == \"Debian\" ]]; then\n      useradd -r -s /sbin/nologin -d \"$DATA_PATH\" \"$SVC_USER\"\n    else\n      addgroup -S \"$SVC_GROUP\" 2>/dev/null || true\n      adduser -S -D -H -G \"$SVC_GROUP\" -h \"$DATA_PATH\" -s /sbin/nologin \"$SVC_USER\" 2>/dev/null || true\n    fi\n  fi\n  mkdir -p \"$DATA_PATH\" \"$LOG_PATH\"\n  chown -R \"$SVC_USER:$SVC_GROUP\" \"$DATA_PATH\" \"$LOG_PATH\"\n  chmod 755 \"$DATA_PATH\" \"$LOG_PATH\"\n  msg_ok \"User/Group/Dirs ready\"\n}\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    rc-service copyparty stop &>/dev/null || true\n    rc-update del copyparty &>/dev/null || true\n    rm -f \"$SERVICE_PATH\"\n  else\n    systemctl disable --now copyparty.service &>/dev/null || true\n    rm -f \"$SERVICE_PATH\"\n  fi\n  rm -f \"$BIN_PATH\" \"$CONF_PATH\"\n  rm -rf \"$DATA_PATH\" \"$LOG_PATH\"\n  userdel \"$SVC_USER\" 2>/dev/null || true\n  groupdel \"$SVC_GROUP\" 2>/dev/null || true\n  rm -f \"/usr/local/bin/update_copyparty\"\n  rm -f \"$HOME/.copyparty\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"copyparty-sfx.py\" \"9001/copyparty\"; then\n    msg_info \"Stopping service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service copyparty stop &>/dev/null || true\n    else\n      systemctl stop copyparty.service &>/dev/null || true\n    fi\n    msg_ok \"Stopped service\"\n\n    msg_info \"Updating ${APP}\"\n    curl -fsSL \"$SRC_URL\" -o \"$BIN_PATH\"\n    chmod +x \"$BIN_PATH\"\n    chown \"$SVC_USER:$SVC_GROUP\" \"$BIN_PATH\"\n    msg_ok \"Updated ${APP}\"\n\n    msg_info \"Starting service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service copyparty start\n    else\n      systemctl start copyparty.service\n    fi\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  local port data_path enable_auth admin_user admin_pass\n\n  echo \"\"\n  read -rp \"${TAB}Enter port for ${APP} [${DEFAULT_PORT}]: \" port\n  port=${port:-$DEFAULT_PORT}\n\n  read -rp \"${TAB}Set data directory [${DATA_PATH}]: \" data_path\n  data_path=${data_path:-$DATA_PATH}\n\n  echo -n \"${TAB}Enable authentication? (Y/n): \"\n  read -r enable_auth\n  if [[ \"${enable_auth,,}\" =~ ^(n|no)$ ]]; then\n    admin_user=\"\"\n    admin_pass=\"\"\n    msg_ok \"Configured without authentication\"\n  else\n    read -rp \"${TAB}Set admin username [admin]: \" admin_user\n    admin_user=${admin_user:-admin}\n    read -rsp \"${TAB}Set admin password [community-scripts.org]: \" admin_pass\n    echo \"\"\n    admin_pass=${admin_pass:-community-scripts.org}\n    msg_ok \"Configured with admin user: ${admin_user}\"\n  fi\n\n  msg_info \"Installing dependencies\"\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    $STD $PKG_MANAGER python3 python3-pil ffmpeg curl\n  else\n    $STD $PKG_MANAGER python3 py3-pillow ffmpeg curl\n  fi\n  msg_ok \"Dependencies installed (with thumbnail support)\"\n\n  setup_user_and_dirs\n\n  # Use data_path if provided\n  if [[ \"$data_path\" != \"$DATA_PATH\" ]]; then\n    DATA_PATH=\"$data_path\"\n    mkdir -p \"$DATA_PATH\"\n    chown \"$SVC_USER:$SVC_GROUP\" \"$DATA_PATH\"\n  fi\n\n  msg_info \"Downloading ${APP}\"\n  curl -fsSL \"$SRC_URL\" -o \"$BIN_PATH\"\n  chmod +x \"$BIN_PATH\"\n  chown \"$SVC_USER:$SVC_GROUP\" \"$BIN_PATH\"\n  msg_ok \"Downloaded to ${BIN_PATH}\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONF_PATH\"\n[global]\n  p: ${port}\n  ansi\n  e2dsa\n  e2ts\n  theme: 2\n  grid\n  no-robots\n  force-js\n  lo: ${LOG_PATH}/cpp-%Y-%m%d.txt.xz\n\nEOF\n\n  if [[ -n \"$admin_user\" && -n \"$admin_pass\" ]]; then\n    cat <<EOF >>\"$CONF_PATH\"\n[accounts]\n  ${admin_user}: ${admin_pass}\n\nEOF\n  fi\n\n  cat <<EOF >>\"$CONF_PATH\"\n[/]\n  ${DATA_PATH}\n  accs:\nEOF\n\n  if [[ -n \"$admin_user\" ]]; then\n    cat <<EOF >>\"$CONF_PATH\"\n    rw: *\n    rwmda: ${admin_user}\nEOF\n  else\n    cat <<EOF >>\"$CONF_PATH\"\n    rw: *\nEOF\n  fi\n\n  chmod 640 \"$CONF_PATH\"\n  chown \"$SVC_USER:$SVC_GROUP\" \"$CONF_PATH\"\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    cat <<'SERVICEEOF' >\"$SERVICE_PATH\"\n#!/sbin/openrc-run\n\nname=\"copyparty\"\ndescription=\"CopyParty file server\"\ncommand=\"$(command -v python3)\"\ncommand_args=\"/usr/local/bin/copyparty-sfx.py -c /etc/copyparty.conf\"\ncommand_background=true\ndirectory=\"/var/lib/copyparty\"\npidfile=\"/run/copyparty.pid\"\noutput_log=\"/var/log/copyparty/copyparty.log\"\nerror_log=\"/var/log/copyparty/copyparty.err\"\n\ndepend() {\n    need net\n}\nSERVICEEOF\n    chmod +x \"$SERVICE_PATH\"\n    $STD rc-update add copyparty default\n    $STD rc-service copyparty start\n  else\n    cat <<SERVICEEOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=CopyParty file server\nAfter=network.target\n\n[Service]\nUser=${SVC_USER}\nGroup=${SVC_GROUP}\nWorkingDirectory=${DATA_PATH}\nExecStart=/usr/bin/python3 ${BIN_PATH} -c ${CONF_PATH}\nRestart=always\nStandardOutput=append:${LOG_PATH}/copyparty.log\nStandardError=append:${LOG_PATH}/copyparty.err\n\n[Install]\nWantedBy=multi-user.target\nSERVICEEOF\n    systemctl daemon-reload\n    systemctl enable --now copyparty.service &>/dev/null\n  fi\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  ensure_usr_local_bin_persist\n  cat <<'UPDATEEOF' >/usr/local/bin/update_copyparty\n#!/usr/bin/env bash\n# CopyParty Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/copyparty.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_copyparty\n  msg_ok \"Created update script (/usr/local/bin/update_copyparty)\"\n\n  echo \"\"\n  msg_ok \"${APP} installed successfully\"\n  msg_ok \"Web UI: ${BL}http://${LOCAL_IP}:${port}${CL}\"\n  msg_ok \"Storage: ${BL}${DATA_PATH}${CL}\"\n  msg_ok \"Config: ${BL}${CONF_PATH}${CL}\"\n  if [[ -n \"$admin_user\" ]]; then\n    echo \"\"\n    msg_ok \"Login: ${GN}${admin_user}${CL} / ${GN}${admin_pass}${CL}\"\n  fi\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -f \"$BIN_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -f \"$BIN_PATH\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - CopyParty (Python file server)\"\necho -e \"${TAB}  - Thumbnail support (Pillow, FFmpeg)\"\necho -e \"${TAB}  - Systemd/OpenRC service\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/cronmaster.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/fccview/cronmaster\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"cronmaster\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"CronMaster\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/cronmaster\"\nCONFIG_PATH=\"/opt/cronmaster/.env\"\nSERVICE_PATH=\"/etc/systemd/system/cronmaster.service\"\nDEFAULT_PORT=3000\n\n# ==============================================================================\n# HEADER\n# ==============================================================================\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ______                __  ___           __\n  / ____/________  ____ /  |/  /___ ______/ /____  _____\n / /   / ___/ __ \\/ __ \\/ /|_/ / __ `/ ___/ __/ _ \\/ ___/\n/ /___/ /  / /_/ / / / / /  / / /_/ (__  ) /_/  __/ /\n\\____/_/   \\____/_/ /_/_/  /_/\\__,_/____/\\__/\\___/_/\n\nEOF\n}\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then\n  echo -e \"${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n  systemctl disable --now cronmaster.service &>/dev/null || true\n  rm -f \"$SERVICE_PATH\"\n  rm -rf \"$INSTALL_PATH\"\n  rm -f \"/usr/local/bin/update_cronmaster\"\n  rm -f \"$HOME/.cronmaster\"\n  rm -f \"/root/cronmaster.creds\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"cronmaster\" \"fccview/cronmaster\"; then\n    msg_info \"Stopping service\"\n    systemctl stop cronmaster.service &>/dev/null || true\n    msg_ok \"Stopped service\"\n\n    msg_info \"Backing up configuration\"\n    cp \"$CONFIG_PATH\" /tmp/cronmaster.env.bak 2>/dev/null || true\n    msg_ok \"Backed up configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"cronmaster\" \"fccview/cronmaster\" \"prebuild\" \"latest\" \"$INSTALL_PATH\" \"cronmaster_*_prebuild.tar.gz\"\n\n    msg_info \"Restoring configuration\"\n    cp /tmp/cronmaster.env.bak \"$CONFIG_PATH\" 2>/dev/null || true\n    rm -f /tmp/cronmaster.env.bak\n    msg_ok \"Restored configuration\"\n\n    msg_info \"Starting service\"\n    systemctl start cronmaster\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  # Setup Node.js (only installs if not present or different version)\n  if command -v node &>/dev/null; then\n    msg_ok \"Node.js already installed ($(node -v))\"\n  else\n    NODE_VERSION=\"22\" setup_nodejs\n  fi\n\n  fetch_and_deploy_gh_release \"cronmaster\" \"fccview/cronmaster\" \"prebuild\" \"latest\" \"$INSTALL_PATH\" \"cronmaster_*_prebuild.tar.gz\"\n\n  local AUTH_PASS\n  AUTH_PASS=\"$(openssl rand -base64 18 | cut -c1-13)\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"\nNODE_ENV=production\nAUTH_PASSWORD=${AUTH_PASS}\nPORT=${DEFAULT_PORT}\nHOSTNAME=0.0.0.0\nNEXT_TELEMETRY_DISABLED=1\nEOF\n  chmod 600 \"$CONFIG_PATH\"\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=CronMaster Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=${INSTALL_PATH}\nEnvironmentFile=${CONFIG_PATH}\nExecStart=/usr/bin/node ${INSTALL_PATH}/server.js\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now cronmaster\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  ensure_usr_local_bin_persist\n  cat <<'EOF' >/usr/local/bin/update_cronmaster\n#!/usr/bin/env bash\n# CronMaster Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/cronmaster.sh)\"\nEOF\n  chmod +x /usr/local/bin/update_cronmaster\n  msg_ok \"Created update script (/usr/local/bin/update_cronmaster)\"\n\n  # Save credentials\n  local CREDS_FILE=\"/root/cronmaster.creds\"\n  cat <<EOF >\"$CREDS_FILE\"\nCronMaster Credentials\n======================\nPassword: ${AUTH_PASS}\n\nWeb UI: http://${LOCAL_IP}:${DEFAULT_PORT}\nEOF\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n  msg_ok \"Credentials saved to: ${BL}${CREDS_FILE}${CL}\"\n  echo \"\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -d \"$INSTALL_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -n \"$(ls -A \"$INSTALL_PATH\" 2>/dev/null)\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Node.js 22\"\necho -e \"${TAB}  - CronMaster (prebuild)\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/crowdsec.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.crowdsec.net/ | Github: https://github.com/crowdsecurity/crowdsec\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nAPP=\"CrowdSec\"\nhostname=\"$(hostname)\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"crowdsec\" \"addon\"\n\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\n\nfunction error_exit() {\n  trap - ERR\n  local reason=\"Unknown failure occured.\"\n  local msg=\"${1:-$reason}\"\n  local flag=\"${RD}‼ ERROR ${CL}$EXIT@$LINE\"\n  echo -e \"$flag $msg\" 1>&2\n  exit \"$EXIT\"\n}\nif command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Can't Install on Proxmox \"\n  exit\nfi\nwhile true; do\n  read -p \"This will Install ${APP} on $hostname. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nclear\nfunction header_info() {\n  echo -e \"${BL}\n   _____                      _  _____           \n  / ____|                    | |/ ____|          \n | |     _ __ _____      ____| | (___   ___  ___ \n | |    |  __/ _ \\ \\ /\\ / / _  |\\___ \\ / _ \\/ __|\n | |____| | | (_) \\ V  V / (_| |____) |  __/ (__ \n  \\_____|_|  \\___/ \\_/\\_/ \\__ _|_____/ \\___|\\___|\n${CL}\"\n}\n\nheader_info\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_info \"Setting up ${APP} Repository\"\napt-get update &>/dev/null\napt-get install -y curl &>/dev/null\napt-get install -y gnupg &>/dev/null\ncurl -fsSL \"https://install.crowdsec.net\" | bash &>/dev/null\nmsg_ok \"Setup ${APP} Repository\"\n\nmsg_info \"Installing ${APP}\"\napt-get update &>/dev/null\napt-get install -y crowdsec &>/dev/null\nmsg_ok \"Installed ${APP} on $hostname\"\n\nmsg_info \"Installing ${APP} Common Bouncer\"\napt-get install -y crowdsec-firewall-bouncer-iptables &>/dev/null\nmsg_ok \"Installed ${APP} Common Bouncer\"\n\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "tools/addon/dockge.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Addon: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://dockge.kuma.pet/ | Github: https://github.com/louislam/dockge\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  if [[ -f /etc/alpine-release ]]; then\n    apk update >/dev/null 2>&1\n    apk add --no-cache curl >/dev/null 2>&1\n  else\n    apt-get update >/dev/null 2>&1\n    apt-get install -y curl >/dev/null 2>&1\n  fi\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"dockge\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Dockge\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/dockge\"\nSTACKS_PATH=\"/opt/stacks\"\nCOMPOSE_FILE=\"${INSTALL_PATH}/compose.yaml\"\nDEFAULT_PORT=5001\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n\n  if [[ -f \"$COMPOSE_FILE\" ]]; then\n    msg_info \"Stopping and removing Docker containers\"\n    cd \"$INSTALL_PATH\"\n    $STD docker compose down --remove-orphans\n    msg_ok \"Stopped and removed Docker containers\"\n  fi\n\n  rm -rf \"$INSTALL_PATH\"\n  msg_ok \"${APP} has been uninstalled\"\n  msg_warn \"Stacks directory ${STACKS_PATH} was NOT removed. Delete manually if no longer needed.\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  msg_info \"Pulling latest ${APP} image\"\n  cd \"$INSTALL_PATH\"\n  $STD docker compose pull\n  msg_ok \"Pulled latest image\"\n\n  msg_info \"Restarting ${APP}\"\n  $STD docker compose up -d --remove-orphans\n  msg_ok \"Restarted ${APP}\"\n\n  msg_ok \"Updated successfully\"\n  exit\n}\n\n# ==============================================================================\n# PROXMOX HOST CHECK\n# ==============================================================================\nfunction check_proxmox_host() {\n  if command -v pveversion &>/dev/null; then\n    msg_error \"Running on the Proxmox host is NOT recommended!\"\n    msg_error \"This should be executed inside an LXC container.\"\n    echo \"\"\n    echo -n \"${TAB}Continue anyway? (y/N): \"\n    read -r confirm\n    if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n      msg_warn \"Aborted. Please run this inside an LXC container.\"\n      exit 0\n    fi\n    msg_warn \"Proceeding on Proxmox host at your own risk!\"\n  fi\n}\n\n# ==============================================================================\n# CHECK / INSTALL DOCKER\n# ==============================================================================\nfunction check_or_install_docker() {\n  if command -v docker &>/dev/null; then\n    msg_ok \"Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available\"\n    if docker compose version &>/dev/null; then\n      msg_ok \"Docker Compose is available\"\n    else\n      msg_error \"Docker Compose plugin is not available. Please install it.\"\n      exit 10\n    fi\n    return\n  fi\n\n  msg_warn \"Docker is not installed.\"\n  echo -n \"${TAB}Install Docker now? (y/N): \"\n  read -r install_docker_prompt\n  if [[ ! \"${install_docker_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_error \"Docker is required for ${APP}. Exiting.\"\n    exit 10\n  fi\n\n  msg_info \"Installing Docker\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add docker docker-cli-compose\n    $STD rc-service docker start\n    $STD rc-update add docker default\n  else\n    DOCKER_CONFIG_PATH='/etc/docker/daemon.json'\n    mkdir -p \"$(dirname \"$DOCKER_CONFIG_PATH\")\"\n    echo -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n    $STD sh <(curl -fsSL https://get.docker.com)\n  fi\n  msg_ok \"Installed Docker\"\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  check_or_install_docker\n\n  msg_info \"Creating install directories\"\n  mkdir -p \"$INSTALL_PATH\" \"$STACKS_PATH\"\n  msg_ok \"Created ${INSTALL_PATH} and ${STACKS_PATH}\"\n\n  msg_info \"Downloading Docker Compose file\"\n  curl -fsSL \"https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml\" -o \"$COMPOSE_FILE\"\n  msg_ok \"Downloaded Docker Compose file\"\n\n  msg_info \"Starting ${APP}\"\n  cd \"$INSTALL_PATH\"\n  $STD docker compose up -d\n  msg_ok \"Started ${APP}\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -f \"$COMPOSE_FILE\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\ncheck_proxmox_host\nget_lxc_ip\n\n# Check if already installed\nif [[ -f \"$COMPOSE_FILE\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Dockge (via Docker Compose)\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/dokploy.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://dokploy.com/ | Github: https://github.com/Dokploy/dokploy\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  if [[ -f /etc/alpine-release ]]; then\n    apk update >/dev/null 2>&1\n    apk add --no-cache curl >/dev/null 2>&1\n  else\n    apt-get update >/dev/null 2>&1\n    apt-get install -y curl >/dev/null 2>&1\n  fi\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"dokploy\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Dokploy\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/etc/dokploy\"\nDEFAULT_PORT=3000\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n\n  if command -v docker &>/dev/null; then\n    msg_info \"Stopping and removing Docker containers\"\n    $STD docker stop $(docker ps -aq) 2>/dev/null || true\n    $STD docker rm $(docker ps -aq) 2>/dev/null || true\n    $STD docker network prune -f 2>/dev/null || true\n    msg_ok \"Stopped and removed Docker containers\"\n  fi\n\n  rm -rf \"$INSTALL_PATH\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  msg_info \"Updating ${APP}\"\n  $STD curl -sSL https://dokploy.com/install.sh | bash -s update\n  msg_ok \"Updated ${APP}\"\n\n  msg_ok \"Updated successfully\"\n  exit\n}\n\n# ==============================================================================\n# PROXMOX HOST CHECK\n# ==============================================================================\nfunction check_proxmox_host() {\n  if command -v pveversion &>/dev/null; then\n    msg_error \"Running on the Proxmox host is NOT recommended!\"\n    msg_error \"This should be executed inside an LXC container.\"\n    echo \"\"\n    echo -n \"${TAB}Continue anyway? (y/N): \"\n    read -r confirm\n    if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n      msg_warn \"Aborted. Please run this inside an LXC container.\"\n      exit 0\n    fi\n    msg_warn \"Proceeding on Proxmox host at your own risk!\"\n  fi\n}\n\n# ==============================================================================\n# CHECK / INSTALL DOCKER\n# ==============================================================================\nfunction check_or_install_docker() {\n  if command -v docker &>/dev/null; then\n    msg_ok \"Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available\"\n    if docker compose version &>/dev/null; then\n      msg_ok \"Docker Compose is available\"\n    else\n      msg_error \"Docker Compose plugin is not available. Please install it.\"\n      exit 10\n    fi\n    return\n  fi\n\n  msg_warn \"Docker is not installed.\"\n  echo -n \"${TAB}Install Docker now? (y/N): \"\n  read -r install_docker_prompt\n  if [[ ! \"${install_docker_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_error \"Docker is required for ${APP}. Exiting.\"\n    exit 10\n  fi\n\n  msg_info \"Installing Docker\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add docker docker-cli-compose\n    $STD rc-service docker start\n    $STD rc-update add docker default\n  else\n    DOCKER_CONFIG_PATH='/etc/docker/daemon.json'\n    mkdir -p \"$(dirname \"$DOCKER_CONFIG_PATH\")\"\n    echo -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n    $STD sh <(curl -fsSL https://get.docker.com)\n  fi\n  msg_ok \"Installed Docker\"\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  check_or_install_docker\n\n  msg_info \"Installing dependencies\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add --no-cache git openssl\n  else\n    $STD apt-get update\n    $STD apt-get install -y git openssl redis\n  fi\n  msg_ok \"Installed dependencies\"\n\n  msg_warn \"WARNING: This will run an external installer from https://dokploy.com/\"\n  msg_warn \"The following code is NOT maintained or audited by our repository.\"\n  echo \"\"\n  echo -n \"${TAB}Do you want to continue? (y/N): \"\n  read -r confirm\n  if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Installation cancelled. Exiting.\"\n    exit 0\n  fi\n\n  msg_info \"Installing ${APP} (this installs Docker and pulls containers)\"\n  $STD bash <(curl -sSL https://dokploy.com/install.sh)\n  msg_ok \"Installed ${APP}\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -d \"$INSTALL_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\ncheck_proxmox_host\nget_lxc_ip\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Dokploy (via external installer)\"\necho -e \"${TAB}  - Docker (if not already installed)\"\necho -e \"${TAB}  - Redis\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/filebrowser-quantum.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n    _______ __     ____                                       ____                    __\n   / ____(_) /__  / __ )_________ _      __________  _____   / __ \\__  ______ _____  / /___  ______ ___\n  / /_  / / / _ \\/ __  / ___/ __ \\ | /| / / ___/ _ \\/ ___/  / / / / / / / __ `/ __ \\/ __/ / / / __ `__ \\\n / __/ / / /  __/ /_/ / /  / /_/ / |/ |/ (__  )  __/ /     / /_/ / /_/ / /_/ / / / / /_/ /_/ / / / / / /\n/_/   /_/_/\\___/_____/_/   \\____/|__/|__/____/\\___/_/      \\___\\_\\__,_/\\__,_/_/ /_/\\__/\\__,_/_/ /_/ /_/\n\nEOF\n}\n\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nRD=$(echo \"\\033[01;31m\")\nBL=$(echo \"\\033[36m\")\nCL=$(echo \"\\033[m\")\nCM=\"${GN}✔️${CL}\"\nCROSS=\"${RD}✖️${CL}\"\nINFO=\"${BL}ℹ️${CL}\"\n\nAPP=\"FileBrowser Quantum\"\nINSTALL_PATH=\"/usr/local/bin/filebrowser\"\nCONFIG_PATH=\"/usr/local/community-scripts/fq-config.yaml\"\nDEFAULT_PORT=8080\nSRC_DIR=\"/\"\nTMP_BIN=\"/tmp/filebrowser.$$\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"filebrowser-quantum\" \"addon\"\n\n# Get primary IP\nIFACE=$(ip -4 route | awk '/default/ {print $5; exit}')\nIP=$(ip -4 addr show \"$IFACE\" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)\n[[ -z \"$IP\" ]] && IP=$(hostname -I | awk '{print $1}')\n[[ -z \"$IP\" ]] && IP=\"127.0.0.1\"\n\n# Proxmox Host Warning\nif [[ -d \"/etc/pve\" ]]; then\n  echo -e \"${RD}⚠️  Warning: Running this addon directly on the Proxmox host is not recommended!${CL}\"\n  echo -e \"${YW}   Only the boot disk will be visible — passthrough drives will not be indexed.${CL}\"\n  echo -e \"${YW}   This causes incorrect disk usage stats and incomplete file browsing.${CL}\"\n  echo -e \"${YW}   Run this addon inside an LXC or VM instead and mount your drives there.${CL}\"\n  echo \"\"\n  echo -n \"Continue anyway on the Proxmox host? (y/N): \"\n  read -r host_confirm\n  if [[ ! \"${host_confirm,,}\" =~ ^(y|yes)$ ]]; then\n    echo -e \"${YW}Aborted.${CL}\"\n    exit 0\n  fi\nfi\n\n# OS Detection\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  SERVICE_PATH=\"/etc/init.d/filebrowser\"\n  PKG_MANAGER=\"apk add --no-cache\"\nelif [[ -f \"/etc/debian_version\" ]]; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/filebrowser.service\"\n  PKG_MANAGER=\"apt-get install -y\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\nheader_info\n\nfunction msg_info() { echo -e \"${INFO} ${YW}$1...${CL}\"; }\nfunction msg_ok() { echo -e \"${CM} ${GN}$1${CL}\"; }\nfunction msg_error() { echo -e \"${CROSS} ${RD}$1${CL}\"; }\n\n# Detect legacy FileBrowser installation\nLEGACY_DB=\"/usr/local/community-scripts/filebrowser.db\"\nLEGACY_BIN=\"/usr/local/bin/filebrowser\"\nLEGACY_SERVICE_DEB=\"/etc/systemd/system/filebrowser.service\"\nLEGACY_SERVICE_ALP=\"/etc/init.d/filebrowser\"\n\nif [[ -f \"$LEGACY_DB\" || -f \"$LEGACY_BIN\" && ! -f \"$CONFIG_PATH\" ]]; then\n  echo -e \"${YW}⚠️ Detected legacy FileBrowser installation.${CL}\"\n  echo -n \"Uninstall legacy FileBrowser and continue with Quantum install? (y/n): \"\n  read -r remove_legacy\n  if [[ \"${remove_legacy,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Uninstalling legacy FileBrowser\"\n    if [[ -f \"$LEGACY_SERVICE_DEB\" ]]; then\n      systemctl disable --now filebrowser.service &>/dev/null\n      rm -f \"$LEGACY_SERVICE_DEB\"\n    elif [[ -f \"$LEGACY_SERVICE_ALP\" ]]; then\n      rc-service filebrowser stop &>/dev/null\n      rc-update del filebrowser &>/dev/null\n      rm -f \"$LEGACY_SERVICE_ALP\"\n    fi\n    rm -f \"$LEGACY_BIN\" \"$LEGACY_DB\"\n    msg_ok \"Legacy FileBrowser removed\"\n  else\n    echo -e \"${YW}❌ Installation aborted by user.${CL}\"\n    exit 0\n  fi\nfi\n\n# Existing installation\nif [[ -f \"$INSTALL_PATH\" ]]; then\n  echo -e \"${YW}⚠️ ${APP} is already installed.${CL}\"\n  echo -n \"Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Uninstalling ${APP}\"\n    if [[ \"$OS\" == \"Debian\" ]]; then\n      systemctl disable --now filebrowser.service &>/dev/null\n      rm -f \"$SERVICE_PATH\"\n    else\n      rc-service filebrowser stop &>/dev/null\n      rc-update del filebrowser &>/dev/null\n      rm -f \"$SERVICE_PATH\"\n    fi\n    rm -f \"$INSTALL_PATH\" \"$CONFIG_PATH\"\n    msg_ok \"${APP} has been uninstalled.\"\n    exit 0\n  fi\n\n  echo -n \"Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Updating ${APP}\"\n    if ! command -v curl &>/dev/null; then $PKG_MANAGER curl &>/dev/null; fi\n    curl -fsSL https://github.com/gtsteffaniak/filebrowser/releases/latest/download/linux-arm64-filebrowser -o \"$TMP_BIN\"\n    chmod +x \"$TMP_BIN\"\n    mv -f \"$TMP_BIN\" /usr/local/bin/filebrowser\n    msg_ok \"Updated ${APP}\"\n    exit 0\n  else\n    echo -e \"${YW}⚠️ Update skipped. Exiting.${CL}\"\n    exit 0\n  fi\nfi\n\necho -e \"${YW}⚠️ ${APP} is not installed.${CL}\"\necho -n \"Enter port number (Default: ${DEFAULT_PORT}): \"\nread -r PORT\nPORT=${PORT:-$DEFAULT_PORT}\n\necho -n \"Install ${APP}? (y/n): \"\nread -r install_prompt\nif ! [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  echo -e \"${YW}⚠️ Installation skipped. Exiting.${CL}\"\n  exit 0\nfi\n\nmsg_info \"Installing ${APP} on ${OS}\"\n$PKG_MANAGER curl ffmpeg &>/dev/null\ncurl -fsSL https://github.com/gtsteffaniak/filebrowser/releases/latest/download/linux-arm64-filebrowser -o \"$TMP_BIN\"\nchmod +x \"$TMP_BIN\"\nmv -f \"$TMP_BIN\" /usr/local/bin/filebrowser\nmsg_ok \"Installed ${APP}\"\n\nmsg_info \"Preparing configuration directory\"\nmkdir -p /usr/local/community-scripts\nchown root:root /usr/local/community-scripts\nchmod 755 /usr/local/community-scripts\nmsg_ok \"Directory prepared\"\n\necho -n \"Use No Authentication? (y/N): \"\nread -r noauth_prompt\n\n# === YAML CONFIG GENERATION ===\nif [[ \"${noauth_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  cat <<EOF >\"$CONFIG_PATH\"\nserver:\n  port: $PORT\n  sources:\n    - path: \"$SRC_DIR\"      \n      name: \"RootFS\"\n      config:\n        denyByDefault: false\n        disableIndexing: false\n        indexingIntervalMinutes: 240\n        conditionals:\n          rules:\n            - neverWatchPath: \"/proc\"\n            - neverWatchPath: \"/sys\"\n            - neverWatchPath: \"/dev\"\n            - neverWatchPath: \"/run\"\n            - neverWatchPath: \"/tmp\"\n            - neverWatchPath: \"/lost+found\"\nauth:\n  methods:\n    noauth: true\nEOF\n  msg_ok \"Configured with no authentication\"\nelse\n  cat <<EOF >\"$CONFIG_PATH\"\nserver:\n  port: $PORT\n  sources:\n    - path: \"$SRC_DIR\"\n      name: \"RootFS\"\n      config:\n        denyByDefault: false\n        disableIndexing: false\n        indexingIntervalMinutes: 240\n        conditionals:\n          rules:\n            - neverWatchPath: \"/proc\"\n            - neverWatchPath: \"/sys\"\n            - neverWatchPath: \"/dev\"\n            - neverWatchPath: \"/run\"\n            - neverWatchPath: \"/tmp\"\n            - neverWatchPath: \"/lost+found\"\nauth:\n  adminUsername: admin\n  adminPassword: community-scripts.org\nEOF\n  msg_ok \"Configured with default admin (admin / community-scripts.org)\"\nfi\n\nmsg_info \"Creating service\"\nif [[ \"$OS\" == \"Debian\" ]]; then\n  cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=FileBrowser Quantum\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/usr/local/community-scripts\nExecStart=/usr/local/bin/filebrowser -c $CONFIG_PATH\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable --now filebrowser &>/dev/null\nelse\n  cat <<EOF >\"$SERVICE_PATH\"\n#!/sbin/openrc-run\n\ncommand=\"/usr/local/bin/filebrowser\"\ncommand_args=\"-c $CONFIG_PATH\"\ncommand_background=true\ndirectory=\"/usr/local/community-scripts\"\npidfile=\"/usr/local/community-scripts/pidfile\"\n\ndepend() {\n    need net\n}\nEOF\n  chmod +x \"$SERVICE_PATH\"\n  rc-update add filebrowser default &>/dev/null\n  rc-service filebrowser start &>/dev/null\nfi\n\nmsg_ok \"Service created successfully\"\necho -e \"${CM} ${GN}${APP} is reachable at: ${BL}http://$IP:$PORT${CL}\"\n"
  },
  {
    "path": "tools/addon/filebrowser.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | Co-Author: MickLesk\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    _______ __     ____\n   / ____(_) /__  / __ )_________ _      __________  _____\n  / /_  / / / _ \\/ __  / ___/ __ \\ | /| / / ___/ _ \\/ ___/\n / __/ / / /  __/ /_/ / /  / /_/ / |/ |/ (__  )  __/ /\n/_/   /_/_/\\___/_____/_/   \\____/|__/|__/____/\\___/_/\nEOF\n}\n\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nRD=$(echo \"\\033[01;31m\")\nBL=$(echo \"\\033[36m\")\nCL=$(echo \"\\033[m\")\nCM=\"${GN}✔️${CL}\"\nCROSS=\"${RD}✖️${CL}\"\nINFO=\"${BL}ℹ️${CL}\"\n\nAPP=\"FileBrowser\"\nINSTALL_PATH=\"/usr/local/bin/filebrowser\"\nDB_PATH=\"/usr/local/community-scripts/filebrowser.db\"\nDEFAULT_PORT=8080\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"filebrowser\" \"addon\"\n\n# Get first non-loopback IP & Detect primary network interface dynamically\nIFACE=$(ip -4 route | awk '/default/ {print $5; exit}')\nIP=$(ip -4 addr show \"$IFACE\" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)\n\n[[ -z \"$IP\" ]] && IP=$(hostname -I | awk '{print $1}')\n[[ -z \"$IP\" ]] && IP=\"127.0.0.1\"\n\n# Proxmox Host Warning\nif [[ -d \"/etc/pve\" ]]; then\n  echo -e \"${RD}⚠️  Warning: Running this addon directly on the Proxmox host is not recommended!${CL}\"\n  echo -e \"${YW}   Only the boot disk will be visible — passthrough drives will not be indexed.${CL}\"\n  echo -e \"${YW}   This causes incorrect disk usage stats and incomplete file browsing.${CL}\"\n  echo -e \"${YW}   Run this addon inside an LXC or VM instead and mount your drives there.${CL}\"\n  echo \"\"\n  echo -n \"Continue anyway on the Proxmox host? (y/N): \"\n  read -r host_confirm\n  if [[ ! \"${host_confirm,,}\" =~ ^(y|yes)$ ]]; then\n    echo -e \"${YW}Aborted.${CL}\"\n    exit 0\n  fi\nfi\n\n# Detect OS\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  SERVICE_PATH=\"/etc/init.d/filebrowser\"\n  PKG_MANAGER=\"apk add --no-cache\"\nelif [[ -f \"/etc/debian_version\" ]]; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/filebrowser.service\"\n  PKG_MANAGER=\"apt-get install -y\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\nheader_info\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -e \"${INFO} ${YW}${msg}...${CL}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${CM} ${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${CROSS} ${RD}${msg}${CL}\"\n}\n\nif [ -f \"$INSTALL_PATH\" ]; then\n  echo -e \"${YW}⚠️ ${APP} is already installed.${CL}\"\n  read -r -p \"Would you like to uninstall ${APP}? (y/N): \" uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Uninstalling ${APP}\"\n    if [[ \"$OS\" == \"Debian\" ]]; then\n      systemctl disable --now filebrowser.service &>/dev/null\n      rm -f \"$SERVICE_PATH\"\n    else\n      rc-service filebrowser stop &>/dev/null\n      rc-update del filebrowser &>/dev/null\n      rm -f \"$SERVICE_PATH\"\n    fi\n    rm -f \"$INSTALL_PATH\" \"$DB_PATH\"\n    msg_ok \"${APP} has been uninstalled.\"\n    exit 0\n  fi\n\n  read -r -p \"Would you like to update ${APP}? (y/N): \" update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Updating ${APP}\"\n    if ! command -v curl &>/dev/null; then $PKG_MANAGER curl &>/dev/null; fi\n    curl -fsSL \"https://github.com/filebrowser/filebrowser/releases/latest/download/linux-arm64-filebrowser.tar.gz\" | tar -xzv -C /usr/local/bin &>/dev/null\n    chmod +x \"$INSTALL_PATH\"\n    msg_ok \"Updated ${APP}\"\n    exit 0\n  else\n    echo -e \"${YW}⚠️ Update skipped. Exiting.${CL}\"\n    exit 0\n  fi\nfi\n\necho -e \"${YW}⚠️ ${APP} is not installed.${CL}\"\nread -r -p \"Enter port number (Default: ${DEFAULT_PORT}): \" PORT\nPORT=${PORT:-$DEFAULT_PORT}\n\nread -r -p \"Would you like to install ${APP}? (y/n): \" install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing ${APP} on ${OS}\"\n  $PKG_MANAGER wget tar curl &>/dev/null\n  curl -fsSL \"https://github.com/filebrowser/filebrowser/releases/latest/download/linux-arm64-filebrowser.tar.gz\" | tar -xzv -C /usr/local/bin &>/dev/null\n  chmod +x \"$INSTALL_PATH\"\n  msg_ok \"Installed ${APP}\"\n\n  msg_info \"Creating FileBrowser directory\"\n  mkdir -p /usr/local/community-scripts\n  chown root:root /usr/local/community-scripts\n  chmod 755 /usr/local/community-scripts\n  touch \"$DB_PATH\"\n  chown root:root \"$DB_PATH\"\n  chmod 644 \"$DB_PATH\"\n  msg_ok \"Directory created successfully\"\n\n  read -r -p \"Would you like to use No Authentication? (y/N): \" auth_prompt\n  if [[ \"${auth_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_info \"Configuring No Authentication\"\n    cd /usr/local/community-scripts\n    filebrowser config init -a '0.0.0.0' -p \"$PORT\" -d \"$DB_PATH\" &>/dev/null\n    filebrowser config set -a '0.0.0.0' -p \"$PORT\" -d \"$DB_PATH\" &>/dev/null\n    filebrowser config set --auth.method=noauth --database \"$DB_PATH\" &>/dev/null\n    if ! filebrowser users update 1 --perm.admin --database \"$DB_PATH\" &>/dev/null; then\n      filebrowser users add admin community-scripts.org --perm.admin --database \"$DB_PATH\" &>/dev/null\n    fi\n    msg_ok \"No Authentication configured\"\n  else\n    msg_info \"Setting up default authentication\"\n    cd /usr/local/community-scripts\n    filebrowser config init -a '0.0.0.0' -p \"$PORT\" -d \"$DB_PATH\" &>/dev/null\n    filebrowser config set -a '0.0.0.0' -p \"$PORT\" -d \"$DB_PATH\" &>/dev/null\n    filebrowser users add admin community-scripts.org --perm.admin --database \"$DB_PATH\" &>/dev/null\n    msg_ok \"Default authentication configured (admin:community-scripts.org)\"\n  fi\n\n  msg_info \"Creating service\"\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=Filebrowser\nAfter=network-online.target\n\n[Service]\nUser=root\nWorkingDirectory=/usr/local/community-scripts\nExecStartPre=/bin/touch /usr/local/community-scripts/filebrowser.db\nExecStartPre=/usr/local/bin/filebrowser config set -a \"0.0.0.0\" -p ${PORT} -d /usr/local/community-scripts/filebrowser.db\nExecStart=/usr/local/bin/filebrowser -r / -d /usr/local/community-scripts/filebrowser.db -p ${PORT}\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl enable -q --now filebrowser\n  else\n    cat <<EOF >\"$SERVICE_PATH\"\n#!/sbin/openrc-run\n\ncommand=\"/usr/local/bin/filebrowser\"\ncommand_args=\"-r / -d $DB_PATH -p $PORT\"\ncommand_background=true\npidfile=\"/var/run/filebrowser.pid\"\ndirectory=\"/usr/local/community-scripts\"\n\ndepend() {\n    need net\n}\nEOF\n    chmod +x \"$SERVICE_PATH\"\n    rc-update add filebrowser default &>/dev/null\n    rc-service filebrowser start &>/dev/null\n  fi\n  msg_ok \"Service created successfully\"\n\n  echo -e \"${CM} ${GN}${APP} is reachable at: ${BL}http://$IP:$PORT${CL}\"\nelse\n  echo -e \"${YW}⚠️ Installation skipped. Exiting.${CL}\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/glances.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ________\n  / ____/ /___ _____  ________  _____\n / / __/ / __ `/ __ \\/ ___/ _ \\/ ___/\n/ /_/ / / /_/ / / / / /__/  __(__  )\n\\____/_/\\__,_/_/ /_/\\___/\\___/____/\n\nEOF\n}\n\nAPP=\"Glances\"\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nRD=$(echo \"\\033[01;31m\")\nBL=$(echo \"\\033[36m\")\nCL=$(echo \"\\033[m\")\nCM=\"${GN}✔️${CL}\"\nCROSS=\"${RD}✖️${CL}\"\nINFO=\"${BL}ℹ️${CL}\"\n\nfunction msg_info() { echo -e \"${INFO} ${YW}$1...${CL}\"; }\nfunction msg_ok() { echo -e \"${CM} ${GN}$1${CL}\"; }\nfunction msg_error() { echo -e \"${CROSS} ${RD}$1${CL}\"; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"glances\" \"addon\"\n\nget_lxc_ip() {\n  if command -v hostname >/dev/null 2>&1 && hostname -I 2>/dev/null; then\n    hostname -I | awk '{print $1}'\n  elif command -v ip >/dev/null 2>&1; then\n    ip -4 addr show scope global | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1\n  else\n    echo \"127.0.0.1\"\n  fi\n}\nIP=$(get_lxc_ip)\n\ninstall_glances_debian() {\n  msg_info \"Installing dependencies\"\n  apt-get update >/dev/null 2>&1\n  apt-get install -y gcc lm-sensors wireless-tools curl >/dev/null 2>&1\n  msg_ok \"Installed dependencies\"\n\n  msg_info \"Setting up Python + uv\"\n  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\n  setup_uv PYTHON_VERSION=\"3.12\"\n  msg_ok \"Setup Python + uv\"\n\n  msg_info \"Installing $APP (with web UI)\"\n  cd /opt\n  mkdir -p glances\n  cd glances\n  uv venv --clear\n  source .venv/bin/activate >/dev/null 2>&1\n  uv pip install --upgrade pip wheel setuptools >/dev/null 2>&1\n  uv pip install \"glances[web]\" >/dev/null 2>&1\n  deactivate\n  msg_ok \"Installed $APP\"\n\n  msg_info \"Creating systemd service\"\n  cat <<EOF >/etc/systemd/system/glances.service\n[Unit]\nDescription=Glances - An eye on your system\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/opt/glances/.venv/bin/glances -w\nRestart=on-failure\nWorkingDirectory=/opt/glances\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now glances\n  msg_ok \"Created systemd service\"\n\n  echo -e \"\\n$APP is now running at: http://$IP:61208\\n\"\n}\n\n# update on Debian/Ubuntu\nupdate_glances_debian() {\n  if [[ ! -d /opt/glances/.venv ]]; then\n    msg_error \"$APP is not installed\"\n    exit 233\n  fi\n  msg_info \"Updating $APP\"\n  cd /opt/glances\n  source .venv/bin/activate\n  uv pip install --upgrade \"glances[web]\" >/dev/null 2>&1\n  deactivate\n  systemctl restart glances\n  msg_ok \"Updated successfully!\"\n}\n\n# uninstall on Debian/Ubuntu\nuninstall_glances_debian() {\n  msg_info \"Uninstalling $APP\"\n  systemctl disable -q --now glances || true\n  rm -f /etc/systemd/system/glances.service\n  rm -rf /opt/glances\n  msg_ok \"Removed $APP\"\n}\n\n# install on Alpine\ninstall_glances_alpine() {\n  msg_info \"Installing dependencies\"\n  apk update >/dev/null 2>&1\n  $STD apk add --no-cache \\\n    gcc musl-dev linux-headers python3-dev \\\n    python3 py3-pip py3-virtualenv lm-sensors wireless-tools curl >/dev/null 2>&1\n  msg_ok \"Installed dependencies\"\n\n  msg_info \"Setting up Python + uv\"\n  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\n  setup_uv PYTHON_VERSION=\"3.12\"\n  msg_ok \"Setup Python + uv\"\n\n  msg_info \"Installing $APP (with web UI)\"\n  cd /opt\n  mkdir -p glances\n  cd glances\n  uv venv --clear\n  source .venv/bin/activate\n  uv pip install --upgrade pip wheel setuptools >/dev/null 2>&1\n  uv pip install \"glances[web]\" >/dev/null 2>&1\n  deactivate\n  msg_ok \"Installed $APP\"\n\n  msg_info \"Creating OpenRC service\"\n  cat <<'EOF' >/etc/init.d/glances\n#!/sbin/openrc-run\ncommand=\"/opt/glances/.venv/bin/glances\"\ncommand_args=\"-w\"\ncommand_background=\"yes\"\npidfile=\"/run/glances.pid\"\nname=\"glances\"\ndescription=\"Glances monitoring tool\"\nEOF\n  chmod +x /etc/init.d/glances\n  rc-update add glances default\n  rc-service glances start\n  msg_ok \"Created OpenRC service\"\n\n  echo -e \"\\n$APP is now running at: http://$IP:61208\\n\"\n}\n\n# update on Alpine\nupdate_glances_alpine() {\n  if [[ ! -d /opt/glances/.venv ]]; then\n    msg_error \"$APP is not installed\"\n    exit 233\n  fi\n  msg_info \"Updating $APP\"\n  cd /opt/glances\n  source .venv/bin/activate\n  uv pip install --upgrade \"glances[web]\" >/dev/null 2>&1\n  deactivate\n  rc-service glances restart\n  msg_ok \"Updated successfully!\"\n}\n\n# uninstall on Alpine\nuninstall_glances_alpine() {\n  msg_info \"Uninstalling $APP\"\n  rc-service glances stop || true\n  rc-update del glances || true\n  rm -f /etc/init.d/glances\n  rm -rf /opt/glances\n  msg_ok \"Removed $APP\"\n}\n\n# options menu\nOPTIONS=(Install \"Install $APP\"\n  Update \"Update $APP\"\n  Uninstall \"Uninstall $APP\")\n\nCHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$APP\" --menu \"Select an option:\" 12 58 3 \\\n  \"${OPTIONS[@]}\" 3>&1 1>&2 2>&3 || true)\n\n# OS detection\nif grep -qi \"alpine\" /etc/os-release; then\n  case \"$CHOICE\" in\n  Install) install_glances_alpine ;;\n  Update) update_glances_alpine ;;\n  Uninstall) uninstall_glances_alpine ;;\n  *) exit 0 ;;\n  esac\nelse\n  case \"$CHOICE\" in\n  Install) install_glances_debian ;;\n  Update) update_glances_debian ;;\n  Uninstall) uninstall_glances_debian ;;\n  *) exit 0 ;;\n  esac\nfi\n"
  },
  {
    "path": "tools/addon/homebrew.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MorganCSIT | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://brew.sh | Github: https://github.com/Homebrew/brew\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\ninit_tool_telemetry \"\" \"addon\"\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nVERBOSE=${var_verbose:-no}\nAPP=\"homebrew\"\nAPP_TYPE=\"tools\"\nINSTALL_PATH=\"/home/linuxbrew/.linuxbrew\"\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  echo -e \"${CROSS} Alpine is not supported by Homebrew. Exiting.\"\n  exit 1\nelif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n  OS=\"Debian\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 1\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling Homebrew\"\n\n  BREW_USER=$(awk -F: '$3 >= 1000 && $3 < 65534 { print $1; exit }' /etc/passwd)\n  if [[ -n \"$BREW_USER\" ]]; then\n    BREW_USER_HOME=$(getent passwd \"$BREW_USER\" | cut -d: -f6)\n    for rc_file in \"$BREW_USER_HOME/.bashrc\" \"$BREW_USER_HOME/.profile\"; do\n      if [[ -f \"$rc_file\" ]]; then\n        sed -i '/# Homebrew (Linuxbrew)/,/^fi$/d' \"$rc_file\"\n      fi\n    done\n  fi\n\n  rm -rf /home/linuxbrew\n  rm -f /etc/profile.d/homebrew.sh\n  groupdel linuxbrew &>/dev/null || true\n\n  msg_ok \"Homebrew has been uninstalled\"\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  msg_info \"Detecting Non-Root User\"\n  BREW_USER=$(awk -F: '$3 >= 1000 && $3 < 65534 { print $1; exit }' /etc/passwd)\n  if [[ -z \"$BREW_USER\" ]]; then\n    msg_warn \"No non-root user found (uid >= 1000). Homebrew cannot run as root.\"\n    read -r -p \"${TAB}Create a 'brew' user automatically? (y/N): \" create_user_prompt\n    if [[ \"${create_user_prompt,,}\" =~ ^(y|yes)$ ]]; then\n      msg_info \"Creating user 'brew'\"\n      useradd -m -s /bin/bash brew\n      BREW_USER=\"brew\"\n      msg_ok \"Created user 'brew'\"\n    else\n      msg_error \"Cannot install Homebrew without a non-root user. Exiting.\"\n      exit 1\n    fi\n  fi\n  msg_ok \"Detected User: $BREW_USER\"\n\n  msg_info \"Installing Dependencies\"\n  $STD apt update\n  $STD apt install -y build-essential git file procps\n  msg_ok \"Installed Dependencies\"\n\n  msg_info \"Setting Up Homebrew Prefix\"\n  export PATH=\"/usr/sbin:$PATH\"\n  groupadd -f linuxbrew\n  mkdir -p /home/linuxbrew/.linuxbrew\n  chown -R \"$BREW_USER\":linuxbrew /home/linuxbrew\n  chmod 2775 /home/linuxbrew\n  chmod 2775 /home/linuxbrew/.linuxbrew\n  usermod -aG linuxbrew \"$BREW_USER\"\n  msg_ok \"Set Up Homebrew Prefix\"\n\n  msg_info \"Installing Homebrew\"\n  $STD su - \"$BREW_USER\" -c 'NONINTERACTIVE=1 /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"'\n  msg_ok \"Installed Homebrew\"\n\n  msg_info \"Configuring Shell Integration\"\n  cat <<'EOF' >/etc/profile.d/homebrew.sh\n#!/bin/bash\nif [ -d \"/home/linuxbrew/.linuxbrew\" ]; then\n    eval \"$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\"\nfi\nEOF\n  chmod +x /etc/profile.d/homebrew.sh\n\n  BREW_USER_HOME=$(getent passwd \"$BREW_USER\" | cut -d: -f6)\n  BREW_SHELL_BLOCK='\\n# Homebrew (Linuxbrew)\\nif [ -d \"/home/linuxbrew/.linuxbrew\" ]; then\\n    eval \"$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\"\\nfi'\n  for rc_file in \"$BREW_USER_HOME/.bashrc\" \"$BREW_USER_HOME/.profile\"; do\n    if ! grep -q 'linuxbrew' \"$rc_file\" 2>/dev/null; then\n      echo -e \"$BREW_SHELL_BLOCK\" >>\"$rc_file\"\n    fi\n  done\n  msg_ok \"Configured Shell Integration\"\n\n  msg_info \"Verifying Installation\"\n  $STD su - \"$BREW_USER\" -c 'eval \"$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\" && brew --version'\n  msg_ok \"Homebrew Verified\"\n\n  echo \"\"\n  msg_ok \"Homebrew installed successfully\"\n  msg_ok \"Ready for user: ${BL}${BREW_USER}${CL}\"\n  echo \"\"\n  echo -e \"${TAB}${INFO} Usage: Switch to the brew user with a login shell:\"\n  echo -e \"${TAB}  ${BL}su - ${BREW_USER}${CL}\"\n  echo -e \"${TAB}  Then run: ${BL}brew install <package>${CL}\"\n  echo -e \"${TAB}  Update with: ${BL}brew update${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\n\nif [[ -d \"$INSTALL_PATH\" ]]; then\n  msg_warn \"Homebrew is already installed.\"\n  echo \"\"\n\n  read -r -p \"${TAB}Uninstall Homebrew? (y/N): \" uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"Homebrew is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Homebrew (Linuxbrew) package manager\"\necho -e \"${TAB}  - Shell integration for the detected non-root user\"\necho \"\"\n\nread -r -p \"${TAB}Install Homebrew? (y/N): \" install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/immich-public-proxy.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/alangrainger/immich-public-proxy\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func) 2>/dev/null || true\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Immich Public Proxy\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/immich-proxy\"\nCONFIG_PATH=\"/opt/immich-proxy/app\"\nDEFAULT_PORT=3000\n\n# Initialize all core functions (colors, formatting, icons, $STD mode)\nload_functions\n\n# ==============================================================================\n# HEADER\n# ==============================================================================\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____                    _      __          ____\n   /  _/___ ___  ____ ___  (_)____/ /_        / __ \\_________  _  ____  __\n   / // __ `__ \\/ __ `__ \\/ / ___/ __ \\______/ /_/ / ___/ __ \\| |/_/ / / /\n _/ // / / / / / / / / / / / /__/ / / /_____/ ____/ /  / /_/ />  </ /_/ /\n/___/_/ /_/ /_/_/ /_/ /_/_/\\___/_/ /_/     /_/   /_/   \\____/_/|_|\\__, /\n                                                                 /____/\nEOF\n}\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  msg_error \"Alpine is not supported for ${APP}. Use Debian.\"\n  exit 238\nelif [[ -f \"/etc/debian_version\" ]]; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/immich-proxy.service\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n  systemctl disable --now immich-proxy.service &>/dev/null || true\n  rm -f \"$SERVICE_PATH\"\n  rm -rf \"$INSTALL_PATH\"\n  rm -f \"/usr/local/bin/update_immich-public-proxy\"\n  rm -f \"$HOME/.immichpublicproxy\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"Immich Public Proxy\" \"alangrainger/immich-public-proxy\"; then\n    msg_info \"Stopping service\"\n    systemctl stop immich-proxy.service &>/dev/null || true\n    msg_ok \"Stopped service\"\n\n    msg_info \"Backing up configuration\"\n    cp \"$CONFIG_PATH\"/.env /tmp/ipp.env.bak 2>/dev/null || true\n    cp \"$CONFIG_PATH\"/config.json /tmp/ipp.config.json.bak 2>/dev/null || true\n    msg_ok \"Backed up configuration\"\n\n    NODE_VERSION=\"24\" setup_nodejs\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"Immich Public Proxy\" \"alangrainger/immich-public-proxy\" \"tarball\" \"latest\" \"$INSTALL_PATH\"\n\n    msg_info \"Restoring configuration\"\n    cp /tmp/ipp.env.bak \"$CONFIG_PATH\"/.env 2>/dev/null || true\n    cp /tmp/ipp.config.json.bak \"$CONFIG_PATH\"/config.json 2>/dev/null || true\n    rm -f /tmp/ipp.*.bak\n    msg_ok \"Restored configuration\"\n\n    msg_info \"Installing dependencies\"\n    cd \"$CONFIG_PATH\"\n    $STD npm install\n    msg_ok \"Installed dependencies\"\n\n    msg_info \"Building ${APP}\"\n    $STD npm run build\n    msg_ok \"Built ${APP}\"\n\n    msg_info \"Updating service\"\n    create_service\n    msg_ok \"Updated service\"\n\n    msg_info \"Starting service\"\n    systemctl start immich-proxy\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully\"\n    exit\n  fi\n}\n\nfunction create_service() {\n  cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=Immich Public Proxy\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=${INSTALL_PATH}/app\nEnvironmentFile=${CONFIG_PATH}/.env\nExecStart=/usr/bin/node ${INSTALL_PATH}/app/dist/index.js\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl daemon-reload\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  NODE_VERSION=\"24\" setup_nodejs\n\n  # Force fresh download by removing version cache\n  rm -f \"$HOME/.immichpublicproxy\"\n  fetch_and_deploy_gh_release \"Immich Public Proxy\" \"alangrainger/immich-public-proxy\" \"tarball\" \"latest\" \"$INSTALL_PATH\"\n\n  msg_info \"Installing dependencies\"\n  cd \"$CONFIG_PATH\"\n  $STD npm install\n  msg_ok \"Installed dependencies\"\n\n  msg_info \"Building ${APP}\"\n  $STD npm run build\n  msg_ok \"Built ${APP}\"\n\n  MAX_ATTEMPTS=3\n  attempt=0\n  while true; do\n    attempt=$((attempt + 1))\n    read -rp \"${TAB3}Enter your LOCAL Immich IP or domain (ex. 192.168.1.100 or immich.local.lan): \" DOMAIN\n    if [[ -z \"$DOMAIN\" ]]; then\n      if ((attempt >= MAX_ATTEMPTS)); then\n        DOMAIN=\"${LOCAL_IP:-localhost}\"\n        msg_warn \"Using fallback: $DOMAIN\"\n        break\n      fi\n      msg_warn \"Domain cannot be empty! (Attempt $attempt/$MAX_ATTEMPTS)\"\n    elif [[ \"$DOMAIN\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n      valid_ip=true\n      IFS='.' read -ra octets <<<\"$DOMAIN\"\n      for octet in \"${octets[@]}\"; do\n        if ((octet > 255)); then\n          valid_ip=false\n          break\n        fi\n      done\n      if $valid_ip; then\n        break\n      else\n        msg_warn \"Invalid IP address!\"\n      fi\n    elif [[ \"$DOMAIN\" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\\.[a-zA-Z]{2,}$ || \"$DOMAIN\" == \"localhost\" ]]; then\n      break\n    else\n      msg_warn \"Invalid domain format!\"\n    fi\n  done\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"/.env\nNODE_ENV=production\nIMMICH_URL=http://${DOMAIN}:2283\nEOF\n  chmod 600 \"$CONFIG_PATH\"/.env\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  create_service\n  systemctl enable -q --now immich-proxy\n  msg_ok \"Created and started service\"\n\n  # Create update script (simple wrapper that calls this addon with type=update)\n  msg_info \"Creating update script\"\n  cat <<'UPDATEEOF' >/usr/local/bin/update_immich-public-proxy\n#!/usr/bin/env bash\n# Immich Public Proxy Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/immich-public-proxy.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_immich-public-proxy\n  msg_ok \"Created update script (/usr/local/bin/update_immich-public-proxy)\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n  echo \"\"\n  msg_warn \"Additional configuration is available at '/opt/immich-proxy/app/config.json'\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -d \"$INSTALL_PATH\" && -f \"$SERVICE_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\nget_lxc_ip\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -f \"$SERVICE_PATH\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Node.js 24\"\necho -e \"${TAB}  - Immich Public Proxy\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/jellystat.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/CyferShepard/Jellystat\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func) 2>/dev/null || true\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Jellystat\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/jellystat\"\nCONFIG_PATH=\"/opt/jellystat/.env\"\nDEFAULT_PORT=3000\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# HEADER\n# ==============================================================================\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n       __     ____           __        __\n      / /__  / / /_  _______/ /_____ _/ /_\n __  / / _ \\/ / / / / / ___/ __/ __ `/ __/\n/ /_/ /  __/ / / /_/ (__  ) /_/ /_/ / /_\n\\____/\\___/_/_/\\__, /____/\\__/\\__,_/\\__/\n              /____/\nEOF\n}\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  msg_error \"Alpine is not supported for ${APP}. Use Debian/Ubuntu.\"\n  exit 238\nelif [[ -f \"/etc/debian_version\" ]]; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/jellystat.service\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n  systemctl disable --now jellystat.service &>/dev/null || true\n  rm -f \"$SERVICE_PATH\"\n  rm -rf \"$INSTALL_PATH\"\n  rm -f \"/usr/local/bin/update_jellystat\"\n  rm -f \"$HOME/.jellystat\"\n  msg_ok \"${APP} has been uninstalled\"\n\n  # Ask about PostgreSQL database removal\n  echo \"\"\n  echo -n \"${TAB}Also remove PostgreSQL database 'jellystat'? (y/N): \"\n  read -r db_prompt\n  if [[ \"${db_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    if command -v psql &>/dev/null; then\n      msg_info \"Removing PostgreSQL database and user\"\n      $STD sudo -u postgres psql -c \"DROP DATABASE IF EXISTS jellystat;\" &>/dev/null || true\n      $STD sudo -u postgres psql -c \"DROP USER IF EXISTS jellystat;\" &>/dev/null || true\n      msg_ok \"Removed PostgreSQL database 'jellystat' and user 'jellystat'\"\n    else\n      msg_warn \"PostgreSQL not found - database may have been removed already\"\n    fi\n  else\n    msg_warn \"PostgreSQL database was NOT removed. Remove manually if needed:\"\n    echo -e \"${TAB}  sudo -u postgres psql -c \\\"DROP DATABASE jellystat;\\\"\"\n    echo -e \"${TAB}  sudo -u postgres psql -c \\\"DROP USER jellystat;\\\"\"\n  fi\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"jellystat\" \"CyferShepard/Jellystat\"; then\n    msg_info \"Stopping service\"\n    systemctl stop jellystat.service &>/dev/null || true\n    msg_ok \"Stopped service\"\n\n    msg_info \"Backing up configuration\"\n    cp \"$CONFIG_PATH\" /tmp/jellystat.env.bak 2>/dev/null || true\n    msg_ok \"Backed up configuration\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"jellystat\" \"CyferShepard/Jellystat\" \"tarball\" \"latest\" \"$INSTALL_PATH\"\n\n    msg_info \"Restoring configuration\"\n    cp /tmp/jellystat.env.bak \"$CONFIG_PATH\" 2>/dev/null || true\n    rm -f /tmp/jellystat.env.bak\n    msg_ok \"Restored configuration\"\n\n    msg_info \"Installing dependencies\"\n    cd \"$INSTALL_PATH\"\n    $STD npm install\n    msg_ok \"Installed dependencies\"\n\n    msg_info \"Building ${APP}\"\n    $STD npm run build\n    msg_ok \"Built ${APP}\"\n\n    msg_info \"Starting service\"\n    systemctl start jellystat\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  # Setup Node.js (only installs if not present or different version)\n  if command -v node &>/dev/null; then\n    msg_ok \"Node.js already installed ($(node -v))\"\n  else\n    NODE_VERSION=\"22\" setup_nodejs\n  fi\n\n  # Setup PostgreSQL (only installs if not present)\n  if command -v psql &>/dev/null; then\n    msg_ok \"PostgreSQL already installed\"\n  else\n    PG_VERSION=\"17\" setup_postgresql\n  fi\n\n  # Create database and user (skip if already exists)\n  local DB_NAME=\"jellystat\"\n  local DB_USER=\"jellystat\"\n  local DB_PASS\n\n  msg_info \"Setting up PostgreSQL database\"\n\n  # Check if database already exists\n  if sudo -u postgres psql -lqt 2>/dev/null | cut -d \\| -f 1 | grep -qw \"$DB_NAME\"; then\n    msg_warn \"Database '${DB_NAME}' already exists - skipping creation\"\n    echo -n \"${TAB}Enter existing database password for '${DB_USER}': \"\n    read -rs DB_PASS\n    echo \"\"\n  else\n    # Generate new password\n    DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)\n\n    # Check if user exists, create if not\n    if sudo -u postgres psql -tAc \"SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'\" 2>/dev/null | grep -q 1; then\n      msg_info \"User '${DB_USER}' exists, updating password\"\n      $STD sudo -u postgres psql -c \"ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}';\" || {\n        msg_error \"Failed to update PostgreSQL user\"\n        return 1\n      }\n    else\n      $STD sudo -u postgres psql -c \"CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';\" || {\n        msg_error \"Failed to create PostgreSQL user\"\n        return 1\n      }\n    fi\n\n    # Create database (use template0 for UTF8 encoding compatibility)\n    $STD sudo -u postgres psql -c \"CREATE DATABASE ${DB_NAME} WITH OWNER ${DB_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;\" || {\n      msg_error \"Failed to create PostgreSQL database\"\n      return 1\n    }\n    $STD sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};\" || {\n      msg_error \"Failed to grant privileges\"\n      return 1\n    }\n\n    # Grant schema permissions (required for PostgreSQL 15+)\n    $STD sudo -u postgres psql -d \"${DB_NAME}\" -c \"GRANT ALL ON SCHEMA public TO ${DB_USER};\" || true\n\n    # Configure pg_hba.conf for password authentication on localhost\n    local PG_HBA\n    PG_HBA=$(sudo -u postgres psql -tAc \"SHOW hba_file;\" 2>/dev/null | tr -d ' ')\n    if [[ -n \"$PG_HBA\" && -f \"$PG_HBA\" ]]; then\n      # Check if md5/scram-sha-256 auth is already configured for local connections\n      if ! grep -qE \"^host\\s+${DB_NAME}\\s+${DB_USER}\\s+127.0.0.1\" \"$PG_HBA\"; then\n        msg_info \"Configuring PostgreSQL authentication\"\n        # Add password auth for jellystat user on localhost (before the default rules)\n        sed -i \"/^# IPv4 local connections:/a host    ${DB_NAME}    ${DB_USER}    127.0.0.1/32    scram-sha-256\" \"$PG_HBA\"\n        sed -i \"/^# IPv4 local connections:/a host    ${DB_NAME}    ${DB_USER}    ::1/128         scram-sha-256\" \"$PG_HBA\"\n        # Reload PostgreSQL to apply changes\n        systemctl reload postgresql\n        msg_ok \"Configured PostgreSQL authentication\"\n      fi\n    fi\n\n    msg_ok \"Created PostgreSQL database '${DB_NAME}'\"\n  fi\n\n  # Generate JWT Secret\n  local JWT_SECRET\n  JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)\n\n  # Force fresh download by removing version cache\n  rm -f \"$HOME/.jellystat\"\n  fetch_and_deploy_gh_release \"jellystat\" \"CyferShepard/Jellystat\" \"tarball\" \"latest\" \"$INSTALL_PATH\"\n\n  msg_info \"Installing dependencies\"\n  cd \"$INSTALL_PATH\"\n  $STD npm install\n  msg_ok \"Installed dependencies\"\n\n  msg_info \"Building ${APP}\"\n  $STD npm run build\n  msg_ok \"Built ${APP}\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"\n# Jellystat Configuration\n# Database\nPOSTGRES_USER=${DB_USER}\nPOSTGRES_PASSWORD=${DB_PASS}\nPOSTGRES_IP=localhost\nPOSTGRES_PORT=5432\nPOSTGRES_DB=${DB_NAME}\n\n# Security\nJWT_SECRET=${JWT_SECRET}\n\n# Server\nJS_LISTEN_IP=0.0.0.0\nJS_BASE_URL=/\nTZ=$(cat /etc/timezone 2>/dev/null || echo \"UTC\")\n\n# Optional: GeoLite for IP Geolocation\n# JS_GEOLITE_ACCOUNT_ID=\n# JS_GEOLITE_LICENSE_KEY=\n\n# Optional: Master Override (if you forget your password)\n# JS_USER=admin\n# JS_PASSWORD=admin\n\n# Optional: Minimum playback duration to record (seconds)\n# MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK=1\n\n# Optional: Self-signed certificates\nREJECT_SELF_SIGNED_CERTIFICATES=true\nEOF\n  chmod 600 \"$CONFIG_PATH\"\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=Jellystat - Statistics for Jellyfin\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=${INSTALL_PATH}/backend\nEnvironmentFile=${CONFIG_PATH}\nExecStart=/usr/bin/node ${INSTALL_PATH}/backend/server.js\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable --now jellystat &>/dev/null\n  msg_ok \"Created and started service\"\n\n  # Create update script (simple wrapper that calls this addon with type=update)\n  msg_info \"Creating update script\"\n  cat <<'UPDATEEOF' >/usr/local/bin/update_jellystat\n#!/usr/bin/env bash\n# Jellystat Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/jellystat.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_jellystat\n  msg_ok \"Created update script (/usr/local/bin/update_jellystat)\"\n\n  # Save credentials\n  local CREDS_FILE=\"/root/jellystat.creds\"\n  cat <<EOF >\"$CREDS_FILE\"\nJellystat Credentials\n=====================\nDatabase User: ${DB_USER}\nDatabase Password: ${DB_PASS}\nDatabase Name: ${DB_NAME}\nJWT Secret: ${JWT_SECRET}\n\nWeb UI: http://${LOCAL_IP}:${DEFAULT_PORT}\nEOF\n  chmod 600 \"$CREDS_FILE\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n  msg_ok \"Credentials saved to: ${BL}${CREDS_FILE}${CL}\"\n  echo \"\"\n  msg_warn \"On first access, you'll need to configure your Jellyfin server connection.\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/package.json\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\nget_lxc_ip\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/package.json\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Node.js 22\"\necho -e \"${TAB}  - PostgreSQL 17\"\necho -e \"${TAB}  - Jellystat\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/komodo.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://komo.do/ | Github: https://github.com/mbecker20/komodo\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1 || apk update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1 || apk add --no-cache curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"komodo\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Komodo\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/komodo\"\nCOMPOSE_ENV=\"${INSTALL_PATH}/compose.env\"\nDEFAULT_PORT=9120\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# HELPERS\n# ==============================================================================\nfunction find_compose_file() {\n  COMPOSE_FILE=$(find \"$INSTALL_PATH\" -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)\n  if [[ -z \"${COMPOSE_FILE:-}\" ]]; then\n    msg_error \"No valid compose file found in ${INSTALL_PATH}!\"\n    exit 233\n  fi\n  COMPOSE_BASENAME=$(basename \"$COMPOSE_FILE\")\n}\n\nfunction check_legacy_db() {\n  if [[ \"$COMPOSE_BASENAME\" == \"sqlite.compose.yaml\" || \"$COMPOSE_BASENAME\" == \"postgres.compose.yaml\" ]]; then\n    msg_error \"Detected outdated Komodo setup using SQLite or PostgreSQL (FerretDB v1).\"\n    echo -e \"${YW}This configuration is no longer supported since Komodo v1.18.0.${CL}\"\n    echo -e \"${YW}Please follow the migration guide:${CL}\"\n    echo -e \"${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/5689${CL}\\n\"\n    exit 238\n  fi\n}\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n\n  find_compose_file\n  msg_info \"Stopping and removing Docker containers\"\n  cd \"$INSTALL_PATH\"\n  $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file \"$COMPOSE_ENV\" down --volumes --remove-orphans\n  msg_ok \"Stopped and removed Docker containers\"\n\n  rm -rf \"$INSTALL_PATH\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  find_compose_file\n  check_legacy_db\n\n  msg_info \"Updating ${APP}\"\n  BACKUP_FILE=\"${INSTALL_PATH}/${COMPOSE_BASENAME}.bak_$(date +%Y%m%d_%H%M%S)\"\n  cp \"$COMPOSE_FILE\" \"$BACKUP_FILE\" || {\n    msg_error \"Failed to create backup of ${COMPOSE_BASENAME}!\"\n    exit 235\n  }\n  cp \"$COMPOSE_ENV\" \"${COMPOSE_ENV}.bak_$(date +%Y%m%d_%H%M%S)\" 2>/dev/null || true\n\n  GITHUB_URL=\"https://raw.githubusercontent.com/moghtech/komodo/main/compose/${COMPOSE_BASENAME}\"\n  if ! curl -fsSL \"$GITHUB_URL\" -o \"$COMPOSE_FILE\"; then\n    msg_error \"Failed to download ${COMPOSE_BASENAME} from GitHub!\"\n    mv \"$BACKUP_FILE\" \"$COMPOSE_FILE\"\n    exit 115\n  fi\n\n  # === v2 migration: image tag (latest is deprecated) ===\n  if grep -q '^COMPOSE_KOMODO_IMAGE_TAG=latest' \"$COMPOSE_ENV\"; then\n    msg_info \"Migrating to Komodo v2 image tag\"\n    sed -i 's/^COMPOSE_KOMODO_IMAGE_TAG=latest/COMPOSE_KOMODO_IMAGE_TAG=2/' \"$COMPOSE_ENV\"\n    msg_ok \"Migrated image tag to :2\"\n  fi\n\n  # === v2 migration: DB credential variable names ===\n  if grep -q '^KOMODO_DB_USERNAME=' \"$COMPOSE_ENV\"; then\n    msg_info \"Migrating database credential variables\"\n    sed -i 's/^KOMODO_DB_USERNAME=/KOMODO_DATABASE_USERNAME=/' \"$COMPOSE_ENV\"\n    sed -i 's/^KOMODO_DB_PASSWORD=/KOMODO_DATABASE_PASSWORD=/' \"$COMPOSE_ENV\"\n    msg_ok \"Migrated DB credential variables\"\n  fi\n\n  # === v2 migration: remove deprecated passkey (replaced by PKI) ===\n  if grep -q '^KOMODO_PASSKEY=' \"$COMPOSE_ENV\"; then\n    sed -i '/^KOMODO_PASSKEY=/d' \"$COMPOSE_ENV\"\n  fi\n\n  # === v2 migration: ensure PERIPHERY_CORE_PUBLIC_KEYS is set ===\n  if ! grep -q 'PERIPHERY_CORE_PUBLIC_KEYS' \"$COMPOSE_ENV\"; then\n    echo '## Use the public key generated by Core.' >> \"$COMPOSE_ENV\"\n    echo 'PERIPHERY_CORE_PUBLIC_KEYS=file:/config/keys/core.pub' >> \"$COMPOSE_ENV\"\n  fi\n\n  # === ensure backups path is set ===\n  if ! grep -q 'COMPOSE_KOMODO_BACKUPS_PATH=' \"$COMPOSE_ENV\"; then\n    echo 'COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups' >>\"$COMPOSE_ENV\"\n  fi\n\n  $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file \"$COMPOSE_ENV\" pull\n  $STD docker compose -p komodo -f \"$COMPOSE_FILE\" --env-file \"$COMPOSE_ENV\" up -d\n  msg_ok \"Updated ${APP}\"\n\n  msg_ok \"Updated successfully\"\n  exit\n}\n\n# ==============================================================================\n# PROXMOX HOST CHECK\n# ==============================================================================\nfunction check_proxmox_host() {\n  if command -v pveversion &>/dev/null; then\n    msg_error \"Running on the Proxmox host is NOT recommended!\"\n    msg_error \"This should be executed inside an LXC container.\"\n    echo \"\"\n    echo -n \"${TAB}Continue anyway? (y/N): \"\n    read -r confirm\n    if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n      msg_warn \"Aborted. Please run this inside an LXC container.\"\n      exit 0\n    fi\n    msg_warn \"Proceeding on Proxmox host at your own risk!\"\n  fi\n}\n\n# ==============================================================================\n# CHECK / INSTALL DOCKER\n# ==============================================================================\nfunction ensure_openssl() {\n  if command -v openssl &>/dev/null; then\n    return\n  fi\n  msg_info \"Installing openssl\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add openssl\n  elif command -v apt-get &>/dev/null; then\n    $STD apt-get update\n    $STD apt-get install -y openssl\n  else\n    msg_error \"openssl is required but could not be installed automatically.\"\n    exit 10\n  fi\n  msg_ok \"Installed openssl\"\n}\n\nfunction check_or_install_docker() {\n  if command -v docker &>/dev/null; then\n    msg_ok \"Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available\"\n    if docker compose version &>/dev/null; then\n      msg_ok \"Docker Compose is available\"\n    else\n      msg_error \"Docker Compose plugin is not available. Please install it.\"\n      exit 10\n    fi\n    ensure_openssl\n    return\n  fi\n\n  msg_warn \"Docker is not installed.\"\n  echo -n \"${TAB}Install Docker now? (y/N): \"\n  read -r install_docker_prompt\n  if [[ ! \"${install_docker_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_error \"Docker is required for ${APP}. Exiting.\"\n    exit 10\n  fi\n\n  msg_info \"Installing Docker\"\n  if [[ -f /etc/alpine-release ]]; then\n    $STD apk add docker docker-cli-compose\n    $STD rc-service docker start\n    $STD rc-update add docker default\n  else\n    DOCKER_CONFIG_PATH='/etc/docker/daemon.json'\n    mkdir -p \"$(dirname \"$DOCKER_CONFIG_PATH\")\"\n    echo -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n    $STD sh <(curl -fsSL https://get.docker.com)\n  fi\n  msg_ok \"Installed Docker\"\n\n  ensure_openssl\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  check_or_install_docker\n\n  echo -e \"${TAB}Choose the database for Komodo:\"\n  echo -e \"${TAB}  1) MongoDB (recommended)\"\n  echo -e \"${TAB}  2) FerretDB\"\n  echo -n \"${TAB}Enter your choice (default: 1): \"\n  read -r DB_CHOICE\n  DB_CHOICE=${DB_CHOICE:-1}\n\n  case $DB_CHOICE in\n  1) DB_COMPOSE_FILE=\"mongo.compose.yaml\" ;;\n  2) DB_COMPOSE_FILE=\"ferretdb.compose.yaml\" ;;\n  *)\n    msg_warn \"Invalid choice. Defaulting to MongoDB.\"\n    DB_COMPOSE_FILE=\"mongo.compose.yaml\"\n    ;;\n  esac\n\n  msg_info \"Creating install directory\"\n  mkdir -p \"$INSTALL_PATH\"\n  msg_ok \"Created ${INSTALL_PATH}\"\n\n  msg_info \"Downloading Docker Compose file\"\n  curl -fsSL \"https://raw.githubusercontent.com/moghtech/komodo/main/compose/$DB_COMPOSE_FILE\" -o \"${INSTALL_PATH}/${DB_COMPOSE_FILE}\"\n  msg_ok \"Downloaded ${DB_COMPOSE_FILE}\"\n\n  msg_info \"Configuring environment\"\n  curl -fsSL \"https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env\" -o \"$COMPOSE_ENV\"\n\n  DB_PASSWORD=$(openssl rand -base64 16 | tr -d '/+=')\n  ADMIN_PASSWORD=$(openssl rand -base64 8 | tr -d '/+=')\n  WEBHOOK_SECRET=$(openssl rand -base64 24 | tr -d '/+=')\n  JWT_SECRET=$(openssl rand -base64 24 | tr -d '/+=')\n\n  sed -i \"s/^KOMODO_DATABASE_USERNAME=.*/KOMODO_DATABASE_USERNAME=komodo_admin/\" \"$COMPOSE_ENV\"\n  sed -i \"s/^KOMODO_DATABASE_PASSWORD=.*/KOMODO_DATABASE_PASSWORD=${DB_PASSWORD}/\" \"$COMPOSE_ENV\"\n  sed -i \"s/^KOMODO_INIT_ADMIN_PASSWORD=changeme/KOMODO_INIT_ADMIN_PASSWORD=${ADMIN_PASSWORD}/\" \"$COMPOSE_ENV\"\n  sed -i \"s/^KOMODO_WEBHOOK_SECRET=.*/KOMODO_WEBHOOK_SECRET=${WEBHOOK_SECRET}/\" \"$COMPOSE_ENV\"\n  sed -i \"s/^KOMODO_JWT_SECRET=.*/KOMODO_JWT_SECRET=${JWT_SECRET}/\" \"$COMPOSE_ENV\"\n  msg_ok \"Configured environment\"\n\n  msg_info \"Starting ${APP}\"\n  cd \"$INSTALL_PATH\"\n  $STD docker compose -p komodo -f \"${INSTALL_PATH}/${DB_COMPOSE_FILE}\" --env-file \"$COMPOSE_ENV\" up -d\n  msg_ok \"Started ${APP}\"\n\n  {\n    echo \"Komodo Credentials\"\n    echo \"\"\n    echo \"Admin User    : admin\"\n    echo \"Admin Password: $ADMIN_PASSWORD\"\n  } >>~/komodo.creds\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n  echo \"\"\n  echo -e \"  Komodo Credentials\"\n  echo -e \"  ==================\"\n  echo -e \"  User    : admin\"\n  echo -e \"  Password: ${ADMIN_PASSWORD}\"\n  echo \"\"\n  msg_info \"Credentials saved to ~/komodo.creds\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  COMPOSE_FILE=\"\"\n  COMPOSE_BASENAME=\"\"\n  if [[ -d \"$INSTALL_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nheader_info\ncheck_proxmox_host\nget_lxc_ip\n\n# Declare variables used by find_compose_file\nCOMPOSE_FILE=\"\"\nCOMPOSE_BASENAME=\"\"\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Komodo (via Docker Compose)\"\necho -e \"${TAB}  - MongoDB or FerretDB (your choice)\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/netdata.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.netdata.cloud/ | Github: https://github.com/netdata/netdata\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    _   __     __  ____        __\n   / | / /__  / /_/ __ \\____ _/ /_____ _\n  /  |/ / _ \\/ __/ / / / __ `/ __/ __ `/\n / /|  /  __/ /_/ /_/ / /_/ / /_/ /_/ /\n/_/ |_/\\___/\\__/_____/\\__,_/\\__/\\__,_/\n\nEOF\n}\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nsilent() { \"$@\" >/dev/null 2>&1; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"netdata\" \"addon\"\n\nset -e\nheader_info\necho \"Loading...\"\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nfunction msg_error() { echo -e \"${RD}✗ $1${CL}\"; }\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x and 9.0–9.1.x\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0–9.1.x\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not yet supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0–9.1.x\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.9 or 9.0–9.1.x\"\n  exit 105\n}\n\ndetect_codename() {\n  source /etc/os-release\n  if [[ \"$ID\" != \"debian\" ]]; then\n    msg_error \"Unsupported base OS: $ID (only Proxmox VE / Debian supported).\"\n    exit 238\n  fi\n  CODENAME=\"${VERSION_CODENAME:-}\"\n  if [[ -z \"$CODENAME\" ]]; then\n    msg_error \"Could not detect Debian codename.\"\n    exit 71\n  fi\n  echo \"$CODENAME\"\n}\n\nget_latest_repo_pkg() {\n  local REPO_URL=$1\n  curl -fsSL \"$REPO_URL\" |\n    grep -oP 'netdata-repo_[^\"]+all\\.deb' |\n    sort -V |\n    tail -n1\n}\n\ninstall() {\n  header_info\n  while true; do\n    read -p \"Are you sure you want to install NetData on Proxmox VE host. Proceed(y/n)? \" yn\n    case $yn in\n    [Yy]*) break ;;\n    [Nn]*) exit ;;\n    *) echo \"Please answer yes or no.\" ;;\n    esac\n  done\n\n  read -r -p \"Verbose mode? <y/N> \" prompt\n  [[ ${prompt,,} =~ ^(y|yes)$ ]] && STD=\"\" || STD=\"silent\"\n\n  CODENAME=$(detect_codename)\n  REPO_URL=\"https://repo.netdata.cloud/repos/repoconfig/debian/${CODENAME}/\"\n\n  msg_info \"Setting up repository\"\n  $STD apt-get install -y debian-keyring\n  PKG=$(get_latest_repo_pkg \"$REPO_URL\")\n  if [[ -z \"$PKG\" ]]; then\n    msg_error \"Could not find netdata-repo package for Debian $CODENAME\"\n    exit 237\n  fi\n  curl -fsSL \"${REPO_URL}${PKG}\" -o \"$PKG\"\n  $STD dpkg -i \"$PKG\"\n  rm -f \"$PKG\"\n  msg_ok \"Set up repository\"\n\n  msg_info \"Installing Netdata\"\n  $STD apt-get update\n  $STD apt-get install -y netdata\n  msg_ok \"Installed Netdata\"\n  msg_ok \"Completed successfully!\\n\"\n  echo -e \"\\n Netdata should be reachable at${BL} http://$(hostname -I | awk '{print $1}'):19999 ${CL}\\n\"\n}\n\nuninstall() {\n  header_info\n  read -r -p \"Verbose mode? <y/N> \" prompt\n  [[ ${prompt,,} =~ ^(y|yes)$ ]] && STD=\"\" || STD=\"silent\"\n\n  msg_info \"Uninstalling Netdata\"\n  systemctl stop netdata || true\n  rm -rf /var/log/netdata /var/lib/netdata /var/cache/netdata /etc/netdata/go.d\n  rm -rf /etc/apt/trusted.gpg.d/netdata-archive-keyring.gpg /etc/apt/sources.list.d/netdata.list\n  $STD apt-get remove --purge -y netdata netdata-repo\n  systemctl daemon-reload\n  $STD apt autoremove -y\n  $STD userdel netdata || true\n  msg_ok \"Uninstalled Netdata\"\n  msg_ok \"Completed successfully!\\n\"\n}\n\nheader_info\npve_check\n\nOPTIONS=(Install \"Install NetData on Proxmox VE\"\n  Uninstall \"Uninstall NetData from Proxmox VE\")\n\nCHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"NetData\" \\\n  --menu \"Select an option:\" 10 58 2 \"${OPTIONS[@]}\" 3>&1 1>&2 2>&3)\n\ncase $CHOICE in\n\"Install\") install ;;\n\"Uninstall\") uninstall ;;\n*)\n  echo \"Exiting...\"\n  exit 0\n  ;;\nesac\n"
  },
  {
    "path": "tools/addon/nextcloud-exporter.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/xperimental/nextcloud-exporter\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func) 2>/dev/null || true\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nVERBOSE=${var_verbose:-no}\nAPP=\"nextcloud-exporter\"\nAPP_TYPE=\"tools\"\nBINARY_PATH=\"/usr/bin/nextcloud-exporter\"\nCONFIG_PATH=\"/etc/nextcloud-exporter.env\"\nSERVICE_PATH=\"/etc/systemd/system/nextcloud-exporter.service\"\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then\n  echo -e \"${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling Nextcloud-Exporter\"\n  systemctl disable -q --now nextcloud-exporter\n  rm -f \"$SERVICE_PATH\"\n\n  if dpkg -l | grep -q nextcloud-exporter; then\n    $STD apt-get remove -y nextcloud-exporter || $STD dpkg -r nextcloud-exporter\n  fi\n\n  rm -f \"$CONFIG_PATH\"\n  rm -f \"/usr/local/bin/update_nextcloud-exporter\"\n  rm -f \"$HOME/.nextcloud-exporter\"\n  msg_ok \"Nextcloud-Exporter has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"nextcloud-exporter\" \"xperimental/nextcloud-exporter\"; then\n    msg_info \"Stopping service\"\n    systemctl stop nextcloud-exporter\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"nextcloud-exporter\" \"xperimental/nextcloud-exporter\" \"binary\" \"latest\"\n\n    msg_info \"Starting service\"\n    systemctl start nextcloud-exporter\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  read -erp \"Enter URL of Nextcloud, example: (http://127.0.0.1:8080): \" NEXTCLOUD_SERVER\n  read -rsp \"Enter Nextcloud auth token (press Enter to use username/password instead): \" NEXTCLOUD_AUTH_TOKEN\n  printf \"\\n\"\n\n  if [[ -z \"$NEXTCLOUD_AUTH_TOKEN\" ]]; then\n    read -erp \"Enter Nextcloud username: \" NEXTCLOUD_USERNAME\n    read -rsp \"Enter Nextcloud password: \" NEXTCLOUD_PASSWORD\n    printf \"\\n\"\n  fi\n\n  read -erp \"Query additional info for apps? [Y/n]: \" QUERY_APPS\n  if [[ \"${QUERY_APPS,,}\" =~ ^(n|no)$ ]]; then\n    NEXTCLOUD_INFO_APPS=\"false\"\n  fi\n\n  read -erp \"Query update information? [Y/n]: \" QUERY_UPDATES\n  if [[ \"${QUERY_UPDATES,,}\" =~ ^(n|no)$ ]]; then\n    NEXTCLOUD_INFO_UPDATE=\"false\"\n  fi\n\n  read -erp \"Do you want to skip TLS-Verification (if using a self-signed Certificate on Nextcloud) [y/N]: \" SKIP_TLS\n  if [[ \"${SKIP_TLS,,}\" =~ ^(y|yes)$ ]]; then\n    NEXTCLOUD_TLS_SKIP_VERIFY=\"true\"\n  fi\n\n  fetch_and_deploy_gh_release \"nextcloud-exporter\" \"xperimental/nextcloud-exporter\" \"binary\" \"latest\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"\n# https://github.com/xperimental/nextcloud-exporter\nNEXTCLOUD_SERVER=\"${NEXTCLOUD_SERVER}\"\nNEXTCLOUD_AUTH_TOKEN=\"${NEXTCLOUD_AUTH_TOKEN:-}\"\nNEXTCLOUD_USERNAME=\"${NEXTCLOUD_USERNAME:-}\"\nNEXTCLOUD_PASSWORD=\"${NEXTCLOUD_PASSWORD:-}\"\nNEXTCLOUD_INFO_UPDATE=${NEXTCLOUD_INFO_UPDATE:-\"true\"}\nNEXTCLOUD_INFO_APPS=${NEXTCLOUD_INFO_APPS:-\"true\"}\nNEXTCLOUD_TLS_SKIP_VERIFY=${NEXTCLOUD_TLS_SKIP_VERIFY:-\"false\"}\nNEXTCLOUD_LISTEN_ADDRESS=\":9205\"\nEOF\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=nextcloud-exporter\nAfter=network.target\n\n[Service]\nUser=root\nEnvironmentFile=$CONFIG_PATH\nExecStart=$BINARY_PATH\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl daemon-reload\n  systemctl enable -q --now nextcloud-exporter\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  ensure_usr_local_bin_persist\n  cat <<'UPDATEEOF' >/usr/local/bin/update_nextcloud-exporter\n#!/usr/bin/env bash\n# nextcloud-exporter Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/nextcloud-exporter.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_nextcloud-exporter\n  msg_ok \"Created update script (/usr/local/bin/update_nextcloud-exporter)\"\n\n  echo \"\"\n  msg_ok \"Nextcloud-Exporter installed successfully\"\n  msg_ok \"Metrics: ${BL}http://${LOCAL_IP}:9205/metrics${CL}\"\n  msg_ok \"Config: ${BL}${CONFIG_PATH}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -f \"$BINARY_PATH\" ]]; then\n    update\n  else\n    msg_error \"Nextcloud-Exporter is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -f \"$BINARY_PATH\" ]]; then\n  msg_warn \"Nextcloud-Exporter is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall Nextcloud-Exporter? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update Nextcloud-Exporter? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"Nextcloud-Exporter is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Nextcloud Exporter (binary)\"\necho -e \"${TAB}  - Systemd service\"\necho \"\"\n\necho -n \"${TAB}Install Nextcloud-Exporter? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/olivetin.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://docs.olivetin.app/ | Github: https://github.com/OliveTin/OliveTin\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ____  ___          _______     \n  / __ \\/ (_)   _____/_  __(_)___ \n / / / / / / | / / _ \\/ / / / __ \\\n/ /_/ / / /| |/ /  __/ / / / / / /\n\\____/_/_/ |___/\\___/_/ /_/_/ /_/ \n                                  \nEOF\n}\n\nIP=$(hostname -I | awk '{print $1}')\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nAPP=\"OliveTin\"\nhostname=\"$(hostname)\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"olivetin\" \"addon\"\n\nset-e\nheader_info\n\nwhile true; do\n  read -p \"This will Install ${APP} on $hostname. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nheader_info\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_info \"Installing ${APP}\"\nif ! command -v curl &>/dev/null; then\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\ncurl -fsSL \"https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin_linux_arm64.deb\" -o $(basename \"https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin_linux_arm64.deb\")\ndpkg -i OliveTin_linux_arm64.deb &>/dev/null\nsystemctl enable --now OliveTin &>/dev/null\nrm OliveTin_linux_arm64.deb\nmsg_ok \"Installed ${APP} on $hostname\"\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${APP} should be reachable by going to the following URL.\n         ${BL}http://$IP:1337${CL} \\n\"\n"
  },
  {
    "path": "tools/addon/phpmyadmin.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____  __          __  ___      ___       __          _\n   / __ \\/ /_  ____  /  |/  /_  __/   | ____/ /___ ___  (_)___\n  / /_/ / __ \\/ __ \\/ /|_/ / / / / /| |/ __  / __ `__ \\/ / __ \\\n / ____/ / / / /_/ / /  / / /_/ / ___ / /_/ / / / / / / / / / /\n/_/   /_/ /_/ .___/_/  /_/\\__, /_/  |_\\__,_/_/ /_/ /_/_/_/ /_/\n           /_/           /____/\nEOF\n}\n\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nRD=$(echo \"\\033[01;31m\")\nBL=$(echo \"\\033[36m\")\nCL=$(echo \"\\033[m\")\nCM=\"${GN}✔️${CL}\"\nCROSS=\"${RD}✖️${CL}\"\nINFO=\"${BL}ℹ️${CL}\"\n\nAPP=\"phpMyAdmin\"\nINSTALL_DIR_DEBIAN=\"/var/www/html/phpMyAdmin\"\nINSTALL_DIR_ALPINE=\"/usr/share/phpmyadmin\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"phpmyadmin\" \"addon\"\n\nIFACE=$(ip -4 route | awk '/default/ {print $5; exit}')\nIP=$(ip -4 addr show \"$IFACE\" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)\n[[ -z \"$IP\" ]] && IP=$(hostname -I | awk '{print $1}')\n[[ -z \"$IP\" ]] && IP=\"127.0.0.1\"\n\n# Detect OS\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  PKG_MANAGER_INSTALL=\"apk add --no-cache\"\n  PKG_QUERY=\"apk info -e\"\n  INSTALL_DIR=\"$INSTALL_DIR_ALPINE\"\nelif [[ -f \"/etc/debian_version\" ]]; then\n  OS=\"Debian\"\n  PKG_MANAGER_INSTALL=\"apt-get install -y\"\n  PKG_QUERY=\"dpkg -l\"\n  INSTALL_DIR=\"$INSTALL_DIR_DEBIAN\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\nheader_info\n\nfunction msg_info() { echo -e \"${INFO} ${YW}${1}...${CL}\"; }\nfunction msg_ok() { echo -e \"${CM} ${GN}${1}${CL}\"; }\nfunction msg_error() { echo -e \"${CROSS} ${RD}${1}${CL}\"; }\n\nfunction check_internet() {\n  if ! command -v curl &>/dev/null; then\n    apt-get update >/dev/null 2>&1\n    apt-get install -y curl >/dev/null 2>&1\n  fi\n  msg_info \"Checking Internet connectivity to GitHub\"\n  HTTP_CODE=$(curl -s -o /dev/null -w \"%{http_code}\" https://github.com)\n  if [[ \"$HTTP_CODE\" -ge 200 && \"$HTTP_CODE\" -lt 400 ]]; then\n    msg_ok \"Internet connectivity OK\"\n  else\n    msg_error \"Internet connectivity or GitHub unreachable (Status $HTTP_CODE). Exiting.\"\n    exit 115\n  fi\n}\n\nfunction is_phpmyadmin_installed() {\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    [[ -f \"$INSTALL_DIR/config.inc.php\" ]]\n  else\n    [[ -d \"$INSTALL_DIR_ALPINE\" ]] && rc-service lighttpd status &>/dev/null\n  fi\n}\n\nfunction install_php_and_modules() {\n  msg_info \"Checking existing PHP installation\"\n  if command -v php >/dev/null 2>&1; then\n    PHP_VERSION=$(php -r 'echo PHP_VERSION;')\n    msg_ok \"Found PHP version $PHP_VERSION\"\n  else\n    msg_info \"PHP not found, will install PHP core\"\n  fi\n\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    PHP_MODULES=(\"php\" \"php-mysqli\" \"php-mbstring\" \"php-zip\" \"php-gd\" \"php-json\" \"php-curl\")\n    MISSING_PACKAGES=()\n    for pkg in \"${PHP_MODULES[@]}\"; do\n      if ! dpkg -l | grep -qw \"$pkg\"; then\n        MISSING_PACKAGES+=(\"$pkg\")\n      fi\n    done\n    if [[ ${#MISSING_PACKAGES[@]} -gt 0 ]]; then\n      msg_info \"Installing missing PHP packages: ${MISSING_PACKAGES[*]}\"\n      if ! apt-get update &>/dev/null || ! apt-get install -y \"${MISSING_PACKAGES[@]}\" &>/dev/null; then\n        msg_error \"Failed to install required PHP modules. Exiting.\"\n        exit 237\n      fi\n      msg_ok \"Installed missing PHP packages\"\n    else\n      msg_ok \"All required PHP modules are already installed\"\n    fi\n  else\n    msg_info \"Installing Lighttpd and PHP for Alpine\"\n    $PKG_MANAGER_INSTALL lighttpd php php-fpm php-session php-json php-mysqli curl tar openssl &>/dev/null\n    msg_ok \"Installed Lighttpd and PHP\"\n  fi\n}\n\nfunction install_phpmyadmin() {\n  msg_info \"Fetching latest phpMyAdmin release from GitHub\"\n  LATEST_VERSION_RAW=$(curl -s https://api.github.com/repos/phpmyadmin/phpmyadmin/releases/latest | grep tag_name | cut -d '\"' -f4)\n  LATEST_VERSION=$(echo \"$LATEST_VERSION_RAW\" | sed -e 's/^RELEASE_//' -e 's/_/./g')\n  if [[ -z \"$LATEST_VERSION\" ]]; then\n    msg_error \"Could not determine latest phpMyAdmin version from GitHub – falling back to 5.2.2\"\n    LATEST_VERSION=\"RELEASE_5_2_2\"\n  fi\n  msg_ok \"Latest version: $LATEST_VERSION\"\n\n  TARBALL_URL=\"https://files.phpmyadmin.net/phpMyAdmin/${LATEST_VERSION}/phpMyAdmin-${LATEST_VERSION}-all-languages.tar.gz\"\n  msg_info \"Downloading ${TARBALL_URL}\"\n  if ! curl -fsSL \"$TARBALL_URL\" -o /tmp/phpmyadmin.tar.gz; then\n    msg_error \"Download failed: $TARBALL_URL\"\n    exit 115\n  fi\n\n  mkdir -p \"$INSTALL_DIR\"\n  tar xf /tmp/phpmyadmin.tar.gz --strip-components=1 -C \"$INSTALL_DIR\"\n}\n\nfunction configure_phpmyadmin() {\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    cp \"$INSTALL_DIR/config.sample.inc.php\" \"$INSTALL_DIR/config.inc.php\"\n    SECRET=$(openssl rand -base64 24)\n    sed -i \"s#\\$cfg\\['blowfish_secret'\\] = '';#\\$cfg['blowfish_secret'] = '${SECRET}';#\" \"$INSTALL_DIR/config.inc.php\"\n    chmod 660 \"$INSTALL_DIR/config.inc.php\"\n    chown -R www-data:www-data \"$INSTALL_DIR\"\n    systemctl restart apache2\n    msg_ok \"Configured phpMyAdmin with Apache\"\n  else\n    msg_info \"Configuring Lighttpd for phpMyAdmin (Alpine detected)\"\n\n    mkdir -p /etc/lighttpd\n    cat <<EOF >/etc/lighttpd/lighttpd.conf\nserver.modules = (\n    \"mod_access\",\n    \"mod_alias\",\n    \"mod_accesslog\",\n    \"mod_fastcgi\"\n)\n\nserver.document-root = \"${INSTALL_DIR}\"\nserver.port = 80\n\nindex-file.names = ( \"index.php\", \"index.html\" )\n\nfastcgi.server = ( \".php\" =>\n  ((\n    \"host\" => \"127.0.0.1\",\n    \"port\" => 9000,\n    \"check-local\" => \"disable\"\n  ))\n)\n\nalias.url = ( \"/phpMyAdmin/\" => \"${INSTALL_DIR}/\" )\n\naccesslog.filename = \"/var/log/lighttpd/access.log\"\nserver.errorlog = \"/var/log/lighttpd/error.log\"\nEOF\n\n    msg_info \"Starting PHP-FPM and Lighttpd\"\n\n    PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION . PHP_MINOR_VERSION;')\n    PHP_FPM_SERVICE=\"php-fpm${PHP_VERSION}\"\n\n    if $STD rc-service \"$PHP_FPM_SERVICE\" start && $STD rc-update add \"$PHP_FPM_SERVICE\" default; then\n      msg_ok \"Started PHP-FPM service: $PHP_FPM_SERVICE\"\n    else\n      msg_error \"Failed to start PHP-FPM service: $PHP_FPM_SERVICE\"\n      exit 150\n    fi\n\n    $STD rc-service lighttpd start\n    $STD rc-update add lighttpd default\n    msg_ok \"Configured and started Lighttpd successfully\"\n\n  fi\n}\n\nfunction uninstall_phpmyadmin() {\n  msg_info \"Stopping Webserver\"\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    systemctl stop apache2\n  else\n    $STD rc-service lighttpd stop\n    $STD rc-service php-fpm stop\n  fi\n\n  msg_info \"Removing phpMyAdmin directory\"\n  rm -rf \"$INSTALL_DIR\"\n\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    msg_info \"Removing Lighttpd config\"\n    rm -f /etc/lighttpd/lighttpd.conf\n    $STD rc-service php-fpm restart\n    $STD rc-service lighttpd restart\n  else\n    $STD systemctl restart apache2\n  fi\n  msg_ok \"Uninstalled phpMyAdmin\"\n}\n\nfunction update_phpmyadmin() {\n  msg_info \"Fetching latest phpMyAdmin release from GitHub\"\n  LATEST_VERSION_RAW=$(curl -s https://api.github.com/repos/phpmyadmin/phpmyadmin/releases/latest | grep tag_name | cut -d '\"' -f4)\n  LATEST_VERSION=$(echo \"$LATEST_VERSION_RAW\" | sed -e 's/^RELEASE_//' -e 's/_/./g')\n\n  if [[ -z \"$LATEST_VERSION\" ]]; then\n    msg_error \"Could not determine latest phpMyAdmin version from GitHub – falling back to 5.2.2\"\n    LATEST_VERSION=\"5.2.2\"\n  fi\n  msg_ok \"Latest version: $LATEST_VERSION\"\n\n  TARBALL_URL=\"https://files.phpmyadmin.net/phpMyAdmin/${LATEST_VERSION}/phpMyAdmin-${LATEST_VERSION}-all-languages.tar.gz\"\n  msg_info \"Downloading ${TARBALL_URL}\"\n\n  if ! curl -fsSL \"$TARBALL_URL\" -o /tmp/phpmyadmin.tar.gz; then\n    msg_error \"Download failed: $TARBALL_URL\"\n    exit 115\n  fi\n\n  BACKUP_DIR=\"/tmp/phpmyadmin-backup-$(date +%Y%m%d-%H%M%S)\"\n  mkdir -p \"$BACKUP_DIR\"\n  BACKUP_ITEMS=(\"config.inc.php\" \"upload\" \"save\" \"tmp\" \"themes\")\n\n  msg_info \"Backing up existing phpMyAdmin data\"\n  for item in \"${BACKUP_ITEMS[@]}\"; do\n    [[ -e \"$INSTALL_DIR/$item\" ]] && cp -a \"$INSTALL_DIR/$item\" \"$BACKUP_DIR/\" && echo \"  ↪︎ $item\"\n  done\n  msg_ok \"Backup completed: $BACKUP_DIR\"\n\n  tar xf /tmp/phpmyadmin.tar.gz --strip-components=1 -C \"$INSTALL_DIR\"\n  msg_ok \"Extracted phpMyAdmin $LATEST_VERSION\"\n\n  msg_info \"Restoring preserved files\"\n  for item in \"${BACKUP_ITEMS[@]}\"; do\n    [[ -e \"$BACKUP_DIR/$item\" ]] && cp -a \"$BACKUP_DIR/$item\" \"$INSTALL_DIR/\" && echo \"  ↪︎ $item restored\"\n  done\n  msg_ok \"Restoration completed\"\n\n  configure_phpmyadmin\n}\n\nif is_phpmyadmin_installed; then\n  echo -e \"${YW}⚠️ ${APP} is already installed at ${INSTALL_DIR}.${CL}\"\n  read -r -p \"Would you like to Update (1), Uninstall (2) or Cancel (3)? [1/2/3]: \" action\n  action=\"${action//[[:space:]]/}\"\n  case \"$action\" in\n  1)\n    check_internet\n    update_phpmyadmin\n    ;;\n  2)\n    uninstall_phpmyadmin\n    ;;\n  3)\n    echo -e \"${YW}⚠️ Action cancelled. Exiting.${CL}\"\n    exit 0\n    ;;\n  *)\n    echo -e \"${YW}⚠️ Invalid input. Exiting.${CL}\"\n    exit 112\n    ;;\n  esac\nelse\n  read -r -p \"Would you like to install ${APP}? (y/n): \" install_prompt\n  install_prompt=\"${install_prompt//[[:space:]]/}\"\n  if [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    check_internet\n    install_php_and_modules\n    install_phpmyadmin\n    configure_phpmyadmin\n    if [[ \"$OS\" == \"Debian\" ]]; then\n      echo -e \"${CM} ${GN}${APP} is reachable at: ${BL}http://${IP}/phpMyAdmin${CL}\"\n    else\n      echo -e \"${CM} ${GN}${APP} is reachable at: ${BL}http://${IP}/${CL}\"\n    fi\n  else\n    echo -e \"${YW}⚠️ Installation skipped. Exiting.${CL}\"\n    exit 0\n  fi\nfi\n"
  },
  {
    "path": "tools/addon/pihole-exporter.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/eko/pihole-exporter/\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pihole-exporter\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nVERBOSE=${var_verbose:-no}\nAPP=\"pihole-exporter\"\nAPP_TYPE=\"tools\"\nINSTALL_PATH=\"/opt/pihole-exporter\"\nCONFIG_PATH=\"/opt/pihole-exporter.env\"\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  SERVICE_PATH=\"/etc/init.d/pihole-exporter\"\nelif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/pihole-exporter.service\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling Pihole-Exporter\"\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    rc-service pihole-exporter stop &>/dev/null\n    rc-update del pihole-exporter &>/dev/null\n    rm -f \"$SERVICE_PATH\"\n  else\n    systemctl disable -q --now pihole-exporter\n    rm -f \"$SERVICE_PATH\"\n  fi\n  rm -rf \"$INSTALL_PATH\" \"$CONFIG_PATH\"\n  rm -f \"/usr/local/bin/update_pihole-exporter\"\n  rm -f \"$HOME/.pihole-exporter\"\n  msg_ok \"Pihole-Exporter has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"pihole-exporter\" \"eko/pihole-exporter\"; then\n    msg_info \"Stopping service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service pihole-exporter stop &>/dev/null\n    else\n      systemctl stop pihole-exporter\n    fi\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"pihole-exporter\" \"eko/pihole-exporter\" \"tarball\" \"latest\"\n    setup_go\n\n    msg_info \"Building Pihole-Exporter\"\n    cd /opt/pihole-exporter/\n    $STD /usr/local/bin/go build -o ./pihole-exporter\n    msg_ok \"Built Pihole-Exporter\"\n\n    msg_info \"Starting service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service pihole-exporter start &>/dev/null\n    else\n      systemctl start pihole-exporter\n    fi\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  read -erp \"Enter the protocol to use (http/https), default https: \" pihole_PROTOCOL\n  read -erp \"Enter the hostname of Pihole, example: (127.0.0.1): \" pihole_HOSTNAME\n  read -erp \"Enter the port of Pihole, default 443: \" pihole_PORT\n  read -rsp \"Enter Pihole password: \" pihole_PASSWORD\n  printf \"\\n\"\n  read -erp \"Do you want to skip TLS-Verification (if using a self-signed Certificate on Pi-Hole) [y/N]: \" SKIP_TLS\n  if [[ \"${SKIP_TLS,,}\" =~ ^(y|yes)$ ]]; then\n    pihole_SKIP_TLS=\"true\"\n  fi\n\n  fetch_and_deploy_gh_release \"pihole-exporter\" \"eko/pihole-exporter\" \"tarball\" \"latest\"\n  setup_go\n  msg_info \"Building Pihole-Exporter on ${OS}\"\n  cd /opt/pihole-exporter/\n  $STD /usr/local/bin/go build -o ./pihole-exporter\n  msg_ok \"Built Pihole-Exporter\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"\n# https://github.com/eko/pihole-exporter/?tab=readme-ov-file#available-cli-options\nPIHOLE_PASSWORD=\"${pihole_PASSWORD}\"\nPIHOLE_HOSTNAME=\"${pihole_HOSTNAME:-127.0.0.1}\"\nPIHOLE_PORT=\"${pihole_PORT:-443}\"\nSKIP_TLS_VERIFICATION=\"${pihole_SKIP_TLS:-false}\"\nPIHOLE_PROTOCOL=\"${pihole_PROTOCOL:-https}\"\nEOF\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=pihole-exporter\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/pihole-exporter\nEnvironmentFile=$CONFIG_PATH\nExecStart=/opt/pihole-exporter/pihole-exporter\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    systemctl enable -q --now pihole-exporter\n  else\n    cat <<EOF >\"$SERVICE_PATH\"\n#!/sbin/openrc-run\n\nname=\"pihole-exporter\"\ndescription=\"Pi-hole Exporter for Prometheus\"\ncommand=\"${INSTALL_PATH}/pihole-exporter\"\ncommand_background=true\ndirectory=\"/opt/pihole-exporter\"\npidfile=\"/run/\\${RC_SVCNAME}.pid\"\noutput_log=\"/var/log/pihole-exporter.log\"\nerror_log=\"/var/log/pihole-exporter.log\"\n\ndepend() {\n    need net\n    after firewall\n}\n\nstart_pre() {\n    if [ -f \"$CONFIG_PATH\" ]; then\n        export \\$(grep -v '^#' $CONFIG_PATH | xargs)\n    fi\n}\nEOF\n    chmod +x \"$SERVICE_PATH\"\n    $STD rc-update add pihole-exporter default\n    $STD rc-service pihole-exporter start\n  fi\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  ensure_usr_local_bin_persist\n  cat <<'UPDATEEOF' >/usr/local/bin/update_pihole-exporter\n#!/usr/bin/env bash\n# pihole-exporter Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/pihole-exporter.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_pihole-exporter\n  msg_ok \"Created update script (/usr/local/bin/update_pihole-exporter)\"\n\n  echo \"\"\n  msg_ok \"Pihole-Exporter installed successfully\"\n  msg_ok \"Metrics: ${BL}http://${LOCAL_IP}:9617/metrics${CL}\"\n  msg_ok \"Config: ${BL}${CONFIG_PATH}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/pihole-exporter\" ]]; then\n    update\n  else\n    msg_error \"Pihole-Exporter is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/pihole-exporter\" ]]; then\n  msg_warn \"Pihole-Exporter is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall Pihole-Exporter? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update Pihole-Exporter? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"Pihole-Exporter is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Pi-hole Exporter (Go binary)\"\necho -e \"${TAB}  - Systemd/OpenRC service\"\necho \"\"\n\necho -n \"${TAB}Install Pihole-Exporter? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/prometheus-paperless-ngx-exporter.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/hansmi/prometheus-paperless-exporter\n\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"prometheus-paperless-ngx-exporter\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nVERBOSE=${var_verbose:-no}\nAPP=\"prometheus-paperless-ngx-exporter\"\nAPP_TYPE=\"tools\"\nBINARY_PATH=\"/usr/bin/prometheus-paperless-exporter\"\nCONFIG_PATH=\"/etc/prometheus-paperless-ngx-exporter/config.env\"\nSERVICE_PATH=\"/etc/systemd/system/prometheus-paperless-ngx-exporter.service\"\nAUTH_TOKEN_FILE=\"/etc/prometheus-paperless-ngx-exporter/paperless_auth_token_file\"\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then\n  echo -e \"${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling Prometheus-Paperless-NGX-Exporter\"\n  systemctl disable -q --now prometheus-paperless-ngx-exporter\n\n  if dpkg -l | grep -q prometheus-paperless-exporter; then\n    $STD apt-get remove -y prometheus-paperless-exporter || $STD dpkg -r prometheus-paperless-exporter\n  fi\n\n  rm -f \"$SERVICE_PATH\"\n  rm -rf /etc/prometheus-paperless-ngx-exporter\n  rm -f \"/usr/local/bin/update_prometheus-paperless-ngx-exporter\"\n  rm -f \"$HOME/.prometheus-paperless-ngx-exporter\"\n  msg_ok \"Prometheus-Paperless-NGX-Exporter has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"prom-paperless-exp\" \"hansmi/prometheus-paperless-exporter\"; then\n    msg_info \"Stopping service\"\n    systemctl stop prometheus-paperless-ngx-exporter\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"prom-paperless-exp\" \"hansmi/prometheus-paperless-exporter\" \"binary\" \"latest\"\n\n    msg_info \"Starting service\"\n    systemctl start prometheus-paperless-ngx-exporter\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  read -erp \"Enter URL of Paperless-NGX, example: (http://127.0.0.1:8000): \" PAPERLESS_URL\n  read -rsp \"Enter Paperless-NGX authentication token: \" PAPERLESS_AUTH_TOKEN\n  printf \"\\n\"\n\n  fetch_and_deploy_gh_release \"prom-paperless-exp\" \"hansmi/prometheus-paperless-exporter\" \"binary\" \"latest\"\n\n  msg_info \"Creating configuration\"\n  mkdir -p /etc/prometheus-paperless-ngx-exporter\n  cat <<EOF >\"$CONFIG_PATH\"\n# https://github.com/hansmi/prometheus-paperless-exporter\nPAPERLESS_URL=\"${PAPERLESS_URL}\"\nEOF\n  echo \"${PAPERLESS_AUTH_TOKEN}\" >\"$AUTH_TOKEN_FILE\"\n  chmod 600 \"$AUTH_TOKEN_FILE\"\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=Prometheus Paperless NGX Exporter\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nUser=root\nEnvironmentFile=$CONFIG_PATH\nExecStart=$BINARY_PATH \\\\\n    --paperless_url=\\${PAPERLESS_URL} \\\\\n    --paperless_auth_token_file=$AUTH_TOKEN_FILE\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl daemon-reload\n  systemctl enable -q --now prometheus-paperless-ngx-exporter\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  ensure_usr_local_bin_persist\n  cat <<'UPDATEEOF' >/usr/local/bin/update_prometheus-paperless-ngx-exporter\n#!/usr/bin/env bash\n# prometheus-paperless-ngx-exporter Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/prometheus-paperless-ngx-exporter.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_prometheus-paperless-ngx-exporter\n  msg_ok \"Created update script (/usr/local/bin/update_prometheus-paperless-ngx-exporter)\"\n\n  echo \"\"\n  msg_ok \"Prometheus-Paperless-NGX-Exporter installed successfully\"\n  msg_ok \"Metrics: ${BL}http://${LOCAL_IP}:8081/metrics${CL}\"\n  msg_ok \"Config: ${BL}${CONFIG_PATH}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -f \"$BINARY_PATH\" ]]; then\n    update\n  else\n    msg_error \"Prometheus-Paperless-NGX-Exporter is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -f \"$BINARY_PATH\" ]]; then\n  msg_warn \"Prometheus-Paperless-NGX-Exporter is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall Prometheus-Paperless-NGX-Exporter? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update Prometheus-Paperless-NGX-Exporter? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"Prometheus-Paperless-NGX-Exporter is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Prometheus Paperless NGX Exporter (binary)\"\necho -e \"${TAB}  - Systemd service\"\necho \"\"\n\necho -n \"${TAB}Install Prometheus-Paperless-NGX-Exporter? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/pyenv.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://pyenv.run/ | Github: https://github.com/pyenv/pyenv\n\nset -e\nYW=$(echo \"\\033[33m\")\nRD=$(echo \"\\033[01;31m\")\nBL=$(echo \"\\033[36m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pyenv\" \"addon\"\n\nif command -v pveversion >/dev/null 2>&1; then\n  msg_error \"Can't Install on Proxmox \"\n  exit\nfi\nmsg_info \"Installing pyenv\"\napt-get install -y \\\n  make \\\n  build-essential \\\n  libjpeg-dev \\\n  libpcap-dev \\\n  libssl-dev \\\n  zlib1g-dev \\\n  libbz2-dev \\\n  libreadline-dev \\\n  libsqlite3-dev \\\n  autoconf \\\n  git \\\n  curl \\\n  sudo \\\n  llvm \\\n  libncursesw5-dev \\\n  xz-utils \\\n  tk-dev \\\n  libxml2-dev \\\n  libxmlsec1-dev \\\n  libffi-dev \\\n  libopenjp2-7 \\\n  libtiff5 \\\n  libturbojpeg0-dev \\\n  liblzma-dev &>/dev/null\n\ngit clone https://github.com/pyenv/pyenv.git ~/.pyenv &>/dev/null\nset +e\necho 'export PYENV_ROOT=\"$HOME/.pyenv\"' >>~/.bashrc\necho 'export PATH=\"$PYENV_ROOT/bin:$PATH\"' >>~/.bashrc\necho -e 'if command -v pyenv 1>/dev/null 2>&1; then\\n eval \"$(pyenv init --path)\"\\nfi' >>~/.bashrc\nmsg_ok \"Installed pyenv\"\n. ~/.bashrc\nset -e\nmsg_info \"Installing Python 3.11.1\"\npyenv install 3.11.1 &>/dev/null\npyenv global 3.11.1\nmsg_ok \"Installed Python 3.11.1\"\nread -r -p \"Would you like to install Home Assistant Beta? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Home Assistant Beta\"\n  cat <<EOF >/etc/systemd/system/homeassistant.service\n[Unit]\nDescription=Home Assistant\nAfter=network-online.target\n[Service]\nType=simple\nWorkingDirectory=/root/.homeassistant\nExecStart=/srv/homeassistant/bin/hass -c \"/root/.homeassistant\"\nRestartForceExitStatus=100\n[Install]\nWantedBy=multi-user.target\nEOF\n  mkdir /srv/homeassistant\n  cd /srv/homeassistant\n  python3 -m venv .\n  source bin/activate\n  python3 -m pip install wheel &>/dev/null\n  pip3 install --upgrade pip &>/dev/null\n  pip3 install psycopg2-binary &>/dev/null\n  pip3 install --pre homeassistant &>/dev/null\n  systemctl enable homeassistant &>/dev/null\n  msg_ok \"Installed Home Assistant Beta\"\n  echo -e \" Go to $(hostname -I | awk '{print $1}'):8123\"\n  hass\nfi\n\nread -r -p \"Would you like to install ESPHome Beta? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing ESPHome Beta\"\n  mkdir /srv/esphome\n  cd /srv/esphome\n  python3 -m venv .\n  source bin/activate\n  python3 -m pip install wheel &>/dev/null\n  pip3 install --upgrade pip &>/dev/null\n  pip3 install --pre esphome &>/dev/null\n  cat <<EOF >/srv/esphome/start.sh\n#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource /srv/esphome/bin/activate\nesphome dashboard /srv/esphome/\nEOF\n  chmod +x start.sh\n  cat <<EOF >/etc/systemd/system/esphomedashboard.service\n[Unit]\nDescription=ESPHome Dashboard Service\nAfter=network.target\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/srv/esphome\nExecStart=/srv/esphome/start.sh\nRestartSec=30\nRestart=on-failure\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable --now esphomedashboard &>/dev/null\n  msg_ok \"Installed ESPHome Beta\"\n  echo -e \" Go to $(hostname -I | awk '{print $1}'):6052\"\n  exec $SHELL\nfi\n\nread -r -p \"Would you like to install Matter-Server (Beta)? <y/N> \" prompt\nif [[ \"${prompt,,}\" =~ ^(y|yes)$ ]]; then\n  msg_info \"Installing Matter Server\"\n  apt-get install -y \\\n    libcairo2-dev \\\n    libjpeg62-turbo-dev \\\n    libgirepository1.0-dev \\\n    libpango1.0-dev \\\n    libgif-dev \\\n    g++ &>/dev/null\n  python3 -m pip install wheel\n  pip3 install --upgrade pip\n  pip install python-matter-server[server]\n  msg_ok \"Installed Matter Server\"\n  echo -e \"Start server > python -m matter_server.server\"\nfi\nmsg_ok \"\\nFinished\\n\"\nexec $SHELL\n"
  },
  {
    "path": "tools/addon/qbittorrent-exporter.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: CrazyWolf13\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://github.com/martabal/qbittorrent-exporter\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"qbittorrent-exporter\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nVERBOSE=${var_verbose:-no}\nAPP=\"qbittorrent-exporter\"\nAPP_TYPE=\"tools\"\nINSTALL_PATH=\"/opt/qbittorrent-exporter\"\nCONFIG_PATH=\"/opt/qbittorrent-exporter.env\"\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif [[ -f \"/etc/alpine-release\" ]]; then\n  OS=\"Alpine\"\n  SERVICE_PATH=\"/etc/init.d/qbittorrent-exporter\"\nelif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then\n  OS=\"Debian\"\n  SERVICE_PATH=\"/etc/systemd/system/qbittorrent-exporter.service\"\nelse\n  echo -e \"${CROSS} Unsupported OS detected. Exiting.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling qBittorrent-Exporter\"\n  if [[ \"$OS\" == \"Alpine\" ]]; then\n    rc-service qbittorrent-exporter stop &>/dev/null\n    rc-update del qbittorrent-exporter &>/dev/null\n    rm -f \"$SERVICE_PATH\"\n  else\n    systemctl disable -q --now qbittorrent-exporter\n    rm -f \"$SERVICE_PATH\"\n  fi\n  rm -rf \"$INSTALL_PATH\" \"$CONFIG_PATH\"\n  rm -f \"/usr/local/bin/update_qbittorrent-exporter\"\n  rm -f \"$HOME/.qbittorrent-exporter\"\n  msg_ok \"qBittorrent-Exporter has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"qbittorrent-exporter\" \"martabal/qbittorrent-exporter\"; then\n    if [[ \"$(printf '%s\\n' \"2.0.0\" \"$CHECK_UPDATE_RELEASE\" | sort -V | tail -n1)\" == \"$CHECK_UPDATE_RELEASE\" ]] && \\\n       ! grep -q \"QBITTORRENT_API_KEY\" \"$CONFIG_PATH\" 2>/dev/null; then\n      echo \"\"\n      msg_warn \"Version 2.0.0 introduces a breaking change: username/password login has been replaced by an API key.\"\n      echo -e \"${TAB3}${INFO} You must create an API key in qBittorrent under Tools > Options > Web UI > API key\"\n      echo \"\"\n      echo -n \"${TAB3}Enter your qBittorrent API key (or press Enter to abort): \"\n      read -r QBITTORRENT_API_KEY\n      if [[ -z \"$QBITTORRENT_API_KEY\" ]]; then\n        msg_warn \"No API key provided. Update aborted.\"\n        exit 0\n      fi\n      sed -i '/^QBITTORRENT_USERNAME=/d' \"$CONFIG_PATH\"\n      sed -i '/^QBITTORRENT_PASSWORD=/d' \"$CONFIG_PATH\"\n      echo \"QBITTORRENT_API_KEY=\\\"${QBITTORRENT_API_KEY}\\\"\" >>\"$CONFIG_PATH\"\n      msg_ok \"API key saved to configuration\"\n    fi\n\n    msg_info \"Stopping service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service qbittorrent-exporter stop &>/dev/null\n    else\n      systemctl stop qbittorrent-exporter\n    fi\n    msg_ok \"Stopped service\"\n\n    fetch_and_deploy_gh_release \"qbittorrent-exporter\" \"martabal/qbittorrent-exporter\" \"tarball\" \"latest\"\n    setup_go\n\n    msg_info \"Building qBittorrent-Exporter\"\n    cd /opt/qbittorrent-exporter\n    $STD /usr/local/bin/go build -o ./qbittorrent-exporter\n    msg_ok \"Built qBittorrent-Exporter\"\n\n    msg_info \"Starting service\"\n    if [[ \"$OS\" == \"Alpine\" ]]; then\n      rc-service qbittorrent-exporter start &>/dev/null\n    else\n      systemctl start qbittorrent-exporter\n    fi\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully!\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  read -erp \"${TAB3}Enter URL of qBittorrent, example: (http://127.0.0.1:8080): \" QBITTORRENT_BASE_URL\n  echo -e \"${TAB3}${INFO} Create an API key in qBittorrent under Tools > Options > Web UI > API key\"\n  read -erp \"${TAB3}Enter qBittorrent API key: \" QBITTORRENT_API_KEY\n\n  fetch_and_deploy_gh_release \"qbittorrent-exporter\" \"martabal/qbittorrent-exporter\" \"tarball\" \"latest\"\n  setup_go\n  msg_info \"Building qBittorrent-Exporter on ${OS}\"\n  cd /opt/qbittorrent-exporter\n  $STD /usr/local/bin/go build -o ./qbittorrent-exporter\n  msg_ok \"Built qBittorrent-Exporter\"\n\n  msg_info \"Creating configuration\"\n  cat <<EOF >\"$CONFIG_PATH\"\n# https://github.com/martabal/qbittorrent-exporter?tab=readme-ov-file#parameters\nQBITTORRENT_BASE_URL=\"${QBITTORRENT_BASE_URL}\"\nQBITTORRENT_API_KEY=\"${QBITTORRENT_API_KEY}\"\nEOF\n  msg_ok \"Created configuration\"\n\n  msg_info \"Creating service\"\n  if [[ \"$OS\" == \"Debian\" ]]; then\n    cat <<EOF >\"$SERVICE_PATH\"\n[Unit]\nDescription=qbittorrent-exporter\nAfter=network.target\n\n[Service]\nUser=root\nWorkingDirectory=/opt/qbittorrent-exporter\nEnvironmentFile=$CONFIG_PATH\nExecStart=/opt/qbittorrent-exporter/qbittorrent-exporter\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\n    systemctl daemon-reload\n    systemctl enable -q --now qbittorrent-exporter\n  else\n    cat <<EOF >\"$SERVICE_PATH\"\n#!/sbin/openrc-run\n\nname=\"qbittorrent-exporter\"\ndescription=\"qBittorrent Exporter for Prometheus\"\ncommand=\"${INSTALL_PATH}/qbittorrent-exporter\"\ncommand_background=true\ndirectory=\"/opt/qbittorrent-exporter\"\npidfile=\"/run/\\${RC_SVCNAME}.pid\"\noutput_log=\"/var/log/qbittorrent-exporter.log\"\nerror_log=\"/var/log/qbittorrent-exporter.log\"\n\ndepend() {\n    need net\n    after firewall\n}\n\nstart_pre() {\n    if [ -f \"$CONFIG_PATH\" ]; then\n        export \\$(grep -v '^#' $CONFIG_PATH | xargs)\n    fi\n}\nEOF\n    chmod +x \"$SERVICE_PATH\"\n    $STD rc-update add qbittorrent-exporter default\n    $STD rc-service qbittorrent-exporter start\n  fi\n  msg_ok \"Created and started service\"\n\n  # Create update script\n  msg_info \"Creating update script\"\n  ensure_usr_local_bin_persist\n  cat <<'UPDATEEOF' >/usr/local/bin/update_qbittorrent-exporter\n#!/usr/bin/env bash\n# qbittorrent-exporter Update Script\ntype=update bash -c \"$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/tools/addon/qbittorrent-exporter.sh)\"\nUPDATEEOF\n  chmod +x /usr/local/bin/update_qbittorrent-exporter\n  msg_ok \"Created update script (/usr/local/bin/update_qbittorrent-exporter)\"\n\n  echo \"\"\n  msg_ok \"qBittorrent-Exporter installed successfully\"\n  msg_ok \"Metrics: ${BL}http://${LOCAL_IP}:8090/metrics${CL}\"\n  msg_ok \"Config: ${BL}${CONFIG_PATH}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/qbittorrent-exporter\" ]]; then\n    update\n  else\n    msg_error \"qBittorrent-Exporter is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -f \"$INSTALL_PATH/qbittorrent-exporter\" ]]; then\n  msg_warn \"qBittorrent-Exporter is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall qBittorrent-Exporter? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update qBittorrent-Exporter? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"qBittorrent-Exporter is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - qBittorrent Exporter (Go binary)\"\necho -e \"${TAB}  - Systemd/OpenRC service\"\necho \"\"\n\necho -n \"${TAB}Install qBittorrent-Exporter? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/runtipi.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://runtipi.io/ | Github: https://github.com/runtipi/runtipi\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  if [[ -f /etc/alpine-release ]]; then\n    apk update >/dev/null 2>&1\n    apk add --no-cache curl >/dev/null 2>&1\n  else\n    apt-get update >/dev/null 2>&1\n    apt-get install -y curl >/dev/null 2>&1\n  fi\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"runtipi\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"Runtipi\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/runtipi\"\nDEFAULT_PORT=80\n\n# Initialize all core functions (colors, formatting, icons, STD mode)\nload_functions\n\n# ==============================================================================\n# PROXMOX HOST CHECK\n# ==============================================================================\nfunction check_proxmox_host() {\n  if command -v pveversion &>/dev/null; then\n    msg_error \"Running on the Proxmox host is NOT recommended!\"\n    msg_error \"This should be executed inside an LXC container.\"\n    echo \"\"\n    echo -n \"${TAB}Continue anyway? (y/N): \"\n    read -r confirm\n    if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n      msg_warn \"Aborted. Please run this inside an LXC container.\"\n      exit 0\n    fi\n    msg_warn \"Proceeding on Proxmox host at your own risk!\"\n  fi\n}\n\n# ==============================================================================\n# CHECK / INSTALL DOCKER\n# ==============================================================================\nfunction check_or_install_docker() {\n  if command -v docker &>/dev/null; then\n    msg_ok \"Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available\"\n    if docker compose version &>/dev/null; then\n      msg_ok \"Docker Compose is available\"\n    else\n      msg_error \"Docker Compose plugin is not available. Please install it.\"\n      exit 10\n    fi\n    return\n  fi\n\n  msg_warn \"Docker is not installed.\"\n  echo -n \"${TAB}Install Docker now? (y/N): \"\n  read -r install_docker_prompt\n  if [[ ! \"${install_docker_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    msg_error \"Docker is required for ${APP}. Exiting.\"\n    exit 10\n  fi\n\n  msg_info \"Installing Docker\"\n  DOCKER_CONFIG_PATH='/etc/docker/daemon.json'\n  mkdir -p \"$(dirname \"$DOCKER_CONFIG_PATH\")\"\n  echo -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n  $STD sh <(curl -fsSL https://get.docker.com)\n  msg_ok \"Installed Docker\"\n}\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n\n  if [[ -f \"${INSTALL_PATH}/runtipi-cli\" ]]; then\n    msg_info \"Stopping ${APP}\"\n    cd \"$INSTALL_PATH\"\n    $STD ./runtipi-cli stop 2>/dev/null || true\n    msg_ok \"Stopped ${APP}\"\n  fi\n\n  if command -v docker &>/dev/null; then\n    msg_info \"Removing Docker containers\"\n    cd \"$INSTALL_PATH\" 2>/dev/null && $STD docker compose down --remove-orphans 2>/dev/null || true\n    msg_ok \"Removed Docker containers\"\n  fi\n\n  rm -rf \"$INSTALL_PATH\"\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  msg_info \"Updating ${APP}\"\n  cd \"$INSTALL_PATH\"\n  $STD ./runtipi-cli update latest\n  msg_ok \"Updated ${APP}\"\n\n  msg_ok \"Updated successfully\"\n  exit\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  check_or_install_docker\n\n  msg_info \"Installing dependencies\"\n  $STD apt-get update\n  $STD apt-get install -y openssl\n  msg_ok \"Installed dependencies\"\n\n  msg_warn \"WARNING: This will run an external installer from https://runtipi.io/\"\n  msg_warn \"The following code is NOT maintained or audited by our repository.\"\n  msg_warn \"Review: https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh\"\n  echo \"\"\n  echo -n \"${TAB}Do you want to continue? (y/N): \"\n  read -r confirm\n  if [[ ! \"${confirm,,}\" =~ ^(y|yes)$ ]]; then\n    msg_warn \"Installation cancelled. Exiting.\"\n    exit 0\n  fi\n\n  msg_info \"Installing ${APP} (this pulls Docker containers)\"\n  DOCKER_CONFIG_PATH='/etc/docker/daemon.json'\n  mkdir -p \"$(dirname \"$DOCKER_CONFIG_PATH\")\"\n  [[ ! -f \"$DOCKER_CONFIG_PATH\" ]] && echo -e '{\\n  \"log-driver\": \"journald\"\\n}' >\"$DOCKER_CONFIG_PATH\"\n  cd /opt\n  curl -fsSL \"https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh\" -o \"install.sh\"\n  chmod +x install.sh\n  $STD ./install.sh\n  chmod 660 /opt/runtipi/state/settings.json 2>/dev/null || true\n  rm -f /opt/install.sh\n  msg_ok \"Installed ${APP}\"\n\n  echo \"\"\n  msg_ok \"${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  header_info\n  if [[ -d \"$INSTALL_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\nif [[ -f /etc/alpine-release ]]; then\n  msg_error \"${APP} does not support Alpine Linux. Please use a Debian or Ubuntu based LXC.\"\n  exit 238\nfi\n\nheader_info\ncheck_proxmox_host\nget_lxc_ip\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - Runtipi (via external installer)\"\necho -e \"${TAB}  - Docker (if not already installed)\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/sparkyfitness-garmin.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Tom Frenzel (tomfrenzel)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/CodeWithCJ/SparkyFitness\n\nif ! command -v curl &>/dev/null; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"sparkyfitness-garmin\" \"addon\"\n\n# Enable error handling\nset -Eeuo pipefail\ntrap 'error_handler' ERR\nload_functions\n\n# ==============================================================================\n# CONFIGURATION\n# ==============================================================================\nAPP=\"SparkyFitness-Garmin\"\nAPP_TYPE=\"addon\"\nINSTALL_PATH=\"/opt/sparkyfitness-garmin\"\nCONFIG_PATH=\"/etc/sparkyfitness-garmin/.env\"\nSERVICE_PATH=\"/etc/systemd/system/sparkyfitness-garmin.service\"\nDEFAULT_PORT=8000\n\n# ==============================================================================\n# OS DETECTION\n# ==============================================================================\nif ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then\n  echo -e \"${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu.\"\n  exit 238\nfi\n\n# ==============================================================================\n# SparkyFitness LXC DETECTION\n# ==============================================================================\nif [[ ! -d /opt/sparkyfitness ]]; then\n  echo -e \"${CROSS} No SparkyFitness installation detected. This addon must be installed within a container that already has SparkyFitness installed.\"\n  exit 238\nfi\n\n# ==============================================================================\n# UNINSTALL\n# ==============================================================================\nfunction uninstall() {\n  msg_info \"Uninstalling ${APP}\"\n  systemctl disable --now sparkyfitness-garmin.service &>/dev/null || true\n  rm -rf \"$SERVICE_PATH\" \"$CONFIG_PATH\" \"$INSTALL_PATH\" ~/.sparkyfitness-garmin\n  msg_ok \"${APP} has been uninstalled\"\n}\n\n# ==============================================================================\n# UPDATE\n# ==============================================================================\nfunction update() {\n  if check_for_gh_release \"sparkyfitness-garmin\" \"CodeWithCJ/SparkyFitness\"; then\n    PYTHON_VERSION=\"3.13\" setup_uv\n\n    msg_info \"Stopping service\"\n    systemctl stop sparkyfitness-garmin.service &>/dev/null || true\n    msg_ok \"Stopped service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"sparkyfitness-garmin\" \"CodeWithCJ/SparkyFitness\" \"tarball\" \"latest\" $INSTALL_PATH\n    cd $INSTALL_PATH/SparkyFitnessGarmin\n    $STD uv venv --clear .venv\n    $STD uv pip install -r requirements.txt\n\n    msg_info \"Starting service\"\n    systemctl start sparkyfitness-garmin\n    msg_ok \"Started service\"\n    msg_ok \"Updated successfully\"\n    exit\n  fi\n}\n\n# ==============================================================================\n# INSTALL\n# ==============================================================================\nfunction install() {\n  PYTHON_VERSION=\"3.13\" setup_uv\n  fetch_and_deploy_gh_release \"sparkyfitness-garmin\" \"CodeWithCJ/SparkyFitness\" \"tarball\" \"latest\" $INSTALL_PATH\n\n  msg_info \"Setting up ${APP}\"\n  mkdir -p \"/etc/sparkyfitness-garmin\"\n  cp \"/opt/sparkyfitness-garmin/docker/.env.example\" $CONFIG_PATH\n  cd $INSTALL_PATH/SparkyFitnessGarmin\n  $STD uv venv --clear .venv\n  $STD uv pip install -r requirements.txt\n  sed -i -e \"s|^#\\?GARMIN_MICROSERVICE_URL=.*|GARMIN_MICROSERVICE_URL=http://${LOCAL_IP}:${DEFAULT_PORT}|\" $CONFIG_PATH\n  cat <<EOF >/etc/systemd/system/sparkyfitness-garmin.service\n[Unit]\nDescription=${APP}\nAfter=network.target sparkyfitness-server.service\nRequires=sparkyfitness-server.service\n\n[Service]\nType=simple\nWorkingDirectory=$INSTALL_PATH/SparkyFitnessGarmin\nEnvironmentFile=$CONFIG_PATH\nExecStart=$INSTALL_PATH/SparkyFitnessGarmin/.venv/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port ${DEFAULT_PORT}\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n  systemctl enable -q --now sparkyfitness-garmin\n  msg_ok \"Set up ${APP} - reachable at http://${LOCAL_IP}:${DEFAULT_PORT}\"\n  msg_ok \"You might need to update the GARMIN_MICROSERVICE_URL in your SparkyFitness .env file to http://${LOCAL_IP}:${DEFAULT_PORT}\"\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\nheader_info\nensure_usr_local_bin_persist\nget_lxc_ip\n\n# Handle type=update (called from update script)\nif [[ \"${type:-}\" == \"update\" ]]; then\n  if [[ -d \"$INSTALL_PATH\" ]]; then\n    update\n  else\n    msg_error \"${APP} is not installed. Nothing to update.\"\n    exit 233\n  fi\n  exit 0\nfi\n\n# Check if already installed\nif [[ -d \"$INSTALL_PATH\" && -n \"$(ls -A \"$INSTALL_PATH\" 2>/dev/null)\" ]]; then\n  msg_warn \"${APP} is already installed.\"\n  echo \"\"\n\n  echo -n \"${TAB}Uninstall ${APP}? (y/N): \"\n  read -r uninstall_prompt\n  if [[ \"${uninstall_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    uninstall\n    exit 0\n  fi\n\n  echo -n \"${TAB}Update ${APP}? (y/N): \"\n  read -r update_prompt\n  if [[ \"${update_prompt,,}\" =~ ^(y|yes)$ ]]; then\n    update\n    exit 0\n  fi\n\n  msg_warn \"No action selected. Exiting.\"\n  exit 0\nfi\n\n# Fresh installation\nmsg_warn \"${APP} is not installed.\"\necho \"\"\necho -e \"${TAB}${INFO} This will install:\"\necho -e \"${TAB}  - UV (Python Version Manager)\"\necho -e \"${TAB}  - SparkyFitness Garmin Microservice\"\necho \"\"\n\necho -n \"${TAB}Install ${APP}? (y/N): \"\nread -r install_prompt\nif [[ \"${install_prompt,,}\" =~ ^(y|yes)$ ]]; then\n  install\nelse\n  msg_warn \"Installation cancelled. Exiting.\"\n  exit 0\nfi\n"
  },
  {
    "path": "tools/addon/webmin.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.webmin.com/ | Github: https://github.com/webmin/webmin\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n  _      __    __         _\n | | /| / /__ / /  __ _  (_)__\n | |/ |/ / -_) _ \\/  ' \\/ / _ \\\n |__/|__/\\__/_.__/_/_/_/_/_//_/\n\nEOF\n}\nset -eEuo pipefail\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nCM=\"${GN}✓${CL}\"\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\n\nmsg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nmsg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"webmin\" \"addon\"\n\nheader_info\n\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Webmin Installer\" --yesno \"This Will Install Webmin on this LXC Container. Proceed?\" 10 58\n\nmsg_info \"Installing Prerequisites\"\napt update &>/dev/null\napt-get -y install libnet-ssleay-perl libauthen-pam-perl libio-pty-perl unzip shared-mime-info curl &>/dev/null\nmsg_ok \"Installed Prerequisites\"\n\nLATEST=$(curl -fsSL https://api.github.com/repos/webmin/webmin/releases/latest | grep '\"tag_name\":' | cut -d'\"' -f4)\n\nmsg_info \"Downloading Webmin\"\ncurl -fsSL \"https://github.com/webmin/webmin/releases/download/$LATEST/webmin_${LATEST}_all.deb\" -o $(basename \"https://github.com/webmin/webmin/releases/download/$LATEST/webmin_${LATEST}_all.deb\")\nmsg_ok \"Downloaded Webmin\"\n\nmsg_info \"Installing Webmin\"\ndpkg -i webmin_${LATEST}_all.deb &>/dev/null\n/usr/share/webmin/changepass.pl /etc/webmin root root &>/dev/null\nrm -rf /root/webmin_${LATEST}_all.deb\nmsg_ok \"Installed Webmin\"\n\nIP=$(hostname -I | cut -f1 -d ' ')\necho -e \"Successfully Installed!! Webmin should be reachable by going to ${BL}https://${IP}:10000${CL}\"\n"
  },
  {
    "path": "tools/copy-data/README.md",
    "content": "<h2><p align=\"center\">Copy data to another LXC (run in the Proxmox Shell)</p></h2>\n<div align=\"center\"> To copy data from Home Assistant Container to Home Assistant Container </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/home-assistant-container-copy-data-home-assistant-container.sh)\"\n```\n<div align=\"center\"> To copy data from Home Assistant Container to Home Assistant Core </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/home-assistant-container-copy-data-home-assistant-core.sh)\"\n```\n<div align=\"center\"> To copy data from Home Assistant Container to Podman Home Assistant </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/home-assistant-container-copy-data-podman-home-assistant.sh)\"\n```\n<div align=\"center\"> To copy data from Podman Home Assistant to Home Assistant Container </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/podman-home-assistant-copy-data-home-assistant-container.sh)\"\n```\n<div align=\"center\"> To copy data from Home Assistant Core to Home Assistant Container </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/home-assistant-core-copy-data-home-assistant-container.sh)\"\n```\n<div align=\"center\"> To copy data from Home Assistant Core to Home Assistant Core </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/home-assistant-core-copy-data-home-assistant-core.sh)\"\n```\n<div align=\"center\"> To copy data from Plex to Plex </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/plex-copy-data-plex.sh)\"\n```\n<div align=\"center\"> To copy data from Zigbee2MQTT to Zigbee2MQTT </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/z2m-copy-data-z2m.sh)\"\n```\n<div align=\"center\"> To copy data from Zwavejs2MQTT to Zwave JS UI </div>\n\n```\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/copy-data/zwavejs2mqtt-copy-data-zwavejsui.sh)\"\n```\n"
  },
  {
    "path": "tools/copy-data/home-assistant-container-copy-data-home-assistant-container.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Use to copy all data from one Home Assistant LXC to another\n# run from the Proxmox Shell\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from one Home Assistant LXC to another. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit \"$EXIT\"\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount \"$CTID_FROM\"\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount \"$CTID_TO\"\n  popd >/dev/null\n  rm -rf \"$TEMP_DIR\"\n}\nTEMP_DIR=$(mktemp -d)\npushd \"$TEMP_DIR\" >/dev/null\n\nTITLE=\"Home Assistant LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<\"${CTID_MENU[$i + 1]}\")\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<\"${CTID_MENU[$i + 1]}\")\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.01.23\" 13 50\ninfo \"Home Assistant Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status \"$CTID_TO\" | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop \"$CTID_TO\"\nfi\nmsg \"Mounting Container Disks...\"\nDOCKER_PATH=/var/lib/docker/volumes/hass_config/\nCTID_FROM_PATH=$(pct mount \"$CTID_FROM\" | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${DOCKER_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount \"$CTID_TO\" | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${DOCKER_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_TO' not found.\"\n\nrm -rf \"${CTID_TO_PATH}\"${DOCKER_PATH}\nmkdir \"${CTID_TO_PATH}\"${DOCKER_PATH}\n\nmsg \"Copying Data Between Containers...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Docker Data ========>\"\nrsync \"${RSYNC_OPTIONS[*]}\" \"${CTID_FROM_PATH}\"${DOCKER_PATH} \"${CTID_TO_PATH}\"${DOCKER_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from one Home Assistant LXC to another\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//home-assistant-container-copy-data-home-assistant-container.sh)\"\n"
  },
  {
    "path": "tools/copy-data/home-assistant-container-copy-data-home-assistant-core.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from a Home Assistant Container LXC to a Home Assistant Core LXC. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Home Assistant LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Container LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Core LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.10.02\" 13 50\ninfo \"Home Assistant Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nDOCKER_PATH=/var/lib/docker/volumes/hass_config/_data\nCORE_PATH=/root/.homeassistant\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${DOCKER_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${CORE_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_TO' not found.\"\n\nmsg \"Copying Data...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Docker Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${DOCKER_PATH} ${CTID_TO_PATH}${CORE_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from a Home Assistant Container LXC to a Home Assistant Core LXC\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//home-assistant-container-copy-data-home-assistant-core.sh)\"\n"
  },
  {
    "path": "tools/copy-data/home-assistant-container-copy-data-podman-home-assistant.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Use to copy all data from a Home Assistant LXC to a Podman Home Assistant LXC.\n# run from the Proxmox Shell\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from a Home Assistant LXC to a Podman Home Assistant LXC. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Home Assistant LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Podman LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.02.12\" 13 50\ninfo \"Home Assistant Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nDOCKER_PATH=/var/lib/docker/volumes/hass_config/\nPODMAN_PATH=/var/lib/containers/storage/volumes/hass_config/\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${DOCKER_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${PODMAN_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_TO' not found.\"\n\nrm -rf ${CTID_TO_PATH}${PODMAN_PATH}\nmkdir ${CTID_TO_PATH}${PODMAN_PATH}\n\nmsg \"Copying Data Between Containers...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Docker Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${DOCKER_PATH} ${CTID_TO_PATH}${PODMAN_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from a Home Assistant LXC to a Podman Home Assistant LXC\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//home-assistant-container-copy-data-podman-home-assistant.sh)\"\n"
  },
  {
    "path": "tools/copy-data/home-assistant-core-copy-data-home-assistant-container.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from a Home Assistant Core LXC to a Home Assistant Container LXC. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Home Assistant LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Core LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Container LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.10.02\" 13 50\ninfo \"Home Assistant Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nDOCKER_PATH=/var/lib/docker/volumes/hass_config/_data\nCORE_PATH=/root/.homeassistant\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${CORE_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${DOCKER_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_TO' not found.\"\n\nmsg \"Copying Data...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Docker Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${CORE_PATH} ${CTID_TO_PATH}${DOCKER_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from a Home Assistant Core LXC to a Home Assistant Container LXC\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//home-assistant-core-copy-data-home-assistant-container.sh)\"\n"
  },
  {
    "path": "tools/copy-data/home-assistant-core-copy-data-home-assistant-core.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from a Home Assistant Core LXC to a Home Assistant Core LXC. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Home Assistant LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Core LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Core LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.10.03\" 13 50\ninfo \"Home Assistant Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_FROM | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_FROM'...\"\n  pct stop $CTID_FROM\nfi\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nCORE_PATH=/root/.homeassistant\nCORE_PATH2=/root/\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${CORE_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${CORE_PATH2}\" ] ||\n  die \"Home Assistant directories in '$CTID_TO' not found.\"\n\nmsg \"Copying Data...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Docker Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${CORE_PATH} ${CTID_TO_PATH}${CORE_PATH2}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from a Home Assistant Core LXC to a Home Assistant Container LXC\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//home-assistant-core-copy-data-home-assistant-core.sh)\"\n"
  },
  {
    "path": "tools/copy-data/plex-copy-data-plex.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Use to copy all data from one Plex Media Server LXC to another\n# run from the Proxmox Shell\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from one Plex Media Server LXC to another. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Plex Media Server LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich Plex Media Server LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich Plex Media Server LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.01.24\" 13 50\ninfo \"Plex Media Server Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nDATA_PATH=/var/lib/plexmediaserver/Library/\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${DATA_PATH}\" ] ||\n  die \"Plex Media Server directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${DATA_PATH}\" ] ||\n  die \"Plex Media Server directories in '$CTID_TO' not found.\"\n\n#rm -rf ${CTID_TO_PATH}${DATA_PATH}\n#mkdir ${CTID_TO_PATH}${DATA_PATH}\n\nmsg \"Copying Data Between Containers...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Plex Media Server Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${DATA_PATH} ${CTID_TO_PATH}${DATA_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from one Plex Media Server LXC to another\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//plex-copy-data-plex.sh)\"\n"
  },
  {
    "path": "tools/copy-data/podman-home-assistant-copy-data-home-assistant-container.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Use to copy all data from a Podman Home Assistant LXC to a Docker Home Assistant LXC.\n# run from the Proxmox Shell\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from a Podman Home Assistant LXC to a Docker Home Assistant LXC. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Home Assistant LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA Podman LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich HA LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.03.31\" 13 50\ninfo \"Home Assistant Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nDOCKER_PATH=/var/lib/docker/volumes/hass_config/\nPODMAN_PATH=/var/lib/containers/storage/volumes/hass_config/\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${PODMAN_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${DOCKER_PATH}\" ] ||\n  die \"Home Assistant directories in '$CTID_TO' not found.\"\n\nrm -rf ${CTID_TO_PATH}${DOCKER_PATH}\nmkdir ${CTID_TO_PATH}${DOCKER_PATH}\n\nmsg \"Copying Data Between Containers...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${PODMAN_PATH} ${CTID_TO_PATH}${DOCKER_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from a Podman Home Assistant LXC to a Docker Home Assistant LXC.\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//podman-home-assistant-copy-data-home-assistant-container.sh)\"\n"
  },
  {
    "path": "tools/copy-data/z2m-copy-data-z2m.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Use to copy all data from one Zigbee2MQTT LXC to another\n# run from the Proxmox Shell\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from one Zigbee2MQTT LXC to another. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit $EXIT\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount $CTID_FROM\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount $CTID_TO\n  popd >/dev/null\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nTITLE=\"Zigbee2MQTT LXC Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich Zigbee2MQTT LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich Zigbee2MQTT LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<${CTID_MENU[$i + 1]})\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.01.23\" 13 50\ninfo \"Zigbee2MQTT Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status $CTID_TO | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop $CTID_TO\nfi\nmsg \"Mounting Container Disks...\"\nDATA_PATH=/opt/zigbee2mqtt/data/\nCTID_FROM_PATH=$(pct mount $CTID_FROM | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${DATA_PATH}\" ] ||\n  die \"Zigbee2igbee2MQTT directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount $CTID_TO | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${DATA_PATH}\" ] ||\n  die \"Zigbee2MQTT directories in '$CTID_TO' not found.\"\n\n#rm -rf ${CTID_TO_PATH}${DATA_PATH}\n#mkdir ${CTID_TO_PATH}${DATA_PATH}\n\nmsg \"Copying Data Between Containers...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Zigbee2MQTT Data ========>\"\nrsync ${RSYNC_OPTIONS[*]} ${CTID_FROM_PATH}${DATA_PATH} ${CTID_TO_PATH}${DATA_PATH}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from one Zigbee2MQTT LXC to another\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//z2m-copy-data-z2m.sh)\"\n"
  },
  {
    "path": "tools/copy-data/zwavejs2mqtt-copy-data-zwavejsui.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Use to copy all data from a Zwavejs2MQTT LXC to a Z-wave JS UI LXC\n# run from the Proxmox Shell\nclear\nif ! command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Run from the Proxmox Shell\"\n  exit\nfi\nwhile true; do\n  read -p \"Use to copy all data from a Zwavejs2MQTT LXC to a Z-wave JS UI LXC. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nclear\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\n\nfunction error_exit() {\n  trap - ERR\n  local DEFAULT='Unknown failure occured.'\n  local REASON=\"\\e[97m${1:-$DEFAULT}\\e[39m\"\n  local FLAG=\"\\e[91m[ERROR] \\e[93m$EXIT@$LINE\"\n  msg \"$FLAG $REASON\"\n  exit \"$EXIT\"\n}\nfunction warn() {\n  local REASON=\"\\e[97m$1\\e[39m\"\n  local FLAG=\"\\e[93m[WARNING]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction info() {\n  local REASON=\"$1\"\n  local FLAG=\"\\e[36m[INFO]\\e[39m\"\n  msg \"$FLAG $REASON\"\n}\nfunction msg() {\n  local TEXT=\"$1\"\n  echo -e \"$TEXT\"\n}\nfunction cleanup() {\n  [ -d \"${CTID_FROM_PATH:-}\" ] && pct unmount \"$CTID_FROM\"\n  [ -d \"${CTID_TO_PATH:-}\" ] && pct unmount \"$CTID_TO\"\n  popd >/dev/null\n  rm -rf \"$TEMP_DIR\"\n}\nTEMP_DIR=$(mktemp -d)\npushd \"$TEMP_DIR\" >/dev/null\n\nTITLE=\"Zigbee2MQTT to Z-wave JS UI Data Copy\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nwhile [ -z \"${CTID_FROM:+x}\" ]; do\n  CTID_FROM=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich Zwavejs2MQTT LXC would you like to copy FROM?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nwhile [ -z \"${CTID_TO:+x}\" ]; do\n  CTID_TO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"$TITLE\" --radiolist \\\n    \"\\nWhich Z-wave JS UI LXC would you like to copy TO?\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\nfor i in ${!CTID_MENU[@]}; do\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_FROM\" ] &&\n    CTID_FROM_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<\"${CTID_MENU[$i + 1]}\")\n  [ \"${CTID_MENU[$i]}\" == \"$CTID_TO\" ] &&\n    CTID_TO_HOSTNAME=$(sed 's/[[:space:]]*$//' <<<\"${CTID_MENU[$i + 1]}\")\ndone\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"$TITLE\" --yesno \\\n  \"Are you sure you want to copy data between the following LXCs?\n$CTID_FROM (${CTID_FROM_HOSTNAME}) -> $CTID_TO (${CTID_TO_HOSTNAME})\nVersion: 2022.09.21\" 13 50\ninfo \"Zwavejs2MQTT Data from '$CTID_FROM' to '$CTID_TO'\"\nif [ $(pct status \"$CTID_TO\" | sed 's/.* //') == 'running' ]; then\n  msg \"Stopping '$CTID_TO'...\"\n  pct stop \"$CTID_TO\"\nfi\nmsg \"Mounting Container Disks...\"\nDATA_PATH=/opt/zwavejs2mqtt/store/\nDATA_PATH_NEW=/opt/zwave-js-ui/store/\nCTID_FROM_PATH=$(pct mount \"$CTID_FROM\" | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_FROM}'.\"\n[ -d \"${CTID_FROM_PATH}${DATA_PATH}\" ] ||\n  die \"Zwavejs2MQTT directories in '$CTID_FROM' not found.\"\nCTID_TO_PATH=$(pct mount \"$CTID_TO\" | sed -n \"s/.*'\\(.*\\)'/\\1/p\") ||\n  die \"There was a problem mounting the root disk of LXC '${CTID_TO}'.\"\n[ -d \"${CTID_TO_PATH}${DATA_PATH_NEW}\" ] ||\n  die \"Zwavejs2MQTT directories in '$CTID_TO' not found.\"\n\n#rm -rf ${CTID_TO_PATH}${DATA_PATH}\n#mkdir ${CTID_TO_PATH}${DATA_PATH}\n\nmsg \"Copying Data Between Containers...\"\nRSYNC_OPTIONS=(\n  --archive\n  --hard-links\n  --sparse\n  --xattrs\n  --no-inc-recursive\n  --info=progress2\n)\nmsg \"<======== Zwavejs Data ========>\"\nrsync \"${RSYNC_OPTIONS[*]}\" \"${CTID_FROM_PATH}\"${DATA_PATH} \"${CTID_TO_PATH}\"${DATA_PATH_NEW}\necho -en \"\\e[1A\\e[0K\\e[1A\\e[0K\"\n\ninfo \"Successfully Transferred Data.\"\n\n# Use to copy all data from a Zwavejs2MQTT LXC to a Z-wave JS UI LXC\n# run from the Proxmox Shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/mainmain/tools/copy-data//zwavejs2mqtt-copy-data-zwavejsui.sh)\"\n"
  },
  {
    "path": "tools/headers/add-iptag",
    "content": "    ________      ______           \n   /  _/ __ \\    /_  __/___ _____ _\n   / // /_/ /_____/ / / __ `/ __ `/\n _/ // ____/_____/ / / /_/ / /_/ / \n/___/_/         /_/  \\__,_/\\__, /  \n                          /____/   \n"
  },
  {
    "path": "tools/headers/adguardhome-sync",
    "content": "    ___       ________                     ____  __                          _____                 \n   /   | ____/ / ____/_  ______ __________/ / / / /___  ____ ___  ___       / ___/__  ______  _____\n  / /| |/ __  / / __/ / / / __ `/ ___/ __  / /_/ / __ \\/ __ `__ \\/ _ \\______\\__ \\/ / / / __ \\/ ___/\n / ___ / /_/ / /_/ / /_/ / /_/ / /  / /_/ / __  / /_/ / / / / / /  __/_____/__/ / /_/ / / / / /__  \n/_/  |_\\__,_/\\____/\\__,_/\\__,_/_/   \\__,_/_/ /_/\\____/_/ /_/ /_/\\___/     /____/\\__, /_/ /_/\\___/  \n                                                                               /____/              \n"
  },
  {
    "path": "tools/headers/arcane",
    "content": "    ___                             \n   /   |  ______________ _____  ___ \n  / /| | / ___/ ___/ __ `/ __ \\/ _ \\\n / ___ |/ /  / /__/ /_/ / / / /  __/\n/_/  |_/_/   \\___/\\__,_/_/ /_/\\___/ \n                                    \n"
  },
  {
    "path": "tools/headers/coder-code-server",
    "content": "   ______          __             ______          __        _____                          \n  / ____/___  ____/ /__  _____   / ____/___  ____/ /__     / ___/___  ______   _____  _____\n / /   / __ \\/ __  / _ \\/ ___/  / /   / __ \\/ __  / _ \\    \\__ \\/ _ \\/ ___/ | / / _ \\/ ___/\n/ /___/ /_/ / /_/ /  __/ /     / /___/ /_/ / /_/ /  __/   ___/ /  __/ /   | |/ /  __/ /    \n\\____/\\____/\\__,_/\\___/_/      \\____/\\____/\\__,_/\\___/   /____/\\___/_/    |___/\\___/_/     \n                                                                                           \n"
  },
  {
    "path": "tools/headers/container-restore-from-backup",
    "content": "    __  __                        ___              _      __              __     ______            __        _                \n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_   / ____/___  ____  / /_____ _(_)___  ___  _____\n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/  / /   / __ \\/ __ \\/ __/ __ `/ / __ \\/ _ \\/ ___/\n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_   / /___/ /_/ / / / / /_/ /_/ / / / / /  __/ /    \n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/   \\____/\\____/_/ /_/\\__/\\__,_/_/_/ /_/\\___/_/     \n                                                                                                                              \n"
  },
  {
    "path": "tools/headers/coolify",
    "content": "   ______            ___ ____     \n  / ____/___  ____  / (_) __/_  __\n / /   / __ \\/ __ \\/ / / /_/ / / /\n/ /___/ /_/ / /_/ / / / __/ /_/ / \n\\____/\\____/\\____/_/_/_/  \\__, /  \n                         /____/   \n"
  },
  {
    "path": "tools/headers/copyparty",
    "content": "   ______                  ____             __       \n  / ____/___  ____  __  __/ __ \\____ ______/ /___  __\n / /   / __ \\/ __ \\/ / / / /_/ / __ `/ ___/ __/ / / /\n/ /___/ /_/ / /_/ / /_/ / ____/ /_/ / /  / /_/ /_/ / \n\\____/\\____/ .___/\\__, /_/    \\__,_/_/   \\__/\\__, /  \n          /_/    /____/                     /____/   \n"
  },
  {
    "path": "tools/headers/core-restore-from-backup",
    "content": "    __  __                        ___              _      __              __     ______              \n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_   / ____/___  ________ \n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/  / /   / __ \\/ ___/ _ \\\n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_   / /___/ /_/ / /  /  __/\n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/   \\____/\\____/_/   \\___/ \n                                                                                                     \n"
  },
  {
    "path": "tools/headers/cronmaster",
    "content": "   ______                 __  ___           __           \n  / ____/________  ____  /  |/  /___ ______/ /____  _____\n / /   / ___/ __ \\/ __ \\/ /|_/ / __ `/ ___/ __/ _ \\/ ___/\n/ /___/ /  / /_/ / / / / /  / / /_/ (__  ) /_/  __/ /    \n\\____/_/   \\____/_/ /_/_/  /_/\\__,_/____/\\__/\\___/_/     \n                                                         \n"
  },
  {
    "path": "tools/headers/crowdsec",
    "content": "   ______                       _______          \n  / ____/________ _      ______/ / ___/___  _____\n / /   / ___/ __ \\ | /| / / __  /\\__ \\/ _ \\/ ___/\n/ /___/ /  / /_/ / |/ |/ / /_/ /___/ /  __/ /__  \n\\____/_/   \\____/|__/|__/\\__,_//____/\\___/\\___/  \n                                                 \n"
  },
  {
    "path": "tools/headers/dockge",
    "content": "    ____             __            \n   / __ \\____  _____/ /______ ____ \n  / / / / __ \\/ ___/ //_/ __ `/ _ \\\n / /_/ / /_/ / /__/ ,< / /_/ /  __/\n/_____/\\____/\\___/_/|_|\\__, /\\___/ \n                      /____/       \n"
  },
  {
    "path": "tools/headers/dokploy",
    "content": "    ____        __         __           \n   / __ \\____  / /______  / /___  __  __\n  / / / / __ \\/ //_/ __ \\/ / __ \\/ / / /\n / /_/ / /_/ / ,< / /_/ / / /_/ / /_/ / \n/_____/\\____/_/|_/ .___/_/\\____/\\__, /  \n                /_/            /____/   \n"
  },
  {
    "path": "tools/headers/filebrowser",
    "content": "    _______ __     ____                                   \n   / ____(_) /__  / __ )_________ _      __________  _____\n  / /_  / / / _ \\/ __  / ___/ __ \\ | /| / / ___/ _ \\/ ___/\n / __/ / / /  __/ /_/ / /  / /_/ / |/ |/ (__  )  __/ /    \n/_/   /_/_/\\___/_____/_/   \\____/|__/|__/____/\\___/_/     \n                                                          \n"
  },
  {
    "path": "tools/headers/filebrowser-quantum",
    "content": "    _______ __     ____                                       ____                    __                \n   / ____(_) /__  / __ )_________ _      __________  _____   / __ \\__  ______ _____  / /___  ______ ___ \n  / /_  / / / _ \\/ __  / ___/ __ \\ | /| / / ___/ _ \\/ ___/  / / / / / / / __ `/ __ \\/ __/ / / / __ `__ \\\n / __/ / / /  __/ /_/ / /  / /_/ / |/ |/ (__  )  __/ /     / /_/ / /_/ / /_/ / / / / /_/ /_/ / / / / / /\n/_/   /_/_/\\___/_____/_/   \\____/|__/|__/____/\\___/_/      \\___\\_\\__,_/\\__,_/_/ /_/\\__/\\__,_/_/ /_/ /_/ \n                                                                                                        \n"
  },
  {
    "path": "tools/headers/glances",
    "content": "   ________                          \n  / ____/ /___ _____  ________  _____\n / / __/ / __ `/ __ \\/ ___/ _ \\/ ___/\n/ /_/ / / /_/ / / / / /__/  __(__  ) \n\\____/_/\\__,_/_/ /_/\\___/\\___/____/  \n                                     \n"
  },
  {
    "path": "tools/headers/homebrew",
    "content": "    __                         __                     \n   / /_  ____  ____ ___  ___  / /_  ________ _      __\n  / __ \\/ __ \\/ __ `__ \\/ _ \\/ __ \\/ ___/ _ \\ | /| / /\n / / / / /_/ / / / / / /  __/ /_/ / /  /  __/ |/ |/ / \n/_/ /_/\\____/_/ /_/ /_/\\___/_.___/_/   \\___/|__/|__/  \n                                                      \n"
  },
  {
    "path": "tools/headers/immich-public-proxy",
    "content": "    ____                    _      __       ____        __    ___         ____                       \n   /  _/___ ___  ____ ___  (_)____/ /_     / __ \\__  __/ /_  / (_)____   / __ \\_________  _  ____  __\n   / // __ `__ \\/ __ `__ \\/ / ___/ __ \\   / /_/ / / / / __ \\/ / / ___/  / /_/ / ___/ __ \\| |/_/ / / /\n _/ // / / / / / / / / / / / /__/ / / /  / ____/ /_/ / /_/ / / / /__   / ____/ /  / /_/ />  </ /_/ / \n/___/_/ /_/ /_/_/ /_/ /_/_/\\___/_/ /_/  /_/    \\__,_/_.___/_/_/\\___/  /_/   /_/   \\____/_/|_|\\__, /  \n                                                                                            /____/   \n"
  },
  {
    "path": "tools/headers/jellystat",
    "content": "       __     ____           __        __ \n      / /__  / / /_  _______/ /_____ _/ /_\n __  / / _ \\/ / / / / / ___/ __/ __ `/ __/\n/ /_/ /  __/ / / /_/ (__  ) /_/ /_/ / /_  \n\\____/\\___/_/_/\\__, /____/\\__/\\__,_/\\__/  \n              /____/                      \n"
  },
  {
    "path": "tools/headers/komodo",
    "content": "    __ __                          __    \n   / //_/___  ____ ___  ____  ____/ /___ \n  / ,< / __ \\/ __ `__ \\/ __ \\/ __  / __ \\\n / /| / /_/ / / / / / / /_/ / /_/ / /_/ /\n/_/ |_\\____/_/ /_/ /_/\\____/\\__,_/\\____/ \n                                         \n"
  },
  {
    "path": "tools/headers/nextcloud-exporter",
    "content": "                    __       __                __                                 __           \n   ____  ___  _  __/ /______/ /___  __  ______/ /     ___  _  ______  ____  _____/ /____  _____\n  / __ \\/ _ \\| |/_/ __/ ___/ / __ \\/ / / / __  /_____/ _ \\| |/_/ __ \\/ __ \\/ ___/ __/ _ \\/ ___/\n / / / /  __/>  </ /_/ /__/ / /_/ / /_/ / /_/ /_____/  __/>  </ /_/ / /_/ / /  / /_/  __/ /    \n/_/ /_/\\___/_/|_|\\__/\\___/_/\\____/\\__,_/\\__,_/      \\___/_/|_/ .___/\\____/_/   \\__/\\___/_/     \n                                                            /_/                                \n"
  },
  {
    "path": "tools/headers/olivetin",
    "content": "   ____  ___          _______     \n  / __ \\/ (_)   _____/_  __(_)___ \n / / / / / / | / / _ \\/ / / / __ \\\n/ /_/ / / /| |/ /  __/ / / / / / /\n\\____/_/_/ |___/\\___/_/ /_/_/ /_/ \n                                  \n"
  },
  {
    "path": "tools/headers/phpmyadmin",
    "content": "           __          __  ___      ___       __          _     \n    ____  / /_  ____  /  |/  /_  __/   | ____/ /___ ___  (_)___ \n   / __ \\/ __ \\/ __ \\/ /|_/ / / / / /| |/ __  / __ `__ \\/ / __ \\\n  / /_/ / / / / /_/ / /  / / /_/ / ___ / /_/ / / / / / / / / / /\n / .___/_/ /_/ .___/_/  /_/\\__, /_/  |_\\__,_/_/ /_/ /_/_/_/ /_/ \n/_/         /_/           /____/                                \n"
  },
  {
    "path": "tools/headers/pihole-exporter",
    "content": "           _ __          __                                      __           \n    ____  (_) /_  ____  / /__        ___  _  ______  ____  _____/ /____  _____\n   / __ \\/ / __ \\/ __ \\/ / _ \\______/ _ \\| |/_/ __ \\/ __ \\/ ___/ __/ _ \\/ ___/\n  / /_/ / / / / / /_/ / /  __/_____/  __/>  </ /_/ / /_/ / /  / /_/  __/ /    \n / .___/_/_/ /_/\\____/_/\\___/      \\___/_/|_/ .___/\\____/_/   \\__/\\___/_/     \n/_/                                        /_/                                \n"
  },
  {
    "path": "tools/headers/prometheus-paperless-ngx-exporter",
    "content": "                                     __  __                                                      __                                                                       __           \n    ____  _________  ____ ___  ___  / /_/ /_  ___  __  _______      ____  ____ _____  ___  _____/ /__  __________      ____  ____ __  __      ___  _  ______  ____  _____/ /____  _____\n   / __ \\/ ___/ __ \\/ __ `__ \\/ _ \\/ __/ __ \\/ _ \\/ / / / ___/_____/ __ \\/ __ `/ __ \\/ _ \\/ ___/ / _ \\/ ___/ ___/_____/ __ \\/ __ `/ |/_/_____/ _ \\| |/_/ __ \\/ __ \\/ ___/ __/ _ \\/ ___/\n  / /_/ / /  / /_/ / / / / / /  __/ /_/ / / /  __/ /_/ (__  )_____/ /_/ / /_/ / /_/ /  __/ /  / /  __(__  |__  )_____/ / / / /_/ />  </_____/  __/>  </ /_/ / /_/ / /  / /_/  __/ /    \n / .___/_/   \\____/_/ /_/ /_/\\___/\\__/_/ /_/\\___/\\__,_/____/     / .___/\\__,_/ .___/\\___/_/  /_/\\___/____/____/     /_/ /_/\\__, /_/|_|      \\___/_/|_/ .___/\\____/_/   \\__/\\___/_/     \n/_/                                                             /_/         /_/                                           /____/                    /_/                                \n"
  },
  {
    "path": "tools/headers/pve-privilege-converter",
    "content": "    ____ _    ________     ____       _       _ __                      ______                           __           \n   / __ \\ |  / / ____/    / __ \\_____(_)   __(_) /__  ____ ____        / ____/___  ____ _   _____  _____/ /____  _____\n  / /_/ / | / / __/______/ /_/ / ___/ / | / / / / _ \\/ __ `/ _ \\______/ /   / __ \\/ __ \\ | / / _ \\/ ___/ __/ _ \\/ ___/\n / ____/| |/ / /__/_____/ ____/ /  / /| |/ / / /  __/ /_/ /  __/_____/ /___/ /_/ / / / / |/ /  __/ /  / /_/  __/ /    \n/_/     |___/_____/    /_/   /_/  /_/ |___/_/_/\\___/\\__, /\\___/      \\____/\\____/_/ /_/|___/\\___/_/   \\__/\\___/_/     \n                                                   /____/                                                             \n"
  },
  {
    "path": "tools/headers/qbittorrent-exporter",
    "content": "         __    _ __  __                             __                                   __           \n  ____ _/ /_  (_) /_/ /_____  _____________  ____  / /_      ___  _  ______  ____  _____/ /____  _____\n / __ `/ __ \\/ / __/ __/ __ \\/ ___/ ___/ _ \\/ __ \\/ __/_____/ _ \\| |/_/ __ \\/ __ \\/ ___/ __/ _ \\/ ___/\n/ /_/ / /_/ / / /_/ /_/ /_/ / /  / /  /  __/ / / / /_/_____/  __/>  </ /_/ / /_/ / /  / /_/  __/ /    \n\\__, /_.___/_/\\__/\\__/\\____/_/  /_/   \\___/_/ /_/\\__/      \\___/_/|_/ .___/\\____/_/   \\__/\\___/_/     \n  /_/                                                              /_/                                \n"
  },
  {
    "path": "tools/headers/runtipi",
    "content": "    ____              __  _       _ \n   / __ \\__  ______  / /_(_)___  (_)\n  / /_/ / / / / __ \\/ __/ / __ \\/ / \n / _, _/ /_/ / / / / /_/ / /_/ / /  \n/_/ |_|\\__,_/_/ /_/\\__/_/ .___/_/   \n                       /_/          \n"
  },
  {
    "path": "tools/headers/sparkyfitness-garmin",
    "content": "   _____                  __         _______ __                             ______                     _     \n  / ___/____  ____ ______/ /____  __/ ____(_) /_____  ___  __________      / ____/___ __________ ___  (_)___ \n  \\__ \\/ __ \\/ __ `/ ___/ //_/ / / / /_  / / __/ __ \\/ _ \\/ ___/ ___/_____/ / __/ __ `/ ___/ __ `__ \\/ / __ \\\n ___/ / /_/ / /_/ / /  / ,< / /_/ / __/ / / /_/ / / /  __(__  |__  )_____/ /_/ / /_/ / /  / / / / / / / / / /\n/____/ .___/\\__,_/_/  /_/|_|\\__, /_/   /_/\\__/_/ /_/\\___/____/____/      \\____/\\__,_/_/  /_/ /_/ /_/_/_/ /_/ \n    /_/                    /____/                                                                            \n"
  },
  {
    "path": "tools/maintenance.py",
    "content": "#!/usr/bin/env python3\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport re\nfrom pathlib import Path\n\n# Repo-root relative paths\nREPO_ROOT = Path(__file__).resolve().parents[1]\nJSON_DIR = REPO_ROOT / \"frontend\" / \"public\" / \"json\"\nSTATUSES_FILE = JSON_DIR / \"statuses.json\"\n\nEXCLUDE_FILES = {\"versions.json\", \"metadata.json\", \"statuses.json\"}\n\n\ndef load_statuses() -> dict:\n    if not STATUSES_FILE.exists():\n        return {}\n    return json.loads(STATUSES_FILE.read_text(encoding=\"utf-8\"))\n\n\ndef save_statuses(statuses: dict) -> None:\n    sorted_statuses = dict(sorted(statuses.items(), key=lambda kv: kv[0]))\n    STATUSES_FILE.parent.mkdir(parents=True, exist_ok=True)\n    STATUSES_FILE.write_text(\n        json.dumps(sorted_statuses, indent=4, ensure_ascii=False) + \"\\n\",\n        encoding=\"utf-8\",\n    )\n    print(f\"Statuses updated: {STATUSES_FILE}\")\n\n\ndef iter_json_script_files() -> list[str]:\n    if not JSON_DIR.exists():\n        return []\n    files = []\n    for p in sorted(JSON_DIR.iterdir()):\n        if p.is_file() and p.suffix == \".json\" and p.name not in EXCLUDE_FILES:\n            files.append(p.name)\n    return files\n\n\ndef add_pending_status() -> None:\n    \"\"\"Set status to 🚧 for any JSON script missing a status, then sort and save.\"\"\"\n    statuses = load_statuses()\n    changed = False\n\n    for fname in iter_json_script_files():\n        if fname not in statuses or statuses.get(fname) is None:\n            statuses[fname] = \"🚧\"\n            print(f\"Pending status added for {fname}\")\n            changed = True\n\n    if changed:\n        save_statuses(statuses)\n    else:\n        print(\"No missing statuses found.\")\n\n\ndef remove_missing_statuses() -> None:\n    \"\"\"Remove entries from statuses.json where the corresponding file no longer exists.\"\"\"\n    statuses = load_statuses()\n    existing = set(iter_json_script_files())\n\n    removed_any = False\n    for fname in list(statuses.keys()):\n        if fname in EXCLUDE_FILES:\n            continue\n        if fname not in existing:\n            statuses.pop(fname, None)\n            print(f\"Removed status for missing script: {fname}\")\n            removed_any = True\n\n    if removed_any:\n        save_statuses(statuses)\n    else:\n        print(\"No missing scripts found in statuses.json.\")\n\n\ndef replace_build_func_url() -> None:\n    \"\"\"Replace build.func URL in ./ct files.\"\"\"\n    old_url = \"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func\"\n    new_url = \"https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func\"\n    ct_dir = REPO_ROOT / \"ct\"\n\n    print(\"Checking and replacing build.func URLs...\")\n    updated = 0\n\n    for path in ct_dir.rglob(\"*\"):\n        if not path.is_file():\n            continue\n        # most of these are .sh, but avoid missing any with no suffix\n        if path.name.startswith(\".\"):\n            continue\n        try:\n            content = path.read_text(encoding=\"utf-8\")\n        except UnicodeDecodeError:\n            continue\n\n        if old_url in content:\n            path.write_text(content.replace(old_url, new_url), encoding=\"utf-8\")\n            updated += 1\n            print(f\"Updated: {path.relative_to(REPO_ROOT)}\")\n\n    print(f\"build.func URL updates: {updated}\")\n\n\ndef update_license_url() -> None:\n    \"\"\"Replace old license header URL with this fork's license URL.\"\"\"\n    old = \"# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\"\n    new = \"# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\"\n\n    print(\"Checking and updating license headers...\")\n    updated = 0\n\n    for path in REPO_ROOT.rglob(\"*\"):\n        if not path.is_file():\n            continue\n        if path.suffix not in {\".sh\", \".func\"}:\n            continue\n        try:\n            content = path.read_text(encoding=\"utf-8\")\n        except UnicodeDecodeError:\n            continue\n\n        if old in content:\n            path.write_text(content.replace(old, new), encoding=\"utf-8\")\n            updated += 1\n            print(f\"Updated license in {path.relative_to(REPO_ROOT)}\")\n\n    print(f\"License header updates: {updated}\")\n\n\ndef search_externally_managed() -> None:\n    \"\"\"Search ./install for EXTERNALLY-MANAGED removal + setup_uv.\"\"\"\n    target_dir = REPO_ROOT / \"install\"\n    pattern = re.compile(r\"rm -rf /usr/lib/python3\\.\\*/EXTERNALLY-MANAGED\")\n\n    for path in target_dir.rglob(\"*\"):\n        if not path.is_file():\n            continue\n        try:\n            content = path.read_text(encoding=\"utf-8\")\n        except (UnicodeDecodeError, PermissionError):\n            continue\n        if pattern.search(content) and \"setup_uv\" in content:\n            print(path.relative_to(REPO_ROOT))\n\n\ndef post_merge_maintenance() -> None:\n    \"\"\"Run the standard maintenance pass after an upstream merge.\"\"\"\n    add_pending_status()\n    remove_missing_statuses()\n    update_license_url()\n    replace_build_func_url()\n\n\ndef build_parser() -> argparse.ArgumentParser:\n    p = argparse.ArgumentParser(description=\"Proxmox fork maintenance helpers\")\n\n    sub = p.add_subparsers(dest=\"cmd\", required=True)\n\n    sub.add_parser(\"post-merge\", help=\"Run the standard post-merge maintenance pass\")\n    sub.add_parser(\"add-pending-status\", help=\"Set 🚧 for scripts missing a status\")\n    sub.add_parser(\"remove-missing-statuses\", help=\"Remove statuses for scripts that no longer exist\")\n    sub.add_parser(\"update-license-url\", help=\"Update license header URL in scripts\")\n    sub.add_parser(\"replace-build-func-url\", help=\"Replace build.func URL in ct scripts\")\n    sub.add_parser(\"search-externally-managed\", help=\"Search install/ for EXTERNALLY-MANAGED with setup_uv\")\n\n    return p\n\n\ndef main() -> int:\n    args = build_parser().parse_args()\n\n    if args.cmd == \"post-merge\":\n        post_merge_maintenance()\n    elif args.cmd == \"add-pending-status\":\n        add_pending_status()\n    elif args.cmd == \"remove-missing-statuses\":\n        remove_missing_statuses()\n    elif args.cmd == \"update-license-url\":\n        update_license_url()\n    elif args.cmd == \"replace-build-func-url\":\n        replace_build_func_url()\n    elif args.cmd == \"search-externally-managed\":\n        search_externally_managed()\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n"
  },
  {
    "path": "tools/pve/add-iptag.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (Canbiz) && Desert_Gamer\n# License: MIT\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n ___ ____     _____\n|_ _|  _ \\ _ |_   _|_ _  __ _\n | || |_) (_)  | |/ _` |/ _` |\n | ||  __/ _   | | (_| | (_| |\n|___|_|   (_)  |_|\\__,_|\\__, |\n                        |___/\nEOF\n}\n\nclear\nheader_info\nAPP=\"IP-Tag\"\nhostname=$(hostname)\n\n# Color variables\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nRD=$(echo \"\\033[01;31m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nCM=\"${GN}✓${CL} \"\nCROSS=\"${RD}✗${CL} \"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"add-iptag\" \"pve\"\n\n# Stop any running spinner\nstop_spinner() {\n  if [ -n \"$SPINNER_PID\" ] && kill -0 \"$SPINNER_PID\" 2>/dev/null; then\n    kill -TERM \"$SPINNER_PID\" 2>/dev/null\n    wait \"$SPINNER_PID\" 2>/dev/null\n  fi\n  SPINNER_PID=\"\"\n  printf \"\\e[?25h\\r\"\n}\n\n# Error handler for displaying error messages\nerror_handler() {\n  stop_spinner\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  echo -e \"\\n$error_message\\n\"\n}\n\n# Spinner for progress indication\nspinner() {\n  local msg=\"$1\"\n  local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')\n  local spin_i=0\n  local interval=0.1\n\n  trap 'exit 0' TERM INT\n  printf \"\\e[?25l\" 2>/dev/null\n\n  while true; do\n    printf \"\\r%s ${YW}%s${CL}\" \"${frames[spin_i]}\" \"$msg\" 2>/dev/null || exit 0\n    spin_i=$(((spin_i + 1) % ${#frames[@]}))\n    sleep \"$interval\" || exit 0\n  done\n}\n\n# Info message\nmsg_info() {\n  local msg=\"$1\"\n  stop_spinner\n  spinner \"$msg\" &\n  SPINNER_PID=$!\n  disown $SPINNER_PID 2>/dev/null\n}\n\n# Success message\nmsg_ok() {\n  stop_spinner\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\n# Error message\nmsg_error() {\n  stop_spinner\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\n# Migrate configuration from old path to new\nmigrate_config() {\n  local old_config=\"/opt/lxc-iptag\"\n  local new_config=\"/opt/iptag/iptag.conf\"\n\n  if [[ -f \"$old_config\" ]]; then\n    msg_info \"Migrating configuration from old path\"\n    if cp \"$old_config\" \"$new_config\" &>/dev/null; then\n      rm -rf \"$old_config\" &>/dev/null\n      msg_ok \"Configuration migrated and old config removed\"\n    else\n      msg_error \"Failed to migrate configuration\"\n    fi\n  fi\n}\n\n# Update existing installation\nupdate_installation() {\n  msg_info \"Updating IP-Tag Scripts\"\n  systemctl stop iptag.service &>/dev/null\n  msg_ok \"Stopped IP-Tag service\"\n\n  # Create directory if it doesn't exist\n  if [[ ! -d \"/opt/iptag\" ]]; then\n    mkdir -p /opt/iptag\n  fi\n\n  # Create new config file (check if exists and ask user)\n  if [[ -f \"/opt/iptag/iptag.conf\" ]]; then\n    echo -e \"\\n${YW}Configuration file already exists.${CL}\"\n    echo -e \"${YW}Note: No critical changes were made in this version.${CL}\"\n    while true; do\n      read -p \"Do you want to replace it with defaults? (y/n): \" yn\n      case $yn in\n      [Yy]*)\n        interactive_config_setup\n        msg_info \"Replacing configuration file\"\n        generate_config >/opt/iptag/iptag.conf\n        msg_ok \"Configuration file replaced with defaults\"\n        break\n        ;;\n      [Nn]*)\n        echo -e \"${GN}✓ Keeping existing configuration file${CL}\"\n        break\n        ;;\n      *)\n        echo -e \"${RD}Please answer yes or no.${CL}\"\n        ;;\n      esac\n    done\n  else\n    interactive_config_setup\n    msg_info \"Creating new configuration file\"\n    generate_config >/opt/iptag/iptag.conf\n    msg_ok \"Created new configuration file at /opt/iptag/iptag.conf\"\n  fi\n\n  # Update main script\n  msg_info \"Updating main script\"\n  generate_main_script >/opt/iptag/iptag\n  chmod +x /opt/iptag/iptag\n  msg_ok \"Updated main script\"\n\n  # Update service file\n  msg_info \"Updating service file\"\n  generate_service >/lib/systemd/system/iptag.service\n  msg_ok \"Updated service file\"\n\n  msg_info \"Creating manual run command\"\n  cat <<'EOF' >/usr/local/bin/iptag-run\n#!/usr/bin/env bash\nCONFIG_FILE=\"/opt/iptag/iptag.conf\"\nSCRIPT_FILE=\"/opt/iptag/iptag\"\nif [[ ! -f \"$SCRIPT_FILE\" ]]; then\n  echo \"✗ Main script not found: $SCRIPT_FILE\"\n  exit 1\nfi\nexport FORCE_SINGLE_RUN=true\nexec \"$SCRIPT_FILE\"\nEOF\n  chmod +x /usr/local/bin/iptag-run\n  msg_ok \"Created iptag-run executable - You can execute this manually by entering “iptag-run” in the Proxmox host, so the script is executed by hand.\"\n\n  msg_info \"Restarting service\"\n  systemctl daemon-reload &>/dev/null\n  systemctl enable -q --now iptag.service &>/dev/null\n  msg_ok \"Updated IP-Tag Scripts\"\n\n  # Show configuration information after update\n  show_post_install_info\n}\n\n# Install only command without service\ninstall_command_only() {\n  msg_info \"Installing IP-Tag Command Only\"\n\n  # Create directory if it doesn't exist\n  if [[ ! -d \"/opt/iptag\" ]]; then\n    mkdir -p /opt/iptag\n  fi\n\n  # Migrate config if needed\n  migrate_config\n\n  # Interactive configuration setup\n  if [[ ! -f /opt/iptag/iptag.conf ]]; then\n    interactive_config_setup_command\n    msg_info \"Setup Configuration\"\n    generate_config >/opt/iptag/iptag.conf\n    msg_ok \"Created configuration file at /opt/iptag/iptag.conf\"\n  else\n    stop_spinner\n    echo -e \"\\n${YW}Configuration file already exists.${CL}\"\n    read -p \"Do you want to reconfigure tag format? (y/n): \" reconfigure\n    case $reconfigure in\n    [Yy]*)\n      interactive_config_setup_command\n      msg_info \"Updating Configuration\"\n      generate_config >/opt/iptag/iptag.conf\n      msg_ok \"Updated configuration file\"\n      ;;\n    *)\n      msg_ok \"Keeping existing configuration file\"\n      ;;\n    esac\n  fi\n\n  # Setup main script\n  msg_info \"Setup Main Script\"\n  generate_main_script >/opt/iptag/iptag\n  chmod +x /opt/iptag/iptag\n  msg_ok \"Created main script\"\n\n  # Create manual run command\n  msg_info \"Creating iptag-run command\"\n  cat <<'EOF' >/usr/local/bin/iptag-run\n#!/usr/bin/env bash\nCONFIG_FILE=\"/opt/iptag/iptag.conf\"\nSCRIPT_FILE=\"/opt/iptag/iptag\"\nif [[ ! -f \"$SCRIPT_FILE\" ]]; then\n  echo \"✗ Main script not found: $SCRIPT_FILE\"\n  exit 1\nfi\nexport FORCE_SINGLE_RUN=true\nexec \"$SCRIPT_FILE\"\nEOF\n  chmod +x /usr/local/bin/iptag-run\n  msg_ok \"Created iptag-run command\"\n\n  msg_ok \"IP-Tag Command installed successfully! Use 'iptag-run' to run manually.\"\n}\n\n# Show post-installation information\nshow_post_install_info() {\n  stop_spinner\n  echo -e \"\\n${YW}=== Next Steps ===${CL}\"\n\n  # Show usage information\n  if command -v iptag-run >/dev/null 2>&1; then\n    echo -e \"${YW}Run IP tagging manually: ${GN}iptag-run${CL}\"\n    echo -e \"${YW}Add to cron for scheduled execution if needed${CL}\"\n    echo -e \"\"\n  fi\n\n  echo -e \"${RD}IMPORTANT: Configure your network subnets!${CL}\"\n  echo -e \"\"\n  echo -e \"${YW}Configuration file: ${GN}/opt/iptag/iptag.conf${CL}\"\n  echo -e \"\"\n  echo -e \"${YW}Edit CIDR_LIST with your actual subnets:${CL}\"\n  echo -e \"${GN}nano /opt/iptag/iptag.conf${CL} ${YW}or${CL} ${GN}vim /opt/iptag/iptag.conf${CL}\"\n  echo -e \"\"\n  echo -e \"${YW}Example configuration:${CL}\"\n  echo -e \"${GN}CIDR_LIST=(${CL}\"\n  echo -e \"${GN}  192.168.1.0/24    # Your actual subnet${CL}\"\n  echo -e \"${GN}  10.10.0.0/16      # Another subnet${CL}\"\n  echo -e \"${GN})${CL}\"\n  echo -e \"\"\n}\n\n# Interactive configuration setup for command-only (TAG_FORMAT only)\ninteractive_config_setup_command() {\n  echo -e \"\\n${YW}=== Configuration Setup ===${CL}\"\n\n  # TAG_FORMAT configuration\n  echo -e \"\\n${YW}Select tag format:${CL}\"\n  echo -e \"${GN}1)${CL} last_two_octets - Show last two octets (e.g., 0.100) [Default]\"\n  echo -e \"${GN}2)${CL} last_octet - Show only last octet (e.g., 100)\"\n  echo -e \"${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)\"\n\n  while true; do\n    read -p \"Enter your choice (1-3) [1]: \" tag_choice\n    case ${tag_choice:-1} in\n    1)\n      TAG_FORMAT=\"last_two_octets\"\n      echo -e \"${GN}✓ Selected: last_two_octets${CL}\"\n      break\n      ;;\n    2)\n      TAG_FORMAT=\"last_octet\"\n      echo -e \"${GN}✓ Selected: last_octet${CL}\"\n      break\n      ;;\n    3)\n      TAG_FORMAT=\"full\"\n      echo -e \"${GN}✓ Selected: full${CL}\"\n      break\n      ;;\n    *)\n      echo -e \"${RD}Please enter 1, 2, or 3.${CL}\"\n      ;;\n    esac\n  done\n\n  # Set default LOOP_INTERVAL for command mode\n  LOOP_INTERVAL=300\n}\n\n# Interactive configuration setup for service (TAG_FORMAT + LOOP_INTERVAL)\ninteractive_config_setup() {\n  echo -e \"\\n${YW}=== Configuration Setup ===${CL}\"\n\n  # TAG_FORMAT configuration\n  echo -e \"\\n${YW}Select tag format:${CL}\"\n  echo -e \"${GN}1)${CL} last_two_octets - Show last two octets (e.g., 0.100) [Default]\"\n  echo -e \"${GN}2)${CL} last_octet - Show only last octet (e.g., 100)\"\n  echo -e \"${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)\"\n\n  while true; do\n    read -p \"Enter your choice (1-3) [1]: \" tag_choice\n    case ${tag_choice:-1} in\n    1)\n      TAG_FORMAT=\"last_two_octets\"\n      echo -e \"${GN}✓ Selected: last_two_octets${CL}\"\n      break\n      ;;\n    2)\n      TAG_FORMAT=\"last_octet\"\n      echo -e \"${GN}✓ Selected: last_octet${CL}\"\n      break\n      ;;\n    3)\n      TAG_FORMAT=\"full\"\n      echo -e \"${GN}✓ Selected: full${CL}\"\n      break\n      ;;\n    *)\n      echo -e \"${RD}Please enter 1, 2, or 3.${CL}\"\n      ;;\n    esac\n  done\n\n  # LOOP_INTERVAL configuration\n  echo -e \"\\n${YW}Set check interval (in seconds):${CL}\"\n  echo -e \"${YW}Default: 300 seconds (5 minutes)${CL}\"\n  echo -e \"${YW}Recommended range: 300-3600 seconds${CL}\"\n\n  while true; do\n    read -p \"Enter interval in seconds [300]: \" interval_input\n    interval_input=${interval_input:-300}\n\n    if [[ $interval_input =~ ^[0-9]+$ ]] && [ $interval_input -ge 300 ] && [ $interval_input -le 7200 ]; then\n      LOOP_INTERVAL=$interval_input\n      echo -e \"${GN}✓ Selected: ${LOOP_INTERVAL} seconds${CL}\"\n      break\n    else\n      echo -e \"${RD}Please enter a valid number between 300 and 7200 seconds.${CL}\"\n    fi\n  done\n}\n\n# Generate configuration file content\ngenerate_config() {\n  cat <<EOF\n# Configuration file for IP tagging\n\n# List of allowed CIDRs\nCIDR_LIST=(\n  192.168.0.0/16\n  10.0.0.0/8\n  100.64.0.0/10\n)\n\n# Tag format options:\n# - \"full\": full IP address (e.g., 192.168.0.100)\n# - \"last_octet\": only the last octet (e.g., 100)\n# - \"last_two_octets\": last two octets (e.g., 0.100)\nTAG_FORMAT=\"${TAG_FORMAT:-last_two_octets}\"\n\n\n# Check interval (in seconds)\nLOOP_INTERVAL=${LOOP_INTERVAL:-300}\n\n# Debug settings (set to true to enable debugging)\nDEBUG=false\nEOF\n}\n\n# Generate systemd service file content\ngenerate_service() {\n  cat <<EOF\n[Unit]\nDescription=IP-Tag service\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/opt/iptag/iptag\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n}\n\n# Generate main script content\ngenerate_main_script() {\n  cat <<'EOF'\n#!/bin/bash\n# =============== CONFIGURATION =============== #\nreadonly CONFIG_FILE=\"/opt/iptag/iptag.conf\"\nreadonly DEFAULT_TAG_FORMAT=\"full\"\nreadonly DEFAULT_CHECK_INTERVAL=60\n\n# Load the configuration file if it exists\nif [ -f \"$CONFIG_FILE\" ]; then\n    # shellcheck source=./iptag.conf\n    source \"$CONFIG_FILE\"\nfi\n\n# Set default DEBUG value if not defined\nDEBUG=${DEBUG:-false}\n\n# Debug logging function\ndebug_log() {\n    if [[ \"$DEBUG\" == \"true\" || \"$DEBUG\" == \"1\" ]]; then\n        echo \"[DEBUG] $*\" >&2\n    fi\n}\n\n# Color constants\nreadonly RED='\\033[0;31m'\nreadonly GREEN='\\033[0;32m'\nreadonly YELLOW='\\033[0;33m'\nreadonly BLUE='\\033[0;34m'\nreadonly PURPLE='\\033[0;35m'\nreadonly CYAN='\\033[0;36m'\nreadonly WHITE='\\033[1;37m'\nreadonly GRAY='\\033[0;37m'\nreadonly NC='\\033[0m' # No Color\n\n# Logging functions with colors\nlog_success() {\n    echo -e \"${GREEN}✓${NC} $*\"\n}\n\nlog_info() {\n    echo -e \"${BLUE}ℹ${NC} $*\"\n}\n\nlog_warning() {\n    echo -e \"${YELLOW}⚠${NC} $*\"\n}\n\nlog_error() {\n    echo -e \"${RED}✗${NC} $*\"\n}\n\nlog_change() {\n    echo -e \"${CYAN}~${NC} $*\"\n}\n\nlog_unchanged() {\n    echo -e \"${GRAY}=${NC} $*\"\n}\n\n# Check if IP is in CIDR\nip_in_cidr() {\n    local ip=\"$1\" cidr=\"$2\"\n    debug_log \"ip_in_cidr: checking '$ip' against '$cidr'\"\n    \n    # Manual CIDR check - более надёжный метод\n    debug_log \"ip_in_cidr: using manual check (bypassing ipcalc)\"\n        local network prefix\n        IFS='/' read -r network prefix <<< \"$cidr\"\n        \n        # Convert IP and network to integers for comparison\n        local ip_int net_int mask\n        IFS='.' read -r a b c d <<< \"$ip\"\n        ip_int=$(( (a << 24) + (b << 16) + (c << 8) + d ))\n        \n        IFS='.' read -r a b c d <<< \"$network\"\n        net_int=$(( (a << 24) + (b << 16) + (c << 8) + d ))\n        \n    # Create subnet mask\n        mask=$(( 0xFFFFFFFF << (32 - prefix) ))\n        \n    # Apply mask and compare\n    local ip_masked=$((ip_int & mask))\n    local net_masked=$((net_int & mask))\n    \n    debug_log \"ip_in_cidr: IP=$ip ($ip_int), Network=$network ($net_int), Prefix=$prefix\"\n    debug_log \"ip_in_cidr: Mask=$mask (hex: $(printf '0x%08x' $mask))\"\n    debug_log \"ip_in_cidr: IP&Mask=$ip_masked ($(printf '%d.%d.%d.%d' $((ip_masked>>24&255)) $((ip_masked>>16&255)) $((ip_masked>>8&255)) $((ip_masked&255))))\"\n    debug_log \"ip_in_cidr: Net&Mask=$net_masked ($(printf '%d.%d.%d.%d' $((net_masked>>24&255)) $((net_masked>>16&255)) $((net_masked>>8&255)) $((net_masked&255))))\"\n    \n    if (( ip_masked == net_masked )); then\n        debug_log \"ip_in_cidr: manual check PASSED - IP is in CIDR\"\n        return 0\n    else\n        debug_log \"ip_in_cidr: manual check FAILED - IP is NOT in CIDR\"\n        return 1\n    fi\n}\n\n# Format IP address according to the configuration\nformat_ip_tag() {\n    local ip=\"$1\"\n    [[ -z \"$ip\" ]] && return\n    local format=\"${TAG_FORMAT:-$DEFAULT_TAG_FORMAT}\"\n    case \"$format\" in\n        \"last_octet\")     echo \"${ip##*.}\" ;;\n        \"last_two_octets\") echo \"${ip#*.*.}\" ;;\n        *)               echo \"$ip\" ;;\n    esac\n}\n\n\n# Check if IP is in any CIDRs\nip_in_cidrs() {\n    local ip=\"$1\" cidrs=\"$2\"\n    [[ -z \"$cidrs\" ]] && return 1\n    local IFS=' '\n    debug_log \"Checking IP '$ip' against CIDRs: '$cidrs'\"\n    for cidr in $cidrs; do \n        debug_log \"Testing IP '$ip' against CIDR '$cidr'\"\n        if ip_in_cidr \"$ip\" \"$cidr\"; then\n            debug_log \"IP '$ip' matches CIDR '$cidr' - PASSED\"\n            return 0\n        else\n            debug_log \"IP '$ip' does not match CIDR '$cidr'\"\n        fi\n    done\n    debug_log \"IP '$ip' failed all CIDR checks\"\n    return 1\n}\n\n# Check if IP is valid\nis_valid_ipv4() {\n    local ip=\"$1\"\n    [[ \"$ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]] || return 1\n    \n    local IFS='.' parts\n    read -ra parts <<< \"$ip\"\n    for part in \"${parts[@]}\"; do\n        (( part >= 0 && part <= 255 )) || return 1\n    done\n    return 0\n}\n\n# Get VM IPs using improved methods\nget_vm_ips() {\n    local vmid=$1 ips=\"\"\n    local vm_config=\"/etc/pve/qemu-server/${vmid}.conf\"\n    [[ ! -f \"$vm_config\" ]] && return\n    \n    debug_log \"vm $vmid: starting IP detection\"\n    \n    # Check if VM is running first\n    local vm_status=\"\"\n    if command -v qm >/dev/null 2>&1; then\n        vm_status=$(qm status \"$vmid\" 2>/dev/null | awk '{print $2}')\n    fi\n    \n    if [[ \"$vm_status\" != \"running\" ]]; then\n        debug_log \"vm $vmid: not running (status: $vm_status)\"\n        return\n    fi\n    \n    # Get MAC addresses from config\n    local mac_addresses=$(grep -E \"^net[0-9]+:\" \"$vm_config\" | grep -oE \"([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\" | head -3)\n    debug_log \"vm $vmid: found MACs: $mac_addresses\"\n    \n    # Method 1: QM guest agent (most reliable for current IP)\n    if command -v qm >/dev/null 2>&1; then\n        debug_log \"vm $vmid: trying qm guest agent first\"\n        local qm_ips=$(timeout 8 qm guest cmd \"$vmid\" network-get-interfaces 2>/dev/null | grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}' | grep -v \"127.0.0.1\" | head -3)\n        for qm_ip in $qm_ips; do\n            if [[ \"$qm_ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n                debug_log \"vm $vmid: found IP $qm_ip via qm guest cmd\"\n                ips+=\"$qm_ip \"\n            fi\n        done\n    fi\n    \n    # Method 2: Fresh ARP table lookup (force refresh)\n    if [[ -n \"$mac_addresses\" ]]; then\n        debug_log \"vm $vmid: refreshing ARP table and checking\"\n        # Try to refresh ARP table by pinging network ranges\n        for mac in $mac_addresses; do\n            local mac_lower=$(echo \"$mac\" | tr '[:upper:]' '[:lower:]')\n            \n            # First check current ARP table\n            local current_ip=$(ip neighbor show | grep \"$mac_lower\" | grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}' | head -1)\n            \n            # If found in ARP, verify it's still valid by trying to ping\n            if [[ -n \"$current_ip\" && \"$current_ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n                debug_log \"vm $vmid: found IP $current_ip in ARP table for MAC $mac_lower, verifying...\"\n                # Quick ping test to verify IP is still active\n                if timeout 2 ping -c 1 \"$current_ip\" >/dev/null 2>&1; then\n                    debug_log \"vm $vmid: verified IP $current_ip is active via ping\"\n                    ips+=\"$current_ip \"\n                else\n                    debug_log \"vm $vmid: IP $current_ip failed ping verification, removing from ARP\"\n                    # Remove stale ARP entry\n                    ip neighbor del \"$current_ip\" dev $(ip route get \"$current_ip\" 2>/dev/null | grep -oE 'dev [^ ]+' | cut -d' ' -f2) 2>/dev/null || true\n                fi\n            fi\n        done\n    fi\n    \n    # Method 3: DHCP leases (backup method)\n    if [[ -z \"$ips\" ]]; then\n        debug_log \"vm $vmid: checking DHCP leases as fallback\"\n        for mac in $mac_addresses; do\n            local mac_lower=$(echo \"$mac\" | tr '[:upper:]' '[:lower:]')\n            for dhcp_file in \"/var/lib/dhcp/dhcpd.leases\" \"/var/lib/dhcpcd5/dhcpcd.leases\"; do\n                if [[ -f \"$dhcp_file\" ]]; then\n                    # Look for most recent lease for this MAC\n                    local dhcp_ip=$(tac \"$dhcp_file\" 2>/dev/null | grep -A 10 \"ethernet $mac_lower\" | grep \"binding state active\" -A 5 | grep -oE \"([0-9]{1,3}\\.){3}[0-9]{1,3}\" | head -1)\n                    if [[ -n \"$dhcp_ip\" && \"$dhcp_ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n                        debug_log \"vm $vmid: found IP $dhcp_ip via DHCP leases\"\n                        # Verify this IP responds\n                        if timeout 2 ping -c 1 \"$dhcp_ip\" >/dev/null 2>&1; then\n                            debug_log \"vm $vmid: verified DHCP IP $dhcp_ip is active\"\n                            ips+=\"$dhcp_ip \"\n                            break 2\n                        else\n                            debug_log \"vm $vmid: DHCP IP $dhcp_ip failed ping test\"\n                        fi\n                    fi\n                fi\n            done\n        done\n    fi\n    \n    # Remove duplicates and clean up\n    local unique_ips=$(echo \"$ips\" | tr ' ' '\\n' | sort -u | tr '\\n' ' ')\n    unique_ips=\"${unique_ips% }\"\n    \n    debug_log \"vm $vmid: final IPs: '$unique_ips'\"\n    echo \"$unique_ips\"\n}\n\n# Cache for configs to avoid repeated reads\ndeclare -A CONFIG_CACHE\ndeclare -A IP_CACHE\n\n# Update tags for container or VM\nupdate_tags() {\n    local type=\"$1\" vmid=\"$2\"\n    local current_ips_full\n    local current_tags_raw=\"\"\n\n    # Get IPs with caching\n    local cache_key=\"${type}_${vmid}\"\n    if [[ -n \"${IP_CACHE[$cache_key]:-}\" ]]; then\n        current_ips_full=\"${IP_CACHE[$cache_key]}\"\n        debug_log \"$type $vmid: using cached IPs\"\n    else\n        if [[ \"$type\" == \"lxc\" ]]; then\n            current_ips_full=$(get_lxc_ips \"${vmid}\")\n        else\n            current_ips_full=$(get_vm_ips \"${vmid}\")\n        fi\n        IP_CACHE[$cache_key]=\"$current_ips_full\"\n    fi\n\n    # Get current tags (optimized file reading)\n    if [[ \"$type\" == \"lxc\" ]]; then\n        local config_file=\"/etc/pve/lxc/${vmid}.conf\"\n        if [[ -f \"$config_file\" ]]; then\n            current_tags_raw=$(grep \"^tags:\" \"$config_file\" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//')\n        fi\n    else\n        local vm_config=\"/etc/pve/qemu-server/${vmid}.conf\"\n        if [[ -f \"$vm_config\" ]]; then\n            current_tags_raw=$(grep \"^tags:\" \"$vm_config\" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//')\n        fi\n    fi\n\n    local current_tags=() next_tags=() current_ip_tags=()\n    if [[ -n \"$current_tags_raw\" ]]; then\n        mapfile -t current_tags < <(echo \"$current_tags_raw\" | sed 's/;/\\n/g')\n    fi\n\n    # Separate IP/numeric and user tags\n    for tag in \"${current_tags[@]}\"; do\n        if is_valid_ipv4 \"${tag}\" || [[ \"$tag\" =~ ^[0-9]+(\\.[0-9]+)*$ ]]; then\n            current_ip_tags+=(\"${tag}\")\n        else\n            next_tags+=(\"${tag}\")\n        fi\n    done\n\n    # Generate new IP tags from current IPs\n    local formatted_ips=()\n    debug_log \"$type $vmid current_ips_full: '$current_ips_full'\"\n    debug_log \"$type $vmid CIDR_LIST: ${CIDR_LIST[*]}\"\n    for ip in $current_ips_full; do\n        [[ -z \"$ip\" ]] && continue\n        debug_log \"$type $vmid processing IP: '$ip'\"\n        if is_valid_ipv4 \"$ip\"; then\n            debug_log \"$type $vmid IP '$ip' is valid\"\n            if ip_in_cidrs \"$ip\" \"${CIDR_LIST[*]}\"; then\n                debug_log \"$type $vmid IP '$ip' passed CIDR check\"\n                local formatted_ip=$(format_ip_tag \"$ip\")\n                debug_log \"$type $vmid formatted '$ip' -> '$formatted_ip'\"\n                [[ -n \"$formatted_ip\" ]] && formatted_ips+=(\"$formatted_ip\")\n            else\n                debug_log \"$type $vmid IP '$ip' failed CIDR check\"\n            fi\n        else\n            debug_log \"$type $vmid IP '$ip' is invalid\"\n        fi\n    done\n    debug_log \"$type $vmid final formatted_ips: ${formatted_ips[*]}\"\n\n    # If LXC and no IPs detected, do not touch tags at all\n    if [[ \"$type\" == \"lxc\" && ${#formatted_ips[@]} -eq 0 ]]; then\n        log_unchanged \"LXC ${GRAY}${vmid}${NC}: No IP detected, tags unchanged\"\n        return\n    fi\n\n    # Prepend new IP tags to the beginning of the tag list\n    local final_tags=()\n    for new_ip in \"${formatted_ips[@]}\"; do\n        final_tags+=(\"$new_ip\")\n    done\n    for tag in \"${next_tags[@]}\"; do\n        final_tags+=(\"$tag\")\n    done\n    next_tags=(\"${final_tags[@]}\")\n\n    # Update tags if there are changes\n    local old_tags_str=$(IFS=';'; echo \"${current_tags[*]}\")\n    local new_tags_str=$(IFS=';'; echo \"${next_tags[*]}\")\n    \n    debug_log \"$type $vmid old_tags: '$old_tags_str'\"\n    debug_log \"$type $vmid new_tags: '$new_tags_str'\"\n    debug_log \"$type $vmid tags_equal: $([[ \"$old_tags_str\" == \"$new_tags_str\" ]] && echo true || echo false)\"\n    \n    if [[ \"$old_tags_str\" != \"$new_tags_str\" ]]; then\n        # Determine what changed\n        local old_ip_tags_count=${#current_ip_tags[@]}\n        local new_ip_tags_count=${#formatted_ips[@]}\n        \n        # Build detailed change message\n        local change_details=\"\"\n        \n        if [[ $old_ip_tags_count -eq 0 ]]; then\n            change_details=\"added ${new_ip_tags_count} IP tag(s): [${GREEN}${formatted_ips[*]}${NC}]\"\n        else\n            # Compare old and new IP tags\n            local added_tags=() removed_tags=() common_tags=()\n            \n            # Find removed tags\n            for old_tag in \"${current_ip_tags[@]}\"; do\n                local found=false\n                for new_tag in \"${formatted_ips[@]}\"; do\n                    if [[ \"$old_tag\" == \"$new_tag\" ]]; then\n                        found=true\n                        break\n                    fi\n                done\n                if [[ \"$found\" == false ]]; then\n                    removed_tags+=(\"$old_tag\")\n                else\n                    common_tags+=(\"$old_tag\")\n                fi\n            done\n            \n            # Find added tags\n            for new_tag in \"${formatted_ips[@]}\"; do\n                local found=false\n                for old_tag in \"${current_ip_tags[@]}\"; do\n                    if [[ \"$new_tag\" == \"$old_tag\" ]]; then\n                        found=true\n                        break\n                    fi\n                done\n                if [[ \"$found\" == false ]]; then\n                    added_tags+=(\"$new_tag\")\n                fi\n            done\n            \n            # Build change message\n            local change_parts=()\n            if [[ ${#added_tags[@]} -gt 0 ]]; then\n                change_parts+=(\"added [${GREEN}${added_tags[*]}${NC}]\")\n            fi\n            if [[ ${#removed_tags[@]} -gt 0 ]]; then\n                change_parts+=(\"removed [${YELLOW}${removed_tags[*]}${NC}]\")\n            fi\n            if [[ ${#common_tags[@]} -gt 0 ]]; then\n                change_parts+=(\"kept [${GRAY}${common_tags[*]}${NC}]\")\n            fi\n            \n            change_details=$(IFS=', '; echo \"${change_parts[*]}\")\n        fi\n        \n        log_change \"${type^^} ${CYAN}${vmid}${NC}: ${change_details}\"\n        \n        if [[ \"$type\" == \"lxc\" ]]; then\n            pct set \"${vmid}\" -tags \"$(IFS=';'; echo \"${next_tags[*]}\")\" &>/dev/null\n        else\n            local vm_config=\"/etc/pve/qemu-server/${vmid}.conf\"\n            if [[ -f \"$vm_config\" ]]; then\n                sed -i '/^tags:/d' \"$vm_config\"\n                if [[ ${#next_tags[@]} -gt 0 ]]; then\n                    echo \"tags: $(IFS=';'; echo \"${next_tags[*]}\")\" >> \"$vm_config\"\n                fi\n            fi\n        fi\n    else\n        # Tags unchanged\n        local ip_count=${#formatted_ips[@]}\n        local status_msg=\"\"\n        \n        if [[ $ip_count -eq 0 ]]; then\n            status_msg=\"No IPs detected\"\n        elif [[ $ip_count -eq 1 ]]; then\n            status_msg=\"IP tag [${GRAY}${formatted_ips[0]}${NC}] unchanged\"\n        else\n            status_msg=\"${ip_count} IP tags [${GRAY}${formatted_ips[*]}${NC}] unchanged\"\n        fi\n        \n        log_unchanged \"${type^^} ${GRAY}${vmid}${NC}: ${status_msg}\"\n    fi\n}\n\n# Update all instances of specified type\nupdate_all_tags() {\n    local type=\"$1\" vmids count=0\n    \n    # Get list of all containers/VMs\n    if [[ \"$type\" == \"lxc\" ]]; then\n        vmids=($(pct list 2>/dev/null | grep -v VMID | awk '{print $1}'))\n    else\n        # More efficient: direct file listing instead of ls+sed\n        vmids=()\n        for conf in /etc/pve/qemu-server/*.conf; do\n            [[ -f \"$conf\" ]] || continue\n            local basename=\"${conf##*/}\"\n            vmids+=(\"${basename%.conf}\")\n        done\n    fi\n    \n    count=${#vmids[@]}\n    [[ $count -eq 0 ]] && return\n    \n    # Display processing header with color\n    if [[ \"$type\" == \"lxc\" ]]; then\n        log_info \"Processing ${WHITE}${count}${NC} LXC container(s)\"\n    else\n        log_info \"Processing ${WHITE}${count}${NC} virtual machine(s)\"\n    fi\n    \n    # Process each VM/LXC container\n    local processed=0\n    for vmid in \"${vmids[@]}\"; do\n        update_tags \"$type\" \"$vmid\"\n        ((processed++))\n    done\n    \n    # Add completion message\n    if [[ \"$type\" == \"lxc\" ]]; then\n        log_success \"Completed processing LXC containers\"\n    else\n        log_success \"Completed processing virtual machines\"\n    fi\n}\n\n# Main check function\ncheck() {\n    local start_time=$(date +%s)\n    \n    log_info \"Starting periodic check\"\n    \n    # Clear caches before each run\n    CONFIG_CACHE=()\n    IP_CACHE=()\n    \n    # Update LXC containers\n    update_all_tags \"lxc\"\n    \n    # Update VMs  \n    update_all_tags \"vm\"\n    \n    local end_time=$(date +%s)\n    local duration=$((end_time - start_time))\n    log_success \"Check completed in ${WHITE}${duration}${NC} seconds\"\n}\n\n# Main loop\nmain() {\n    # Display startup message\n    echo -e \"\\n${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n    log_success \"IP-Tag service started successfully\"\n    echo -e \"${BLUE}ℹ${NC} Loop interval: ${WHITE}${LOOP_INTERVAL:-300}${NC} seconds\"\n    echo -e \"${BLUE}ℹ${NC} Debug mode: ${WHITE}${DEBUG:-false}${NC}\"\n    echo -e \"${BLUE}ℹ${NC} Tag format: ${WHITE}${TAG_FORMAT:-full}${NC}\"\n    echo -e \"${BLUE}ℹ${NC} Allowed CIDRs: ${WHITE}${CIDR_LIST[*]}${NC}\"\n    echo -e \"${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\\n\"\n\n    if [[ \"$FORCE_SINGLE_RUN\" == \"true\" ]]; then\n        check\n        exit 0\n    fi\n\n    while true; do\n        check\n        sleep \"${LOOP_INTERVAL:-300}\"\n    done\n}\n\n\n\n\n\n# Simple LXC IP detection\nget_lxc_ips() {\n    local vmid=$1\n    \n    debug_log \"lxc $vmid: starting IP detection\"\n    \n    # Check if LXC is running\n    local lxc_status=$(pct status \"${vmid}\" 2>/dev/null | awk '{print $2}')\n    if [[ \"$lxc_status\" != \"running\" ]]; then\n        debug_log \"lxc $vmid: not running (status: $lxc_status)\"\n        return\n    fi\n    \n    local ips=\"\"\n    \n    # Method 1: Check Proxmox config for ALL static IPs (multiple interfaces)\n    local pve_lxc_config=\"/etc/pve/lxc/${vmid}.conf\"\n    if [[ -f \"$pve_lxc_config\" ]]; then\n        local static_ips=$(grep -E \"^net[0-9]+:\" \"$pve_lxc_config\" 2>/dev/null | grep -oE 'ip=([0-9]{1,3}\\.){3}[0-9]{1,3}' | cut -d'=' -f2)\n        if [[ -n \"$static_ips\" ]]; then\n            while IFS= read -r ip; do\n                if [[ \"$ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n                    debug_log \"lxc $vmid: found static IP $ip in config\"\n                    ips=\"${ips}${ips:+ }${ip}\"\n                fi\n            done <<< \"$static_ips\"\n        fi\n    fi\n    \n    # Method 2: ARP table lookup for ALL MAC addresses if no static IPs found\n    if [[ -z \"$ips\" && -f \"$pve_lxc_config\" ]]; then\n        local mac_addrs=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' \"$pve_lxc_config\" | cut -d'=' -f2)\n        if [[ -n \"$mac_addrs\" ]]; then\n            while IFS= read -r mac_addr; do\n                [[ -z \"$mac_addr\" ]] && continue\n                local arp_ip=$(ip neighbor show | grep -i \"$mac_addr\" | grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}' | head -1)\n                if [[ -n \"$arp_ip\" && \"$arp_ip\" =~ ^([0-9]{1,3}\\.){3}[0-9]{1,3}$ ]]; then\n                    debug_log \"lxc $vmid: found IP $arp_ip via ARP table for MAC $mac_addr\"\n                    ips=\"${ips}${ips:+ }${arp_ip}\"\n                fi\n            done <<< \"$mac_addrs\"\n        fi\n    fi\n    \n    # Method 3: Direct container command to get ALL IPs if previous methods failed\n    if [[ -z \"$ips\" ]]; then\n        local container_ips=$(timeout 5s pct exec \"$vmid\" -- ip -4 addr show 2>/dev/null | grep -oE 'inet ([0-9]{1,3}\\.){3}[0-9]{1,3}' | awk '{print $2}' | grep -v '127.0.0.1')\n        if [[ -n \"$container_ips\" ]]; then\n            while IFS= read -r ip; do\n                if is_valid_ipv4 \"$ip\"; then\n                    debug_log \"lxc $vmid: found IP $ip via pct exec\"\n                    ips=\"${ips}${ips:+ }${ip}\"\n                fi\n            done <<< \"$container_ips\"\n        fi\n    fi\n    \n    # Remove duplicates and clean up\n    local unique_ips=$(echo \"$ips\" | tr ' ' '\\n' | sort -u | tr '\\n' ' ')\n    unique_ips=\"${unique_ips% }\"\n    \n    debug_log \"lxc $vmid: final IPs: '$unique_ips'\"\n    echo \"$unique_ips\"\n}\n\nmain\nEOF\n}\n\n# Choose installation/update mode\necho -e \"\\n${YW}Choose action:${CL}\"\necho -e \"${GN}1)${CL} Install with automatic service (recommended)\"\necho -e \"${GN}2)${CL} Install command only (manual execution)\"\necho -e \"${GN}3)${CL} Update existing installation\"\necho -e \"${RD}4)${CL} Cancel\"\n\nwhile true; do\n  read -p \"Enter your choice (1-4): \" choice\n  case $choice in\n  1)\n    INSTALL_MODE=\"service\"\n    echo -e \"${GN}✓ Selected: Service installation${CL}\"\n    break\n    ;;\n  2)\n    INSTALL_MODE=\"command\"\n    echo -e \"${GN}✓ Selected: Command-only installation${CL}\"\n    break\n    ;;\n  3)\n    echo -e \"${GN}✓ Selected: Update installation${CL}\"\n    update_installation\n    exit 0\n    ;;\n  4)\n    msg_error \"Action cancelled.\"\n    exit 0\n    ;;\n  *)\n    msg_error \"Please enter 1, 2, 3, or 4.\"\n    ;;\n  esac\ndone\n\necho -e \"\\n${YW}This will install ${APP} on ${hostname} in $INSTALL_MODE mode.${CL}\"\nwhile true; do\n  read -p \"Proceed? (y/n): \" yn\n  case $yn in\n  [Yy]*)\n    break\n    ;;\n  [Nn]*)\n    msg_error \"Installation cancelled.\"\n    exit\n    ;;\n  *)\n    msg_error \"Please answer yes or no.\"\n    ;;\n  esac\ndone\n\nif ! pveversion | grep -Eq \"pve-manager/(8\\.[0-4]|9\\.[0-9]+)(\\.[0-9]+)*\"; then\n  msg_error \"This version of Proxmox Virtual Environment is not supported\"\n  msg_error \"⚠ Requires Proxmox Virtual Environment Version 8.0–8.4 or 9.x.\"\n  msg_error \"Exiting...\"\n  sleep 2\n  exit\nfi\n\nmsg_info \"Installing Dependencies\"\napt-get update &>/dev/null\napt-get install -y ipcalc net-tools &>/dev/null\nmsg_ok \"Installed Dependencies\"\n\n# Execute installation based on selected mode\nif [[ \"$INSTALL_MODE\" == \"service\" ]]; then\n  # Full service installation\n  msg_info \"Setting up IP-Tag Scripts\"\n  mkdir -p /opt/iptag\n  msg_ok \"Setup IP-Tag Scripts\"\n\n  # Migrate config if needed\n  migrate_config\n\n  # Interactive configuration setup\n  if [[ ! -f /opt/iptag/iptag.conf ]]; then\n    interactive_config_setup\n    msg_info \"Setup Default Config\"\n    generate_config >/opt/iptag/iptag.conf\n    msg_ok \"Setup default config\"\n  else\n    stop_spinner\n    echo -e \"\\n${YW}Configuration file already exists.${CL}\"\n    read -p \"Do you want to reconfigure tag format and loop interval? (y/n): \" reconfigure\n    case $reconfigure in\n    [Yy]*)\n      interactive_config_setup\n      msg_info \"Updating Configuration\"\n      generate_config >/opt/iptag/iptag.conf\n      msg_ok \"Updated configuration file\"\n      ;;\n    *)\n      msg_ok \"Keeping existing configuration file\"\n      ;;\n    esac\n  fi\n\n  msg_info \"Setup Main Function\"\n  generate_main_script >/opt/iptag/iptag\n  chmod +x /opt/iptag/iptag\n  msg_ok \"Setup Main Function\"\n\n  msg_info \"Creating Service\"\n  generate_service >/lib/systemd/system/iptag.service\n  msg_ok \"Created Service\"\n\n  msg_info \"Starting Service\"\n  systemctl daemon-reload &>/dev/null\n  systemctl enable -q --now iptag.service &>/dev/null\n  msg_ok \"Started Service\"\n\n  msg_info \"Creating manual run command\"\n  cat <<'EOF' >/usr/local/bin/iptag-run\n#!/usr/bin/env bash\nCONFIG_FILE=\"/opt/iptag/iptag.conf\"\nSCRIPT_FILE=\"/opt/iptag/iptag\"\nif [[ ! -f \"$SCRIPT_FILE\" ]]; then\n  echo \"✗ Main script not found: $SCRIPT_FILE\"\n  exit 1\nfi\nexport FORCE_SINGLE_RUN=true\nexec \"$SCRIPT_FILE\"\nEOF\n  chmod +x /usr/local/bin/iptag-run\n  msg_ok \"Created iptag-run command\"\n\n  echo -e \"\\n${GN}${APP} service installation completed successfully! ${CL}\"\n  echo -e \"${YW}The service is now running automatically.${CL}\"\n  echo -e \"${YW}You can also run it manually with: ${GN}iptag-run${CL}\\n\"\n\n  # Show configuration information\n  show_post_install_info\n\nelif [[ \"$INSTALL_MODE\" == \"command\" ]]; then\n  # Command-only installation\n  install_command_only\n\n  stop_spinner\n  echo -e \"\\n${GN}${APP} command installation completed successfully! ${CL}\"\n\n  # Show configuration information\n  show_post_install_info\nfi\n\n# Clean up any running spinner and exit\nstop_spinner\nexit 0\n"
  },
  {
    "path": "tools/pve/clean-lxcs.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n   ________                    __   _  ________\n  / ____/ /__  ____ _____     / /  | |/ / ____/\n / /   / / _ \\/ __ `/ __ \\   / /   |   / /\n/ /___/ /  __/ /_/ / / / /  / /___/   / /___\n\\____/_/\\___/\\__,_/_/ /_/  /_____/_/|_\\____/\n\nEOF\n}\n\nset -eEuo pipefail\nBL=\"\\033[36m\"\nRD=\"\\033[01;31m\"\nCM='\\xE2\\x9C\\x94\\033'\nGN=\"\\033[1;92m\"\nCL=\"\\033[m\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"clean-lxcs\" \"pve\"\n\nheader_info\necho \"Loading...\"\n\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE LXC Updater\" --yesno \"This will clean logs, cache and update package lists on selected LXC Containers. Proceed?\" 10 58\n\nNODE=$(hostname)\nEXCLUDE_MENU=()\nMSG_MAX_LENGTH=0\n\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  EXCLUDE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\n\nexcluded_containers=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --checklist \"\\nSelect containers to skip from cleaning:\\n\" \\\n  16 $((MSG_MAX_LENGTH + 23)) 6 \"${EXCLUDE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\nif [ $? -ne 0 ]; then\n  exit\nfi\n\nfunction run_lxc_clean() {\n  local container=$1\n  header_info\n  name=$(pct exec \"$container\" hostname)\n\n  pct exec \"$container\" -- bash -c '\n    BL=\"\\033[36m\"; GN=\"\\033[1;92m\"; CL=\"\\033[m\"\n    name=$(hostname)\n    if [ -e /etc/alpine-release ]; then\n      echo -e \"${BL}[Info]${GN} Cleaning $name (Alpine)${CL}\\n\"\n      apk cache clean\n      find /var/log -type f -delete 2>/dev/null\n      find /tmp -mindepth 1 -delete 2>/dev/null\n      apk update\n    elif [ -e /etc/redhat-release ]; then\n      echo -e \"${BL}[Info]${GN} Cleaning $name (CentOS)${CL}\\n\"\n      yum clean all\n      find /var/log -type f -delete 2>/dev/null\n      find /tmp -mindepth 1 -delete 2>/dev/null\n      yum update\n      yum upgrade -y\n    else\n      echo -e \"${BL}[Info]${GN} Cleaning $name (Debian/Ubuntu)${CL}\\n\"\n      find /var/cache -type f -delete 2>/dev/null\n      find /var/log -type f -delete 2>/dev/null\n      find /tmp -mindepth 1 -delete 2>/dev/null\n      apt -y --purge autoremove\n      apt -y autoclean\n      rm -rf /var/lib/apt/lists/*\n      apt update\n    fi\n  '\n}\n\nfor container in $(pct list | awk '{if(NR>1) print $1}'); do\n  if [[ \" ${excluded_containers[@]} \" =~ \" $container \" ]]; then\n    header_info\n    echo -e \"${BL}[Info]${GN} Skipping ${BL}$container${CL}\"\n    sleep 1\n    continue\n  fi\n\n  os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n  # Supported: debian, ubuntu, alpine, centos\n  if [ \"$os\" != \"debian\" ] && [ \"$os\" != \"ubuntu\" ] && [ \"$os\" != \"alpine\" ] && [ \"$os\" != \"centos\" ]; then\n    header_info\n    echo -e \"${BL}[Info]${GN} Skipping ${RD}$container is not Debian, Ubuntu, Alpine or Red Hat Compatible${CL} \\n\"\n    sleep 1\n    continue\n  fi\n\n  status=$(pct status \"$container\")\n  template=$(pct config \"$container\" | grep -q \"template:\" && echo \"true\" || echo \"false\")\n\n  if [ \"$template\" == \"false\" ] && [ \"$status\" == \"status: stopped\" ]; then\n    echo -e \"${BL}[Info]${GN} Starting${BL} $container ${CL} \\n\"\n    pct start \"$container\"\n    echo -e \"${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL} \\n\"\n    sleep 5\n    run_lxc_clean \"$container\"\n    echo -e \"${BL}[Info]${GN} Shutting down${BL} $container ${CL} \\n\"\n    pct shutdown \"$container\" &\n  elif [ \"$status\" == \"status: running\" ]; then\n    run_lxc_clean \"$container\"\n  fi\ndone\n\nwait\nheader_info\necho -e \"${GN} Finished, Selected Containers Cleaned. ${CL} \\n\"\n"
  },
  {
    "path": "tools/pve/clean-orphaned-lvm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____                                          ________                    ____             __                         __   __ _    ____  ___    \n   / __ \\_________  _  ______ ___  ____  _  __   / ____/ /__  ____ _____     / __ \\_________  / /_  ____ _____  ___  ____/ /  / /| |  / /  |/  /____\n  / /_/ / ___/ __ \\| |/_/ __ `__ \\/ __ \\| |/_/  / /   / / _ \\/ __ `/ __ \\   / / / / ___/ __ \\/ __ \\/ __ `/ __ \\/ _ \\/ __  /  / / | | / / /|_/ / ___/\n / ____/ /  / /_/ />  </ / / / / / /_/ />  <   / /___/ /  __/ /_/ / / / /  / /_/ / /  / /_/ / / / / /_/ / / / /  __/ /_/ /  / /__| |/ / /  / (__  ) \n/_/   /_/   \\____/_/|_/_/ /_/ /_/\\____/_/|_|   \\____/_/\\___/\\__,_/_/ /_/   \\____/_/  / .___/_/ /_/\\__,_/_/ /_/\\___/\\__,_/  /_____/___/_/  /_/____/  \n                                                                                    /_/                                                             \nEOF\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"clean-orphaned-lvm\" \"pve\"\n\n# Function to check for orphaned LVM volumes\nfunction find_orphaned_lvm {\n  echo -e \"\\n🔍 Scanning for orphaned LVM volumes...\\n\"\n\n  orphaned_volumes=()\n  while read -r lv vg size seg_type; do\n    # Exclude system-critical LVs and Ceph OSDs\n    if [[ \"$lv\" == \"data\" || \"$lv\" == \"root\" || \"$lv\" == \"swap\" || \"$lv\" =~ ^osd-block- ]]; then\n      continue\n    fi\n\n    # Exclude thin pools (any name)\n    if [[ \"$seg_type\" == \"thin-pool\" ]]; then\n      continue\n    fi\n\n    container_id=$(echo \"$lv\" | grep -oE \"[0-9]+\" | head -1)\n    # Check if the ID exists as a VM or LXC container on any cluster node\n    if compgen -G \"/etc/pve/nodes/*/lxc/${container_id}.conf\" >/dev/null 2>&1 ||\n      compgen -G \"/etc/pve/nodes/*/qemu-server/${container_id}.conf\" >/dev/null 2>&1; then\n      continue\n    fi\n\n    orphaned_volumes+=(\"$lv\" \"$vg\" \"$size\")\n  done < <(lvs --noheadings -o lv_name,vg_name,lv_size,seg_type --separator ' ' 2>/dev/null | awk '{print $1, $2, $3, $4}')\n\n  # Display orphaned volumes\n  echo -e \"❗ The following orphaned LVM volumes were found:\\n\"\n  printf \"%-25s %-10s %-10s\\n\" \"LV Name\" \"VG\" \"Size\"\n  printf \"%-25s %-10s %-10s\\n\" \"-------------------------\" \"----------\" \"----------\"\n\n  for ((i = 0; i < ${#orphaned_volumes[@]}; i += 3)); do\n    printf \"%-25s %-10s %-10s\\n\" \"${orphaned_volumes[i]}\" \"${orphaned_volumes[i + 1]}\" \"${orphaned_volumes[i + 2]}\"\n  done\n  echo \"\"\n}\n\n# Function to delete selected volumes\nfunction delete_orphaned_lvm {\n  for ((i = 0; i < ${#orphaned_volumes[@]}; i += 3)); do\n    lv=\"${orphaned_volumes[i]}\"\n    vg=\"${orphaned_volumes[i + 1]}\"\n    size=\"${orphaned_volumes[i + 2]}\"\n\n    read -p \"❓ Do you want to delete $lv (VG: $vg, Size: $size)? [y/N]: \" confirm\n    if [[ \"$confirm\" =~ ^[Yy]$ ]]; then\n      echo -e \"🗑️  Deleting $lv from $vg...\"\n      lvremove -f \"$vg/$lv\"\n      if [ $? -eq 0 ]; then\n        echo -e \"✅ Successfully deleted $lv.\\n\"\n      else\n        echo -e \"❌ Failed to delete $lv.\\n\"\n      fi\n    else\n      echo -e \"⚠️  Skipping $lv.\\n\"\n    fi\n  done\n}\n\n# Run script\nheader_info\nfind_orphaned_lvm\ndelete_orphaned_lvm\n\necho -e \"✅ Cleanup process completed!\\n\"\n"
  },
  {
    "path": "tools/pve/container-restore-from-backup.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nclear\nif command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Can't Run from the Proxmox Shell\"\n  exit\nfi\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\nAPP=\"Home Assistant Container\"\nwhile true; do\n  read -p \"This will restore ${APP} from a backup. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nclear\nfunction header_info {\n  cat <<\"EOF\"\n    __  __                        ___              _      __              __              \n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_   \n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/  \n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_   \n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/   \n                        RESTORE FROM BACKUP                                \nEOF\n}\n\nheader_info\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"container-restore\" \"pve\"\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\nif [ -z \"$(ls -A /var/lib/docker/volumes/hass_config/_data/backups/)\" ]; then\n  msg_error \"No backups found! \\n\"\n  exit 235\nfi\nDIR=/var/lib/docker/volumes/hass_config/_data/restore\nif [ -d \"$DIR\" ]; then\n  msg_ok \"Restore Directory Exists.\"\nelse\n  mkdir -p /var/lib/docker/volumes/hass_config/_data/restore\n  msg_ok \"Created Restore Directory.\"\nfi\ncd /var/lib/docker/volumes/hass_config/_data/backups/\nPS3=\"Please enter your choice: \"\nfiles=\"$(ls -A .)\"\nselect filename in ${files}; do\n  msg_ok \"You selected ${BL}${filename}${CL}\"\n  break\ndone\nmsg_info \"Stopping Home Assistant\"\ndocker stop homeassistant &>/dev/null\nmsg_ok \"Stopped Home Assistant\"\nmsg_info \"Restoring Home Assistant using ${filename}\"\ntar xvf ${filename} -C /var/lib/docker/volumes/hass_config/_data/restore &>/dev/null\ncd /var/lib/docker/volumes/hass_config/_data/restore\ntar -xvf homeassistant.tar.gz &>/dev/null\nif ! command -v rsync >/dev/null 2>&1; then apt-get install -y rsync &>/dev/null; fi\nrsync -a /var/lib/docker/volumes/hass_config/_data/restore/data/ /var/lib/docker/volumes/hass_config/_data\nrm -rf /var/lib/docker/volumes/hass_config/_data/restore/*\nmsg_ok \"Restore Complete\"\nmsg_ok \"Starting Home Assistant \\n\"\ndocker start homeassistant\n"
  },
  {
    "path": "tools/pve/core-restore-from-backup.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nclear\nif command -v pveversion >/dev/null 2>&1; then\n  echo -e \"⚠️  Can't Run from the Proxmox Shell\"\n  exit\nfi\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\nAPP=\"Home Assistant Core\"\nwhile true; do\n  read -p \"This will restore ${APP} from a backup. Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nclear\nfunction header_info {\n  cat <<\"EOF\"\n    __  __                        ___              _      __              __     ______              \n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_   / ____/___  ________ \n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/  / /   / __ \\/ ___/ _ \\\n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_   / /___/ /_/ / /  /  __/\n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/   \\____/\\____/_/   \\___/ \n                                     RESTORE FROM BACKUP                                \nEOF\n}\n\nheader_info\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"core-restore\" \"pve\"\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\nif [ -z \"$(ls -A /root/.homeassistant/backups/)\" ]; then\n  msg_error \"No backups found! \\n\"\n  exit 235\nfi\nDIR=/root/.homeassistant/restore\nif [ -d \"$DIR\" ]; then\n  msg_ok \"Restore Directory Exists.\"\nelse\n  mkdir -p /root/.homeassistant/restore\n  msg_ok \"Created Restore Directory.\"\nfi\ncd /root/.homeassistant/backups/\nPS3=\"Please enter your choice: \"\nfiles=\"$(ls -A .)\"\nselect filename in ${files}; do\n  msg_ok \"You selected ${BL}${filename}${CL}\"\n  break\ndone\nmsg_info \"Stopping Home Assistant\"\nsudo service homeassistant stop\nmsg_ok \"Stopped Home Assistant\"\nmsg_info \"Restoring Home Assistant using ${filename}\"\ntar xvf ${filename} -C /root/.homeassistant/restore &>/dev/null\ncd /root/.homeassistant/restore\ntar -xvf homeassistant.tar.gz &>/dev/null\nif ! command -v rsync >/dev/null 2>&1; then apt-get install -y rsync &>/dev/null; fi\nrsync -a /root/.homeassistant/restore/data/ /root/.homeassistant\nrm -rf /root/.homeassistant/restore/*\nmsg_ok \"Restore Complete\"\nmsg_ok \"Starting Home Assistant \\n\"\nsudo service homeassistant start\n"
  },
  {
    "path": "tools/pve/cron-update-lxcs.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n#\n# This script manages a local cron job for automatic LXC container OS updates.\n# The update script is downloaded once, displayed for review, and installed\n# locally. Cron runs the local copy — no remote code execution at runtime.\n#\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh)\"\n\nset -euo pipefail\n\nREPO_URL=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main\"\nSCRIPT_URL=\"${REPO_URL}/tools/pve/update-lxcs-cron.sh\"\nLOCAL_SCRIPT=\"/usr/local/bin/update-lxcs.sh\"\nCONF_FILE=\"/etc/update-lxcs.conf\"\nLOG_FILE=\"/var/log/update-lxcs-cron.log\"\nCRON_ENTRY=\"0 0 * * 0 ${LOCAL_SCRIPT} >>${LOG_FILE} 2>&1\"\n\nclear\ncat <<\"EOF\"\n   ______                    __  __          __      __          __   _  ________\n  / ____/________  ____     / / / /___  ____/ /___ _/ /____     / /  | |/ / ____/____\n / /   / ___/ __ \\/ __ \\   / / / / __ \\/ __  / __ `/ __/ _ \\   / /   |   / /   / ___/\n/ /___/ /  / /_/ / / / /  / /_/ / /_/ / /_/ / /_/ / /_/  __/  / /___/   / /___(__  )\n\\____/_/   \\____/_/ /_/   \\____/ .___/\\__,_/\\__,_/\\__/\\___/  /_____/_/|_\\____/____/\n                              /_/\nEOF\n\ninfo() { echo -e \"\\n \\e[36m[Info]\\e[0m $1\"; }\nok() { echo -e \" \\e[32m[OK]\\e[0m $1\"; }\nerr() { echo -e \" \\e[31m[Error]\\e[0m $1\" >&2; }\n\nconfirm() {\n  local prompt=\"${1:-Proceed?}\"\n  while true; do\n    read -rp \" ${prompt} (y/n): \" yn\n    case $yn in\n    [Yy]*) return 0 ;;\n    [Nn]*) return 1 ;;\n    *) echo \"  Please answer yes or no.\" ;;\n    esac\n  done\n}\n\ndownload_script() {\n  local tmp\n  tmp=$(mktemp)\n  if ! curl -fsSL -o \"$tmp\" \"$SCRIPT_URL\"; then\n    err \"Failed to download script from:\\n  ${SCRIPT_URL}\"\n    rm -f \"$tmp\"\n    return 1\n  fi\n  echo \"$tmp\"\n}\n\nreview_script() {\n  local file=\"$1\"\n  local hash\n  hash=$(sha256sum \"$file\" | awk '{print $1}')\n  echo \"\"\n  echo -e \" \\e[1;33m─── Script Content ───────────────────────────────────────────\\e[0m\"\n  cat \"$file\"\n  echo -e \" \\e[1;33m──────────────────────────────────────────────────────────────\\e[0m\"\n  echo -e \" \\e[36mSHA256:\\e[0m ${hash}\"\n  echo -e \" \\e[36mSource:\\e[0m ${SCRIPT_URL}\"\n  echo \"\"\n}\n\nremove_legacy_cron() {\n  if crontab -l -u root 2>/dev/null | grep -q \"update-lxcs-cron.sh\"; then\n    (crontab -l -u root 2>/dev/null | grep -v \"update-lxcs-cron.sh\") | crontab -u root -\n    ok \"Removed legacy curl-based cron entry\"\n  fi\n}\n\nadd() {\n  info \"Downloading update script...\"\n  local tmp\n  tmp=$(download_script) || exit 1\n\n  local hash\n  hash=$(sha256sum \"$tmp\" | awk '{print $1}')\n  echo \"\"\n  echo -e \" \\e[1;33m─── Installation Summary ─────────────────────────────────────\\e[0m\"\n  echo -e \" \\e[36mSource:\\e[0m       ${SCRIPT_URL}\"\n  echo -e \" \\e[36mSHA256:\\e[0m       ${hash}\"\n  echo -e \" \\e[36mInstall to:\\e[0m   ${LOCAL_SCRIPT}\"\n  echo -e \" \\e[36mConfig:\\e[0m       ${CONF_FILE}\"\n  echo -e \" \\e[36mLog file:\\e[0m     ${LOG_FILE}\"\n  echo -e \" \\e[36mCron schedule:\\e[0m Every Sunday at midnight (0 0 * * 0)\"\n  echo -e \" \\e[1;33m──────────────────────────────────────────────────────────────\\e[0m\"\n  echo \"\"\n\n  if confirm \"Review script content before installing?\"; then\n    review_script \"$tmp\"\n  fi\n\n  if ! confirm \"Install this script and activate cron schedule?\"; then\n    rm -f \"$tmp\"\n    echo \" Aborted.\"\n    exit 0\n  fi\n\n  remove_legacy_cron\n\n  install -m 0755 \"$tmp\" \"$LOCAL_SCRIPT\"\n  rm -f \"$tmp\"\n  ok \"Installed script to ${LOCAL_SCRIPT}\"\n\n  if [[ ! -f \"$CONF_FILE\" ]]; then\n    cat >\"$CONF_FILE\" <<'CONF'\n# Configuration for automatic LXC container OS updates.\n# Add container IDs to exclude from updates (comma-separated):\n# EXCLUDE=100,101,102\nEXCLUDE=\nCONF\n    ok \"Created config ${CONF_FILE}\"\n  fi\n\n  (\n    crontab -l -u root 2>/dev/null | grep -v \"${LOCAL_SCRIPT}\" || true\n    echo \"${CRON_ENTRY}\"\n  ) | crontab -u root -\n  ok \"Added cron schedule: Every Sunday at midnight\"\n  echo \"\"\n  echo -e \" \\e[36mLocal script:\\e[0m ${LOCAL_SCRIPT}\"\n  echo -e \" \\e[36mConfig:\\e[0m      ${CONF_FILE}\"\n  echo -e \" \\e[36mLog file:\\e[0m    ${LOG_FILE}\"\n  echo \"\"\n}\n\nremove() {\n  if crontab -l -u root 2>/dev/null | grep -q \"${LOCAL_SCRIPT}\"; then\n    (crontab -l -u root 2>/dev/null | grep -v \"${LOCAL_SCRIPT}\") | crontab -u root -\n    ok \"Removed cron schedule\"\n  fi\n  remove_legacy_cron\n  [[ -f \"$LOCAL_SCRIPT\" ]] && rm -f \"$LOCAL_SCRIPT\" && ok \"Removed ${LOCAL_SCRIPT}\"\n  [[ -f \"$LOG_FILE\" ]] && rm -f \"$LOG_FILE\" && ok \"Removed ${LOG_FILE}\"\n  echo -e \"\\n Cron Update LXCs has been fully removed.\"\n  echo -e \" \\e[90mNote: ${CONF_FILE} was kept (remove manually if desired).\\e[0m\"\n}\n\nupdate_script() {\n  if [[ ! -f \"$LOCAL_SCRIPT\" ]]; then\n    err \"No local script found at ${LOCAL_SCRIPT}. Use 'Add' first.\"\n    exit 1\n  fi\n\n  info \"Downloading latest version...\"\n  local tmp\n  tmp=$(download_script) || exit 1\n\n  if command -v diff &>/dev/null; then\n    local changes\n    changes=$(diff --color=auto \"$LOCAL_SCRIPT\" \"$tmp\" 2>/dev/null || true)\n    if [[ -z \"$changes\" ]]; then\n      ok \"Script is already up-to-date (no changes).\"\n      rm -f \"$tmp\"\n      return\n    fi\n    echo \"\"\n    echo -e \" \\e[1;33m─── Changes ──────────────────────────────────────────────────\\e[0m\"\n    echo \"$changes\"\n    echo -e \" \\e[1;33m──────────────────────────────────────────────────────────────\\e[0m\"\n  else\n    review_script \"$tmp\"\n  fi\n\n  local new_hash old_hash\n  new_hash=$(sha256sum \"$tmp\" | awk '{print $1}')\n  old_hash=$(sha256sum \"$LOCAL_SCRIPT\" | awk '{print $1}')\n  echo -e \" \\e[36mCurrent SHA256:\\e[0m ${old_hash}\"\n  echo -e \" \\e[36mNew SHA256:\\e[0m     ${new_hash}\"\n  echo \"\"\n\n  if ! confirm \"Apply update?\"; then\n    rm -f \"$tmp\"\n    echo \" Aborted.\"\n    return\n  fi\n\n  install -m 0755 \"$tmp\" \"$LOCAL_SCRIPT\"\n  rm -f \"$tmp\"\n  ok \"Updated ${LOCAL_SCRIPT}\"\n}\n\nview_script() {\n  if [[ ! -f \"$LOCAL_SCRIPT\" ]]; then\n    err \"No local script found at ${LOCAL_SCRIPT}. Use 'Add' first.\"\n    exit 1\n  fi\n\n  local view_choice\n  view_choice=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"View Script\" --menu \"What do you want to view?\" 12 60 3 \\\n    \"Worker\" \"Installed update script (${LOCAL_SCRIPT##*/})\" \\\n    \"Cron\" \"Cron schedule & configuration\" \\\n    \"Both\" \"Show everything\" \\\n    3>&1 1>&2 2>&3) || return 0\n\n  case \"$view_choice\" in\n  \"Worker\") view_worker_script ;;\n  \"Cron\") view_cron_config ;;\n  \"Both\") view_cron_config && echo \"\" && view_worker_script ;;\n  esac\n}\n\nview_worker_script() {\n  local hash\n  hash=$(sha256sum \"$LOCAL_SCRIPT\" | awk '{print $1}')\n  echo \"\"\n  echo -e \" \\e[1;33m─── ${LOCAL_SCRIPT} ───\\e[0m\"\n  cat \"$LOCAL_SCRIPT\"\n  echo -e \" \\e[1;33m──────────────────────────────────────────────────────────────\\e[0m\"\n  echo -e \" \\e[36mSHA256:\\e[0m    ${hash}\"\n  echo -e \" \\e[36mInstalled:\\e[0m $(stat -c '%y' \"$LOCAL_SCRIPT\" 2>/dev/null | cut -d. -f1)\"\n  echo \"\"\n}\n\nview_cron_config() {\n  echo \"\"\n  echo -e \" \\e[1;33m─── Cron Configuration ───────────────────────────────────────\\e[0m\"\n  if crontab -l -u root 2>/dev/null | grep -q \"${LOCAL_SCRIPT}\"; then\n    local entry\n    entry=$(crontab -l -u root 2>/dev/null | grep \"${LOCAL_SCRIPT}\")\n    echo -e \" \\e[36mCron entry:\\e[0m  ${entry}\"\n    local schedule\n    schedule=$(echo \"$entry\" | awk '{print $1,$2,$3,$4,$5}')\n    echo -e \" \\e[36mSchedule:\\e[0m    ${schedule} ($(cron_to_human \"$schedule\"))\"\n  else\n    echo -e \" \\e[31mCron:\\e[0m        Not configured\"\n  fi\n  if [[ -f \"$CONF_FILE\" ]]; then\n    echo -e \" \\e[36mConfig file:\\e[0m ${CONF_FILE}\"\n    local excludes\n    excludes=$(grep -oP '^\\s*EXCLUDE\\s*=\\s*\\K.*' \"$CONF_FILE\" 2>/dev/null || true)\n    echo -e \" \\e[36mExcluded:\\e[0m    ${excludes:-(none)}\"\n    echo \"\"\n    echo -e \" \\e[90m--- ${CONF_FILE} ---\\e[0m\"\n    cat \"$CONF_FILE\"\n  else\n    echo -e \" \\e[36mConfig file:\\e[0m (not created yet)\"\n  fi\n  if [[ -f \"$LOG_FILE\" ]]; then\n    local log_size\n    log_size=$(du -h \"$LOG_FILE\" | awk '{print $1}')\n    echo -e \" \\e[36mLog file:\\e[0m    ${LOG_FILE} (${log_size})\"\n  fi\n  echo -e \" \\e[1;33m──────────────────────────────────────────────────────────────\\e[0m\"\n  echo \"\"\n}\n\ncron_to_human() {\n  local schedule=\"$1\"\n  case \"$schedule\" in\n  \"0 0 * * 0\") echo \"Every Sunday at midnight\" ;;\n  \"0 0 * * *\") echo \"Daily at midnight\" ;;\n  \"0 * * * *\") echo \"Every hour\" ;;\n  *) echo \"Custom schedule\" ;;\n  esac\n}\n\nshow_status() {\n  echo \"\"\n  if [[ -f \"$LOCAL_SCRIPT\" ]]; then\n    local hash\n    hash=$(sha256sum \"$LOCAL_SCRIPT\" | awk '{print $1}')\n    ok \"Script installed: ${LOCAL_SCRIPT}\"\n    echo -e \"   \\e[36mSHA256:\\e[0m    ${hash}\"\n    echo -e \"   \\e[36mInstalled:\\e[0m $(stat -c '%y' \"$LOCAL_SCRIPT\" 2>/dev/null | cut -d. -f1)\"\n  else\n    err \"Script not installed\"\n  fi\n\n  if crontab -l -u root 2>/dev/null | grep -q \"${LOCAL_SCRIPT}\"; then\n    local schedule\n    schedule=$(crontab -l -u root 2>/dev/null | grep \"${LOCAL_SCRIPT}\" | awk '{print $1,$2,$3,$4,$5}')\n    ok \"Cron active: ${schedule}\"\n  else\n    err \"Cron not configured\"\n  fi\n\n  if [[ -f \"$CONF_FILE\" ]]; then\n    local excludes\n    excludes=$(grep -oP '^\\s*EXCLUDE\\s*=\\s*\\K.*' \"$CONF_FILE\" 2>/dev/null || echo \"(none)\")\n    echo -e \"   \\e[36mExcluded:\\e[0m  ${excludes:-\"(none)\"}\"\n  fi\n\n  if [[ -f \"$LOG_FILE\" ]]; then\n    local log_size last_run\n    log_size=$(du -h \"$LOG_FILE\" | awk '{print $1}')\n    last_run=$(grep -oP '^\\s+\\K\\w.*' \"$LOG_FILE\" | tail -1)\n    echo -e \"   \\e[36mLog file:\\e[0m  ${LOG_FILE} (${log_size})\"\n    [[ -n \"${last_run:-}\" ]] && echo -e \"   \\e[36mLast run:\\e[0m  ${last_run}\"\n  else\n    echo -e \"   \\e[36mLog file:\\e[0m  (no runs yet)\"\n  fi\n  echo \"\"\n}\n\nrun_now() {\n  if [[ ! -f \"$LOCAL_SCRIPT\" ]]; then\n    err \"No local script found at ${LOCAL_SCRIPT}. Use 'Add' first.\"\n    exit 1\n  fi\n  info \"Running update script now...\"\n  bash \"$LOCAL_SCRIPT\" | tee -a \"$LOG_FILE\"\n  ok \"Run completed. Log appended to ${LOG_FILE}\"\n}\n\nrotate_log() {\n  if [[ ! -f \"$LOG_FILE\" ]]; then\n    info \"No log file to rotate.\"\n    return\n  fi\n  local log_size\n  log_size=$(stat -c '%s' \"$LOG_FILE\" 2>/dev/null || echo 0)\n  local log_size_h\n  log_size_h=$(du -h \"$LOG_FILE\" | awk '{print $1}')\n  if confirm \"Rotate log file? (current size: ${log_size_h})\"; then\n    mv \"$LOG_FILE\" \"${LOG_FILE}.old\"\n    ok \"Rotated: ${LOG_FILE} → ${LOG_FILE}.old\"\n  fi\n}\n\nOPTIONS=(\n  Add \"Download, review & install cron schedule\"\n  Remove \"Remove cron schedule & local script\"\n  Update \"Update local script from repository\"\n  Status \"Show installation status & last run\"\n  Run \"Run update script now (manual trigger)\"\n  View \"View cron config & installed script\"\n  Rotate \"Rotate log file\"\n)\n\nCHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Cron Update LXCs\" --menu \"Select an option:\" 16 68 7 \\\n  \"${OPTIONS[@]}\" 3>&1 1>&2 2>&3) || exit 0\n\ncase $CHOICE in\n\"Add\") add ;;\n\"Remove\") remove ;;\n\"Update\") update_script ;;\n\"Status\") show_status ;;\n\"Run\") run_now ;;\n\"View\") view_script ;;\n\"Rotate\") rotate_log ;;\nesac\n"
  },
  {
    "path": "tools/pve/execute.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jeroenzwart\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n     ______                     __          __   _  ________\n   / ____/  _____  _______  __/ /____     / /  | |/ / ____/\n  / __/ | |/_/ _ \\/ ___/ / / / __/ _ \\   / /   |   / /     \n / /____>  </  __/ /__/ /_/ / /_/  __/  / /___/   / /___   \n/_____/_/|_|\\___/\\___/\\__,_/\\__/\\___/  /_____/_/|_\\____/   \n                                                           \nEOF\n}\nset -eEuo pipefail\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nCM='\\xE2\\x9C\\x94\\033'\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"execute-lxcs\" \"pve\"\n\nheader_info\necho \"Loading...\"\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE LXC Execute\" --yesno \"This will execute a command inside selected LXC Containers. Proceed?\" 10 58\nNODE=$(hostname)\nEXCLUDE_MENU=()\nMSG_MAX_LENGTH=0\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  EXCLUDE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nexcluded_containers=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --checklist \"\\nSelect containers to skip from executing:\\n\" \\\n  16 $((MSG_MAX_LENGTH + 23)) 6 \"${EXCLUDE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\nif [ $? -ne 0 ]; then\n  exit\nfi\n\nread -r -p \"Enter here command for inside the containers: \" custom_command\n\nheader_info\necho \"One moment please...\\n\"\n\nfunction execute_in() {\n  container=$1\n  name=$(pct exec \"$container\" hostname)\n  echo -e \"${BL}[Info]${GN} Execute inside${BL} ${name}${GN} with output: ${CL}\"\n  if ! pct exec \"$container\" -- bash -c \"command ${custom_command} >/dev/null 2>&1\"; then\n    echo -e \"${BL}[Info]${GN} Skipping ${name} ${RD}$container has no command: ${custom_command}\"\n  else\n    pct exec \"$container\" -- bash -c \"${custom_command}\" | tee\n  fi\n}\n\nfor container in $(pct list | awk '{if(NR>1) print $1}'); do\n  if [[ \" ${excluded_containers[@]} \" =~ \" $container \" ]]; then\n    echo -e \"${BL}[Info]${GN} Skipping ${BL}$container${CL}\"\n  else\n    os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n    if [ \"$os\" != \"debian\" ] && [ \"$os\" != \"ubuntu\" ]; then\n      echo -e \"${BL}[Info]${GN} Skipping ${name} ${RD}$container is not Debian or Ubuntu ${CL}\"\n      continue\n    fi\n\n    status=$(pct status \"$container\")\n    template=$(pct config \"$container\" | grep -q \"template:\" && echo \"true\" || echo \"false\")\n    if [ \"$template\" == \"false\" ] && [ \"$status\" == \"status: stopped\" ]; then\n      echo -e \"${BL}[Info]${GN} Starting${BL} $container ${CL}\"\n      pct start \"$container\"\n      echo -e \"${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL}\"\n      sleep 5\n      execute_in \"$container\"\n      echo -e \"${BL}[Info]${GN} Shutting down${BL} $container ${CL}\"\n      pct shutdown \"$container\" &\n    elif [ \"$status\" == \"status: running\" ]; then\n      execute_in \"$container\"\n    fi\n  fi\ndone\n\nwait\n\necho -e \"${GN} Finished, execute command inside selected containers. ${CL} \\n\"\n"
  },
  {
    "path": "tools/pve/frigate-support.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ____    _           __        ____                        __\n  / __/___(_)__ ____ _/ /____   / __/_ _____  ___  ___  ____/ /_\n / _// __/ / _ `/ _ `/ __/ -_) _\\ \\/ // / _ \\/ _ \\/ _ \\/ __/ __/\n/_/ /_/ /_/\\_, /\\_,_/\\__/\\__/ /___/\\_,_/ .__/ .__/\\___/_/  \\__/\n          /___/                       /_/  /_/\nEOF\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"frigate-support\" \"pve\"\n\nheader_info\nwhile true; do\n  read -p \"This will Prepare a LXC Container for Frigate. Proceed (y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\nheader_info\n\n# The array of device types\n# CHAR_DEVS+=(major:minor)\nCHAR_DEVS+=(\"1:1\")     # mem\nCHAR_DEVS+=(\"29:0\")    # fb0\nCHAR_DEVS+=(\"188:.*\")  # ttyUSB*\nCHAR_DEVS+=(\"189:.*\")  # bus/usb/*\nCHAR_DEVS+=(\"226:0\")   # card0\nCHAR_DEVS+=(\"226:128\") # renderD128\n\n# Proccess char device string\nfor char_dev in ${CHAR_DEVS[@]}; do\n  [ ! -z \"${CHAR_DEV_STRING-}\" ] && CHAR_DEV_STRING+=\" -o\"\n  CHAR_DEV_STRING+=\" -regex \\\".*/${char_dev}\\\"\"\ndone\n\n# Store autodev hook script in a variable\nread -r -d '' HOOK_SCRIPT <<-EOF || true\nfor char_dev in \\$(find /sys/dev/char -regextype sed $CHAR_DEV_STRING); do\n  dev=\"/dev/\\$(sed -n \"/DEVNAME/ s/^.*=\\(.*\\)$/\\1/p\" \\${char_dev}/uevent)\";\n  mkdir -p \\$(dirname \\${LXC_ROOTFS_MOUNT}\\${dev});\n  for link in \\$(udevadm info --query=property \\$dev | sed -n \"s/DEVLINKS=//p\"); do\n    mkdir -p \\${LXC_ROOTFS_MOUNT}\\$(dirname \\$link);\n    cp -dpR \\$link \\${LXC_ROOTFS_MOUNT}\\${link};\n  done;\n  cp -dpR \\$dev \\${LXC_ROOTFS_MOUNT}\\${dev};\ndone;\nEOF\n\n# Remove newline char from the variable\nHOOK_SCRIPT=${HOOK_SCRIPT//$'\\n'/}\n\n# Generate menu of LXC containers in current node\nNODE=$(hostname)\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  ITEM=$(echo \"$line\" | awk '{print substr($0,36)}')\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  CTID_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\n\n# Selection menu for LXC containers\nwhile [ -z \"${CTID:+x}\" ]; do\n  CTID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --radiolist \\\n    \"\\nSelect a container to add support:\\n\" \\\n    16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n    \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3)\ndone\n\n# Add autodev settings\nCTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf\nsed '/autodev/d' \"$CTID_CONFIG_PATH\" >CTID.conf\ncat CTID.conf >\"$CTID_CONFIG_PATH\"\n\ncat <<EOF >>\"$CTID_CONFIG_PATH\"\nlxc.autodev: 1\nlxc.hook.autodev: bash -c '$HOOK_SCRIPT'\nEOF\necho -e \"\\e[1;33m \\nFinished....Reboot ${CTID} LXC to apply the changes.\\n \\e[0m\"\n\n# In the Proxmox web shell run\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/frigate-support.sh)\"\n# Reboot the LXC to apply the changes\n"
  },
  {
    "path": "tools/pve/fstrim.sh",
    "content": "#!/usr/bin/env bash\n\nset -eEuo pipefail\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n    _______ __                     __                    ______     _\n   / ____(_) /__  _______  _______/ /____  ____ ___     /_  __/____(_)___ ___\n  / /_  / / / _ \\/ ___/ / / / ___/ __/ _ \\/ __ `__ \\     / / / ___/ / __ `__ \\\n / __/ / / /  __(__  ) /_/ (__  ) /_/  __/ / / / / /    / / / /  / / / / / / /\n/_/   /_/_/\\___/____/\\__, /____/\\__/\\___/_/ /_/ /_/    /_/ /_/  /_/_/ /_/ /_/\n                    /____/\nEOF\n}\n\nBL=\"\\033[36m\"\nRD=\"\\033[01;31m\"\nGN=\"\\033[1;92m\"\nCL=\"\\033[m\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"fstrim\" \"pve\"\n\nLOGFILE=\"/var/log/fstrim.log\"\ntouch \"$LOGFILE\"\nchmod 600 \"$LOGFILE\"\necho -e \"\\n----- $(date '+%Y-%m-%d %H:%M:%S') | fstrim Run by $(whoami) on $(hostname) -----\" >>\"$LOGFILE\"\n\nheader_info\necho \"Loading...\"\n\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n  --title \"About fstrim (LXC)\" \\\n  --msgbox \"The 'fstrim' command releases unused blocks back to the storage device. This only makes sense for containers on SSD, NVMe, Thin-LVM, or storage with discard/TRIM support.\\n\\nIf your root filesystem or container disks are on classic HDDs, thick LVM, or unsupported storage types, running fstrim will have no effect.\\n\\nRecommended:\\n- Use fstrim only on SSD, NVMe, or thin-provisioned storage with discard enabled.\\n- For ZFS, ensure 'autotrim=on' is set on your pool.\\n\" 16 88\n\nROOT_FS=$(df -Th \"/\" | awk 'NR==2 {print $2}')\nif [ \"$ROOT_FS\" != \"ext4\" ]; then\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n    --title \"Warning\" \\\n    --yesno \"Root filesystem is not ext4 ($ROOT_FS).\\nContinue anyway?\" 12 80 || exit 0\nfi\n\nNODE=$(hostname)\nEXCLUDE_MENU=()\nSTOPPED_MENU=()\nMAX_NAME_LEN=0\nMAX_STAT_LEN=0\n\n# Build arrays with one pct list\nmapfile -t CTLINES < <(pct list | awk 'NR>1')\n\nfor LINE in \"${CTLINES[@]}\"; do\n  CTID=$(awk '{print $1}' <<<\"$LINE\")\n  STATUS=$(awk '{print $2}' <<<\"$LINE\")\n  NAME=$(awk '{print $3}' <<<\"$LINE\")\n  ((${#NAME} > MAX_NAME_LEN)) && MAX_NAME_LEN=${#NAME}\n  ((${#STATUS} > MAX_STAT_LEN)) && MAX_STAT_LEN=${#STATUS}\ndone\n\nFMT=\"%-${MAX_NAME_LEN}s | %-${MAX_STAT_LEN}s\"\n\nfor LINE in \"${CTLINES[@]}\"; do\n  CTID=$(awk '{print $1}' <<<\"$LINE\")\n  STATUS=$(awk '{print $2}' <<<\"$LINE\")\n  NAME=$(awk '{print $3}' <<<\"$LINE\")\n  DESC=$(printf \"$FMT\" \"$NAME\" \"$STATUS\")\n  EXCLUDE_MENU+=(\"$CTID\" \"$DESC\" \"OFF\")\n  if [[ \"$STATUS\" == \"stopped\" ]]; then\n    STOPPED_MENU+=(\"$CTID\" \"$DESC\" \"OFF\")\n  fi\ndone\n\nexcluded_containers_raw=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n  --title \"Containers on $NODE\" \\\n  --checklist \"\\nSelect containers to skip from trimming:\\n\" \\\n  20 $((MAX_NAME_LEN + MAX_STAT_LEN + 20)) 12 \"${EXCLUDE_MENU[@]}\" 3>&1 1>&2 2>&3)\n[ $? -ne 0 ] && exit\nread -ra EXCLUDED <<<$(echo \"$excluded_containers_raw\" | tr -d '\"')\n\nTO_START=()\nif [ ${#STOPPED_MENU[@]} -gt 0 ]; then\n  for ((i = 0; i < ${#STOPPED_MENU[@]}; i += 3)); do\n    CTID=\"${STOPPED_MENU[i]}\"\n    DESC=\"${STOPPED_MENU[i + 1]}\"\n    if [[ \" ${EXCLUDED[*]} \" =~ \" $CTID \" ]]; then\n      continue\n    fi\n    header_info\n    echo -e \"${BL}[Info]${GN} Container $CTID ($DESC) is currently stopped.${CL}\"\n    read -rp \"Temporarily start for fstrim? [y/N]: \" answer\n    if [[ \"$answer\" =~ ^[Yy]$ ]]; then\n      TO_START+=(\"$CTID\")\n    fi\n  done\nfi\n\ndeclare -A WAS_STOPPED\nfor ct in \"${TO_START[@]}\"; do\n  WAS_STOPPED[\"$ct\"]=1\ndone\n\nfunction trim_container() {\n  local container=\"$1\"\n  local name=\"$2\"\n  header_info\n  echo -e \"${BL}[Info]${GN} Trimming ${BL}$container${CL} \\n\"\n\n  local before_trim after_trim\n  local lv_name=\"vm-${container}-disk-0\"\n  if lvs --noheadings -o lv_name 2>/dev/null | grep -qw \"$lv_name\"; then\n    before_trim=$(lvs --noheadings -o lv_name,data_percent 2>/dev/null | awk -v ctid=\"$lv_name\" '$1 == ctid {gsub(/%/, \"\", $2); print $2}')\n    [[ -n \"$before_trim\" ]] && echo -e \"${RD}Data before trim $before_trim%${CL}\" || echo -e \"${RD}Data before trim: not available${CL}\"\n  else\n    before_trim=\"\"\n    echo -e \"${RD}Data before trim: not available (non-LVM storage)${CL}\"\n  fi\n\n  local fstrim_output\n  fstrim_output=$(pct fstrim \"$container\" 2>&1)\n  if echo \"$fstrim_output\" | grep -qi \"not supported\"; then\n    echo -e \"${RD}fstrim isnt supported on this storage!${CL}\"\n  elif echo \"$fstrim_output\" | grep -Eq '([0-9]+(\\.[0-9]+)?\\s*[KMGT]?B)'; then\n    echo -e \"${GN}fstrim result: $fstrim_output${CL}\"\n  else\n    echo -e \"${RD}fstrim result: $fstrim_output${CL}\"\n  fi\n\n  if lvs --noheadings -o lv_name 2>/dev/null | grep -qw \"$lv_name\"; then\n    after_trim=$(lvs --noheadings -o lv_name,data_percent 2>/dev/null | awk -v ctid=\"$lv_name\" '$1 == ctid {gsub(/%/, \"\", $2); print $2}')\n    [[ -n \"$after_trim\" ]] && echo -e \"${GN}Data after trim $after_trim%${CL}\" || echo -e \"${GN}Data after trim: not available${CL}\"\n  else\n    after_trim=\"\"\n    echo -e \"${GN}Data after trim: not available (non-LVM storage)${CL}\"\n  fi\n\n  # Logging\n  echo \"$(date '+%Y-%m-%d %H:%M:%S') | CTID=$container | Name=$name | Before=${before_trim:-N/A}% | After=${after_trim:-N/A}% | fstrim: $fstrim_output\" >>\"$LOGFILE\"\n  sleep 0.5\n}\n\nfor LINE in \"${CTLINES[@]}\"; do\n  CTID=$(awk '{print $1}' <<<\"$LINE\")\n  STATUS=$(awk '{print $2}' <<<\"$LINE\")\n  NAME=$(awk '{print $3}' <<<\"$LINE\")\n  if [[ \" ${EXCLUDED[*]} \" =~ \" $CTID \" ]]; then\n    header_info\n    echo -e \"${BL}[Info]${GN} Skipping $CTID ($NAME, excluded)${CL}\"\n    sleep 0.5\n    continue\n  fi\n  if pct config \"$CTID\" | grep -q \"template:\"; then\n    header_info\n    echo -e \"${BL}[Info]${GN} Skipping $CTID ($NAME, template)${CL}\\n\"\n    sleep 0.5\n    continue\n  fi\n  if [[ \"$STATUS\" != \"running\" ]]; then\n    if [[ -n \"${WAS_STOPPED[$CTID]:-}\" ]]; then\n      header_info\n      echo -e \"${BL}[Info]${GN} Starting $CTID ($NAME) for trim...${CL}\"\n      pct start \"$CTID\"\n      sleep 2\n    else\n      header_info\n      echo -e \"${BL}[Info]${GN} Skipping $CTID ($NAME, not running, not selected)${CL}\"\n      sleep 0.5\n      continue\n    fi\n  fi\n\n  trim_container \"$CTID\" \"$NAME\"\n\n  if [[ -n \"${WAS_STOPPED[$CTID]:-}\" ]]; then\n    read -rp \"Stop LXC $CTID ($NAME) again after trim? [Y/n]: \" answer\n    if [[ ! \"$answer\" =~ ^[Nn]$ ]]; then\n      header_info\n      echo -e \"${BL}[Info]${GN} Stopping $CTID ($NAME) again...${CL}\"\n      pct stop \"$CTID\"\n      sleep 1\n    else\n      header_info\n      echo -e \"${BL}[Info]${GN} Leaving $CTID ($NAME) running as requested.${CL}\"\n      sleep 1\n    fi\n  fi\ndone\n\nheader_info\necho -e \"${GN}Finished, LXC Containers Trimmed.${CL} \\n\"\necho -e \"${BL}If you want to see the complete log: cat $LOGFILE${CL}\"\nexit 0\n"
  },
  {
    "path": "tools/pve/host-backup.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   __ __         __    ___           __\n  / // /__  ___ / /_  / _ )___ _____/ /____ _____\n / _  / _ \\(_-</ __/ / _  / _ `/ __/  '_/ // / _ \\\n/_//_/\\___/___/\\__/ /____/\\_,_/\\__/_/\\_\\\\_,_/ .__/\n                                           /_/\nEOF\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"host-backup\" \"pve\"\n\n# Function to perform backup\nfunction perform_backup {\n  local BACKUP_PATH\n  local DIR\n  local DIR_DASH\n  local BACKUP_FILE\n  local selected_directories=()\n\n  # Get backup path from user\n  BACKUP_PATH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"\\nDefaults to /root/\\ne.g. /mnt/backups/\" 11 68 --title \"Directory to backup to:\" 3>&1 1>&2 2>&3) || return\n\n  # Default to /root/ if no input\n  BACKUP_PATH=\"${BACKUP_PATH:-/root/}\"\n\n  # Get directory to work in from user\n  DIR=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"\\nDefaults to /etc/\\ne.g. /root/, /var/lib/pve-cluster/ etc.\" 11 68 --title \"Directory to work in:\" 3>&1 1>&2 2>&3) || return\n\n  # Default to /etc/ if no input\n  DIR=\"${DIR:-/etc/}\"\n\n  DIR_DASH=$(echo \"$DIR\" | tr '/' '-')\n  BACKUP_FILE=\"$(hostname)${DIR_DASH}backup\"\n\n  # Build a list of directories for backup\n  local CTID_MENU=()\n  CTID_MENU=(\"ALL\" \"Backup all folders\" \"OFF\")\n  while read -r dir; do\n    CTID_MENU+=(\"$(basename \"$dir\")\" \"$dir \" \"OFF\")\n  done < <(ls -d \"${DIR}\"*)\n\n  # Allow the user to select directories\n  local HOST_BACKUP\n  while [ -z \"${HOST_BACKUP:+x}\" ]; do\n    HOST_BACKUP=$(whiptail --backtitle \"Proxmox VE Host Backup\" --title \"Working in the ${DIR} directory \" --checklist \\\n      \"\\nSelect what files/directories to backup:\\n\" 16 $(((${#DIRNAME} + 2) + 88)) 6 \"${CTID_MENU[@]}\" 3>&1 1>&2 2>&3) || return\n\n    for selected_dir in ${HOST_BACKUP//\\\"/}; do\n      if [[ \"$selected_dir\" == \"ALL\" ]]; then\n        # if ALL was chosen, secure all folders\n        selected_directories=(\"${DIR}\"*/)\n        break\n      else\n        selected_directories+=(\"${DIR}$selected_dir\")\n      fi\n    done\n  done\n\n  # Perform the backup\n  header_info\n  echo -e \"This will create a backup in\\e[1;33m $BACKUP_PATH \\e[0mfor these files and directories\\e[1;33m ${selected_directories[*]} \\e[0m\"\n  read -p \"Press ENTER to continue...\"\n  header_info\n  echo \"Working...\"\n  tar -czf \"$BACKUP_PATH$BACKUP_FILE-$(date +%Y_%m_%dT%H_%M).tar.gz\" --absolute-names \"${selected_directories[@]}\"\n  header_info\n  echo -e \"\\nFinished\"\n  echo -e \"\\e[1;33m \\nA backup is rendered ineffective when it remains stored on the host.\\n \\e[0m\"\n  sleep 2\n}\n\n# Main script execution loop\nwhile true; do\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE Host Backup\" --yesno \"This will create backups for particular files and directories located within a designated directory. Proceed?\" 10 88); then\n    perform_backup\n  else\n    break\n  fi\ndone\n"
  },
  {
    "path": "tools/pve/hw-acceleration.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Execute within the Proxmox shell\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/hw-acceleration.sh)\"\n\nset -e\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n\n   __ ___      __  ___              __             __  _\n  / // / | /| / / / _ |___________ / /__ _______ _/ /_(_)__  ___\n / _  /| |/ |/ / / __ / __/ __/ -_) / -_) __/ _ `/ __/ / _ \\/ _ \\\n/_//_/ |__/|__/ /_/ |_\\__/\\__/\\__/_/\\__/_/  \\_,_/\\__/_/\\___/_//_/\n\nEOF\n}\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nset -e\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"hw-acceleration\" \"pve\"\n\nheader_info\necho \"Loading...\"\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nif ! pveversion | grep -Eq \"pve-manager/8\\.[0-4](\\.[0-9]+)*\"; then\n  msg_error \"This version of Proxmox Virtual Environment is not supported\"\n  echo -e \"Requires PVE Version 8.1 or higher\"\n  echo -e \"Exiting...\"\n  sleep 2\n  exit\nfi\n\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Add Intel HW Acceleration\" --yesno \"This Will Add Intel HW Acceleration to an existing LXC Container. Proceed?\" 8 72\nNODE=$(hostname)\nPREV_MENU=()\nMSG_MAX_LENGTH=0\nprivileged_containers=$(pct list | awk 'NR>1 && system(\"grep -q \\047unprivileged: 1\\047 /etc/pve/lxc/\" $1 \".conf\")')\n\nif [ -z \"$privileged_containers\" ]; then\n  whiptail --msgbox \"No Privileged Containers Found.\" 10 58\n  exit\nfi\n\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  PREV_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(echo \"$privileged_containers\")\n\nprivileged_container=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Privileged Containers on $NODE\" --checklist \"\\nSelect a Container To Add Intel HW Acceleration:\\n\" 16 $((MSG_MAX_LENGTH + 23)) 6 \"${PREV_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\nheader_info\nread -r -p \"Verbose mode? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  STD=\"\"\nelse\n  STD=\"silent\"\nfi\nheader_info\n\ncat <<EOF >>/etc/pve/lxc/\"${privileged_container}\".conf\nlxc.cgroup2.devices.allow: c 226:0 rwm\nlxc.cgroup2.devices.allow: c 226:128 rwm\nlxc.cgroup2.devices.allow: c 29:0 rwm\nlxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file\nlxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir\nlxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file\nEOF\n\nread -r -p \"Do you need the intel-media-va-driver-non-free driver (Debian 12 only)? <y/N> \" prompt\nif [[ ${prompt,,} =~ ^(y|yes)$ ]]; then\n  header_info\n  msg_info \"Installing Hardware Acceleration (non-free)\"\n  pct exec \"${privileged_container}\" -- bash -c \"cat <<EOF >/etc/apt/sources.list.d/non-free.list\n\ndeb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware\ndeb-src http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware\n\ndeb http://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware\ndeb-src http://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware\n\ndeb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware\ndeb-src http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware\nEOF\"\n\n  pct exec \"${privileged_container}\" -- bash -c \"silent() { \\\"\\$@\\\" >/dev/null 2>&1; } && $STD apt-get update && $STD apt-get install -y intel-media-va-driver-non-free ocl-icd-libopencl1 intel-opencl-icd vainfo intel-gpu-tools && $STD adduser \\$(id -u -n) video && $STD adduser \\$(id -u -n) render\"\n  msg_ok \"Installed Hardware Acceleration (non-free)\"\nelse\n  header_info\n  msg_info \"Installing Hardware Acceleration\"\n  pct exec \"${privileged_container}\" -- bash -c \"silent() { \\\"\\$@\\\" >/dev/null 2>&1; } && $STD apt-get install -y va-driver-all ocl-icd-libopencl1 intel-opencl-icd vainfo intel-gpu-tools && chgrp video /dev/dri && chmod 755 /dev/dri && $STD adduser \\$(id -u -n) video && $STD adduser \\$(id -u -n) render\"\n  msg_ok \"Installed Hardware Acceleration\"\nfi\nsleep 1\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Added tools\" \"vainfo, execute command 'vainfo'\\nintel-gpu-tools, execute command 'intel_gpu_top'\" 8 58\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"Reboot container ${BL}$privileged_container${CL} to apply the new settings\\n\"\n"
  },
  {
    "path": "tools/pve/kernel-clean.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    __ __                     __   ________\n   / //_/__  _________  ___  / /  / ____/ /__  ____ _____\n  / ,< / _ \\/ ___/ __ \\/ _ \\/ /  / /   / / _ \\/ __ `/ __ \\\n / /| /  __/ /  / / / /  __/ /  / /___/ /  __/ /_/ / / / /\n/_/ |_\\___/_/  /_/ /_/\\___/_/   \\____/_/\\___/\\__,_/_/ /_/\n\nEOF\n}\n\n# Color variables\nYW=\"\\033[33m\"\nGN=\"\\033[1;92m\"\nRD=\"\\033[01;31m\"\nCL=\"\\033[m\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"kernel-clean\" \"pve\"\n\n# Detect current kernel\ncurrent_kernel=$(uname -r)\navailable_kernels=$(dpkg --list | grep 'kernel-.*-pve' | awk '{print $2}' | grep -v \"$current_kernel\" | sort -V)\n\nheader_info\n\nif [ -z \"$available_kernels\" ]; then\n  echo -e \"${GN}No old kernels detected. Current kernel: ${current_kernel}${CL}\"\n  exit 0\nfi\n\necho -e \"${GN}Currently running kernel: ${current_kernel}${CL}\"\necho -e \"${YW}Available kernels for removal:${CL}\"\necho \"$available_kernels\" | nl -w 2 -s '. '\n\necho -e \"\\n${YW}Select kernels to remove (comma-separated, e.g., 1,2):${CL}\"\nread -r selected\n\n# Parse selection\nIFS=',' read -r -a selected_indices <<<\"$selected\"\nkernels_to_remove=()\n\nfor index in \"${selected_indices[@]}\"; do\n  kernel=$(echo \"$available_kernels\" | sed -n \"${index}p\")\n  if [ -n \"$kernel\" ]; then\n    kernels_to_remove+=(\"$kernel\")\n  fi\ndone\n\nif [ ${#kernels_to_remove[@]} -eq 0 ]; then\n  echo -e \"${RD}No valid selection made. Exiting.${CL}\"\n  exit 0\nfi\n\n# Confirm removal\necho -e \"${YW}Kernels to be removed:${CL}\"\nprintf \"%s\\n\" \"${kernels_to_remove[@]}\"\nread -rp \"Proceed with removal? (y/n): \" confirm\nif [[ \"$confirm\" != \"y\" ]]; then\n  echo -e \"${RD}Aborted.${CL}\"\n  exit 0\nfi\n\n# Remove kernels\nfor kernel in \"${kernels_to_remove[@]}\"; do\n  echo -e \"${YW}Removing $kernel...${CL}\"\n  if apt-get purge -y \"$kernel\" >/dev/null 2>&1; then\n    echo -e \"${GN}Successfully removed: $kernel${CL}\"\n  else\n    echo -e \"${RD}Failed to remove: $kernel. Check dependencies.${CL}\"\n  fi\ndone\n\n# Clean up and update GRUB\necho -e \"${YW}Cleaning up...${CL}\"\napt-get autoremove -y >/dev/null 2>&1 && update-grub >/dev/null 2>&1\necho -e \"${GN}Cleanup and GRUB update complete.${CL}\"\n"
  },
  {
    "path": "tools/pve/kernel-pin.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    __ __                     __   ____  _\n   / //_/__  _________  ___  / /  / __ \\(_)___\n  / ,< / _ \\/ ___/ __ \\/ _ \\/ /  / /_/ / / __ \\\n / /| /  __/ /  / / / /  __/ /  / ____/ / / / /\n/_/ |_\\___/_/  /_/ /_/\\___/_/  /_/   /_/_/ /_/\n\nEOF\n}\nYW=$(echo \"\\033[33m\")\nRD=$(echo \"\\033[01;31m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\ncurrent_kernel=$(uname -r)\navailable_kernels=$(dpkg --list | grep 'kernel-.*-pve' | awk '{print substr($2, 16, length($2)-22)}')\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"kernel-pin\" \"pve\"\n\nheader_info\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE Kernel Pin\" --yesno \"This will Pin/Unpin Kernel Images, Proceed?\" 10 68\n\nKERNEL_MENU=()\nMSG_MAX_LENGTH=0\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  KERNEL_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(echo \"$available_kernels\")\n\npin_kernel=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Current Kernel $current_kernel\" --radiolist \"\\nSelect Kernel to pin:\\nCancel to Unpin any Kernel\" 16 $((MSG_MAX_LENGTH + 58)) 6 \"${KERNEL_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n[ -z \"$pin_kernel\" ] && {\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No Kernel Selected\" --msgbox \"It appears that no Kernel was selected\\nUnpinning any pinned Kernel\" 10 68\n  msg_info \"Unpinning any Kernel\"\n  proxmox-boot-tool kernel unpin &>/dev/null\n  msg_ok \"Unpinned any Kernel\\n\"\n  proxmox-boot-tool kernel list\n  echo \"\"\n  msg_ok \"Finished\\n\"\n  echo -e \"${RD} REBOOT${CL}\"\n  exit\n}\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE Kernel Pin\" --yesno \"Would you like to pin the $pin_kernel Kernel?\" 10 68\n\nmsg_info \"Pinning $pin_kernel\"\nproxmox-boot-tool kernel pin $pin_kernel &>/dev/null\nmsg_ok \"Successfully Pinned $pin_kernel\\n\"\nproxmox-boot-tool kernel list\necho \"\"\nmsg_ok \"Finished\\n\"\necho -e \"${RD} REBOOT${CL}\"\n"
  },
  {
    "path": "tools/pve/lxc-delete.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____                                          __   _  ________   ____       __     __\n   / __ \\_________  _  ______ ___  ____  _  __   / /  | |/ / ____/  / __ \\___  / /__  / /____\n  / /_/ / ___/ __ \\| |/_/ __ `__ \\/ __ \\| |/_/  / /   |   / /      / / / / _ \\/ / _ \\/ __/ _ \\\n / ____/ /  / /_/ />  </ / / / / / /_/ />  <   / /___/   / /___   / /_/ /  __/ /  __/ /_/  __/\n/_/   /_/   \\____/_/|_/_/ /_/ /_/\\____/_/|_|  /_____/_/|_\\____/  /_____/\\___/_/\\___/\\__/\\___/\n\nEOF\n}\n\nspinner() {\n  local pid=$1\n  local delay=0.1\n  local spinstr='|/-\\'\n  while ps -p $pid >/dev/null; do\n    printf \" [%c]  \" \"$spinstr\"\n    spinstr=${spinstr#?}${spinstr%\"${spinstr#?}\"}\n    sleep $delay\n    printf \"\\r\"\n  done\n  printf \"    \\r\"\n}\n\nset -eEuo pipefail\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nTAB=\"  \"\nCM=\"${TAB}✔️${TAB}${CL}\"\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"lxc-delete\" \"pve\"\n\nheader_info\necho \"Loading...\"\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE LXC Deletion\" --yesno \"This will delete LXC containers. Proceed?\" 10 58\n\nNODE=$(hostname)\ncontainers=$(pct list | tail -n +2 | awk '{print $0 \" \" $4}')\n\nif [ -z \"$containers\" ]; then\n  whiptail --title \"LXC Container Delete\" --msgbox \"No LXC containers available!\" 10 60\n  exit 234\nfi\n\nmenu_items=(\"ALL\" \"Delete ALL containers\" \"OFF\") # Add as first option\nFORMAT=\"%-10s %-15s %-10s\"\n\nwhile read -r container; do\n  container_id=$(echo $container | awk '{print $1}')\n  container_name=$(echo $container | awk '{print $2}')\n  container_status=$(echo $container | awk '{print $3}')\n  formatted_line=$(printf \"$FORMAT\" \"$container_name\" \"$container_status\")\n  menu_items+=(\"$container_id\" \"$formatted_line\" \"OFF\")\ndone <<<\"$containers\"\n\nCHOICES=$(whiptail --title \"LXC Container Delete\" \\\n  --checklist \"Select LXC containers to delete:\" 25 60 13 \\\n  \"${menu_items[@]}\" 3>&2 2>&1 1>&3)\n\nif [ -z \"$CHOICES\" ]; then\n  whiptail --title \"LXC Container Delete\" \\\n    --msgbox \"No containers selected!\" 10 60\n  exit 0\nfi\n\nread -p \"Delete containers manually or automatically? (Default: manual) m/a: \" DELETE_MODE\nDELETE_MODE=${DELETE_MODE:-m}\n\nselected_ids=$(echo \"$CHOICES\" | tr -d '\"' | tr -s ' ' '\\n')\n\n# If \"ALL\" is selected, override with all container IDs\nif echo \"$selected_ids\" | grep -q \"^ALL$\"; then\n  selected_ids=$(echo \"$containers\" | awk '{print $1}')\nfi\n\nfor container_id in $selected_ids; do\n  status=$(pct status $container_id)\n\n  if [ \"$status\" == \"status: running\" ]; then\n    echo -e \"${BL}[Info]${GN} Stopping container $container_id...${CL}\"\n    pct stop $container_id &\n    sleep 5\n    echo -e \"${BL}[Info]${GN} Container $container_id stopped.${CL}\"\n  fi\n\n  if [[ \"$DELETE_MODE\" == \"a\" ]]; then\n    echo -e \"${BL}[Info]${GN} Automatically deleting container $container_id...${CL}\"\n    pct destroy \"$container_id\" -f &\n    pid=$!\n    spinner $pid\n    [ $? -eq 0 ] && echo \"Container $container_id deleted.\" || whiptail --title \"Error\" --msgbox \"Failed to delete container $container_id.\" 10 60\n  else\n    read -p \"Delete container $container_id? (y/N): \" CONFIRM\n    if [[ \"$CONFIRM\" =~ ^[Yy]$ ]]; then\n      echo -e \"${BL}[Info]${GN} Deleting container $container_id...${CL}\"\n      pct destroy \"$container_id\" -f &\n      pid=$!\n      spinner $pid\n      [ $? -eq 0 ] && echo \"Container $container_id deleted.\" || whiptail --title \"Error\" --msgbox \"Failed to delete container $container_id.\" 10 60\n    else\n      echo -e \"${BL}[Info]${RD} Skipping container $container_id...${CL}\"\n    fi\n  fi\ndone\n\nheader_info\necho -e \"${GN}Deletion process completed.${CL}\\n\"\n"
  },
  {
    "path": "tools/pve/microcode.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____                                               __  ____                                __\n   / __ \\_________  ________  ______________  _____   /  |/  (_)_____________  _________  ____/ /__\n  / /_/ / ___/ __ \\/ ___/ _ \\/ ___/ ___/ __ \\/ ___/  / /|_/ / / ___/ ___/ __ \\/ ___/ __ \\/ __  / _ \\\n / ____/ /  / /_/ / /__/  __(__  |__  ) /_/ / /     / /  / / / /__/ /  / /_/ / /__/ /_/ / /_/ /  __/\n/_/   /_/   \\____/\\___/\\___/____/____/\\____/_/     /_/  /_/_/\\___/_/   \\____/\\___/\\____/\\__,_/\\___/\n\nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nmsg_info() { echo -ne \" ${HOLD} ${YW}$1...\"; }\nmsg_ok() { echo -e \"${BFR} ${CM} ${GN}$1${CL}\"; }\nmsg_error() { echo -e \"${BFR} ${CROSS} ${RD}$1${CL}\"; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"microcode\" \"pve\"\n\nheader_info\ncurrent_microcode=$(journalctl -k | grep -i 'microcode: Current revision:' | grep -oP 'Current revision: \\K0x[0-9a-f]+')\n[ -z \"$current_microcode\" ] && current_microcode=\"Not found.\"\n\nintel() {\n  if ! dpkg -s iucode-tool >/dev/null 2>&1; then\n    msg_info \"Installing iucode-tool (Intel microcode updater)\"\n    apt-get install -y iucode-tool &>/dev/null\n    msg_ok \"Installed iucode-tool\"\n  else\n    msg_ok \"Intel iucode-tool is already installed\"\n    sleep 1\n  fi\n\n  intel_microcode=$(curl -fsSL \"https://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode//\" | grep -o 'href=\"[^\"]*amd64.deb\"' | sed 's/href=\"//;s/\"//')\n  [ -z \"$intel_microcode\" ] && {\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No Microcode Found\" --msgbox \"It appears there were no microcode packages found\\n Try again later.\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  MICROCODE_MENU=()\n  MSG_MAX_LENGTH=0\n\n  while read -r TAG ITEM; do\n    OFFSET=2\n    ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n    MICROCODE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\n  done < <(echo \"$intel_microcode\")\n\n  microcode=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Current Microcode revision:${current_microcode}\" --radiolist \"\\nSelect a microcode package to install:\\n\" 16 $((MSG_MAX_LENGTH + 58)) 6 \"${MICROCODE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\n  [ -z \"$microcode\" ] && {\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No Microcode Selected\" --msgbox \"It appears that no microcode packages were selected\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  msg_info \"Downloading the Intel Processor Microcode Package $microcode\"\n  curl -fsSL \"http://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/$microcode\" -o $(basename \"http://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/$microcode\")\n  msg_ok \"Downloaded the Intel Processor Microcode Package $microcode\"\n\n  msg_info \"Installing $microcode (Patience)\"\n  dpkg -i $microcode &>/dev/null\n  msg_ok \"Installed $microcode\"\n\n  msg_info \"Cleaning up\"\n  rm $microcode\n  msg_ok \"Cleaned\"\n  echo -e \"\\nIn order to apply the changes, a system reboot will be necessary.\\n\"\n}\n\namd() {\n  amd_microcode=$(curl -fsSL \"https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode///\" | grep -o 'href=\"[^\"]*amd64.deb\"' | sed 's/href=\"//;s/\"//')\n\n  [ -z \"$amd_microcode\" ] && {\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No Microcode Found\" --msgbox \"It appears there were no microcode packages found\\n Try again later.\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  MICROCODE_MENU=()\n  MSG_MAX_LENGTH=0\n\n  while read -r TAG ITEM; do\n    OFFSET=2\n    ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n    MICROCODE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\n  done < <(echo \"$amd_microcode\")\n\n  microcode=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Current Microcode revision:${current_microcode}\" --radiolist \"\\nSelect a microcode package to install:\\n\" 16 $((MSG_MAX_LENGTH + 58)) 6 \"${MICROCODE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\n  [ -z \"$microcode\" ] && {\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No Microcode Selected\" --msgbox \"It appears that no microcode packages were selected\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  msg_info \"Downloading the AMD Processor Microcode Package $microcode\"\n  curl -fsSL \"https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode\" -o $(basename \"https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode\")\n  msg_ok \"Downloaded the AMD Processor Microcode Package $microcode\"\n\n  msg_info \"Installing $microcode (Patience)\"\n  dpkg -i $microcode &>/dev/null\n  msg_ok \"Installed $microcode\"\n\n  msg_info \"Cleaning up\"\n  rm $microcode\n  msg_ok \"Cleaned\"\n  echo -e \"\\nIn order to apply the changes, a system reboot will be necessary.\\n\"\n}\n\nif ! command -v pveversion >/dev/null 2>&1; then\n  header_info\n  msg_error \"No PVE Detected!\"\n  exit\nfi\n\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE Processor Microcode\" --yesno \"This will check for CPU microcode packages with the option to install. Proceed?\" 10 58\n\nmsg_info \"Checking CPU Vendor\"\ncpu=$(lscpu | grep -oP 'Vendor ID:\\s*\\K\\S+' | head -n 1)\nif [ \"$cpu\" == \"GenuineIntel\" ]; then\n  msg_ok \"${cpu} was detected\"\n  sleep 1\n  intel\nelif [ \"$cpu\" == \"AuthenticAMD\" ]; then\n  msg_ok \"${cpu} was detected\"\n  sleep 1\n  amd\nelse\n  msg_error \"${cpu} is not supported\"\n  exit\nfi\n"
  },
  {
    "path": "tools/pve/monitor-all.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nclear\ncat <<\"EOF\"\n    __  ___            _ __                ___    ____\n   /  |/  /___  ____  (_) /_____  _____   /   |  / / /\n  / /|_/ / __ \\/ __ \\/ / __/ __ \\/ ___/  / /| | / / /\n / /  / / /_/ / / / / / /_/ /_/ / /     / ___ |/ / /\n/_/  /_/\\____/_/ /_/_/\\__/\\____/_/     /_/  |_/_/_/\n\nEOF\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"monitor-all\" \"pve\"\n\nadd() {\n  echo -e \"\\n IMPORTANT: Tag-Based Monitoring Enabled\"\n  echo \"Only VMs and containers with the tag 'mon-restart' will be automatically restarted by this service.\"\n  echo\n  echo \"🔧 How to add the tag:\"\n  echo \"  → Proxmox Web UI: Go to VM/CT → Options → Tags → Add 'mon-restart'\"\n  echo \"  → CLI: qm set <vmid> -tags mon-restart\"\n  echo \"         pct set <ctid> -tags mon-restart\"\n  echo\n\n  while true; do\n    read -p \"This script will add Monitor All to Proxmox VE. Proceed (y/n)? \" yn\n    case $yn in\n    [Yy]*) break ;;\n    [Nn]*) exit ;;\n    *) echo \"Please answer yes or no.\" ;;\n    esac\n  done\n\n  cat <<'EOF' >/usr/local/bin/ping-instances.sh\n#!/usr/bin/env bash\n\n# Read excluded instances from command line arguments\nexcluded_instances=(\"$@\")\necho \"Excluded instances: ${excluded_instances[@]}\"\n\nwhile true; do\n\n  for instance in $(pct list | awk 'NR>1 {print $1}'; qm list | awk 'NR>1 {print $1}'); do\n    # Skip excluded instances\n    if [[ \" ${excluded_instances[@]} \" =~ \" ${instance} \" ]]; then\n      echo \"Skipping $instance because it is excluded\"\n      continue\n    fi\n\n    # Determine type and set config command\n    if pct status $instance >/dev/null 2>&1; then\n      type=\"ct\"\n      config_cmd=\"pct config\"\n    else\n      type=\"vm\"\n      config_cmd=\"qm config\"\n    fi\n\n    # Skip templates and onboot-disabled\n    onboot=$($config_cmd $instance | grep -q \"onboot: 0\" || ( ! $config_cmd $instance | grep -q \"onboot\" ) && echo \"true\" || echo \"false\")\n    template=$($config_cmd $instance | grep -q \"^template:\" && echo \"true\" || echo \"false\")\n\n    if [ \"$onboot\" == \"true\" ]; then\n      echo \"Skipping $instance because it is set not to boot\"\n      continue\n    elif [ \"$template\" == \"true\" ]; then\n      echo \"Skipping $instance because it is a template\"\n      continue\n    fi\n\n    # Check for mon-restart tag\n    has_tag=$($config_cmd $instance | grep -q \"tags:.*mon-restart\" && echo \"true\" || echo \"false\")\n    if [ \"$has_tag\" != \"true\" ]; then\n      echo \"Skipping $instance because it does not have 'mon-restart' tag\"\n      continue\n    fi\n\n    # Responsiveness check and restart if needed\n    if [ \"$type\" == \"vm\" ]; then\n      # Check if guest agent responds\n      if qm guest cmd $instance ping >/dev/null 2>&1; then\n        echo \"VM $instance is responsive via guest agent\"\n      else\n        echo \"$(date): VM $instance is not responding to agent ping, restarting...\"\n        if qm status $instance | grep -q \"status: running\"; then\n          qm stop $instance >/dev/null 2>&1\n          sleep 5\n        fi\n        qm start $instance >/dev/null 2>&1\n      fi\n    else\n      # Container: get IP and ping\n      IP=$(pct exec $instance ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)\n      if ! ping -c 1 $IP >/dev/null 2>&1; then\n        echo \"$(date): CT $instance is not responding, restarting...\"\n        pct stop $instance >/dev/null 2>&1\n        sleep 5\n        pct start $instance >/dev/null 2>&1\n      else\n        echo \"CT $instance is responsive\"\n      fi\n    fi\n  done\n\n  echo \"$(date): Pausing for 5 minutes...\"\n  sleep 300\n\ndone >/var/log/ping-instances.log 2>&1\nEOF\n\n  touch /var/log/ping-instances.log\n  chmod +x /usr/local/bin/ping-instances.sh\n\n  cat <<EOF >/etc/systemd/system/ping-instances.timer\n[Unit]\nDescription=Delay ping-instances.service by 5 minutes\n\n[Timer]\nOnBootSec=300\nOnUnitActiveSec=300\n\n[Install]\nWantedBy=timers.target\nEOF\n\n  cat <<EOF >/etc/systemd/system/ping-instances.service\n[Unit]\nDescription=Ping instances every 5 minutes and restart if necessary\nAfter=ping-instances.timer\nRequires=ping-instances.timer\n\n[Service]\nType=simple\n# To exclude specific instances, pass IDs to ExecStart, e.g.:\n# ExecStart=/usr/local/bin/ping-instances.sh 100 200\n# Instances must also have the 'mon-restart' tag to be monitored\n\nExecStart=/usr/local/bin/ping-instances.sh\nRestart=always\nStandardOutput=file:/var/log/ping-instances.log\nStandardError=file:/var/log/ping-instances.log\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n  systemctl daemon-reload\n  systemctl enable -q --now ping-instances.timer\n  systemctl enable -q --now ping-instances.service\n  clear\n  echo -e \"\\n Monitor All installed.\"\n  echo \"📄 To view logs: cat /var/log/ping-instances.log\"\n  echo \"⚙️  Make sure your VMs or containers have the 'mon-restart' tag to be monitored.\"\n}\n\nremove() {\n  systemctl disable -q --now ping-instances.timer\n  systemctl disable -q --now ping-instances.service\n  rm -f /etc/systemd/system/ping-instances.service\n  rm -f /etc/systemd/system/ping-instances.timer\n  rm -f /usr/local/bin/ping-instances.sh\n  rm -f /var/log/ping-instances.log\n  echo \"Monitor All removed from Proxmox VE\"\n}\n\nOPTIONS=(Add \"Add Monitor-All to Proxmox VE\"\n  Remove \"Remove Monitor-All from Proxmox VE\")\n\nCHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Monitor-All for Proxmox VE\" --menu \"Select an option:\" 10 58 2 \\\n  \"${OPTIONS[@]}\" 3>&1 1>&2 2>&3)\n\ncase $CHOICE in\n\"Add\") add ;;\n\"Remove\") remove ;;\n*)\n  echo \"Exiting...\"\n  exit 0\n  ;;\nesac\n"
  },
  {
    "path": "tools/pve/nic-offloading-fix.sh",
    "content": "#!/usr/bin/env bash\n\n# Creates a systemd service to disable NIC offloading features for Intel e1000e and e1000 interfaces\n# Author: rcastley\n# License: MIT\n\nYW=$(echo \"\\033[33m\")\nYWB=$'\\e[93m'\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nTAB=\"  \"\nCM=\"${TAB}✔️${TAB}\"\nCROSS=\"${TAB}✖️${TAB}\"\nINFO=\"${TAB}ℹ️${TAB}${CL}\"\nWARN=\"${TAB}⚠️${TAB}${CL}\"\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n\n    _   ____________   ____  __________                ___                ____  _            __    __\n   / | / /  _/ ____/  / __ \\/ __/ __/ /___  ____ _____/ (_)___  ____ _   / __ \\(_)________ _/ /_  / /__  _____\n  /  |/ // // /      / / / / /_/ /_/ / __ \\/ __ `/ __  / / __ \\/ __ `/  / / / / / ___/ __ `/ __ \\/ / _ \\/ ___/\n / /|  // // /___   / /_/ / __/ __/ / /_/ / /_/ / /_/ / / / / / /_/ /  / /_/ / (__  ) /_/ / /_/ / /  __/ /\n/_/ |_/___/\\____/   \\____/_/ /_/ /_/\\____/\\__,_/\\__,_/_/_/ /_/\\__, /  /_____/_/____/\\__,_/_.___/_/\\___/_/\n                                                             /____/\nEnhanced version supporting both e1000e and e1000 drivers\n\nEOF\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"nic-offloading-fix\" \"pve\"\n\nheader_info\n\nfunction msg_info() { echo -e \"${INFO} ${YW}${1}...${CL}\"; }\nfunction msg_ok() { echo -e \"${CM} ${GN}${1}${CL}\"; }\nfunction msg_error() { echo -e \"${CROSS} ${RD}${1}${CL}\"; }\nfunction msg_warn() { echo -e \"${WARN} ${YWB}${1}\"; }\n\n# Check for root privileges\nif [ \"$(id -u)\" -ne 0 ]; then\n  msg_error \"Error: This script must be run as root.\"\n  exit 104\nfi\n\nif ! command -v ethtool >/dev/null 2>&1; then\n  msg_info \"Installing ethtool\"\n  apt-get update &>/dev/null\n  apt-get install -y ethtool &>/dev/null || {\n    msg_error \"Failed to install ethtool. Exiting.\"\n    exit 237\n  }\n  msg_ok \"ethtool installed successfully\"\nfi\n\n# Get list of network interfaces using Intel e1000e or e1000 drivers\nINTERFACES=()\nCOUNT=0\n\nmsg_info \"Searching for Intel e1000e and e1000 interfaces\"\n\nfor device in /sys/class/net/*; do\n  interface=\"$(basename \"$device\")\" # or adjust the rest of the usages below, as mostly you'll use the path anyway\n  # Skip loopback interface and virtual interfaces\n  if [[ \"$interface\" != \"lo\" ]] && [[ ! \"$interface\" =~ ^(tap|fwbr|veth|vmbr|bonding_masters) ]]; then\n    # Check if the interface uses the e1000e or e1000 driver\n    driver=$(basename $(readlink -f /sys/class/net/$interface/device/driver 2>/dev/null) 2>/dev/null)\n\n    if [[ \"$driver\" == \"e1000e\" ]] || [[ \"$driver\" == \"e1000\" ]]; then\n      # Get MAC address for additional identification\n      mac=$(cat /sys/class/net/$interface/address 2>/dev/null)\n      INTERFACES+=(\"$interface\" \"Intel $driver NIC ($mac)\")\n      ((COUNT++))\n    fi\n  fi\ndone\n\n# Check if any Intel e1000e/e1000 interfaces were found\nif [ ${#INTERFACES[@]} -eq 0 ]; then\n  whiptail --title \"Error\" --msgbox \"No Intel e1000e or e1000 network interfaces found!\" 10 60\n  msg_error \"No Intel e1000e or e1000 network interfaces found! Exiting.\"\n  exit 236\nfi\n\nmsg_ok \"Found ${BL}$COUNT${GN} Intel e1000e/e1000 interfaces\"\n\n# Create a checklist for interface selection with all interfaces initially checked\nINTERFACES_CHECKLIST=()\nfor ((i = 0; i < ${#INTERFACES[@]}; i += 2)); do\n  INTERFACES_CHECKLIST+=(\"${INTERFACES[i]}\" \"${INTERFACES[i + 1]}\" \"ON\")\ndone\n\n# Show interface selection checklist\nSELECTED_INTERFACES=$(whiptail --backtitle \"Intel e1000e/e1000 NIC Offloading Disabler\" --title \"Network Interfaces\" \\\n  --separate-output --checklist \"Select Intel e1000e/e1000 network interfaces\\n(Space to toggle, Enter to confirm):\" 15 80 6 \\\n  \"${INTERFACES_CHECKLIST[@]}\" 3>&1 1>&2 2>&3)\n\nexitstatus=$?\nif [ $exitstatus != 0 ]; then\n  msg_info \"User canceled. Exiting.\"\n  exit 0\nfi\n\n# Check if any interfaces were selected\nif [ -z \"$SELECTED_INTERFACES\" ]; then\n  msg_error \"No interfaces selected. Exiting.\"\n  exit 0\nfi\n\n# Convert the selected interfaces into an array\nreadarray -t INTERFACE_ARRAY <<<\"$SELECTED_INTERFACES\"\n\n# Show the number of selected interfaces\nINTERFACE_COUNT=${#INTERFACE_ARRAY[@]}\n\n# Print selected interfaces with their driver types\nfor iface in \"${INTERFACE_ARRAY[@]}\"; do\n  driver=$(basename $(readlink -f /sys/class/net/$iface/device/driver 2>/dev/null) 2>/dev/null)\n  msg_ok \"Selected interface: ${BL}$iface${GN} (${BL}$driver${GN})\"\ndone\n\n# Ask for confirmation with the list of selected interfaces\nCONFIRMATION_MSG=\"You have selected the following interface(s):\\n\\n\"\nfor iface in \"${INTERFACE_ARRAY[@]}\"; do\n  SPEED=$(cat /sys/class/net/$iface/speed 2>/dev/null || echo \"Unknown\")\n  MAC=$(cat /sys/class/net/$iface/address 2>/dev/null)\n  DRIVER=$(basename $(readlink -f /sys/class/net/$iface/device/driver 2>/dev/null) 2>/dev/null)\n  CONFIRMATION_MSG+=\"- $iface (Driver: $DRIVER, MAC: $MAC, Speed: ${SPEED}Mbps)\\n\"\ndone\nCONFIRMATION_MSG+=\"\\nThis will create systemd service(s) to disable offloading features.\\n\\nProceed?\"\n\nif ! whiptail --backtitle \"Intel e1000e/e1000 NIC Offloading Disabler\" --title \"Confirmation\" \\\n  --yesno \"$CONFIRMATION_MSG\" 20 80; then\n  msg_info \"User canceled. Exiting.\"\n  exit 0\nfi\n\n# Loop through all selected interfaces and create services for each\nfor SELECTED_INTERFACE in \"${INTERFACE_ARRAY[@]}\"; do\n  # Get the driver type for this specific interface\n  DRIVER=$(basename $(readlink -f /sys/class/net/$SELECTED_INTERFACE/device/driver 2>/dev/null) 2>/dev/null)\n\n  # Create service name for this interface\n  SERVICE_NAME=\"disable-nic-offload-$SELECTED_INTERFACE.service\"\n  SERVICE_PATH=\"/etc/systemd/system/$SERVICE_NAME\"\n\n  # Create the service file with driver-specific optimizations\n  msg_info \"Creating systemd service for interface: ${BL}$SELECTED_INTERFACE${YW} (${BL}$DRIVER${YW})\"\n\n  # Start with the common part of the service file\n  cat >\"$SERVICE_PATH\" <<EOF\n[Unit]\nDescription=Disable NIC offloading for Intel $DRIVER interface $SELECTED_INTERFACE\nAfter=network.target\n\n[Service]\nType=oneshot\n# Disable all offloading features for Intel $DRIVER\nExecStart=/sbin/ethtool -K $SELECTED_INTERFACE gso off gro off tso off tx off rx off rxvlan off txvlan off sg off\nRemainAfterExit=true\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n  # Check if service file was created successfully\n  if [ ! -f \"$SERVICE_PATH\" ]; then\n    whiptail --title \"Error\" --msgbox \"Failed to create service file for $SELECTED_INTERFACE!\" 10 50\n    msg_error \"Failed to create service file for $SELECTED_INTERFACE! Skipping to next interface.\"\n    continue\n  fi\n\n  # Configure this service\n  {\n    echo \"25\"\n    sleep 0.2\n    # Reload systemd to recognize the new service\n    systemctl daemon-reload\n    echo \"50\"\n    sleep 0.2\n    # Start the service\n    systemctl start \"$SERVICE_NAME\"\n    echo \"75\"\n    sleep 0.2\n    # Enable the service to start on boot\n    systemctl enable \"$SERVICE_NAME\"\n    echo \"100\"\n    sleep 0.2\n  } | whiptail --backtitle \"Intel e1000e/e1000 NIC Offloading Disabler\" --gauge \"Configuring service for $SELECTED_INTERFACE...\" 10 80 0\n\n  # Individual service status\n  if systemctl is-active --quiet \"$SERVICE_NAME\"; then\n    SERVICE_STATUS=\"Active\"\n  else\n    SERVICE_STATUS=\"Inactive\"\n  fi\n\n  if systemctl is-enabled --quiet \"$SERVICE_NAME\"; then\n    BOOT_STATUS=\"Enabled\"\n  else\n    BOOT_STATUS=\"Disabled\"\n  fi\n\n  # Show individual service results\n  msg_ok \"Service for ${BL}$SELECTED_INTERFACE${GN} (${BL}$DRIVER${GN}) created and enabled!\"\n  msg_info \"${TAB}Service: ${BL}$SERVICE_NAME${YW}\"\n  msg_info \"${TAB}Status: ${BL}$SERVICE_STATUS${YW}\"\n  msg_info \"${TAB}Start on boot: ${BL}$BOOT_STATUS${YW}\"\ndone\n\n# Prepare summary of all interfaces\nSUMMARY_MSG=\"Services created successfully!\\n\\n\"\nSUMMARY_MSG+=\"Configured Interfaces:\\n\"\n\nfor iface in \"${INTERFACE_ARRAY[@]}\"; do\n  SERVICE_NAME=\"disable-nic-offload-$iface.service\"\n  DRIVER=$(basename $(readlink -f /sys/class/net/$iface/device/driver 2>/dev/null) 2>/dev/null)\n\n  if systemctl is-active --quiet \"$SERVICE_NAME\"; then\n    SVC_STATUS=\"Active\"\n  else\n    SVC_STATUS=\"Inactive\"\n  fi\n\n  if systemctl is-enabled --quiet \"$SERVICE_NAME\"; then\n    BOOT_SVC_STATUS=\"Enabled\"\n  else\n    BOOT_SVC_STATUS=\"Disabled\"\n  fi\n\n  SUMMARY_MSG+=\"- $iface ($DRIVER): $SVC_STATUS, Boot: $BOOT_SVC_STATUS\\n\"\ndone\n\n# Show summary results\nwhiptail --backtitle \"Intel e1000e/e1000 NIC Offloading Disabler\" --title \"Success\" --msgbox \"$SUMMARY_MSG\" 22 80\n\nmsg_ok \"Intel e1000e/e1000 optimization complete for ${#INTERFACE_ARRAY[@]} interface(s)!\"\n\n# Show verification commands\necho \"\"\nmsg_info \"Verification commands:\"\nfor iface in \"${INTERFACE_ARRAY[@]}\"; do\n  echo -e \"${TAB}${BL}ethtool -k $iface${CL} ${YW}# Check offloading status${CL}\"\n  echo -e \"${TAB}${BL}systemctl status disable-nic-offload-$iface.service${CL} ${YW}# Check service status${CL}\"\ndone\n\nexit 0\n"
  },
  {
    "path": "tools/pve/pbs-microcode.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: DonPablo1010 | Co-Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____                                               __  ____                                __\n   / __ \\_________  ________  ______________  _____   /  |/  (_)_____________  _________  ____/ /__\n  / /_/ / ___/ __ \\/ ___/ _ \\/ ___/ ___/ __ \\/ ___/  / /|_/ / / ___/ ___/ __ \\/ ___/ __ \\/ __  / _ \\\n / ____/ /  / /_/ / /__/  __(__  |__  ) /_/ / /     / /  / / / /__/ /  / /_/ / /__/ /_/ / /_/ /  __/\n/_/   /_/   \\____/\\___/\\___/____/____/\\____/_/     /_/  /_/_/\\___/_/   \\____/\\___/\\____/\\__,_/\\___/\n\n              Proxmox Backup Server Processor Microcode Updater\nEOF\n}\n\n# Color definitions\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nmsg_info() { echo -ne \" ${HOLD} ${YW}$1...\"; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pbs-microcode\" \"pve\"\nmsg_ok() { echo -e \"${BFR} ${CM} ${GN}$1${CL}\"; }\nmsg_error() { echo -e \"${BFR} ${CROSS} ${RD}$1${CL}\"; }\n\nheader_info\n\n# Check if running on bare metal using systemd-detect-virt.\nvirt=$(systemd-detect-virt)\nif [ \"$virt\" != \"none\" ]; then\n  msg_error \"This script must be run on bare metal. Detected virtual environment: $virt\"\n  exit 232\nfi\n\n# Attempt to obtain the current loaded microcode revision\ncurrent_microcode=$(journalctl -k | grep -i 'microcode: Current revision:' | grep -oP 'Current revision: \\K0x[0-9a-f]+')\n[ -z \"$current_microcode\" ] && current_microcode=\"Not found.\"\n\nintel() {\n  if ! dpkg -s iucode-tool >/dev/null 2>&1; then\n    msg_info \"Installing iucode-tool (Intel microcode updater)\"\n    apt-get install -y iucode-tool &>/dev/null\n    msg_ok \"Installed iucode-tool\"\n  else\n    msg_ok \"Intel iucode-tool is already installed\"\n    sleep 1\n  fi\n\n  intel_microcode=$(curl -fsSL \"https://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/\" | grep -o 'href=\"[^\"]*amd64.deb\"' | sed 's/href=\"//;s/\"//')\n  [ -z \"$intel_microcode\" ] && {\n    whiptail --backtitle \"Proxmox Backup Server Helper Scripts\" --title \"No Microcode Found\" --msgbox \"No microcode packages were found.\\nTry again later.\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  MICROCODE_MENU=()\n  MSG_MAX_LENGTH=0\n\n  while read -r TAG ITEM; do\n    OFFSET=2\n    ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))\n    MICROCODE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\n  done < <(echo \"$intel_microcode\")\n\n  microcode=$(whiptail --backtitle \"Proxmox Backup Server Helper Scripts\" \\\n    --title \"Current Microcode Revision: ${current_microcode}\" \\\n    --radiolist \"\\nSelect a microcode package to install:\\n\" \\\n    16 $((MSG_MAX_LENGTH + 58)) 6 \"${MICROCODE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\n  [ -z \"$microcode\" ] && {\n    whiptail --backtitle \"Proxmox Backup Server Helper Scripts\" --title \"No Microcode Selected\" --msgbox \"No microcode package was selected.\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  msg_info \"Downloading Intel processor microcode package $microcode\"\n  curl -fsSL \"http://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/$microcode\" -o $(basename \"http://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/$microcode\")\n  msg_ok \"Downloaded Intel processor microcode package $microcode\"\n\n  msg_info \"Installing $microcode (this might take a while)\"\n  dpkg -i $microcode &>/dev/null\n  msg_ok \"Installed $microcode\"\n\n  msg_info \"Cleaning up\"\n  rm $microcode\n  msg_ok \"Clean up complete\"\n  echo -e \"\\nA system reboot is required to apply the changes.\\n\"\n}\n\namd() {\n  amd_microcode=$(curl -fsSL \"https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/\" | grep -o 'href=\"[^\"]*amd64.deb\"' | sed 's/href=\"//;s/\"//')\n\n  [ -z \"$amd_microcode\" ] && {\n    whiptail --backtitle \"Proxmox Backup Server Helper Scripts\" --title \"No Microcode Found\" --msgbox \"No microcode packages were found.\\nTry again later.\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  MICROCODE_MENU=()\n  MSG_MAX_LENGTH=0\n\n  while read -r TAG ITEM; do\n    OFFSET=2\n    ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))\n    MICROCODE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\n  done < <(echo \"$amd_microcode\")\n\n  microcode=$(whiptail --backtitle \"Proxmox Backup Server Helper Scripts\" \\\n    --title \"Current Microcode Revision: ${current_microcode}\" \\\n    --radiolist \"\\nSelect a microcode package to install:\\n\" \\\n    16 $((MSG_MAX_LENGTH + 58)) 6 \"${MICROCODE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\n  [ -z \"$microcode\" ] && {\n    whiptail --backtitle \"Proxmox Backup Server Helper Scripts\" --title \"No Microcode Selected\" --msgbox \"No microcode package was selected.\" 10 68\n    msg_info \"Exiting\"\n    sleep 1\n    msg_ok \"Done\"\n    exit\n  }\n\n  msg_info \"Downloading AMD processor microcode package $microcode\"\n  curl -fsSL \"https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode\" -o $(basename \"https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode\")\n  msg_ok \"Downloaded AMD processor microcode package $microcode\"\n\n  msg_info \"Installing $microcode (this might take a while)\"\n  dpkg -i $microcode &>/dev/null\n  msg_ok \"Installed $microcode\"\n\n  msg_info \"Cleaning up\"\n  rm $microcode\n  msg_ok \"Clean up complete\"\n  echo -e \"\\nA system reboot is required to apply the changes.\\n\"\n}\n\n# Check if this is a Proxmox Backup Server by verifying the presence of the datastore config.\nif [ ! -f /etc/proxmox-backup/user.cfg ]; then\n  header_info\n  msg_error \"Proxmox Backup Server not detected!\"\n  exit\nfi\n\nwhiptail --backtitle \"Proxmox Backup Server Helper Scripts\" \\\n  --title \"Proxmox Backup Server Processor Microcode\" \\\n  --yesno \"This script searches for CPU microcode packages and offers the option to install them.\\nProceed?\" 10 68\n\nmsg_info \"Checking CPU vendor\"\ncpu=$(lscpu | grep -oP 'Vendor ID:\\s*\\K\\S+' | head -n 1)\nif [ \"$cpu\" == \"GenuineIntel\" ]; then\n  msg_ok \"${cpu} detected\"\n  sleep 1\n  intel\nelif [ \"$cpu\" == \"AuthenticAMD\" ]; then\n  msg_ok \"${cpu} detected\"\n  sleep 1\n  amd\nelse\n  msg_error \"CPU vendor ${cpu} is not supported\"\n  exit\nfi\n"
  },
  {
    "path": "tools/pve/pbs3-upgrade.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n    ____  ____ __________    __  ______  __________  ___    ____  ______\n   / __ \\/ __ ) ___/__  /   / / / / __ \\/ ____/ __ \\/   |  / __ \\/ ____/\n  / /_/ / __  \\__ \\ /_ <   / / / / /_/ / / __/ /_/ / /| | / / / / __/\n / ____/ /_/ /__/ /__/ /  / /_/ / ____/ /_/ / _, _/ ___ |/ /_/ / /___\n/_/   /_____/____/____/   \\____/_/    \\____/_/ |_/_/  |_/_____/_____/\n\nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nmsg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nmsg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pbs3-upgrade\" \"pve\"\n\nstart_routines() {\n  header_info\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS 2 BACKUP\" --menu \"\\nMake a backup of /etc/proxmox-backup to ensure that in the worst case, any relevant configuration can be recovered?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Backing up Proxmox Backup Server 2\"\n    tar czf \"pbs2-etc-backup-$(date -I).tar.gz\" -C \"/etc\" \"proxmox-backup\"\n    msg_ok \"Backed up Proxmox Backup Server 2\"\n    ;;\n  no)\n    msg_error \"Selected no to Backing up Proxmox Backup Server 2\"\n    ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS 3 SOURCES\" --menu \"This will set the correct sources to update and install Proxmox Backup Server 3.\\n \\nChange to Proxmox Backup Server 3 sources?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Changing to Proxmox Backup Server 3 Sources\"\n    cat <<EOF >/etc/apt/sources.list\ndeb http://deb.debian.org/debian bookworm main contrib\ndeb http://deb.debian.org/debian bookworm-updates main contrib\ndeb http://security.debian.org/debian-security bookworm-security main contrib\nEOF\n    msg_ok \"Changed to Proxmox Backup Server 3 Sources\"\n    ;;\n  no)\n    msg_error \"Selected no to Correcting Proxmox Backup Server 3 Sources\"\n    ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS3-ENTERPRISE\" --menu \"The 'pbs-enterprise' repository is only available to users who have purchased a Proxmox VE subscription.\\n \\nDisable 'pbs-enterprise' repository?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Disabling 'pbs-enterprise' repository\"\n    cat <<EOF >/etc/apt/sources.list.d/pbs-enterprise.list\n# deb https://enterprise.proxmox.com/debian/pbs bookworm pbs-enterprise\nEOF\n    msg_ok \"Disabled 'pbs-enterprise' repository\"\n    ;;\n  no)\n    msg_error \"Selected no to Disabling 'pbs-enterprise' repository\"\n    ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS3-NO-SUBSCRIPTION\" --menu \"The 'pbs-no-subscription' repository provides access to all of the open-source components of Proxmox Backup Server.\\n \\nEnable 'pbs-no-subscription' repository?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Enabling 'pbs-no-subscription' repository\"\n    cat <<EOF >/etc/apt/sources.list.d/pbs-install-repo.list\ndeb http://download.proxmox.com/debian/pbs bookworm pbs-no-subscription\nEOF\n    msg_ok \"Enabled 'pbs-no-subscription' repository\"\n    ;;\n  no)\n    msg_error \"Selected no to Enabling 'pbs-no-subscription' repository\"\n    ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS3 TEST\" --menu \"The 'pbstest' repository can give advanced users access to new features and updates before they are officially released.\\n \\nAdd (Disabled) 'pbstest' repository?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Adding 'pbstest' repository and set disabled\"\n    cat <<EOF >/etc/apt/sources.list.d/pbstest-for-beta.list\n# deb http://download.proxmox.com/debian/pbs bookworm pbstest\nEOF\n    msg_ok \"Added 'pbstest' repository\"\n    ;;\n  no)\n    msg_error \"Selected no to Adding 'pbstest' repository\"\n    ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS 3 UPDATE\" --menu \"\\nUpdate to Proxmox Backup Server 3 now?\" 11 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Updating to Proxmox Backup Server 3 (Patience)\"\n    apt-get update\n    DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::=\"--force-confold\" dist-upgrade -y\n    msg_ok \"Updated to Proxmox Backup Server 3\"\n    ;;\n  no)\n    msg_error \"Selected no to Updating to Proxmox Backup Server 3\"\n    ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"REBOOT\" --menu \"\\nReboot Proxmox Backup Server 3 now? (recommended)\" 11 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Rebooting Proxmox Backup Server 3\"\n    sleep 2\n    msg_ok \"Completed Install Routines\"\n    reboot\n    ;;\n  no)\n    msg_error \"Selected no to Rebooting Proxmox Backup Server 3 (Reboot recommended)\"\n    msg_ok \"Completed Install Routines\"\n    ;;\n  esac\n}\n\nheader_info\nwhile true; do\n  read -p \"Start the Update to Proxmox Backup Server 3 Script (y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*)\n    clear\n    exit\n    ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nstart_routines\n"
  },
  {
    "path": "tools/pve/pbs4-upgrade.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n    ____  ____ _____ __ __     __  __                           __   \n   / __ \\/ __ ) ___// // /    / / / /___  ____ __________ _____/ /__ \n  / /_/ / __  \\__ \\/ // /_   / / / / __ \\/ __ `/ ___/ __ `/ __  / _ \\\n / ____/ /_/ /__/ /__  __/  / /_/ / /_/ / /_/ / /  / /_/ / /_/ /  __/\n/_/   /_____/____/  /_/     \\____/ .___/\\__, /_/   \\__,_/\\__,_/\\___/ \n                                /_/    /____/                        \nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nmsg_info() { echo -ne \" ${HOLD} ${YW}$1...\"; }\nmsg_ok() { echo -e \"${BFR} ${CM} ${GN}$1${CL}\"; }\nmsg_error() { echo -e \"${BFR} ${CROSS} ${RD}$1${CL}\"; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pbs4-upgrade\" \"pve\"\n\nstart_routines() {\n  header_info\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS 3 BACKUP\" --menu \\\n    \"\\nMake a backup of /etc/proxmox-backup to ensure recovery in worst case?\" 14 58 2 \\\n    \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Backing up Proxmox Backup Server 3\"\n    tar czf \"pbs3-etc-backup-$(date -I).tar.gz\" -C \"/etc\" \"proxmox-backup\"\n    msg_ok \"Backed up Proxmox Backup Server 3\"\n    ;;\n  no) msg_error \"Selected no to Backup\" ;;\n  esac\n\n  # --- Debian 13 Sources ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS 4 SOURCES\" --menu \\\n    \"Switch to Debian 13 (Trixie) sources for PBS 4?\" 14 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Switching to Debian 13 (Trixie) Sources\"\n    rm -f /etc/apt/sources.list.d/*.list\n    if [ -f /etc/apt/sources.list ]; then\n      sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list\n    fi\n    cat >/etc/apt/sources.list.d/debian.sources <<EOF\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://security.debian.org/debian-security\nSuites: trixie-security\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie-updates\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    msg_ok \"Configured Debian 13 (Trixie) Sources\"\n    ;;\n  no) msg_error \"Selected no to Sources update\" ;;\n  esac\n\n  # --- Enterprise Repo ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS4-ENTERPRISE\" --menu \\\n    \"Add 'pbs-enterprise' repository (for subscription users)?\" 14 58 2 \"yes\" \" \" \"no\" \" \" \\\n    3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Adding 'pbs-enterprise' repository\"\n    cat >/etc/apt/sources.list.d/pbs-enterprise.sources <<EOF\nTypes: deb\nURIs: https://enterprise.proxmox.com/debian/pbs\nSuites: trixie\nComponents: pbs-enterprise\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n    msg_ok \"Added 'pbs-enterprise' repository\"\n    ;;\n  no) msg_error \"Skipped enterprise repo\" ;;\n  esac\n\n  # --- No-Subscription Repo ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS4-NO-SUBSCRIPTION\" --menu \\\n    \"Enable 'pbs-no-subscription' repository?\" 14 58 2 \"yes\" \" \" \"no\" \" \" \\\n    3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Adding 'pbs-no-subscription' repository\"\n    cat >/etc/apt/sources.list.d/proxmox.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pbs\nSuites: trixie\nComponents: pbs-no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n    msg_ok \"Added 'pbs-no-subscription' repository\"\n    ;;\n  no) msg_error \"Skipped no-subscription repo\" ;;\n  esac\n\n  # --- Test Repo ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS4 TEST\" --menu \\\n    \"Add 'pbs-test' repository (disabled by default)?\" 14 58 2 \"yes\" \" \" \"no\" \" \" \\\n    3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Adding 'pbs-test' repository (disabled)\"\n    cat >/etc/apt/sources.list.d/pbs-test.sources <<EOF\n# Types: deb\n# URIs: http://download.proxmox.com/debian/pbs\n# Suites: trixie\n# Components: pbs-test\n# Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n    msg_ok \"Added 'pbs-test' repository\"\n    ;;\n  no) msg_error \"Skipped test repo\" ;;\n  esac\n\n  # --- Upgrade ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS 4 UPGRADE\" --menu \\\n    \"\\nUpgrade to Proxmox Backup Server 4 now?\" 11 58 2 \"yes\" \" \" \"no\" \" \" \\\n    3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Upgrading to Proxmox Backup Server 4 (Patience)\"\n    apt update\n    DEBIAN_FRONTEND=noninteractive apt -o Dpkg::Options::=\"--force-confold\" dist-upgrade -y\n    msg_ok \"System upgraded to PBS 4\"\n    ;;\n  no) msg_error \"Selected no to upgrade\" ;;\n  esac\n\n  # --- Reboot ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"REBOOT\" --menu \\\n    \"\\nReboot Proxmox Backup Server 4 now? (recommended)\" 11 58 2 \"yes\" \" \" \"no\" \" \" \\\n    3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Rebooting PBS 4\"\n    sleep 2\n    msg_ok \"Upgrade Complete\"\n    reboot\n    ;;\n  no)\n    msg_error \"Selected no to Reboot (Reboot recommended)\"\n    msg_ok \"Upgrade Complete\"\n    ;;\n  esac\n}\n\nheader_info\nwhile true; do\n  read -rp \"Start the Upgrade to Proxmox Backup Server 4 Script (y/n)? \" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*)\n    clear\n    exit\n    ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nstart_routines\n"
  },
  {
    "path": "tools/pve/post-install-hook-examples.sh",
    "content": "#!/usr/bin/env bash\n# ============================================================================\n#  Community-Scripts ProxmoxVE — Post-Install Hook: Example Library\n# ----------------------------------------------------------------------------\n#  This file is NOT meant to be executed as-is.\n#  It is a collection of complete, copy-pasteable example hooks for the\n#  optional `var_post_install` feature in build.func.\n#\n#  HOW IT WORKS\n#  ------------\n#  In the ct/*.sh CT scripts (or via Advanced Settings → Step 28) you can\n#  point `var_post_install` to an absolute path on the Proxmox HOST, e.g.:\n#\n#      # in /root/.community-scripts/default.vars\n#      var_post_install=/opt/community-scripts/hooks/notify.sh\n#\n#      # OR per-app, in app.vars\n#      var_post_install=/opt/community-scripts/hooks/vaultwarden-postprovision.sh\n#\n#      # OR interactively in the Advanced Settings whiptail (Step 28).\n#\n#  The hook runs ON THE PROXMOX HOST (NOT inside the LXC) as root,\n#  AFTER the container is fully provisioned, started and the description\n#  is set. stdout/stderr is captured to:\n#\n#      /var/log/community-scripts/post-install-<CTID>.log\n#\n#  AVAILABLE ENV VARIABLES\n#  -----------------------\n#    APP        - Pretty name (e.g. \"Vaultwarden\")\n#    NSAPP      - Slug / lowercase  (e.g. \"vaultwarden\")\n#    CTID       - Numeric container ID (e.g. \"103\")\n#    IP         - IPv4 address of the LXC (e.g. \"192.168.1.50\")\n#    HN         - Hostname (e.g. \"vaultwarden\")\n#    STORAGE    - Storage where the rootfs lives (e.g. \"local-lvm\")\n#    BRG        - Bridge (e.g. \"vmbr0\")\n#\n#  GENERAL TIPS\n#  ------------\n#  - Use `set -euo pipefail` so failures actually surface.\n#  - Use `|| true` on best-effort steps you do not want to abort the hook.\n#  - The file just needs to be a valid script. `+x` is optional — it is\n#    invoked via `bash <path>`. Shebang is honored only if you call it\n#    yourself; otherwise the shebang line is purely cosmetic.\n#  - If the hook exits non-zero, the user gets a whiptail popup with the\n#    last 15 log lines. The LXC creation itself is NOT rolled back.\n#  - Keep hooks idempotent — they may be re-run if you recreate a CT.\n#\n#  HOW TO USE THIS FILE\n#  --------------------\n#    1. Copy ONE example block (between the BEGIN/END markers) into a new\n#       file on the Proxmox host, e.g. /opt/community-scripts/hooks/notify.sh\n#    2. chmod +x /opt/community-scripts/hooks/notify.sh   (optional)\n#    3. Set var_post_install in default.vars / app.vars or pick the path\n#       in Advanced Settings.\n# ============================================================================\n\n# ============================================================================\n# ▼▼▼ EXAMPLE 1 — BEGIN ▼▼▼\n# ----------------------------------------------------------------------------\n#  Name        : minimal-logger.sh\n#  Purpose     : Append every newly created LXC to a single CSV-ish log.\n#  Difficulty  : ⭐ Beginner\n#  Side effects: Writes to /var/log/community-scripts/created-lxcs.log\n#  Use case    : You just want a paper trail of \"what got created when\".\n# ============================================================================\n#!/usr/bin/env bash\nset -euo pipefail\n\nLOG_DIR=\"/var/log/community-scripts\"\nLOG_FILE=\"${LOG_DIR}/created-lxcs.log\"\n\nmkdir -p \"$LOG_DIR\"\n\n# Header on first use\nif [[ ! -s \"$LOG_FILE\" ]]; then\n  echo \"timestamp;ctid;app;hostname;ip;bridge;storage\" >\"$LOG_FILE\"\nfi\n\nprintf '%s;%s;%s;%s;%s;%s;%s\\n' \\\n  \"$(date -Iseconds)\" \\\n  \"${CTID}\" \\\n  \"${APP}\" \\\n  \"${HN}\" \\\n  \"${IP}\" \\\n  \"${BRG}\" \\\n  \"${STORAGE}\" \\\n  >>\"$LOG_FILE\"\n\necho \"Logged ${APP} (CTID=${CTID}) to ${LOG_FILE}\"\n# ▲▲▲ EXAMPLE 1 — END ▲▲▲\n\n# ============================================================================\n# ▼▼▼ EXAMPLE 2 — BEGIN ▼▼▼\n# ----------------------------------------------------------------------------\n#  Name        : discord-gotify-notify.sh\n#  Purpose     : Send a rich Discord embed AND a Gotify push notification\n#                whenever a new LXC is provisioned.\n#  Difficulty  : ⭐⭐ Intermediate\n#  Requires    : curl on the host (default), reachable webhook URLs.\n#  Side effects: Outbound HTTPS to Discord + your Gotify server.\n# ============================================================================\n#!/usr/bin/env bash\nset -euo pipefail\n\n# --- CONFIG (edit me) -------------------------------------------------------\nDISCORD_WEBHOOK=\"https://discord.com/api/webhooks/XXXXXXXX/YYYYYYYY\"\nGOTIFY_URL=\"https://gotify.example.com\"\nGOTIFY_TOKEN=\"AbCdEfGhIjKlMnO\"\nGOTIFY_PRIORITY=5\n# ----------------------------------------------------------------------------\n\n# Resolve the Proxmox node's hostname for context\nNODE=\"$(hostname -s)\"\nTS=\"$(date -Iseconds)\"\n\n# --- Discord embed ----------------------------------------------------------\nread -r -d '' DISCORD_PAYLOAD <<JSON || true\n{\n  \"username\": \"Proxmox - ${NODE}\",\n  \"avatar_url\": \"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png\",\n  \"embeds\": [{\n    \"title\": \"✅ ${APP} LXC created\",\n    \"description\": \"A new community-script LXC has been provisioned on **${NODE}**.\",\n    \"color\": 3066993,\n    \"timestamp\": \"${TS}\",\n    \"fields\": [\n      {\"name\": \"CTID\",     \"value\": \"${CTID}\",    \"inline\": true},\n      {\"name\": \"Hostname\", \"value\": \"${HN}\",      \"inline\": true},\n      {\"name\": \"App\",      \"value\": \"${APP}\",     \"inline\": true},\n      {\"name\": \"IP\",       \"value\": \"${IP}\",      \"inline\": true},\n      {\"name\": \"Bridge\",   \"value\": \"${BRG}\",     \"inline\": true},\n      {\"name\": \"Storage\",  \"value\": \"${STORAGE}\", \"inline\": true}\n    ],\n    \"footer\": {\"text\": \"community-scripts.org\"}\n  }]\n}\nJSON\n\ncurl -fsS --max-time 10 \\\n  -H \"Content-Type: application/json\" \\\n  -X POST \"$DISCORD_WEBHOOK\" \\\n  --data \"$DISCORD_PAYLOAD\" \\\n  >/dev/null ||\n  echo \"WARN: Discord webhook failed (non-fatal)\"\n\n# --- Gotify push ------------------------------------------------------------\ncurl -fsS --max-time 10 \\\n  -H \"X-Gotify-Key: ${GOTIFY_TOKEN}\" \\\n  -F \"title=Proxmox: ${APP} LXC created\" \\\n  -F \"message=CTID=${CTID}  IP=${IP}  HN=${HN}  on ${NODE}\" \\\n  -F \"priority=${GOTIFY_PRIORITY}\" \\\n  \"${GOTIFY_URL}/message\" \\\n  >/dev/null ||\n  echo \"WARN: Gotify push failed (non-fatal)\"\n\necho \"Notifications dispatched for CTID=${CTID}\"\n# ▲▲▲ EXAMPLE 2 — END ▲▲▲\n\n# ============================================================================\n# ▼▼▼ EXAMPLE 3 — BEGIN ▼▼▼\n# ----------------------------------------------------------------------------\n#  Name        : auto-pool-tags-backup.sh\n#  Purpose     : Add the new LXC to a Proxmox pool, append cluster-wide tags,\n#                register a DNS record in pi-hole, and trigger an immediate\n#                snapshot backup to a configured storage.\n#  Difficulty  : ⭐⭐⭐ Advanced\n#  Requires    : pvesh, pct, vzdump (host-side; available by default on PVE),\n#                a reachable pi-hole admin API.\n# ============================================================================\n#!/usr/bin/env bash\nset -euo pipefail\n\n# --- CONFIG (edit me) -------------------------------------------------------\nTARGET_POOL=\"auto-lxc\"\nEXTRA_TAGS=(\"auto-provisioned\" \"${NSAPP}\") # community-script tag is set by build.func\nBACKUP_STORAGE=\"pbs-main\"                  # set to \"\" to skip initial backup\nPIHOLE_HOST=\"192.168.1.5\"\nPIHOLE_PASSWORD=\"changeme\" # web-UI password\nDNS_DOMAIN=\"lan\"           # FQDN will be ${HN}.${DNS_DOMAIN}\n# ----------------------------------------------------------------------------\n\n# 1) Ensure the pool exists, then attach the CT\nif ! pvesh get \"/pools/${TARGET_POOL}\" >/dev/null 2>&1; then\n  echo \"Creating pool: ${TARGET_POOL}\"\n  pvesh create /pools --poolid \"${TARGET_POOL}\" --comment \"Auto-created by post-install hook\" || true\nfi\necho \"Adding CTID=${CTID} to pool=${TARGET_POOL}\"\npvesh set \"/pools/${TARGET_POOL}\" --vms \"${CTID}\" || echo \"WARN: pool attach failed (non-fatal)\"\n\n# 2) Merge new tags with existing ones (preserve community-script etc.)\nCURRENT_TAGS=\"$(pct config \"${CTID}\" | awk -F': ' '/^tags:/{print $2}')\"\ndeclare -A TAG_SET\nIFS=';' read -r -a CUR_ARR <<<\"${CURRENT_TAGS:-}\"\nfor t in \"${CUR_ARR[@]}\"; do [[ -n \"$t\" ]] && TAG_SET[\"$t\"]=1; done\nfor t in \"${EXTRA_TAGS[@]}\"; do [[ -n \"$t\" ]] && TAG_SET[\"$t\"]=1; done\nNEW_TAGS=\"$(\n  IFS=';'\n  echo \"${!TAG_SET[*]}\"\n)\"\necho \"Setting tags: ${NEW_TAGS}\"\npct set \"${CTID}\" --tags \"${NEW_TAGS}\" || echo \"WARN: tag update failed (non-fatal)\"\n\n# 3) Register DNS in pi-hole (custom DNS record)\nFQDN=\"${HN}.${DNS_DOMAIN}\"\necho \"Registering DNS: ${FQDN} → ${IP} on pi-hole ${PIHOLE_HOST}\"\nSID=\"$(curl -fsS --max-time 5 \\\n  -d \"pw=${PIHOLE_PASSWORD}\" \\\n  \"http://${PIHOLE_HOST}/api/auth\" 2>/dev/null |\n  sed -nE 's/.*\"sid\":\"([^\"]+)\".*/\\1/p' || true)\"\n\nif [[ -n \"${SID}\" ]]; then\n  curl -fsS --max-time 5 -X PUT \\\n    -H \"Content-Type: application/json\" \\\n    -H \"sid: ${SID}\" \\\n    -d \"{\\\"hosts\\\":[\\\"${IP} ${FQDN}\\\"]}\" \\\n    \"http://${PIHOLE_HOST}/api/config/dns/hosts\" >/dev/null ||\n    echo \"WARN: pi-hole DNS update failed (non-fatal)\"\n  curl -fsS --max-time 5 -X DELETE -H \"sid: ${SID}\" \"http://${PIHOLE_HOST}/api/auth\" >/dev/null || true\nelse\n  echo \"WARN: could not obtain pi-hole session (skipping DNS)\"\nfi\n\n# 4) Initial backup (best-effort, can take a few minutes)\nif [[ -n \"${BACKUP_STORAGE}\" ]]; then\n  if pvesh get \"/storage/${BACKUP_STORAGE}\" >/dev/null 2>&1; then\n    echo \"Triggering initial backup of CTID=${CTID} to ${BACKUP_STORAGE}\"\n    vzdump \"${CTID}\" \\\n      --storage \"${BACKUP_STORAGE}\" \\\n      --mode snapshot \\\n      --compress zstd \\\n      --notes-template \"Initial backup of ${APP} (CTID=${CTID})\" \\\n      --notification-mode auto ||\n      echo \"WARN: initial backup failed (non-fatal)\"\n  else\n    echo \"Backup storage '${BACKUP_STORAGE}' not found — skipping.\"\n  fi\nfi\n\necho \"Post-provision routine complete for ${APP} (CTID=${CTID})\"\n# ▲▲▲ EXAMPLE 3 — END ▲▲▲\n\n# ============================================================================\n# ▼▼▼ EXAMPLE 4 — BEGIN ▼▼▼\n# ----------------------------------------------------------------------------\n#  Name        : inject-ssh-and-monitoring.sh\n#  Purpose     : Push the host's admin SSH key into the new LXC, install the\n#                Beszel monitoring agent inside the container, and register\n#                an Uptime-Kuma HTTP push monitor for the LXC's IP.\n#  Difficulty  : ⭐⭐⭐ Advanced\n#  Requires    : pct (host), curl (inside LXC), reachable Beszel hub +\n#                Uptime-Kuma push URL.\n# ============================================================================\n#!/usr/bin/env bash\nset -euo pipefail\n\n# --- CONFIG (edit me) -------------------------------------------------------\nADMIN_KEY=\"/root/.ssh/admin_ed25519.pub\"\nBESZEL_HUB_URL=\"http://192.168.1.10:8090\"\nBESZEL_AGENT_KEY=\"ssh-ed25519 AAAA... beszel@hub\" # public key of the hub\nUPTIME_KUMA_PUSH_BASE=\"http://uptime.lan/api/push/abc123\"\n# ----------------------------------------------------------------------------\n\n# 1) Inject the admin SSH key\nif [[ -f \"${ADMIN_KEY}\" ]]; then\n  echo \"Pushing admin SSH key into CTID=${CTID}\"\n  pct exec \"${CTID}\" -- mkdir -p /root/.ssh\n  pct exec \"${CTID}\" -- chmod 700 /root/.ssh\n  pct push \"${CTID}\" \"${ADMIN_KEY}\" /root/.ssh/authorized_keys\n  pct exec \"${CTID}\" -- chmod 600 /root/.ssh/authorized_keys\nelse\n  echo \"WARN: ${ADMIN_KEY} not found on host — skipping SSH key injection\"\nfi\n\n# 2) Wait for outbound networking inside the CT (max 30 s)\necho \"Waiting for network inside CTID=${CTID}…\"\nfor _ in $(seq 1 30); do\n  if pct exec \"${CTID}\" -- bash -c 'getent hosts deb.debian.org >/dev/null 2>&1'; then\n    break\n  fi\n  sleep 1\ndone\n\n# 3) Install Beszel agent inside the LXC\necho \"Installing Beszel agent inside CTID=${CTID}\"\npct exec \"${CTID}\" -- bash -s <<'AGENT_INSTALL' || echo \"WARN: Beszel install failed\"\nset -euo pipefail\nARCH=\"$(uname -m)\"\ncase \"$ARCH\" in\n  x86_64)   ARCH_TAG=amd64 ;;\n  aarch64)  ARCH_TAG=arm64 ;;\n  *) echo \"Unsupported arch: $ARCH\"; exit 1 ;;\nesac\nTMP=$(mktemp -d)\ncd \"$TMP\"\ncurl -fsSL \"https://github.com/henrygd/beszel/releases/latest/download/beszel-agent_linux_${ARCH_TAG}.tar.gz\" \\\n  | tar -xz\ninstall -m 0755 beszel-agent /usr/local/bin/beszel-agent\n\ncat >/etc/systemd/system/beszel-agent.service <<UNIT\n[Unit]\nDescription=Beszel Agent\nAfter=network-online.target\nWants=network-online.target\n[Service]\nEnvironment=\"PORT=45876\"\nEnvironment=\"KEY=__KEY_PLACEHOLDER__\"\nExecStart=/usr/local/bin/beszel-agent\nRestart=always\n[Install]\nWantedBy=multi-user.target\nUNIT\nAGENT_INSTALL\n\n# Inject the configured public key into the unit file (avoids quoting hell)\npct exec \"${CTID}\" -- sed -i \"s|__KEY_PLACEHOLDER__|${BESZEL_AGENT_KEY}|\" \\\n  /etc/systemd/system/beszel-agent.service\n\npct exec \"${CTID}\" -- systemctl daemon-reload\npct exec \"${CTID}\" -- systemctl enable --now beszel-agent.service ||\n  echo \"WARN: could not start beszel-agent\"\n\n# 4) Register an Uptime-Kuma push monitor (host-side, just sends one ping)\necho \"Pinging Uptime-Kuma push monitor for ${HN}\"\ncurl -fsS --max-time 5 \\\n  --get \\\n  --data-urlencode \"status=up\" \\\n  --data-urlencode \"msg=created by community-scripts\" \\\n  --data-urlencode \"ping=1\" \\\n  --data-urlencode \"label=${HN}\" \\\n  \"${UPTIME_KUMA_PUSH_BASE}\" >/dev/null ||\n  echo \"WARN: Uptime-Kuma push failed (non-fatal)\"\n\necho \"Provisioned monitoring for ${APP} (CTID=${CTID}, IP=${IP})\"\n# ▲▲▲ EXAMPLE 4 — END ▲▲▲\n\n# ============================================================================\n# ▼▼▼ EXAMPLE 5 — BEGIN ▼▼▼\n# ----------------------------------------------------------------------------\n#  Name        : per-app-router.sh\n#  Purpose     : Single dispatcher hook that runs different actions\n#                depending on the app being installed (NSAPP). Useful when\n#                you want ONE hook for the whole cluster but distinct\n#                behavior for, e.g., databases vs media services.\n#  Difficulty  : ⭐⭐⭐ Advanced\n# ============================================================================\n#!/usr/bin/env bash\nset -euo pipefail\n\n# --- CONFIG (edit me) -------------------------------------------------------\nDEFAULT_DNS_SUFFIX=\"lan\"\nPROM_FILE_SD_DIR=\"/etc/prometheus/file_sd\" # on the host that runs Prometheus\n# ----------------------------------------------------------------------------\n\nlog() { printf '[%s] %s\\n' \"$(date +%H:%M:%S)\" \"$*\"; }\n\n# ---------- shared helpers --------------------------------------------------\nregister_prometheus_target() {\n  local job=\"$1\" port=\"$2\"\n  local file=\"${PROM_FILE_SD_DIR}/${job}.json\"\n  mkdir -p \"${PROM_FILE_SD_DIR}\"\n  if [[ ! -f \"$file\" ]]; then echo \"[]\" >\"$file\"; fi\n  python3 - \"$file\" \"${IP}:${port}\" \"${HN}\" \"${NSAPP}\" <<'PY'\nimport json, sys\npath, target, hn, app = sys.argv[1:5]\ndata = json.load(open(path))\n# Avoid duplicates\ndata = [b for b in data if target not in b.get(\"targets\", [])]\ndata.append({\"targets\": [target], \"labels\": {\"hostname\": hn, \"app\": app}})\njson.dump(data, open(path, \"w\"), indent=2)\nPY\n  log \"Registered Prometheus target ${IP}:${port} in ${file}\"\n}\n\nset_ct_options() {\n  local cores=\"$1\" mem=\"$2\" desc=\"$3\"\n  pct set \"${CTID}\" --cores \"${cores}\" --memory \"${mem}\" || true\n  pct set \"${CTID}\" --description \"${desc}\" || true\n}\n\n# ---------- per-app dispatch ------------------------------------------------\nlog \"Dispatching post-install for NSAPP=${NSAPP} CTID=${CTID}\"\n\ncase \"${NSAPP}\" in\n\n# ------ Databases ---------------------------------------------------------\npostgresql | mariadb | mongodb | redis | valkey)\n  log \"Database role: bumping resources & adding to backup-critical pool\"\n  set_ct_options 4 4096 \"DB: ${APP}\"\n  pvesh set /pools/db-critical --vms \"${CTID}\" 2>/dev/null || true\n  register_prometheus_target \"${NSAPP}-exporter\" 9187\n  ;;\n\n# ------ *arr media stack --------------------------------------------------\nsonarr | radarr | prowlarr | lidarr | readarr | bazarr)\n  log \"Media-arr role: tagging + Sonarr/Radarr API webhook\"\n  pct set \"${CTID}\" --tags \"community-script;media;arr-stack\" || true\n  curl -fsS --max-time 5 -X POST \\\n    \"http://media-hub.${DEFAULT_DNS_SUFFIX}/hooks/arr-added\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"{\\\"app\\\":\\\"${NSAPP}\\\",\\\"ctid\\\":${CTID},\\\"ip\\\":\\\"${IP}\\\"}\" \\\n    >/dev/null || log \"WARN: media-hub webhook failed\"\n  ;;\n\n# ------ Web apps that should sit behind NPM/Traefik ----------------------\nvaultwarden | paperless-ngx | nextcloud | immich | bookstack)\n  log \"Web app role: registering reverse-proxy entry\"\n  curl -fsS --max-time 5 -X POST \\\n    \"http://traefik.${DEFAULT_DNS_SUFFIX}/api/dynamic-add\" \\\n    -H \"Content-Type: application/json\" \\\n    -d \"$(\n      cat <<JSON\n{\n  \"name\": \"${HN}\",\n  \"host\": \"${HN}.${DEFAULT_DNS_SUFFIX}\",\n  \"backend\": \"http://${IP}\",\n  \"app\": \"${NSAPP}\"\n}\nJSON\n    )\" >/dev/null || log \"WARN: traefik registration failed\"\n  register_prometheus_target \"blackbox-http\" 80\n  ;;\n\n# ------ Default fallback --------------------------------------------------\n*)\n  log \"No special handling for ${NSAPP} — applying generic defaults\"\n  register_prometheus_target \"node-exporter\" 9100\n  ;;\nesac\n\nlog \"Finished dispatcher for ${APP} (CTID=${CTID})\"\n# ▲▲▲ EXAMPLE 5 — END ▲▲▲\n\n# ============================================================================\n#  END OF EXAMPLES\n# ============================================================================\n"
  },
  {
    "path": "tools/pve/post-pbs-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | MickLesk (CanbiZ) | thost96\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n    ____  ____ _____    ____             __     ____           __        ____\n   / __ \\/ __ ) ___/   / __ \\____  _____/ /_   /  _/___  _____/ /_____ _/ / /\n  / /_/ / __  \\__ \\   / /_/ / __ \\/ ___/ __/   / // __ \\/ ___/ __/ __ `/ / / \n / ____/ /_/ /__/ /  / ____/ /_/ (__  ) /_   _/ // / / (__  ) /_/ /_/ / / /  \n/_/   /_____/____/  /_/    \\____/____/\\__/  /___/_/ /_/____/\\__/\\__,_/_/_/   \n\nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nmsg_info() { echo -ne \" ${HOLD} ${YW}$1...\"; }\nmsg_ok() { echo -e \"${BFR} ${CM} ${GN}$1${CL}\"; }\nmsg_error() { echo -e \"${BFR} ${CROSS} ${RD}$1${CL}\"; }\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"post-pbs-install\" \"pve\"\n\n# ---- helpers ----\nget_pbs_codename() {\n  awk -F'=' '/^VERSION_CODENAME=/{print $2}' /etc/os-release\n}\n\nrepo_state_list() {\n  local repo=\"$1\"\n  local file=\"\"\n  local state=\"missing\"\n  for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do\n    [[ -f \"$f\" ]] || continue\n    if grep -q \"$repo\" \"$f\"; then\n      file=\"$f\"\n      if grep -qE \"^[^#].*${repo}\" \"$f\"; then\n        state=\"active\"\n      elif grep -qE \"^#.*${repo}\" \"$f\"; then\n        state=\"disabled\"\n      fi\n      break\n    fi\n  done\n  echo \"$state $file\"\n}\n\ncomponent_exists_in_sources() {\n  local component=\"$1\"\n  grep -h -E \"^[^#]*Components:[^#]*\\b${component}\\b\" /etc/apt/sources.list.d/*.sources 2>/dev/null | grep -q .\n}\n\nrequire_whiptail() {\n  if ! command -v whiptail >/dev/null 2>&1; then\n    msg_error \"Missing dependency: whiptail\"\n    echo -e \"Install it first (e.g. apt update && apt install -y whiptail), then re-run this script.\"\n    exit 127\n  fi\n}\n\n# ---- main ----\nmain() {\n  header_info\n  echo -e \"\\nThis script will Perform Post Install Routines.\\n\"\n  while true; do\n    read -rp \"Start the Proxmox Backup Server Post Install Script (y/n)? \" yn\n    case $yn in\n    [Yy]*) break ;;\n    [Nn]*)\n      clear\n      exit\n      ;;\n    *) echo \"Please answer yes or no.\" ;;\n    esac\n  done\n\n  if command -v pveversion >/dev/null 2>&1; then\n    echo -e \"\\n🛑  PVE Detected, Wrong Script!\\n\"\n    exit 232\n  fi\n\n  local CODENAME\n  CODENAME=\"$(get_pbs_codename)\"\n\n  case \"$CODENAME\" in\n  bookworm)\n    require_whiptail\n    start_routines_3\n    ;;\n  trixie)\n    require_whiptail\n    start_routines_4\n    ;;\n  *)\n    msg_error \"Unsupported Debian codename: $CODENAME\"\n    echo -e \"Supported: bookworm (PBS 3.x) and trixie (PBS 4.x)\"\n    exit 105\n    ;;\n  esac\n}\n\n# ---- PBS 3.x (Bookworm) ----\nstart_routines_3() {\n  header_info\n  local VERSION=\"bookworm\"\n\n  # --- Debian sources ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS SOURCES\" --menu \\\n    \"Correct Debian sources for Proxmox Backup Server 3.x?\" 14 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Correcting Debian Sources\"\n    cat <<EOF >/etc/apt/sources.list\ndeb http://deb.debian.org/debian ${VERSION} main contrib\ndeb http://deb.debian.org/debian ${VERSION}-updates main contrib\ndeb http://security.debian.org/debian-security ${VERSION}-security main contrib\nEOF\n    msg_ok \"Corrected Debian Sources\"\n    ;;\n  no) msg_error \"Selected no to Correcting Debian Sources\" ;;\n  esac\n\n  # --- Enterprise repo ---\n  read -r state file <<<\"$(repo_state_list pbs-enterprise)\"\n  case $state in\n  active)\n    sed -i \"s/^[^#].*pbs-enterprise/# &/\" \"$file\"\n    msg_ok \"Disabled 'pbs-enterprise' repository\"\n    ;;\n  disabled) msg_ok \"'pbs-enterprise' already disabled\" ;;\n  missing)\n    cat >/etc/apt/sources.list.d/pbs-enterprise.list <<EOF\n# deb https://enterprise.proxmox.com/debian/pbs ${VERSION} pbs-enterprise\nEOF\n    msg_ok \"Added 'pbs-enterprise' repository (disabled)\"\n    ;;\n  esac\n\n  # --- No-subscription repo ---\n  read -r state file <<<\"$(repo_state_list pbs-no-subscription)\"\n  if [[ \"$state\" == \"missing\" ]]; then\n    cat >/etc/apt/sources.list.d/pbs-install-repo.list <<EOF\ndeb http://download.proxmox.com/debian/pbs ${VERSION} pbs-no-subscription\nEOF\n    msg_ok \"Enabled 'pbs-no-subscription' repository\"\n  else\n    msg_ok \"'pbs-no-subscription' repository already present\"\n  fi\n\n  # --- Test repo (legacy name pbstest) ---\n  read -r state file <<<\"$(repo_state_list pbstest)\"\n  if [[ \"$state\" == \"missing\" ]]; then\n    cat >/etc/apt/sources.list.d/pbstest-for-beta.list <<EOF\n# deb http://download.proxmox.com/debian/pbs ${VERSION} pbstest\nEOF\n    msg_ok \"Added 'pbstest' repository (disabled)\"\n  else\n    msg_ok \"'pbstest' repository already exists\"\n  fi\n\n  post_routines_common\n}\n\n# ---- PBS 4.x (Trixie, deb822) ----\nstart_routines_4() {\n  header_info\n  local VERSION=\"trixie\"\n\n  # --- Debian sources (deb822) ---\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS SOURCES\" --menu \\\n    \"Correct Debian sources for Proxmox Backup Server 4.x (deb822)?\" 14 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Correcting Debian Sources (deb822)\"\n    rm -f /etc/apt/sources.list.d/*.list\n    if [ -f /etc/apt/sources.list ]; then\n      sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list\n    fi\n    cat >/etc/apt/sources.list.d/debian.sources <<EOF\nTypes: deb\nURIs: http://deb.debian.org/debian/\nSuites: trixie trixie-updates\nComponents: main contrib non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://security.debian.org/debian-security/\nSuites: trixie-security\nComponents: main contrib non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    msg_ok \"Corrected Debian Sources\"\n    ;;\n  no) msg_error \"Selected no to Correcting Debian Sources\" ;;\n  esac\n\n  # --- Enterprise repo ---\n  if component_exists_in_sources \"pbs-enterprise\"; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PBS Enterprise Repository\" --menu \\\n      \"Enterprise repository detected.\n\nYou normally need a valid subscription for this.\nDisable it (recommended)?\" 14 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Disabling 'pbs-enterprise' repository\"\n      # Use Enabled: false instead of commenting to avoid malformed entry\n      if grep -q \"^Enabled:\" /etc/apt/sources.list.d/pbs-enterprise.sources 2>/dev/null; then\n        sed -i 's/^Enabled:.*/Enabled: false/' /etc/apt/sources.list.d/pbs-enterprise.sources\n      else\n        echo \"Enabled: false\" >>/etc/apt/sources.list.d/pbs-enterprise.sources\n      fi\n      msg_ok \"Disabled 'pbs-enterprise' repository\"\n      ;;\n    no)\n      msg_error \"Keeping 'pbs-enterprise' active (subscription required!)\"\n      ;;\n    esac\n  else\n    cat >/etc/apt/sources.list.d/pbs-enterprise.sources <<EOF\nTypes: deb\nURIs: https://enterprise.proxmox.com/debian/pbs\nSuites: trixie\nComponents: pbs-enterprise\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEnabled: false\nEOF\n    msg_ok \"Added 'pbs-enterprise' repository (disabled)\"\n  fi\n\n  # --- No-subscription repo ---\n  if ! component_exists_in_sources \"pbs-no-subscription\"; then\n    cat >/etc/apt/sources.list.d/proxmox.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pbs\nSuites: trixie\nComponents: pbs-no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n    msg_ok \"Added 'pbs-no-subscription' repository\"\n  else\n    msg_ok \"'pbs-no-subscription' repository already present\"\n  fi\n\n  # --- Test repo (pbs-test, renamed) ---\n  if ! component_exists_in_sources \"pbs-test\"; then\n    cat >/etc/apt/sources.list.d/pbs-test.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pbs\nSuites: trixie\nComponents: pbs-test\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEnabled: false\nEOF\n    msg_ok \"Added 'pbs-test' repository (disabled)\"\n  else\n    msg_ok \"'pbs-test' repository already present\"\n  fi\n\n  post_routines_common\n}\n\n# ---- Shared routines ----\npost_routines_common() {\n  # Subscription nag\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SUBSCRIPTION NAG\" --menu \\\n    \"Disable subscription nag in PBS UI?\" 14 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox \\\n      \"Supporting the software's development team is essential.\\nPlease consider buying a subscription.\" 10 58\n    msg_info \"Disabling subscription nag\"\n    echo \"DPkg::Post-Invoke { \\\"if [ -s /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js ] && ! grep -q -F 'NoMoreNagging' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; then sed -i '/data\\\\.status/{s/\\\\!//;s/active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; fi\\\" };\" >/etc/apt/apt.conf.d/no-nag-script\n    msg_ok \"Disabled subscription nag (clear browser cache!)\"\n    ;;\n  no)\n    msg_error \"Selected no to Disabling subscription nag\"\n    rm -f /etc/apt/apt.conf.d/no-nag-script 2>/dev/null\n    ;;\n  esac\n  apt --reinstall install proxmox-widget-toolkit &>/dev/null || msg_error \"Widget toolkit reinstall failed\"\n\n  # Update\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UPDATE\" --menu \\\n    \"Update Proxmox Backup Server now?\" 11 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Updating Proxmox Backup Server (Patience)\"\n    apt update &>/dev/null || msg_error \"apt update failed\"\n    apt -y dist-upgrade &>/dev/null || msg_error \"apt dist-upgrade failed\"\n    msg_ok \"Updated Proxmox Backup Server\"\n    ;;\n  no) msg_error \"Selected no to updating Proxmox Backup Server\" ;;\n  esac\n\n  # Reminder\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Post-Install Reminder\" --msgbox \\\n    \"IMPORTANT:\n\nPlease run this script on every PBS node individually if you have multiple nodes.\n\nAfter completing these steps, it is strongly recommended to REBOOT your node.\n\nAfter the upgrade or post-install routines, always clear your browser cache or perform a hard reload (Ctrl+Shift+R) before using the PBS Web UI to avoid UI display issues.\" 20 80\n\n  # Reboot\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"REBOOT\" --menu \\\n    \"Reboot Proxmox Backup Server now? (recommended)\" 11 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Rebooting PBS\"\n    sleep 2\n    msg_ok \"Completed Post Install Routines\"\n    reboot\n    ;;\n  no)\n    msg_error \"Selected no to Reboot (Reboot recommended)\"\n    msg_ok \"Completed Post Install Routines\"\n    ;;\n  esac\n}\n\nmain\n"
  },
  {
    "path": "tools/pve/post-pmg-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n    ____  __  _________   ____             __     ____           __        ____\n   / __ \\/  |/  / ____/  / __ \\____  _____/ /_   /  _/___  _____/ /_____ _/ / /\n  / /_/ / /|_/ / / __   / /_/ / __ \\/ ___/ __/   / // __ \\/ ___/ __/ __ `/ / /\n / ____/ /  / / /_/ /  / ____/ /_/ (__  ) /_   _/ // / / (__  ) /_/ /_/ / / /\n/_/   /_/  /_/\\____/  /_/    \\____/____/\\__/  /___/_/ /_/____/\\__/\\__,_/_/_/\n\nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nmsg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nmsg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"post-pmg-install\" \"pve\"\n\nif ! dpkg -s proxmox-mailgateway-container >/dev/null 2>&1 &&\n  ! dpkg -s proxmox-mailgateway >/dev/null 2>&1; then\n  msg_error \"This script is only intended for Proxmox Mail Gateway\"\n  exit 232\nfi\n\nrepo_state() {\n  # $1 = repo name (e.g. pmg-enterprise, pmg-no-subscription, pmgtest)\n  local repo=\"$1\"\n  local file=\"\"\n  local state=\"missing\"\n  for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do\n    [[ -f \"$f\" ]] || continue\n    if grep -q \"$repo\" \"$f\"; then\n      file=\"$f\"\n      if [[ \"$f\" == *.sources ]]; then\n        # deb822 format: check Enabled field\n        if grep -qiE '^Enabled:\\s*no' \"$f\"; then\n          state=\"disabled\"\n        else\n          state=\"active\"\n        fi\n      else\n        # legacy format\n        if grep -qE \"^[^#].*${repo}\" \"$f\"; then\n          state=\"active\"\n        elif grep -qE \"^#.*${repo}\" \"$f\"; then\n          state=\"disabled\"\n        fi\n      fi\n      break\n    fi\n  done\n  echo \"$state $file\"\n}\n\ntoggle_repo() {\n  # $1 = file, $2 = action (enable|disable)\n  local file=\"$1\" action=\"$2\"\n  if [[ \"$file\" == *.sources ]]; then\n    if [[ \"$action\" == \"disable\" ]]; then\n      if grep -qiE '^Enabled:' \"$file\"; then\n        sed -i 's/^Enabled:.*/Enabled: no/' \"$file\"\n      else\n        echo \"Enabled: no\" >>\"$file\"\n      fi\n    else\n      sed -i 's/^Enabled:.*/Enabled: yes/' \"$file\"\n    fi\n  else\n    if [[ \"$action\" == \"disable\" ]]; then\n      sed -i '/^[^#]/s/^/# /' \"$file\"\n    else\n      sed -i 's/^# *//' \"$file\"\n    fi\n  fi\n}\n\nstart_routines() {\n  header_info\n  VERSION=\"$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)\"\n\n  # ---- SOURCES ----\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG SOURCES\" --menu \\\n    \"This will set the correct Debian sources for Proxmox Mail Gateway.\\n\\nCorrect sources?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Correcting Debian Sources\"\n    cat <<EOF >/etc/apt/sources.list.d/debian.sources\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: ${VERSION} ${VERSION}-updates\nComponents: main contrib non-free non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://security.debian.org/debian-security\nSuites: ${VERSION}-security\nComponents: main contrib non-free non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n    rm -f /etc/apt/sources.list\n    msg_ok \"Corrected Debian Sources\"\n    ;;\n  no) msg_error \"Selected no to Correcting Debian Sources\" ;;\n  esac\n\n  # ---- PMG-ENTERPRISE ----\n  read -r state file <<<\"$(repo_state pmg-enterprise)\"\n  case $state in\n  active)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG-ENTERPRISE\" \\\n      --menu \"'pmg-enterprise' repository is currently ENABLED.\\n\\nWhat do you want to do?\" 14 58 3 \\\n      \"keep\" \"Keep as is\" \\\n      \"disable\" \"Comment out (disable)\" \\\n      \"delete\" \"Delete repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    keep) msg_ok \"Kept 'pmg-enterprise' repository\" ;;\n    disable)\n      msg_info \"Disabling 'pmg-enterprise' repository\"\n      toggle_repo \"$file\" disable\n      msg_ok \"Disabled 'pmg-enterprise' repository\"\n      ;;\n    delete)\n      msg_info \"Deleting 'pmg-enterprise' repository file\"\n      rm -f \"$file\"\n      msg_ok \"Deleted 'pmg-enterprise' repository file\"\n      ;;\n    esac\n    ;;\n  disabled)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG-ENTERPRISE\" \\\n      --menu \"'pmg-enterprise' repository is currently DISABLED.\\n\\nWhat do you want to do?\" 14 58 3 \\\n      \"enable\" \"Uncomment (enable)\" \\\n      \"keep\" \"Keep disabled\" \\\n      \"delete\" \"Delete repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    enable)\n      msg_info \"Enabling 'pmg-enterprise' repository\"\n      toggle_repo \"$file\" enable\n      msg_ok \"Enabled 'pmg-enterprise' repository\"\n      ;;\n    keep) msg_ok \"Kept 'pmg-enterprise' repository disabled\" ;;\n    delete)\n      msg_info \"Deleting 'pmg-enterprise' repository file\"\n      rm -f \"$file\"\n      msg_ok \"Deleted 'pmg-enterprise' repository file\"\n      ;;\n    esac\n    ;;\n  missing)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG-ENTERPRISE\" \\\n      --menu \"Add 'pmg-enterprise' repository?\\n\\nOnly for subscription customers.\" 14 58 2 \\\n      \"no\" \" \" \\\n      \"yes\" \" \" \\\n      --default-item \"no\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'pmg-enterprise' repository\"\n      cat >/etc/apt/sources.list.d/pmg-enterprise.sources <<EOF\nTypes: deb\nURIs: https://enterprise.proxmox.com/debian/pmg\nSuites: ${VERSION}\nComponents: pmg-enterprise\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n      msg_ok \"Added 'pmg-enterprise' repository\"\n      ;;\n    no) msg_error \"Selected no to Adding 'pmg-enterprise' repository\" ;;\n    esac\n    ;;\n  esac\n\n  # ---- PMG-NO-SUBSCRIPTION ----\n  read -r state file <<<\"$(repo_state pmg-no-subscription)\"\n  case $state in\n  active)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG-NO-SUBSCRIPTION\" \\\n      --menu \"'pmg-no-subscription' repository is currently ENABLED.\\n\\nWhat do you want to do?\" 14 58 3 \\\n      \"keep\" \"Keep as is\" \\\n      \"disable\" \"Comment out (disable)\" \\\n      \"delete\" \"Delete repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    keep) msg_ok \"Kept 'pmg-no-subscription' repository\" ;;\n    disable)\n      msg_info \"Disabling 'pmg-no-subscription' repository\"\n      toggle_repo \"$file\" disable\n      msg_ok \"Disabled 'pmg-no-subscription' repository\"\n      ;;\n    delete)\n      msg_info \"Deleting 'pmg-no-subscription' repository file\"\n      rm -f \"$file\"\n      msg_ok \"Deleted 'pmg-no-subscription' repository file\"\n      ;;\n    esac\n    ;;\n  disabled)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG-NO-SUBSCRIPTION\" \\\n      --menu \"'pmg-no-subscription' repository is currently DISABLED.\\n\\nWhat do you want to do?\" 14 58 3 \\\n      \"enable\" \"Uncomment (enable)\" \\\n      \"keep\" \"Keep disabled\" \\\n      \"delete\" \"Delete repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    enable)\n      msg_info \"Enabling 'pmg-no-subscription' repository\"\n      toggle_repo \"$file\" enable\n      msg_ok \"Enabled 'pmg-no-subscription' repository\"\n      ;;\n    keep) msg_ok \"Kept 'pmg-no-subscription' repository disabled\" ;;\n    delete)\n      msg_info \"Deleting 'pmg-no-subscription' repository file\"\n      rm -f \"$file\"\n      msg_ok \"Deleted 'pmg-no-subscription' repository file\"\n      ;;\n    esac\n    ;;\n  missing)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG-NO-SUBSCRIPTION\" \\\n      --menu \"Add 'pmg-no-subscription' repository?\" 14 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'pmg-no-subscription' repository\"\n      cat >/etc/apt/sources.list.d/pmg-no-subscription.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pmg\nSuites: ${VERSION}\nComponents: pmg-no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n      msg_ok \"Added 'pmg-no-subscription' repository\"\n      ;;\n    no) msg_error \"Selected no to Adding 'pmg-no-subscription' repository\" ;;\n    esac\n    ;;\n  esac\n\n  # ---- PMG-TEST ----\n  read -r state file <<<\"$(repo_state pmgtest)\"\n  case $state in\n  active) msg_ok \"'pmgtest' repository already active (skipped)\" ;;\n  disabled) msg_ok \"'pmgtest' repository already disabled (skipped)\" ;;\n  missing)\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PMG TEST\" \\\n      --menu \"The 'pmgtest' repository can give advanced users access to new features early.\\n\\nAdd (disabled) 'pmgtest' repository?\" 14 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'pmgtest' repository (disabled)\"\n      cat >/etc/apt/sources.list.d/pmgtest.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pmg\nSuites: ${VERSION}\nComponents: pmgtest\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEnabled: no\nEOF\n      msg_ok \"Added 'pmgtest' repository\"\n      ;;\n    no) msg_error \"Selected no to Adding 'pmgtest' repository\" ;;\n    esac\n    ;;\n  esac\n\n  # ---- SUBSCRIPTION NAG ----\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SUBSCRIPTION NAG\" --menu \\\n    \"Disable subscription nag in PMG UI?\" 14 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Support Subscriptions\" \\\n      \"Supporting the software's development team is essential.\\nPlease consider buying a subscription.\" 10 58\n    msg_info \"Disabling subscription nag\"\n    cat >/etc/apt/apt.conf.d/no-nag-script <<'EOF'\nDPkg::Post-Invoke { \"if [ -s /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js ] && ! grep -q -F 'NoMoreNagging' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; then sed -i '/data\\.status/{s/\\!//;s/active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; fi\"; };\nEOF\n\n    cat >/etc/apt/apt.conf.d/no-nag-script-pmgmanagerlib-mobile <<'EOF'\nDPkg::Post-Invoke { \"if [ -s /usr/share/javascript/pmg-gui/js/pmgmanagerlib-mobile.js ] && ! grep -q -F 'NoMoreNagging' /usr/share/javascript/pmg-gui/js/pmgmanagerlib-mobile.js; then sed -i '/data\\.status/{s/\\!//;s/active/NoMoreNagging/}' /usr/share/javascript/pmg-gui/js/pmgmanagerlib-mobile.js; fi\"; };\nEOF\n    msg_ok \"Disabled subscription nag (clear browser cache!)\"\n    ;;\n  no)\n    msg_error \"Selected no to Disabling subscription nag\"\n    rm -f /etc/apt/apt.conf.d/no-nag-script 2>/dev/null\n    ;;\n  esac\n  apt --reinstall install proxmox-widget-toolkit pmg-gui &>/dev/null || msg_error \"Widget toolkit reinstall failed\"\n\n  # ---- UPDATE ----\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UPDATE\" --menu \\\n    \"Update Proxmox Mail Gateway now?\" 11 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Updating Proxmox Mail Gateway (Patience)\"\n    apt update &>/dev/null || msg_error \"apt update failed\"\n    apt -y dist-upgrade &>/dev/null || msg_error \"apt dist-upgrade failed\"\n    msg_ok \"Updated Proxmox Mail Gateway\"\n    ;;\n  no) msg_error \"Selected no to updating Proxmox Mail Gateway\" ;;\n  esac\n\n  # ---- REMINDER ----\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Post-Install Reminder\" --msgbox \\\n    \"IMPORTANT:\n\nPlease run this script on every PMG node individually if you have multiple nodes.\n\nAfter completing these steps, it is strongly recommended to REBOOT your node.\n\nAfter the upgrade or post-install routines, always clear your browser cache or perform a hard reload (Ctrl+Shift+R) before using the PMG Web UI to avoid UI display issues.\" 20 80\n\n  # ---- REBOOT ----\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"REBOOT\" --menu \\\n    \"Reboot Proxmox Mail Gateway now? (recommended)\" 11 58 2 \"yes\" \" \" \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Rebooting Proxmox Mail Gateway\"\n    sleep 2\n    msg_ok \"Completed Post Install Routines\"\n    reboot\n    ;;\n  no)\n    msg_error \"Selected no to reboot (Reboot recommended)\"\n    msg_ok \"Completed Post Install Routines\"\n    ;;\n  esac\n}\n\nheader_info\necho -e \"\\nThis script will Perform Post Install Routines.\\n\"\nwhile true; do\n  read -rp \"Start the Proxmox Mail Gateway Post Install Script (y/n)? \" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*)\n    clear\n    exit\n    ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nstart_routines\n"
  },
  {
    "path": "tools/pve/post-pve-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteckster | MickLesk (CanbiZ)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n    ____ _    ________   ____             __     ____           __        ____\n   / __ \\ |  / / ____/  / __ \\____  _____/ /_   /  _/___  _____/ /_____ _/ / /\n  / /_/ / | / / __/    / /_/ / __ \\/ ___/ __/   / // __ \\/ ___/ __/ __ `/ / /\n / ____/| |/ / /___   / ____/ /_/ (__  ) /_   _/ // / / (__  ) /_/ /_/ / / /\n/_/     |___/_____/  /_/    \\____/____/\\__/  /___/_/ /_/____/\\__/\\__,_/_/_/\n\nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nmsg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nmsg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"post-pve-install\" \"pve\"\n\nget_pve_version() {\n  local pve_ver\n  pve_ver=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n  echo \"$pve_ver\"\n}\n\nget_pve_major_minor() {\n  local ver=\"$1\"\n  local major minor\n  IFS='.' read -r major minor _ <<<\"$ver\"\n  echo \"$major $minor\"\n}\n\ncomponent_exists_in_sources() {\n  local component=\"$1\"\n  grep -h -E \"^[^#]*Components:[^#]*\\b${component}\\b\" /etc/apt/sources.list.d/*.sources 2>/dev/null | grep -q .\n}\n\nmain() {\n  header_info\n  echo -e \"\\nThis script will Perform Post Install Routines.\\n\"\n  while true; do\n    read -p \"Start the Proxmox VE Post Install Script (y/n)? \" yn\n    case $yn in\n    [Yy]*) break ;;\n    [Nn]*)\n      clear\n      exit\n      ;;\n    *) echo \"Please answer yes or no.\" ;;\n    esac\n  done\n\n  local PVE_VERSION PVE_MAJOR PVE_MINOR\n  PVE_VERSION=\"$(get_pve_version)\"\n  read -r PVE_MAJOR PVE_MINOR <<<\"$(get_pve_major_minor \"$PVE_VERSION\")\"\n\n  if [[ \"$PVE_MAJOR\" == \"8\" ]]; then\n    if ((PVE_MINOR < 0 || PVE_MINOR > 9)); then\n      msg_error \"Unsupported Proxmox 8 version\"\n      exit 105\n    fi\n    start_routines_8\n  elif [[ \"$PVE_MAJOR\" == \"9\" ]]; then\n    if ((PVE_MINOR < 0 || PVE_MINOR > 1)); then\n      msg_error \"Only Proxmox 9.0-9.1.x is currently supported\"\n      exit 105\n    fi\n    start_routines_9\n  else\n    msg_error \"Unsupported Proxmox VE major version: $PVE_MAJOR\"\n    echo -e \"Supported: 8.0–8.9.x and 9.0–9.1.x\"\n    exit 105\n  fi\n}\n\nstart_routines_8() {\n  header_info\n\n  # === Bookworm/8.x: .list-Files ===\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SOURCES\" --menu \"The package manager will use the correct sources to update and install packages on your Proxmox VE server.\\n \\nCorrect Proxmox VE sources?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Correcting Proxmox VE Sources\"\n    cat <<EOF >/etc/apt/sources.list\ndeb http://deb.debian.org/debian bookworm main contrib\ndeb http://deb.debian.org/debian bookworm-updates main contrib\ndeb http://security.debian.org/debian-security bookworm-security main contrib\nEOF\n    echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware \"false\";' >/etc/apt/apt.conf.d/no-bookworm-firmware.conf\n    msg_ok \"Corrected Proxmox VE Sources\"\n    ;;\n  no) msg_error \"Selected no to Correcting Proxmox VE Sources\" ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PVE-ENTERPRISE\" --menu \"The 'pve-enterprise' repository is only available to users who have purchased a Proxmox VE subscription.\\n \\nDisable 'pve-enterprise' repository?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Disabling 'pve-enterprise' repository\"\n    cat <<EOF >/etc/apt/sources.list.d/pve-enterprise.list\n# deb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise\nEOF\n    msg_ok \"Disabled 'pve-enterprise' repository\"\n    ;;\n  no) msg_error \"Selected no to Disabling 'pve-enterprise' repository\" ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PVE-NO-SUBSCRIPTION\" --menu \"The 'pve-no-subscription' repository provides access to all of the open-source components of Proxmox VE.\\n \\nEnable 'pve-no-subscription' repository?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Enabling 'pve-no-subscription' repository\"\n    cat <<EOF >/etc/apt/sources.list.d/pve-install-repo.list\ndeb http://download.proxmox.com/debian/pve bookworm pve-no-subscription\nEOF\n    msg_ok \"Enabled 'pve-no-subscription' repository\"\n    ;;\n  no) msg_error \"Selected no to Enabling 'pve-no-subscription' repository\" ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CEPH PACKAGE REPOSITORIES\" --menu \"The 'Ceph Package Repositories' provides access to both the 'no-subscription' and 'enterprise' repositories (initially disabled).\\n \\nCorrect 'ceph package sources?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Correcting 'ceph package repositories'\"\n    cat <<EOF >/etc/apt/sources.list.d/ceph.list\n# deb https://enterprise.proxmox.com/debian/ceph-quincy bookworm enterprise\n# deb http://download.proxmox.com/debian/ceph-quincy bookworm no-subscription\n# deb https://enterprise.proxmox.com/debian/ceph-reef bookworm enterprise\n# deb http://download.proxmox.com/debian/ceph-reef bookworm no-subscription\nEOF\n    msg_ok \"Corrected 'ceph package repositories'\"\n    ;;\n  no) msg_error \"Selected no to Correcting 'ceph package repositories'\" ;;\n  esac\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PVETEST\" --menu \"The 'pvetest' repository can give advanced users access to new features and updates before they are officially released.\\n \\nAdd (Disabled) 'pvetest' repository?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Adding 'pvetest' repository and set disabled\"\n    cat <<EOF >/etc/apt/sources.list.d/pvetest-for-beta.list\n# deb http://download.proxmox.com/debian/pve bookworm pvetest\nEOF\n    msg_ok \"Added 'pvetest' repository\"\n    ;;\n  no) msg_error \"Selected no to Adding 'pvetest' repository\" ;;\n  esac\n\n  post_routines_common\n}\n\nstart_routines_9() {\n  header_info\n\n  # check if deb822 Sources (*.sources) exist\n  if find /etc/apt/sources.list.d/ -maxdepth 1 -name '*.sources' | grep -q .; then\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Deb822 sources detected\" \\\n      --msgbox \"Modern deb822 sources (*.sources) already exist.\\n\\nNo changes to sources format required.\\n\\nYou may still have legacy sources.list or .list files, which you can disable in the next step.\" 12 65 || true\n  else\n    check_and_disable_legacy_sources() {\n      local LEGACY_COUNT=0\n      local listfile=\"/etc/apt/sources.list\"\n\n      # Check sources.list\n      if [[ -f \"$listfile\" ]] && grep -qE '^\\s*deb ' \"$listfile\"; then\n        ((++LEGACY_COUNT))\n      fi\n\n      # Check .list files\n      local list_files\n      list_files=$(find /etc/apt/sources.list.d/ -type f -name \"*.list\" 2>/dev/null)\n      if [[ -n \"$list_files\" ]]; then\n        LEGACY_COUNT=$((LEGACY_COUNT + $(echo \"$list_files\" | wc -l)))\n      fi\n\n      if ((LEGACY_COUNT > 0)); then\n        # Show summary to user\n        local MSG=\"Legacy APT sources found:\\n\"\n        [[ -f \"$listfile\" ]] && MSG+=\" - /etc/apt/sources.list\\n\"\n        [[ -n \"$list_files\" ]] && MSG+=\"$(echo \"$list_files\" | sed 's|^| - |')\\n\"\n        MSG+=\"\\nDo you want to disable (comment out/rename) all legacy sources and use ONLY deb822 .sources format?\\n\\nRecommended for Proxmox VE 9.\"\n\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Disable legacy sources?\" \\\n          --yesno \"$MSG\" 18 80\n        if [[ $? -eq 0 ]]; then\n          # Backup and disable sources.list\n          if [[ -f \"$listfile\" ]] && grep -qE '^\\s*deb ' \"$listfile\"; then\n            cp \"$listfile\" \"$listfile.bak\"\n            sed -i '/^\\s*deb /s/^/# Disabled by Proxmox Helper Script /' \"$listfile\"\n            msg_ok \"Disabled entries in sources.list (backup: sources.list.bak)\"\n          fi\n          # Rename all .list files to .list.bak\n          if [[ -n \"$list_files\" ]]; then\n            while IFS= read -r f; do\n              mv \"$f\" \"$f.bak\"\n            done <<<\"$list_files\"\n            msg_ok \"Renamed legacy .list files to .bak\"\n          fi\n        else\n          msg_error \"Kept legacy sources as-is (may cause APT warnings)\"\n        fi\n      fi\n    }\n\n    check_and_disable_legacy_sources\n    # === Trixie/9.x: deb822 .sources ===\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SOURCES\" --menu \\\n      \"The package manager will use the correct sources to update and install packages on your Proxmox VE 9 server.\\n\\nMigrate to deb822 sources format?\" 14 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Correcting Proxmox VE Sources (deb822)\"\n      # remove all existing .list files\n      rm -f /etc/apt/sources.list.d/*.list\n      # remove bookworm and proxmox entries from sources.list (if it exists)\n      if [ -f /etc/apt/sources.list ]; then\n        sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list\n      fi\n      # Create new deb822 sources\n      cat >/etc/apt/sources.list.d/debian.sources <<EOF\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://security.debian.org/debian-security\nSuites: trixie-security\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\nTypes: deb\nURIs: http://deb.debian.org/debian\nSuites: trixie-updates\nComponents: main contrib\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nEOF\n      msg_ok \"Corrected Proxmox VE 9 (Trixie) Sources\"\n      ;;\n    no) msg_error \"Selected no to Correcting Proxmox VE Sources\" ;;\n    esac\n  fi\n\n  # ---- PVE-ENTERPRISE ----\n  if component_exists_in_sources \"pve-enterprise\"; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"PVE-ENTERPRISE\" \\\n      --menu \"'pve-enterprise' repository already exists.\\n\\nWhat do you want to do?\" 14 58 2 \\\n      \"keep\" \"Keep as is\" \\\n      \"disable\" \"Comment out (disable) this repo\" \\\n      \"delete\" \"Delete this repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    keep)\n      msg_ok \"Kept 'pve-enterprise' repository\"\n      ;;\n    disable)\n      msg_info \"Disabling 'pve-enterprise' repository\"\n      # Use Enabled: false instead of commenting to avoid malformed entry\n      for file in /etc/apt/sources.list.d/*.sources; do\n        if grep -q \"Components:.*pve-enterprise\" \"$file\"; then\n          if grep -q \"^Enabled:\" \"$file\"; then\n            sed -i 's/^Enabled:.*/Enabled: false/' \"$file\"\n          else\n            echo \"Enabled: false\" >>\"$file\"\n          fi\n        fi\n      done\n      msg_ok \"Disabled 'pve-enterprise' repository\"\n      ;;\n    delete)\n      msg_info \"Deleting 'pve-enterprise' repository file\"\n      for file in /etc/apt/sources.list.d/*.sources; do\n        if grep -q \"Components:.*pve-enterprise\" \"$file\"; then\n          rm -f \"$file\"\n        fi\n      done\n      msg_ok \"Deleted 'pve-enterprise' repository file\"\n      ;;\n    esac\n  else\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"PVE-ENTERPRISE\" \\\n      --menu \"The 'pve-enterprise' repository is only available to users who have purchased a Proxmox VE subscription.\\n\\nAdd 'pve-enterprise' repository (deb822)?\" 14 58 2 \\\n      \"no\" \" \" \\\n      \"yes\" \" \" \\\n      --default-item \"no\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'pve-enterprise' repository (deb822)\"\n      cat >/etc/apt/sources.list.d/pve-enterprise.sources <<EOF\nTypes: deb\nURIs: https://enterprise.proxmox.com/debian/pve\nSuites: trixie\nComponents: pve-enterprise\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n      msg_ok \"Added 'pve-enterprise' repository\"\n      ;;\n    no) msg_error \"Selected no to Adding 'pve-enterprise' repository\" ;;\n    esac\n  fi\n\n  # ---- CEPH-ENTERPRISE ----\n  if grep -q \"enterprise.proxmox.com.*ceph\" /etc/apt/sources.list.d/*.sources 2>/dev/null; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"CEPH-ENTERPRISE\" \\\n      --menu \"'ceph enterprise' repository already exists.\\n\\nWhat do you want to do?\" 14 58 2 \\\n      \"keep\" \"Keep as is\" \\\n      \"disable\" \"Comment out (disable) this repo\" \\\n      \"delete\" \"Delete this repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    keep)\n      msg_ok \"Kept 'ceph enterprise' repository\"\n      ;;\n    disable)\n      msg_info \"Disabling 'ceph enterprise' repository\"\n      # Use Enabled: false instead of commenting to avoid malformed entry\n      for file in /etc/apt/sources.list.d/*.sources; do\n        if grep -q \"enterprise.proxmox.com.*ceph\" \"$file\"; then\n          if grep -q \"^Enabled:\" \"$file\"; then\n            sed -i 's/^Enabled:.*/Enabled: false/' \"$file\"\n          else\n            echo \"Enabled: false\" >>\"$file\"\n          fi\n        fi\n      done\n      msg_ok \"Disabled 'ceph enterprise' repository\"\n      ;;\n    delete)\n      msg_info \"Deleting 'ceph enterprise' repository file\"\n      for file in /etc/apt/sources.list.d/*.sources; do\n        if grep -q \"enterprise.proxmox.com.*ceph\" \"$file\"; then\n          rm -f \"$file\"\n        fi\n      done\n      msg_ok \"Deleted 'ceph enterprise' repository file\"\n      ;;\n    esac\n  fi\n\n  # ---- PVE-NO-SUBSCRIPTION ----\n  REPO_FILE=\"\"\n  REPO_ACTIVE=0\n  REPO_COMMENTED=0\n  for file in /etc/apt/sources.list.d/*.sources; do\n    if grep -q \"Components:.*pve-no-subscription\" \"$file\"; then\n      REPO_FILE=\"$file\"\n      if grep -E '^[^#]*Components:.*pve-no-subscription' \"$file\" >/dev/null; then\n        REPO_ACTIVE=1\n      elif grep -E '^#.*Components:.*pve-no-subscription' \"$file\" >/dev/null; then\n        REPO_COMMENTED=1\n      fi\n      break\n    fi\n  done\n\n  if [[ \"$REPO_ACTIVE\" -eq 1 ]]; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"PVE-NO-SUBSCRIPTION\" \\\n      --menu \"'pve-no-subscription' repository is currently ENABLED.\\n\\nWhat do you want to do?\" 14 58 3 \\\n      \"keep\" \"Keep as is\" \\\n      \"disable\" \"Comment out (disable)\" \\\n      \"delete\" \"Delete repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    keep)\n      msg_ok \"Kept 'pve-no-subscription' repository\"\n      ;;\n    disable)\n      msg_info \"Disabling (commenting) 'pve-no-subscription' repository\"\n      sed -i '/^\\s*Types:/,/^$/s/^\\([^#].*\\)$/# \\1/' \"$REPO_FILE\"\n      msg_ok \"Disabled 'pve-no-subscription' repository\"\n      ;;\n    delete)\n      msg_info \"Deleting 'pve-no-subscription' repository file\"\n      rm -f \"$REPO_FILE\"\n      msg_ok \"Deleted 'pve-no-subscription' repository file\"\n      ;;\n    esac\n\n  elif [[ \"$REPO_COMMENTED\" -eq 1 ]]; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n      --title \"PVE-NO-SUBSCRIPTION\" \\\n      --menu \"'pve-no-subscription' repository is currently DISABLED (commented out).\\n\\nWhat do you want to do?\" 14 58 3 \\\n      \"enable\" \"Uncomment (enable)\" \\\n      \"keep\" \"Keep disabled\" \\\n      \"delete\" \"Delete repo file\" \\\n      3>&2 2>&1 1>&3)\n    case $CHOICE in\n    enable)\n      msg_info \"Enabling (uncommenting) 'pve-no-subscription' repository\"\n      sed -i '/^#\\s*Types:/,/^$/s/^#\\s*//' \"$REPO_FILE\"\n      msg_ok \"Enabled 'pve-no-subscription' repository\"\n      ;;\n    keep)\n      msg_ok \"Kept 'pve-no-subscription' repository disabled\"\n      ;;\n    delete)\n      msg_info \"Deleting 'pve-no-subscription' repository file\"\n      rm -f \"$REPO_FILE\"\n      msg_ok \"Deleted 'pve-no-subscription' repository file\"\n      ;;\n    esac\n  else\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PVE-NO-SUBSCRIPTION\" \\\n      --menu \"The 'pve-no-subscription' repository provides access to all of the open-source components of Proxmox VE.\\n\\nAdd 'pve-no-subscription' repository (deb822)?\" 14 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'pve-no-subscription' repository (deb822)\"\n      cat >/etc/apt/sources.list.d/proxmox.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pve\nSuites: trixie\nComponents: pve-no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n      msg_ok \"Added 'pve-no-subscription' repository\"\n      ;;\n    no) msg_error \"Selected no to Adding 'pve-no-subscription' repository\" ;;\n    esac\n  fi\n\n  # ---- CEPH ----\n  if component_exists_in_sources \"no-subscription\"; then\n    msg_ok \"'ceph' package repository (no-subscription) already exists (skipped)\"\n  else\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CEPH PACKAGE REPOSITORIES\" \\\n      --menu \"The 'Ceph Package Repositories' provides access to both the 'no-subscription' and 'enterprise' repositories (deb822).\\n\\nAdd 'ceph package sources?\" 14 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'ceph package repositories' (deb822)\"\n      cat >/etc/apt/sources.list.d/ceph.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/ceph-squid\nSuites: trixie\nComponents: no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEOF\n      msg_ok \"Added 'ceph package repositories'\"\n      ;;\n    no)\n      msg_error \"Selected no to Adding 'ceph package repositories'\"\n      # Use Enabled: false for .sources files, comment for .list files\n      for file in /etc/apt/sources.list.d/*.sources; do\n        if grep -q \"enterprise.proxmox.com.*ceph\" \"$file\" 2>/dev/null; then\n          if grep -q \"^Enabled:\" \"$file\"; then\n            sed -i 's/^Enabled:.*/Enabled: false/' \"$file\"\n          else\n            echo \"Enabled: false\" >>\"$file\"\n          fi\n        fi\n      done\n      find /etc/apt/sources.list.d/ -type f -name \"*.list\" \\\n        -exec sed -i '/enterprise.proxmox.com.*ceph/s/^/# /' {} \\;\n      msg_ok \"Disabled all Ceph Enterprise repositories\"\n      ;;\n    esac\n  fi\n\n  # ---- PVETEST ----\n  if component_exists_in_sources \"pve-test\"; then\n    msg_ok \"'pve-test' repository already exists (skipped)\"\n  else\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"PVETEST\" \\\n      --menu \"The 'pve-test' repository can give advanced users access to new features and updates before they are officially released.\\n\\nAdd (Disabled) 'pvetest' repository (deb822)?\" 14 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Adding 'pve-test' repository (deb822, disabled)\"\n      cat >/etc/apt/sources.list.d/pve-test.sources <<EOF\nTypes: deb\nURIs: http://download.proxmox.com/debian/pve\nSuites: trixie\nComponents: pve-test\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\nEnabled: false\nEOF\n      msg_ok \"Added 'pve-test' repository\"\n      ;;\n    no) msg_error \"Selected no to Adding 'pvetest' repository\" ;;\n    esac\n  fi\n\n  post_routines_common\n}\n\npost_routines_common() {\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SUBSCRIPTION NAG\" --menu \"This will disable the nag message reminding you to purchase a subscription every time you log in to the web interface.\\n \\nDisable subscription nag?\" 14 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Support Subscriptions\" \"Supporting the software's development team is essential. Check their official website's Support Subscriptions for pricing. Without their dedicated work, we wouldn't have this exceptional software.\" 10 58\n    msg_info \"Disabling subscription nag\"\n    # Create external script, this is needed because DPkg::Post-Invoke is fidly with quote interpretation\n    mkdir -p /usr/local/bin\n    cat >/usr/local/bin/pve-remove-nag.sh <<'EOF'\n#!/bin/sh\nWEB_JS=/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js\nif [ -s \"$WEB_JS\" ] && ! grep -q NoMoreNagging \"$WEB_JS\"; then\n    echo \"Patching Web UI nag...\"\n    sed -i -e \"/data\\.status/ s/!//\" -e \"/data\\.status/ s/active/NoMoreNagging/\" \"$WEB_JS\"\nfi\n\nMOBILE_TPL=/usr/share/pve-yew-mobile-gui/index.html.tpl\nMARKER=\"<!-- MANAGED BLOCK FOR MOBILE NAG -->\"\nif [ -f \"$MOBILE_TPL\" ] && ! grep -q \"$MARKER\" \"$MOBILE_TPL\"; then\n    echo \"Patching Mobile UI nag...\"\n    printf \"%s\\n\" \\\n      \"$MARKER\" \\\n      \"<script>\" \\\n      \"  function removeSubscriptionElements() {\" \\\n      \"    // --- Remove subscription dialogs ---\" \\\n      \"    const dialogs = document.querySelectorAll('dialog.pwt-outer-dialog');\" \\\n      \"    dialogs.forEach(dialog => {\" \\\n      \"      const text = (dialog.textContent || '').toLowerCase();\" \\\n      \"      if (text.includes('subscription')) {\" \\\n      \"        dialog.remove();\" \\\n      \"        console.log('Removed subscription dialog');\" \\\n      \"      }\" \\\n      \"    });\" \\\n      \"\" \\\n      \"    // --- Remove subscription cards, but keep Reboot/Shutdown/Console ---\" \\\n      \"    const cards = document.querySelectorAll('.pwt-card.pwt-p-2.pwt-d-flex.pwt-interactive.pwt-justify-content-center');\" \\\n      \"    cards.forEach(card => {\" \\\n      \"      const text = (card.textContent || '').toLowerCase();\" \\\n      \"      const hasButton = card.querySelector('button');\" \\\n      \"      if (!hasButton && text.includes('subscription')) {\" \\\n      \"        card.remove();\" \\\n      \"        console.log('Removed subscription card');\" \\\n      \"      }\" \\\n      \"    });\" \\\n      \"  }\" \\\n      \"\" \\\n      \"  const observer = new MutationObserver(removeSubscriptionElements);\" \\\n      \"  observer.observe(document.body, { childList: true, subtree: true });\" \\\n      \"  removeSubscriptionElements();\" \\\n      \"  setInterval(removeSubscriptionElements, 300);\" \\\n      \"  setTimeout(() => {observer.disconnect();}, 10000);\" \\\n      \"</script>\" \\\n      \"\" >> \"$MOBILE_TPL\"\nfi\nEOF\n    chmod 755 /usr/local/bin/pve-remove-nag.sh\n\n    cat >/etc/apt/apt.conf.d/no-nag-script <<'EOF'\nDPkg::Post-Invoke { \"/usr/local/bin/pve-remove-nag.sh\"; };\nEOF\n    chmod 644 /etc/apt/apt.conf.d/no-nag-script\n\n    msg_ok \"Disabled subscription nag (Delete browser cache)\"\n    ;;\n  no)\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Support Subscriptions\" \"Supporting the software's development team is essential. Check their official website's Support Subscriptions for pricing. Without their dedicated work, we wouldn't have this exceptional software.\" 10 58\n    msg_error \"Selected no to Disabling subscription nag\"\n    [[ -f /etc/apt/apt.conf.d/no-nag-script ]] && rm /etc/apt/apt.conf.d/no-nag-script\n    ;;\n  esac\n  apt --reinstall install proxmox-widget-toolkit &>/dev/null || msg_error \"Widget toolkit reinstall failed\"\n  if ! systemctl is-active --quiet pve-ha-lrm; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HIGH AVAILABILITY\" --menu \"Enable high availability?\" 10 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Enabling high availability\"\n      systemctl enable -q --now pve-ha-lrm\n      systemctl enable -q --now pve-ha-crm\n      systemctl enable -q --now corosync\n      msg_ok \"Enabled high availability\"\n      ;;\n    no) msg_error \"Selected no to Enabling high availability\" ;;\n    esac\n  fi\n\n  if systemctl is-active --quiet pve-ha-lrm; then\n    CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HIGH AVAILABILITY\" --menu \"If you plan to utilize a single node instead of a clustered environment, you can disable unnecessary high availability (HA) services, thus reclaiming system resources.\\n\\nIf HA becomes necessary at a later stage, the services can be re-enabled.\\n\\nDisable high availability?\" 18 58 2 \\\n      \"yes\" \" \" \\\n      \"no\" \" \" 3>&2 2>&1 1>&3)\n    case $CHOICE in\n    yes)\n      msg_info \"Disabling high availability\"\n      systemctl disable -q --now pve-ha-lrm\n      systemctl disable -q --now pve-ha-crm\n      msg_ok \"Disabled high availability\"\n      CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"COROSYNC\" --menu \"Disable Corosync for a Proxmox VE Cluster?\" 10 58 2 \\\n        \"yes\" \" \" \\\n        \"no\" \" \" 3>&2 2>&1 1>&3)\n      case $CHOICE in\n      yes)\n        msg_info \"Disabling Corosync\"\n        systemctl disable -q --now corosync\n        msg_ok \"Disabled Corosync\"\n        ;;\n      no) msg_error \"Selected no to Disabling Corosync\" ;;\n      esac\n      ;;\n    no) msg_error \"Selected no to Disabling high availability\" ;;\n    esac\n  fi\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"UPDATE\" --menu \"\\nUpdate Proxmox VE now?\" 11 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Updating Proxmox VE (Patience)\"\n    apt update &>/dev/null || msg_error \"apt update failed\"\n    apt -y dist-upgrade &>/dev/null || msg_error \"apt dist-upgrade failed\"\n    msg_ok \"Updated Proxmox VE\"\n    ;;\n  no) msg_error \"Selected no to Updating Proxmox VE\" ;;\n  esac\n\n  # Final message for all hosts in cluster and browser cache\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Post-Install Reminder\" --msgbox \\\n    \"IMPORTANT:\n\nIf you have multiple Proxmox VE hosts in a cluster, please make sure to run this script on every node individually.\n\nAfter completing these steps, it is strongly recommended to REBOOT your node.\n\nAfter the upgrade or post-install routines, always clear your browser cache or perform a hard reload (Ctrl+Shift+R) before using the Proxmox VE Web UI to avoid UI display issues.\n\" 20 80\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"REBOOT\" --menu \"\\nReboot Proxmox VE now? (recommended)\" 11 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Rebooting Proxmox VE\"\n    sleep 2\n    msg_ok \"Completed Post Install Routines\"\n    reboot\n    ;;\n  no)\n    msg_error \"Selected no to Rebooting Proxmox VE (Reboot recommended)\"\n    msg_ok \"Completed Post Install Routines\"\n    ;;\n  esac\n}\n\nmain\n"
  },
  {
    "path": "tools/pve/pve-privilege-converter.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk\n# Adapted from onethree7 (https://github.com/onethree7/proxmox-lxc-privilege-converter)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nif ! command -v curl >/dev/null 2>&1; then\n  printf \"\\r\\e[2K%b\" '\\033[93m Setup Source \\033[m' >&2\n  apt-get update >/dev/null 2>&1\n  apt-get install -y curl >/dev/null 2>&1\nfi\nsource <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\nload_functions\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pve-privilege-converter\" \"pve\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nAPP=\"PVE-Privilege-Converter\"\nAPP_TYPE=\"tools\"\nheader_info \"$APP\"\n\ncheck_root() {\n  if [[ $EUID -ne 0 ]]; then\n    msg_error \"Script must be run as root\"\n    exit 104\n  fi\n}\n\nselect_target_storage_and_container_id() {\n  echo -e \"\\nSelect target storage for restored container:\\n\"\n  mapfile -t target_storages < <(pvesm status --content images | awk 'NR > 1 {print $1}')\n  for i in \"${!target_storages[@]}\"; do\n    printf \"%s) %s\\n\" \"$((i + 1))\" \"${target_storages[$i]}\"\n  done\n\n  while true; do\n    read -rp \"Enter number of target storage: \" choice\n    if [[ \"$choice\" =~ ^[0-9]+$ ]] && ((choice >= 1 && choice <= ${#target_storages[@]})); then\n      TARGET_STORAGE=\"${target_storages[$((choice - 1))]}\"\n      break\n    else\n      echo \"Invalid selection. Try again.\"\n    fi\n  done\n\n  next_free_id=$(pvesh get /cluster/nextid 2>/dev/null || echo 999)\n  [[ \"$next_free_id\" =~ ^[0-9]+$ ]] || next_free_id=999\n\n  echo \"\"\n  read -rp \"Suggested next free container ID: $next_free_id. Enter new container ID [default: $next_free_id]: \" NEW_CONTAINER_ID\n  NEW_CONTAINER_ID=\"${NEW_CONTAINER_ID:-$next_free_id}\"\n}\n\nselect_container() {\n  mapfile -t lxc_list_raw < <(pct list | awk 'NR > 1 {print $1, $3}')\n  lxc_list=()\n  for entry in \"${lxc_list_raw[@]}\"; do\n    [[ -n \"$entry\" ]] && lxc_list+=(\"$entry\")\n  done\n\n  if [[ ${#lxc_list[@]} -eq 0 ]]; then\n    msg_error \"No containers found\"\n    exit 234\n  fi\n\n  PS3=\"Enter number of container to convert: \"\n  select opt in \"${lxc_list[@]}\"; do\n    if [[ -n \"$opt\" ]]; then\n      read -r CONTAINER_ID CONTAINER_NAME <<<\"$opt\"\n      CONTAINER_NAME=\"${CONTAINER_NAME:-}\"\n      break\n    else\n      echo \"Invalid selection. Try again.\"\n    fi\n  done\n}\n\nselect_backup_storage() {\n  echo -e \"Select backup storage (temporary vzdump location):\"\n  mapfile -t backup_storages < <(pvesm status --content backup | awk 'NR > 1 {print $1}')\n  local PS3=\"Enter number of backup storage: \"\n\n  select opt in \"${backup_storages[@]}\"; do\n    if [[ -n \"$opt\" ]]; then\n      BACKUP_STORAGE=\"$opt\"\n      break\n    else\n      echo \"Invalid selection. Try again.\"\n    fi\n  done\n}\n\nbackup_container() {\n  msg_custom \"📦\" \"\\e[36m\" \"Backing up container $CONTAINER_ID\"\n  vzdump_output=$(mktemp)\n  vzdump \"$CONTAINER_ID\" --compress zstd --storage \"$BACKUP_STORAGE\" --mode snapshot | tee \"$vzdump_output\"\n  BACKUP_PATH=$(awk '/tar.zst/ {print $NF}' \"$vzdump_output\" | tr -d \"'\")\n  if [ -z \"$BACKUP_PATH\" ] || ! grep -q \"Backup job finished successfully\" \"$vzdump_output\"; then\n    rm \"$vzdump_output\"\n    msg_error \"Backup failed\"\n    exit 235\n  fi\n  rm \"$vzdump_output\"\n  msg_ok \"Backup complete: $BACKUP_PATH\"\n}\n\nperform_conversion() {\n  if pct config \"$CONTAINER_ID\" | grep -q 'unprivileged: 1'; then\n    UNPRIVILEGED=true\n  else\n    UNPRIVILEGED=false\n  fi\n\n  msg_custom \"🛠️\" \"\\e[36m\" \"Restoring as $(if $UNPRIVILEGED; then echo privileged; else echo unprivileged; fi) container\"\n  restore_opts=(\"$NEW_CONTAINER_ID\" \"$BACKUP_PATH\" --storage \"$TARGET_STORAGE\")\n  if $UNPRIVILEGED; then\n    restore_opts+=(--unprivileged false)\n  else\n    restore_opts+=(--unprivileged)\n  fi\n\n  if pct restore \"${restore_opts[@]}\" -ignore-unpack-errors 1; then\n    msg_ok \"Conversion successful\"\n  else\n    msg_error \"Conversion failed\"\n    exit 235\n  fi\n}\n\nmanage_states() {\n  read -rp \"Shutdown source and start new container? [Y/n]: \" answer\n  answer=${answer:-Y}\n  if [[ $answer =~ ^[Yy] ]]; then\n    if pct status \"$CONTAINER_ID\" | grep -q running; then\n      pct shutdown \"$CONTAINER_ID\"\n      for i in {1..36}; do\n        sleep 5\n        ! pct status \"$CONTAINER_ID\" | grep -q running && break\n      done\n      if pct status \"$CONTAINER_ID\" | grep -q running; then\n        read -rp \"Timeout reached. Force shutdown? [Y/n]: \" force\n        if [[ ${force:-Y} =~ ^[Yy] ]]; then\n          pkill -9 -f \"lxc-start -F -n $CONTAINER_ID\"\n        fi\n      fi\n    else\n      msg_custom \"ℹ️\" \"\\e[36m\" \"Source container $CONTAINER_ID is already stopped\"\n    fi\n    pct start \"$NEW_CONTAINER_ID\"\n    msg_ok \"New container started\"\n  else\n    msg_custom \"ℹ️\" \"\\e[36m\" \"Skipped container state change\"\n  fi\n}\n\ncleanup_files() {\n  read -rp \"Delete backup archive? [$BACKUP_PATH] [Y/n]: \" cleanup\n  if [[ ${cleanup:-Y} =~ ^[Yy] ]]; then\n    rm -f \"$BACKUP_PATH\" && msg_ok \"Removed backup archive\"\n  else\n    msg_custom \"💾\" \"\\e[36m\" \"Retained backup archive\"\n  fi\n}\n\nsummary() {\n  local conversion=\"Unknown\"\n  if [[ -n \"${UNPRIVILEGED:-}\" ]]; then\n    if $UNPRIVILEGED; then\n      conversion=\"Unprivileged → Privileged\"\n    else\n      conversion=\"Privileged → Unprivileged\"\n    fi\n  fi\n\n  echo\n  msg_custom \"📄\" \"\\e[36m\" \"Summary:\"\n  msg_custom \"   \" \"\\e[36m\" \"$(printf \"%-22s %s\" \"Original Container:\" \"$CONTAINER_ID ($CONTAINER_NAME)\")\"\n  msg_custom \"   \" \"\\e[36m\" \"$(printf \"%-22s %s\" \"Backup Storage:\" \"$BACKUP_STORAGE\")\"\n  msg_custom \"   \" \"\\e[36m\" \"$(printf \"%-22s %s\" \"Target Storage:\" \"$TARGET_STORAGE\")\"\n  msg_custom \"   \" \"\\e[36m\" \"$(printf \"%-22s %s\" \"Backup Path:\" \"$BACKUP_PATH\")\"\n  msg_custom \"   \" \"\\e[36m\" \"$(printf \"%-22s %s\" \"New Container ID:\" \"$NEW_CONTAINER_ID\")\"\n  msg_custom \"   \" \"\\e[36m\" \"$(printf \"%-22s %s\" \"Privilege Conversion:\" \"$conversion\")\"\n  echo\n}\n\nmain() {\n  header_info\n  check_root\n  select_container\n  select_backup_storage\n  backup_container\n  select_target_storage_and_container_id\n  perform_conversion\n  manage_states\n  cleanup_files\n  summary\n}\n\nmain\n"
  },
  {
    "path": "tools/pve/pve8-upgrade.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n    ____ _    ____________     __  ______  __________  ___    ____  ______\n   / __ \\ |  / / ____( __ )   / / / / __ \\/ ____/ __ \\/   |  / __ \\/ ____/\n  / /_/ / | / / __/ / __  |  / / / / /_/ / / __/ /_/ / /| | / / / / __/\n / ____/| |/ / /___/ /_/ /  / /_/ / ____/ /_/ / _, _/ ___ |/ /_/ / /___\n/_/     |___/_____/\\____/   \\____/_/    \\____/_/ |_/_/  |_/_____/_____/\n\nEOF\n}\n\nRD=$(echo \"\\033[01;31m\")\nYW=$(echo \"\\033[33m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\n\nset -euo pipefail\nshopt -s inherit_errexit nullglob\n\nmsg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nmsg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nmsg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"pve8-upgrade\" \"pve\"\n\nstart_routines() {\n  header_info\n\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"PVE8 SOURCES\" \"This will set the correct sources to update and install Proxmox VE 8.\" 10 58\n  msg_info \"Changing to Proxmox VE 8 Sources\"\n  cat <<EOF >/etc/apt/sources.list\ndeb http://ftp.debian.org/debian bookworm main contrib\ndeb http://ftp.debian.org/debian bookworm-updates main contrib\ndeb http://security.debian.org/debian-security bookworm-security main contrib\nEOF\n  msg_ok \"Changed to Proxmox VE 8 Sources\"\n\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"PVE8-ENTERPRISE\" \"The 'pve-enterprise' repository is only available to users who have purchased a Proxmox VE subscription.\" 10 58\n  msg_info \"Disabling 'pve-enterprise' repository\"\n  cat <<EOF >/etc/apt/sources.list.d/pve-enterprise.list\n# deb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise\nEOF\n  msg_ok \"Disabled 'pve-enterprise' repository\"\n\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"PVE8-NO-SUBSCRIPTION\" \"The 'pve-no-subscription' repository provides access to all of the open-source components of Proxmox VE.\" 10 58\n  msg_info \"Enabling 'pve-no-subscription' repository\"\n  cat <<EOF >/etc/apt/sources.list.d/pve-install-repo.list\ndeb http://download.proxmox.com/debian/pve bookworm pve-no-subscription\nEOF\n  msg_ok \"Enabled 'pve-no-subscription' repository\"\n\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"PVE8 CEPH PACKAGE REPOSITORIES\" \"The 'Ceph Package Repositories' provides access to both the 'no-subscription' and 'enterprise' repositories.\" 10 58\n  msg_info \"Enabling 'ceph package repositories'\"\n  cat <<EOF >/etc/apt/sources.list.d/ceph.list\n# deb https://enterprise.proxmox.com/debian/ceph-quincy bookworm enterprise\ndeb http://download.proxmox.com/debian/ceph-quincy bookworm no-subscription\nEOF\n  msg_ok \"Enabled 'ceph package repositories'\"\n\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"PVE8 TEST\" \"The 'pvetest' repository can give advanced users access to new features and updates before they are officially released (Disabled).\" 10 58\n  msg_info \"Adding 'pvetest' repository and set disabled\"\n  cat <<EOF >/etc/apt/sources.list.d/pvetest-for-beta.list\n# deb http://download.proxmox.com/debian/pve bookworm pvetest\nEOF\n  msg_ok \"Added 'pvetest' repository\"\n\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"PVE8 UPDATE\" \"Updating to Proxmox VE 8\" 10 58\n  msg_info \"Updating to Proxmox VE 8 (Patience)\"\n  apt-get update\n  DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::=\"--force-confold\" dist-upgrade -y\n  msg_ok \"Updated to Proxmox VE 8\"\n\n  CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"REBOOT\" --menu \"\\nReboot Proxmox VE 8 now? (recommended)\" 11 58 2 \\\n    \"yes\" \" \" \\\n    \"no\" \" \" 3>&2 2>&1 1>&3)\n  case $CHOICE in\n  yes)\n    msg_info \"Rebooting Proxmox VE 8\"\n    sleep 2\n    msg_ok \"Completed Install Routines\"\n    reboot\n    ;;\n  no)\n    msg_error \"Selected no to Rebooting Proxmox VE 8 (Reboot recommended)\"\n    msg_ok \"Completed Install Routines\"\n    ;;\n  esac\n}\n\nheader_info\nwhile true; do\n  read -p \"Start the Update to Proxmox VE 8 Script (y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*)\n    clear\n    exit\n    ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nif ! command -v pveversion >/dev/null 2>&1; then\n  header_info\n  msg_error \"\\n No PVE Detected!\\n\"\n  exit\nfi\n\nif ! pveversion | grep -Eq \"pve-manager/(7\\.4-(16|17|18|19))\"; then\n  header_info\n  msg_error \"This version of Proxmox Virtual Environment is not supported\"\n  echo -e \"  PVE Version 7.4-16 or higher is required.\"\n  echo -e \"\\nExiting...\"\n  sleep 3\n  exit\nfi\n\nstart_routines\n"
  },
  {
    "path": "tools/pve/scaling-governor.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\nset -e\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"scaling-governor\" \"pve\"\n\nheader_info() {\n  clear\n  cat <<EOF\n  ________  __  __  _____\n / ___/ _ \\/ / / / / ___/__ _  _____ _______  ___  _______\n/ /__/ ___/ /_/ / / (_ / _ \\ |/ / -_) __/ _ \\/ _ \\/ __(_-<\n\\___/_/   \\____/  \\___/\\___/___/\\__/_/ /_//_/\\___/_/ /___/\nEOF\n}\nheader_info\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU Scaling Governors\" --yesno \"View/Change CPU Scaling Governors. Proceed?\" 10 58\ncurrent_governor=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)\nGOVERNORS_MENU=()\nMSG_MAX_LENGTH=0\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  GOVERNORS_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors | tr ' ' '\\n' | grep -v \"$current_governor\")\nscaling_governor=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Current CPU Scaling Governor is set to $current_governor\" --checklist \"\\nSelect the Scaling Governor to use:\\n\" 16 $((MSG_MAX_LENGTH + 58)) 6 \"${GOVERNORS_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n[ -z \"$scaling_governor\" ] && {\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No CPU Scaling Governor Selected\" --msgbox \"It appears that no CPU Scaling Governor was selected\" 10 68\n  clear\n  exit\n}\necho \"${scaling_governor}\" | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor >/dev/null\ncurrent_governor=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --msgbox --title \"Current CPU Scaling Governor\" \"\\nCurrent CPU Scaling Governor has been set to $current_governor\\n\" 10 60\nCHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU Scaling Governor\" --menu \"This will establish a crontab to maintain the CPU Scaling Governor configuration across reboots.\\n \\nSetup a crontab?\" 14 68 2 \\\n  \"yes\" \" \" \\\n  \"no\" \" \" 3>&2 2>&1 1>&3)\n\ncase $CHOICE in\nyes)\n  set +e\n  NEW_CRONTAB_COMMAND=\"(sleep 60 && echo \\\"$current_governor\\\" | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor)\"\n  EXISTING_CRONTAB=$(crontab -l 2>/dev/null)\n  if [[ -n \"$EXISTING_CRONTAB\" ]]; then\n    TEMP_CRONTAB_FILE=$(mktemp)\n    echo \"$EXISTING_CRONTAB\" | grep -v \"@reboot (sleep 60 && echo*\" >\"$TEMP_CRONTAB_FILE\"\n    crontab \"$TEMP_CRONTAB_FILE\"\n    rm \"$TEMP_CRONTAB_FILE\"\n  fi\n  (\n    crontab -l 2>/dev/null\n    echo \"@reboot $NEW_CRONTAB_COMMAND\"\n  ) | crontab -\n  echo -e \"\\nCrontab Set (use 'crontab -e' to check)\"\n  ;;\nno)\n  echo -e \"\\n\\033[31mNOTE: Settings return to default after reboot\\033[m\\n\"\n  ;;\nesac\necho -e \"Current CPU Scaling Governor is set to \\033[36m$current_governor\\033[m\\n\"\n"
  },
  {
    "path": "tools/pve/update-apps.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: BvdBerg01 | Co-Author: remz1337\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/refs/heads/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"update-apps\" \"pve\"\n\n# =============================================================================\n# CONFIGURATION VARIABLES\n# Set these variables to skip interactive prompts (Whiptail dialogs)\n# =============================================================================\n# var_backup: Enable/disable backup before update\n#   Options: \"yes\" | \"no\" | \"\" (empty = interactive prompt)\nvar_backup=\"${var_backup:-}\"\n\n# var_backup_storage: Storage location for backups (only used if var_backup=yes)\n#   Options: Storage name from /etc/pve/storage.cfg (e.g., \"local\", \"nas-backup\")\n#   Leave empty for interactive selection\nvar_backup_storage=\"${var_backup_storage:-}\"\n\n# var_container: Which containers to update\n#   Options:\n#     - \"all\"         : All containers with community-scripts tags\n#     - \"all_running\" : Only running containers with community-scripts tags\n#     - \"all_stopped\" : Only stopped containers with community-scripts tags\n#     - \"101,102,109\" : Comma-separated list of specific container IDs\n#     - \"\"            : Interactive selection via Whiptail\nvar_container=\"${var_container:-}\"\n\n# var_unattended: Run updates without user interaction inside containers\n#   Options: \"yes\" | \"no\" | \"\" (empty = interactive prompt)\nvar_unattended=\"${var_unattended:-}\"\n\n# var_skip_confirm: Skip initial confirmation dialog\n#   Options: \"yes\" | \"no\" (default: no)\nvar_skip_confirm=\"${var_skip_confirm:-no}\"\n\n# var_auto_reboot: Automatically reboot containers that require it after update\n#   Options: \"yes\" | \"no\" | \"\" (empty = interactive prompt)\nvar_auto_reboot=\"${var_auto_reboot:-}\"\n\n# var_continue_on_error: Continue updating remaining containers if one update fails\n#   Options: \"yes\" | \"no\" (default: no = stop on first error)\n#   Note: containers with backups always attempt restore on failure regardless of this setting\nvar_continue_on_error=\"${var_continue_on_error:-no}\"\n\n# var_dry_run: Check for available updates without applying them\n#   Options: \"yes\" | \"no\" (default: no)\n#   Output: lists each container with current vs. latest version\n#   Note: requires the container to be running; does not modify any container\nvar_dry_run=\"${var_dry_run:-no}\"\n\n# var_tags: Optionally override the tags used for auto-detection\n#   Options: \"community-script|proxmox-helper-scripts\" (default)\nvar_tags=\"${var_tags:-community-script|proxmox-helper-scripts}\"\n# =============================================================================\n# JSON CONFIG EXPORT\n# Run with --export-config to output current configuration as JSON\n# =============================================================================\n\nfunction export_config_json() {\n  cat <<EOF\n{\n  \"var_backup\": \"${var_backup}\",\n  \"var_backup_storage\": \"${var_backup_storage}\",\n  \"var_container\": \"${var_container}\",\n  \"var_unattended\": \"${var_unattended}\",\n  \"var_skip_confirm\": \"${var_skip_confirm}\",\n  \"var_auto_reboot\": \"${var_auto_reboot}\",\n  \"var_continue_on_error\": \"${var_continue_on_error}\",\n  \"var_dry_run\": \"${var_dry_run}\",\n  \"var_tags\": \"${var_tags}\"\n}\nEOF\n}\n\nfunction print_usage() {\n  cat <<EOF\nUsage: $(basename \"$0\") [OPTIONS]\n\nUpdate LXC containers created with community-scripts.\n\nOptions:\n  --help              Show this help message\n  --export-config     Export current configuration as JSON\n\nEnvironment Variables:\n  var_backup          Enable backup before update (yes/no)\n  var_backup_storage  Storage location for backups\n  var_container       Container selection (all/all_running/all_stopped/101,102,...)\n  var_unattended         Run updates unattended (yes/no)\n  var_skip_confirm       Skip initial confirmation (yes/no)\n  var_auto_reboot        Auto-reboot containers if required (yes/no)\n  var_continue_on_error  Continue to next container on update failure (yes/no)\n  var_dry_run            Check for updates without applying them (yes/no)\n  var_tags               Optionally override auto-detection tags (\"prod|smb|community-script\")\n\nExamples:\n  # Run interactively\n  $(basename \"$0\")\n\n  # Update all running containers unattended with backup\n  var_backup=yes var_backup_storage=local var_container=all_running var_unattended=yes var_skip_confirm=yes $(basename \"$0\")\n\n  # Update specific containers without backup\n  var_backup=no var_container=101,102,105 var_unattended=yes var_skip_confirm=yes $(basename \"$0\")\n\n  # Unattended cron-style: skip confirm, continue on error, no backup\n  var_backup=no var_container=all_running var_unattended=yes var_skip_confirm=yes var_continue_on_error=yes $(basename \"$0\")\n\n  # Dry-run: show available updates for all running containers without applying\n  var_container=all_running var_skip_confirm=yes var_dry_run=yes $(basename \"$0\")\n\n  # Export current configuration\n  $(basename \"$0\") --export-config\nEOF\n}\n\n# Handle command line arguments\ncase \"${1:-}\" in\n--help | -h)\n  print_usage\n  exit 0\n  ;;\n--export-config)\n  export_config_json\n  exit 0\n  ;;\nesac\n\n# =============================================================================\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    __   _  ________   __  __          __      __\n   / /  | |/ / ____/  / / / /___  ____/ /___ _/ /____\n  / /   |   / /      / / / / __ \\/ __  / __ `/ __/ _ \\\n / /___/   / /___   / /_/ / /_/ / /_/ / /_/ / /_/  __/\n/_____/_/|_\\____/   \\____/ .___/\\__,_/\\__,_/\\__/\\___/\n                        /_/\nEOF\n}\n\nfunction detect_service() {\n  pushd $(mktemp -d) >/dev/null\n  pct pull \"$1\" /usr/bin/update update 2>/dev/null\n  service=$(cat update | sed 's|.*/ct/||g' | sed 's|\\.sh).*||g')\n  popd >/dev/null\n}\n\nfunction dry_run_container() {\n  local container=\"$1\"\n  local service=\"$2\"\n\n  # Extract app name and source repo directly from check_for_gh_release call in the ct script\n  # Pattern: check_for_gh_release \"appname\" \"owner/repo\"\n  local check_line app_name app_lc source_repo\n  check_line=$(echo \"$script\" | grep -m1 'check_for_gh_release')\n\n  if [[ -z \"$check_line\" ]]; then\n    echo -e \"${YW}[DRY-RUN]${CL} Container $container ($service): no check_for_gh_release found — skipping\"\n    DRY_RUN_RESULT=\"no check_for_gh_release found — skipping\"\n    return\n  fi\n\n  app_name=$(echo \"$check_line\" | cut -d'\"' -f2)\n  source_repo=$(echo \"$check_line\" | cut -d'\"' -f4)\n  app_lc=$(echo \"${app_name,,}\" | tr -d ' ')\n\n  if [[ -z \"$source_repo\" || \"$source_repo\" != *\"/\"* ]]; then\n    echo -e \"${YW}[DRY-RUN]${CL} Container $container ($service): cannot parse source repo — skipping\"\n    DRY_RUN_RESULT=\"cannot parse source repo — skipping\"\n    return\n  fi\n\n  # Read installed version from container (stored by check_for_gh_release as ~/.<appname>)\n  local current_version\n  current_version=$(pct exec \"$container\" -- bash -c \"cat \\$HOME/.${app_lc} 2>/dev/null\" 2>/dev/null || true)\n  current_version=\"${current_version#v}\"\n\n  # Query latest release from GitHub API\n  local latest_version\n  latest_version=$(curl -sSL --max-time 10 \\\n    -H 'Accept: application/vnd.github+json' \\\n    -H 'X-GitHub-Api-Version: 2022-11-28' \\\n    \"https://api.github.com/repos/${source_repo}/releases/latest\" 2>/dev/null |\n    grep '\"tag_name\"' | head -1 | cut -d'\"' -f4 | sed 's/^v//')\n\n  if [[ -z \"$latest_version\" ]]; then\n    echo -e \"${YW}[DRY-RUN]${CL} Container $container ($service): cannot fetch latest version from $source_repo\"\n    DRY_RUN_RESULT=\"cannot fetch latest version from $source_repo\"\n    return\n  fi\n\n  if [[ -z \"$current_version\" ]]; then\n    echo -e \"${BL}[DRY-RUN]${CL} Container $container ($service): installed version unknown, latest: ${latest_version} (${source_repo})\"\n    DRY_RUN_RESULT=\"version unknown — latest: ${latest_version}\"\n  elif [[ \"$current_version\" == \"$latest_version\" ]]; then\n    echo -e \"${GN}[DRY-RUN]${CL} Container $container ($service): up to date (${current_version})\"\n    DRY_RUN_RESULT=\"up to date (${current_version})\"\n  else\n    echo -e \"${YW}[DRY-RUN]${CL} Container $container ($service): update available ${current_version} → ${latest_version}\"\n    DRY_RUN_RESULT=\"update available ${current_version} → ${latest_version}\"\n  fi\n}\n\nfunction backup_container() {\n  msg_info \"Creating backup for container $1\"\n  vzdump $1 --compress zstd --storage $STORAGE_CHOICE -notes-template \"{{guestname}} - community-scripts backup updater\" >/dev/null 2>&1\n  status=$?\n\n  if [ $status -eq 0 ]; then\n    msg_ok \"Backup created\"\n  else\n    msg_error \"Backup failed for container $1\"\n    exit 235\n  fi\n}\n\nfunction get_backup_storages() {\n  STORAGES=$(awk '\n/^[a-z]+:/ {\n    if (name != \"\") {\n        if (has_backup || (!has_content && type == \"dir\")) print name\n    }\n    split($0, a, \":\")\n    type = a[1]\n    name = a[2]\n    gsub(/^[ \\t]+|[ \\t]+$/, \"\", name)\n    has_content = 0\n    has_backup = 0\n}\n/^[ \\t]*content/ {\n    has_content = 1\n    if ($0 ~ /backup/) has_backup = 1\n}\nEND {\n    if (name != \"\") {\n        if (has_backup || (!has_content && type == \"dir\")) print name\n    }\n}\n' /etc/pve/storage.cfg)\n}\n\n# Structured result tracking for the final summary report\n# Each entry: \"CTID|service|STATUS|details\"\ndeclare -a UPDATE_RESULTS=()\nfunction log_result() {\n  # log_result <ctid> <service> <STATUS> <details>\n  UPDATE_RESULTS+=(\"${1}|${2}|${3}|${4}\")\n}\n\nheader_info\n\n# =============================================================================\n# LOGGING SETUP\n# Key events are written directly to a timestamped log file under\n# /usr/local/community-scripts/update_apps/ — this avoids any stdout\n# redirection that would break interactive spinners or whiptail dialogs.\n# The full summary table is appended at the end of the run.\n# =============================================================================\nLOG_DIR=\"/usr/local/community-scripts/update_apps\"\nmkdir -p \"$LOG_DIR\"\nLOG_FILE=\"${LOG_DIR}/$(date '+%Y%m%d_%H%M%S').log\"\necho \"Update started: $(date '+%Y-%m-%d %H:%M:%S')\" >\"$LOG_FILE\"\n\nfunction log_write() {\n  echo \"[$(date '+%H:%M:%S')] $*\" >>\"$LOG_FILE\"\n}\n\n# Skip confirmation if var_skip_confirm is set to yes\nif [[ \"$var_skip_confirm\" != \"yes\" ]]; then\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"LXC App Update\" --yesno \"This will update apps in LXCs installed by Helper-Scripts. Proceed?\" 10 58 || exit\nfi\n\ntags_formatted=\"${var_tags//|/, }\"\nmsg_info \"Loading all possible LXC containers from Proxmox VE with tags: ${tags_formatted}. This may take a few seconds...\"\nNODE=$(hostname)\ncontainers=$(pct list | tail -n +2 | awk '{print $0 \" \" $4}')\n\nif [ -z \"$containers\" ]; then\n  whiptail --title \"LXC Container Update\" --msgbox \"No LXC containers available!\" 10 60\n  exit 234\nfi\n\nmenu_items=()\nFORMAT=\"%-10s %-15s %-10s\"\nTAGS=\"${var_tags:-community-script|proxmox-helper-scripts}\"\n\nwhile read -r container; do\n  container_id=$(echo $container | awk '{print $1}')\n  container_name=$(echo $container | awk '{print $2}')\n  container_status=$(echo $container | awk '{print $3}')\n  formatted_line=$(printf \"$FORMAT\" \"$container_name\" \"$container_status\")\n  if pct config \"$container_id\" | grep -qE \"[^-][; ](${TAGS}).*\"; then\n    menu_items+=(\"$container_id\" \"$formatted_line\" \"OFF\")\n  fi\ndone <<<\"$containers\"\nmsg_ok \"Loaded $((${#menu_items[@]} / 3)) containers\"\n\n# Determine container selection based on var_container\nif [[ -n \"$var_container\" ]]; then\n  case \"$var_container\" in\n  all)\n    # Select all containers with matching tags\n    CHOICE=\"\"\n    for ((i = 0; i < ${#menu_items[@]}; i += 3)); do\n      CHOICE=\"$CHOICE ${menu_items[$i]}\"\n    done\n    CHOICE=$(echo \"$CHOICE\" | xargs)\n    ;;\n  all_running)\n    # Select only running containers with matching tags\n    CHOICE=\"\"\n    for ((i = 0; i < ${#menu_items[@]}; i += 3)); do\n      cid=\"${menu_items[$i]}\"\n      if pct status \"$cid\" 2>/dev/null | grep -q \"running\"; then\n        CHOICE=\"$CHOICE $cid\"\n      fi\n    done\n    CHOICE=$(echo \"$CHOICE\" | xargs)\n    ;;\n  all_stopped)\n    # Select only stopped containers with matching tags\n    CHOICE=\"\"\n    for ((i = 0; i < ${#menu_items[@]}; i += 3)); do\n      cid=\"${menu_items[$i]}\"\n      if pct status \"$cid\" 2>/dev/null | grep -q \"stopped\"; then\n        CHOICE=\"$CHOICE $cid\"\n      fi\n    done\n    CHOICE=$(echo \"$CHOICE\" | xargs)\n    ;;\n  *)\n    # Assume comma-separated list of container IDs\n    CHOICE=$(echo \"$var_container\" | tr ',' ' ')\n    ;;\n  esac\n\n  if [[ -z \"$CHOICE\" ]]; then\n    msg_error \"No containers matched the selection criteria: $var_container ${var_tags:-community-script|proxmox-helper-scripts}\"\n    exit 234\n  fi\n  msg_ok \"Selected containers: $CHOICE\"\nelse\n  CHOICE=$(whiptail --title \"LXC Container Update\" \\\n    --checklist \"Select LXC containers to update:\" 25 60 13 \\\n    \"${menu_items[@]}\" 3>&2 2>&1 1>&3 | tr -d '\"')\n\n  if [ -z \"$CHOICE\" ]; then\n    whiptail --title \"LXC Container Update\" \\\n      --msgbox \"No containers selected!\" 10 60\n    exit 0\n  fi\nfi\n\nheader_info\n\n# Determine backup choice based on var_backup\n# Dry-run never needs a backup — skip the prompt entirely\nif [[ \"$var_dry_run\" == \"yes\" ]]; then\n  BACKUP_CHOICE=\"no\"\nelif [[ -n \"$var_backup\" ]]; then\n  BACKUP_CHOICE=\"$var_backup\"\nelse\n  BACKUP_CHOICE=\"no\"\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"LXC Container Update\" --yesno \"Do you want to backup your containers before update?\" 10 58); then\n    BACKUP_CHOICE=\"yes\"\n  fi\nfi\n\n# Determine unattended update based on var_unattended\n# Dry-run never executes updates — skip the prompt entirely\nif [[ \"$var_dry_run\" == \"yes\" ]]; then\n  UNATTENDED_UPDATE=\"no\"\nelif [[ -n \"$var_unattended\" ]]; then\n  UNATTENDED_UPDATE=\"$var_unattended\"\nelse\n  UNATTENDED_UPDATE=\"no\"\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"LXC Container Update\" --yesno \"Run updates unattended?\" 10 58); then\n    UNATTENDED_UPDATE=\"yes\"\n  fi\nfi\n\nif [ \"$BACKUP_CHOICE\" == \"yes\" ]; then\n  get_backup_storages\n\n  if [ -z \"$STORAGES\" ]; then\n    msg_error \"No storage with 'backup' support found!\"\n    exit 119\n  fi\n\n  # Determine storage based on var_backup_storage\n  if [[ -n \"$var_backup_storage\" ]]; then\n    # Validate that the specified storage exists and supports backups\n    if echo \"$STORAGES\" | grep -qw \"$var_backup_storage\"; then\n      STORAGE_CHOICE=\"$var_backup_storage\"\n      msg_ok \"Using backup storage: $STORAGE_CHOICE\"\n    else\n      msg_error \"Specified backup storage '$var_backup_storage' not found or doesn't support backups!\"\n      msg_info \"Available storages: $(echo $STORAGES | tr '\\n' ' ')\"\n      exit 119\n    fi\n  else\n    MENU_ITEMS=()\n    for STORAGE in $STORAGES; do\n      MENU_ITEMS+=(\"$STORAGE\" \"\")\n    done\n\n    STORAGE_CHOICE=$(whiptail --title \"Select storage device\" --menu \"Select a storage device (Only storage devices with 'backup' support are listed):\" 15 50 5 \"${MENU_ITEMS[@]}\" 3>&1 1>&2 2>&3)\n\n    if [ -z \"$STORAGE_CHOICE\" ]; then\n      msg_error \"No storage selected!\"\n      exit 0\n    fi\n  fi\nfi\n\nUPDATE_CMD=\"update;\"\nif [ \"$UNATTENDED_UPDATE\" == \"yes\" ]; then\n  UPDATE_CMD=\"export PHS_SILENT=1;update;\"\nfi\n\ncontainers_needing_reboot=()\nfor container in $CHOICE; do\n  echo -e \"${BL}[INFO]${CL} Updating container $container\"\n  log_write \"Container $container: starting\"\n\n  if [ \"$BACKUP_CHOICE\" == \"yes\" ]; then\n    backup_container $container\n  fi\n\n  os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n  status=$(pct status $container)\n  template=$(pct config $container | grep -q \"template:\" && echo \"true\" || echo \"false\")\n  if [ \"$template\" == \"false\" ] && [ \"$status\" == \"status: stopped\" ]; then\n    echo -e \"${BL}[Info]${GN} Starting${BL} $container ${CL} \\n\"\n    pct start $container\n    echo -e \"${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL} \\n\"\n    sleep 5\n  fi\n\n  #1) Detect service using the service name in the update command\n  detect_service $container\n\n  #1.1) If update script not detected, return\n  if [ -z \"${service}\" ]; then\n    echo -e \"${YW}[WARN]${CL} Update script not found. Skipping to next container\"\n    log_result \"$container\" \"(unknown)\" \"SKIPPED\" \"No update script found in container\"\n    log_write \"Container $container: SKIPPED — no update script found\"\n    continue\n  else\n    echo -e \"${BL}[INFO]${CL} Detected service: ${GN}${service}${CL}\"\n    log_write \"Container $container: detected service '$service'\"\n  fi\n\n  #2) Extract service build/update resource requirements from config/installation file\n  script=$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/ct/${service}.sh)\n\n  #2.1) Check if the script downloaded successfully\n  if [ $? -ne 0 ]; then\n    echo -e \"${RD}[ERROR]${CL} Issue while downloading install script.\"\n    echo -e \"${YW}[WARN]${CL} Unable to assess build resource requirements. Proceeding with current resources.\"\n  fi\n\n  config=$(pct config \"$container\")\n  build_cpu=$(echo \"$script\" | { grep -m 1 \"var_cpu\" || test $? = 1; } | sed 's|.*=||g' | sed 's|\"||g' | sed 's|.*var_cpu:-||g' | sed 's|}||g')\n  build_ram=$(echo \"$script\" | { grep -m 1 \"var_ram\" || test $? = 1; } | sed 's|.*=||g' | sed 's|\"||g' | sed 's|.*var_ram:-||g' | sed 's|}||g')\n  run_cpu=$(echo \"$script\" | { grep -m 1 \"pct set \\$CTID -cores\" || test $? = 1; } | sed 's|.*cores ||g')\n  run_ram=$(echo \"$script\" | { grep -m 1 \"pct set \\$CTID -memory\" || test $? = 1; } | sed 's|.*memory ||g')\n  current_cpu=$(echo \"$config\" | grep -m 1 \"cores:\" | sed 's|cores: ||g')\n  current_ram=$(echo \"$config\" | grep -m 1 \"memory:\" | sed 's|memory: ||g')\n\n  #Test if all values are valid (>0)\n  if [ -z \"${run_cpu}\" ] || [ \"$run_cpu\" -le 0 ]; then\n    #echo \"No valid value found for run_cpu. Assuming same as current configuration.\"\n    run_cpu=$current_cpu\n  fi\n\n  if [ -z \"${run_ram}\" ] || [ \"$run_ram\" -le 0 ]; then\n    #echo \"No valid value found for run_ram. Assuming same as current configuration.\"\n    run_ram=$current_ram\n  fi\n\n  if [ -z \"${build_cpu}\" ] || [ \"$build_cpu\" -le 0 ]; then\n    #echo \"No valid value found for build_cpu. Assuming same as current configuration.\"\n    build_cpu=$current_cpu\n  fi\n\n  if [ -z \"${build_ram}\" ] || [ \"$build_ram\" -le 0 ]; then\n    #echo \"No valid value found for build_ram. Assuming same as current configuration.\"\n    build_ram=$current_ram\n  fi\n\n  UPDATE_BUILD_RESOURCES=0\n  if [ \"$build_cpu\" -gt \"$run_cpu\" ] || [ \"$build_ram\" -gt \"$run_ram\" ]; then\n    UPDATE_BUILD_RESOURCES=1\n  fi\n\n  #3) if build resources are different than run resources, then:\n  if [ \"$UPDATE_BUILD_RESOURCES\" -eq \"1\" ] && [[ \"$var_dry_run\" != \"yes\" ]]; then\n    pct set \"$container\" --cores \"$build_cpu\" --memory \"$build_ram\"\n  fi\n\n  #3.5) Dry-run: report update availability without applying\n  if [[ \"$var_dry_run\" == \"yes\" ]]; then\n    DRY_RUN_RESULT=\"\"\n    dry_run_container \"$container\" \"$service\"\n    log_result \"$container\" \"$service\" \"DRY-RUN\" \"${DRY_RUN_RESULT:-version check only}\"\n    log_write \"Container $container ($service): DRY-RUN — ${DRY_RUN_RESULT:-version check only}\"\n    continue\n  fi\n\n  #4) Update service, using the update command\n  # Prepend a no-op 'clear' wrapper to PATH so update scripts calling clear\n  # don't fail without a TTY — works for all shells incl. ash (no export -f)\n  SETUP_CMD=\"mkdir -p /tmp/.nc; printf '#!/bin/sh\\n:\\n' > /tmp/.nc/clear; chmod +x /tmp/.nc/clear; export PATH=/tmp/.nc:\\$PATH; export TERM=dumb; \"\n  case \"$os\" in\n  alpine) pct exec \"$container\" -- ash -c \"${SETUP_CMD}${UPDATE_CMD}\" ;;\n  archlinux) pct exec \"$container\" -- bash -c \"${SETUP_CMD}${UPDATE_CMD}\" ;;\n  fedora | rocky | centos | alma) pct exec \"$container\" -- bash -c \"${SETUP_CMD}${UPDATE_CMD}\" ;;\n  ubuntu | debian | devuan) pct exec \"$container\" -- bash -c \"${SETUP_CMD}${UPDATE_CMD}\" ;;\n  opensuse) pct exec \"$container\" -- bash -c \"${SETUP_CMD}${UPDATE_CMD}\" ;;\n  esac\n  exit_code=$?\n\n  #5) if build resources are different than run resources, then:\n  if [ \"$UPDATE_BUILD_RESOURCES\" -eq \"1\" ]; then\n    pct set \"$container\" --cores \"$run_cpu\" --memory \"$run_ram\"\n  fi\n\n  if pct exec \"$container\" -- [ -e \"/var/run/reboot-required\" ]; then\n    # Get the container's hostname and add it to the list\n    container_hostname=$(pct exec \"$container\" hostname)\n    containers_needing_reboot+=(\"$container ($container_hostname)\")\n  fi\n\n  if [ \"$template\" == \"false\" ] && [ \"$status\" == \"status: stopped\" ]; then\n    echo -e \"${BL}[Info]${GN} Shutting down${BL} $container ${CL} \\n\"\n    pct shutdown $container &>/dev/null &\n  fi\n\n  if [ $exit_code -eq 0 ]; then\n    msg_ok \"Updated container $container\"\n    log_result \"$container\" \"$service\" \"OK\" \"Updated successfully\"\n    log_write \"Container $container ($service): OK\"\n  elif [ $exit_code -eq 75 ]; then\n    echo -e \"${YW}[WARN]${CL} Container $container skipped (requires interactive mode)\"\n    log_result \"$container\" \"$service\" \"SKIPPED\" \"Requires interactive mode (exit 75)\"\n    log_write \"Container $container ($service): SKIPPED — requires interactive mode\"\n  elif [ $exit_code -eq 113 ]; then\n    echo -e \"${YW}[WARN]${CL} Container $container skipped (under-provisioned: increase CPU/RAM to match template)\"\n    log_result \"$container\" \"$service\" \"SKIPPED\" \"Under-provisioned — increase CPU/RAM to match template\"\n    log_write \"Container $container ($service): SKIPPED — under-provisioned\"\n  elif [ $exit_code -eq 114 ]; then\n    echo -e \"${YW}[WARN]${CL} Container $container skipped (storage critically low on /boot)\"\n    log_result \"$container\" \"$service\" \"SKIPPED\" \"Storage critically low on /boot (>80%)\"\n    log_write \"Container $container ($service): SKIPPED — storage critically low on /boot\"\n  elif [ \"$BACKUP_CHOICE\" == \"yes\" ]; then\n    msg_error \"Update failed for container $container (exit code: $exit_code) — attempting restore\"\n    log_write \"Container $container ($service): FAILED (exit $exit_code) — attempting restore\"\n    msg_info \"Restoring LXC $container from backup ($STORAGE_CHOICE)\"\n    pct stop $container\n    LXC_STORAGE=$(pct config $container | awk -F '[:,]' '/rootfs/ {print $2}')\n    BACKUP_ENTRY=$(pvesm list \"$STORAGE_CHOICE\" 2>/dev/null | awk -v ctid=\"$container\" '$1 ~ \"vzdump-lxc-\"ctid\"-\" || $1 ~ \"/ct/\"ctid\"/\" {print $1}' | sort -r | head -n1)\n    if [ -z \"$BACKUP_ENTRY\" ]; then\n      msg_error \"No backup found in storage $STORAGE_CHOICE for container $container\"\n      log_result \"$container\" \"$service\" \"FAILED\" \"Update failed (exit $exit_code) — no backup found for restore\"\n      log_write \"Container $container ($service): FAILED — no backup found for restore\"\n      exit 235\n    fi\n    msg_info \"Restoring from: $BACKUP_ENTRY\"\n    pct restore $container \"$BACKUP_ENTRY\" --storage $LXC_STORAGE --force >/dev/null 2>&1\n    restorestatus=$?\n    if [ $restorestatus -eq 0 ]; then\n      pct start $container\n      msg_ok \"Container $container successfully restored from backup\"\n      log_result \"$container\" \"$service\" \"RESTORED\" \"Update failed (exit $exit_code) — restored from backup\"\n      log_write \"Container $container ($service): RESTORED from $BACKUP_ENTRY\"\n    else\n      msg_error \"Restore failed for container $container\"\n      log_result \"$container\" \"$service\" \"FAILED\" \"Update failed (exit $exit_code) — restore also failed\"\n      log_write \"Container $container ($service): FAILED — restore also failed\"\n      exit 235\n    fi\n  else\n    msg_error \"Update failed for container $container (exit code: $exit_code)\"\n    log_result \"$container\" \"$service\" \"FAILED\" \"Exit code $exit_code\"\n    log_write \"Container $container ($service): FAILED (exit $exit_code)\"\n    if [[ \"$var_continue_on_error\" == \"yes\" ]]; then\n      echo -e \"${YW}[WARN]${CL} Continuing to next container (var_continue_on_error=yes)\"\n      continue\n    else\n      exit \"$exit_code\"\n    fi\n  fi\ndone\n\nwait\nheader_info\nif [[ \"$var_dry_run\" == \"yes\" ]]; then\n  echo -e \"${GN}Dry-run complete. No containers were modified.${CL}\\n\"\nelse\n  echo -e \"${GN}The process is complete, and the containers have been successfully updated.${CL}\\n\"\nfi\n\n# =============================================================================\n# SUMMARY REPORT\n# =============================================================================\nif [ \"${#UPDATE_RESULTS[@]}\" -gt 0 ]; then\n  SEPARATOR=\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n  HEADER=$(printf \"  %-8s  %-22s  %-10s  %s\" \"CTID\" \"Service\" \"Status\" \"Details\")\n\n  # terminal output (with colours)\n  echo \"\"\n  echo \"$SEPARATOR\"\n  echo \"$HEADER\"\n  echo \"$SEPARATOR\"\n  for entry in \"${UPDATE_RESULTS[@]}\"; do\n    IFS='|' read -r _ctid _svc _status _details <<<\"$entry\"\n    case \"$_status\" in\n    OK) _color=\"${GN}\" ;;\n    FAILED) _color=\"${RD}\" ;;\n    RESTORED) _color=\"${YW}\" ;;\n    *) _color=\"${YW}\" ;;\n    esac\n    printf \"  %-8s  %-22s  ${_color}%-10s${CL}  %s\\n\" \"$_ctid\" \"$_svc\" \"$_status\" \"$_details\"\n  done\n  echo \"$SEPARATOR\"\n  echo \"\"\n  echo \"Full log: $LOG_FILE\"\n  echo \"\"\n\n  # append plain-text summary to log file\n  {\n    echo \"\"\n    echo \"Update finished: $(date '+%Y-%m-%d %H:%M:%S')\"\n    echo \"$SEPARATOR\"\n    echo \"$HEADER\"\n    echo \"$SEPARATOR\"\n    for entry in \"${UPDATE_RESULTS[@]}\"; do\n      IFS='|' read -r _ctid _svc _status _details <<<\"$entry\"\n      printf \"  %-8s  %-22s  %-10s  %s\\n\" \"$_ctid\" \"$_svc\" \"$_status\" \"$_details\"\n    done\n    echo \"$SEPARATOR\"\n  } >>\"$LOG_FILE\"\nfi\nif [ \"${#containers_needing_reboot[@]}\" -gt 0 ]; then\n  echo -e \"${RD}The following containers require a reboot:${CL}\"\n  for container_name in \"${containers_needing_reboot[@]}\"; do\n    echo \"$container_name\"\n  done\n\n  # Determine reboot choice based on var_auto_reboot\n  REBOOT_CHOICE=\"no\"\n  if [[ -n \"$var_auto_reboot\" ]]; then\n    REBOOT_CHOICE=\"$var_auto_reboot\"\n  else\n    echo -ne \"${INFO} Do you wish to reboot these containers? <yes/No>  \"\n    read -r prompt\n    if [[ ${prompt,,} =~ ^(yes)$ ]]; then\n      REBOOT_CHOICE=\"yes\"\n    fi\n  fi\n\n  if [[ \"$REBOOT_CHOICE\" == \"yes\" ]]; then\n    echo -e \"${CROSS}${HOLD} ${YWB}Rebooting containers.${CL}\"\n    for container_name in \"${containers_needing_reboot[@]}\"; do\n      container=$(echo $container_name | cut -d \" \" -f 1)\n      pct reboot ${container}\n    done\n  fi\nfi\n"
  },
  {
    "path": "tools/pve/update-lxcs-cron.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n#\n# This script is installed locally by cron-update-lxcs.sh and executed\n# by cron. It updates all LXC containers using their native package manager.\n\n# Ensure full PATH when running via cron (pct lives in /usr/sbin)\nexport PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n\nCONF_FILE=\"/etc/update-lxcs.conf\"\n\necho -e \"\\n $(date)\"\n\n# Collect excluded containers from arguments\nexcluded_containers=(\"$@\")\n\n# Merge exclusions from config file if it exists\nif [[ -f \"$CONF_FILE\" ]]; then\n  conf_exclude=$(grep -oP '^\\s*EXCLUDE\\s*=\\s*\\K[0-9,]+' \"$CONF_FILE\" 2>/dev/null || true)\n  IFS=',' read -ra conf_ids <<<\"$conf_exclude\"\n  for id in \"${conf_ids[@]}\"; do\n    id=\"${id// /}\"\n    [[ -n \"$id\" ]] && excluded_containers+=(\"$id\")\n  done\nfi\n\nfunction update_container() {\n  local container=$1\n  local name\n  name=$(pct exec \"$container\" hostname 2>/dev/null || echo \"unknown\")\n  local os\n  os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n  echo -e \"\\n [Info] Updating $container : $name (os: $os)\"\n  case \"$os\" in\n  alpine) pct exec \"$container\" -- ash -c \"apk -U upgrade\" ;;\n  archlinux) pct exec \"$container\" -- bash -c \"pacman -Syyu --noconfirm\" ;;\n  fedora | rocky | centos | alma) pct exec \"$container\" -- bash -c \"dnf -y update && dnf -y upgrade\" ;;\n  ubuntu | debian | devuan) pct exec \"$container\" -- bash -c \"apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::='--force-confold' dist-upgrade -y; rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED\" ;;\n  opensuse) pct exec \"$container\" -- bash -c \"zypper ref && zypper --non-interactive dup\" ;;\n  *) echo \" [Warn] Unknown OS type '$os' for container $container, skipping\" ;;\n  esac\n}\n\nfor container in $(pct list | awk '{if(NR>1) print $1}'); do\n  excluded=false\n  for excluded_container in \"${excluded_containers[@]}\"; do\n    if [ \"$container\" == \"$excluded_container\" ]; then\n      excluded=true\n      break\n    fi\n  done\n  if [ \"$excluded\" == true ]; then\n    echo -e \"[Info] Skipping $container\"\n    sleep 1\n  else\n    status=$(pct status \"$container\")\n    if pct config \"$container\" 2>/dev/null | grep -q \"^template:\"; then\n      echo -e \"[Info] Skipping template $container\"\n      continue\n    fi\n    if [ \"$status\" == \"status: stopped\" ]; then\n      echo -e \"[Info] Starting $container\"\n      pct start \"$container\"\n      sleep 5\n      update_container \"$container\" || echo \" [Error] Update failed for $container\"\n      # check if patchmon agent is present in container and run a report if found\n      if pct exec \"$container\" -- [ -e \"/usr/local/bin/patchmon-agent\" ]; then\n        echo -e \"${BL}[Info]${GN} patchmon-agent found in ${BL} $container ${CL}, triggering report. \\n\"\n        pct exec \"$container\" -- \"/usr/local/bin/patchmon-agent\" \"report\"\n      fi\n      echo -e \"[Info] Shutting down $container\"\n      pct shutdown \"$container\" --timeout 60 &\n    elif [ \"$status\" == \"status: running\" ]; then\n      update_container \"$container\" || echo \" [Error] Update failed for $container\"\n      # check if patchmon agent is present in container and run a report if found\n      if pct exec \"$container\" -- [ -e \"/usr/local/bin/patchmon-agent\" ]; then\n        echo -e \"${BL}[Info]${GN} patchmon-agent found in ${BL} $container ${CL}, triggering report. \\n\"\n        pct exec \"$container\" -- \"/usr/local/bin/patchmon-agent\" \"report\"\n      fi\n    fi\n  fi\ndone\nwait\n"
  },
  {
    "path": "tools/pve/update-lxcs.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n   __  __          __      __          __   _  ________\n  / / / /___  ____/ /___ _/ /____     / /  | |/ / ____/\n / / / / __ \\/ __  / __ `/ __/ _ \\   / /   |   / /\n/ /_/ / /_/ / /_/ / /_/ / /_/  __/  / /___/   / /___\n\\____/ .___/\\__,_/\\__,_/\\__/\\___/  /_____/_/|_\\____/\n    /_/\n\nEOF\n}\nset -eEuo pipefail\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nCM='\\xE2\\x9C\\x94\\033'\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"update-lxcs\" \"pve\"\n\nheader_info\necho \"Loading...\"\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Proxmox VE LXC Updater\" --yesno \"This Will Update LXC Containers. Proceed?\" 10 58\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Skip Not-Running Containers\" --yesno \"Do you want to skip containers that are not currently running?\" 10 58; then\n  SKIP_STOPPED=\"yes\"\nelse\n  SKIP_STOPPED=\"no\"\nfi\n\nNODE=$(hostname)\nEXCLUDE_MENU=()\nMSG_MAX_LENGTH=0\nwhile read -r TAG ITEM; do\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET\n  EXCLUDE_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pct list | awk 'NR>1')\nexcluded_containers=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Containers on $NODE\" --checklist \"\\nSelect containers to skip from updates:\\n\" 16 $((MSG_MAX_LENGTH + 23)) 6 \"${EXCLUDE_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n\nfunction needs_reboot() {\n  local container=$1\n  local os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n  local reboot_required_file=\"/var/run/reboot-required.pkgs\"\n  if [ -f \"$reboot_required_file\" ]; then\n    if [[ \"$os\" == \"ubuntu\" || \"$os\" == \"debian\" ]]; then\n      if pct exec \"$container\" -- [ -s \"$reboot_required_file\" ]; then\n        return 0\n      fi\n    fi\n  fi\n  return 1\n}\n\nfunction update_container() {\n  container=$1\n  header_info\n  name=$(pct exec \"$container\" hostname)\n  os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n  if [[ \"$os\" == \"ubuntu\" || \"$os\" == \"debian\" || \"$os\" == \"fedora\" ]]; then\n    disk_info=$(pct exec \"$container\" df /boot | awk 'NR==2{gsub(\"%\",\"\",$5); printf \"%s %.1fG %.1fG %.1fG\", $5, $3/1024/1024, $2/1024/1024, $4/1024/1024 }')\n    read -ra disk_info_array <<<\"$disk_info\"\n    echo -e \"${BL}[Info]${GN} Updating ${BL}$container${CL} : ${GN}$name${CL} - ${YW}Boot Disk: ${disk_info_array[0]}% full [${disk_info_array[1]}/${disk_info_array[2]} used, ${disk_info_array[3]} free]${CL}\\n\"\n  else\n    echo -e \"${BL}[Info]${GN} Updating ${BL}$container${CL} : ${GN}$name${CL} - ${YW}[No disk info for ${os}]${CL}\\n\"\n  fi\n  case \"$os\" in\n  alpine) pct exec \"$container\" -- ash -c \"apk -U upgrade\" ;;\n  archlinux) pct exec \"$container\" -- bash -c \"pacman -Syyu --noconfirm\" ;;\n  fedora | rocky | centos | alma) pct exec \"$container\" -- bash -c \"dnf -y update && dnf -y upgrade\" ;;\n  ubuntu | debian | devuan) pct exec \"$container\" -- bash -c \"apt-get update 2>/dev/null | grep 'packages.*upgraded'; apt list --upgradable 2>/dev/null | cat && apt-get -yq dist-upgrade 2>&1; rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED || true\" ;;\n  opensuse) pct exec \"$container\" -- bash -c \"zypper ref && zypper --non-interactive dup\" ;;\n  esac\n}\n\ncontainers_needing_reboot=()\nheader_info\nfor container in $(pct list | awk '{if(NR>1) print $1}'); do\n  if [[ \" ${excluded_containers[@]} \" =~ \" $container \" ]]; then\n    header_info\n    echo -e \"${BL}[Info]${GN} Skipping ${BL}$container${CL}\"\n    sleep 1\n  else\n    status=$(pct status $container)\n    if [ \"$SKIP_STOPPED\" == \"yes\" ] && [ \"$status\" == \"status: stopped\" ]; then\n      header_info\n      echo -e \"${BL}[Info]${GN} Skipping ${BL}$container${CL}${GN} (not running)${CL}\"\n      sleep 1\n      continue\n    fi\n    template=$(pct config $container | grep -q \"template:\" && echo \"true\" || echo \"false\")\n    if [ \"$template\" == \"false\" ] && [ \"$status\" == \"status: stopped\" ]; then\n      echo -e \"${BL}[Info]${GN} Starting${BL} $container ${CL} \\n\"\n      pct start $container\n      echo -e \"${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL} \\n\"\n      sleep 5\n      update_container $container\n      echo -e \"${BL}[Info]${GN} Shutting down${BL} $container ${CL} \\n\"\n      pct shutdown $container &\n    elif [ \"$status\" == \"status: running\" ]; then\n      update_container $container\n    fi\n    if [ \"$status\" == \"status: running\" ]; then\n      if pct exec \"$container\" -- [ -e \"/var/run/reboot-required\" ]; then\n        # Get the container's hostname and add it to the list\n        container_hostname=$(pct exec \"$container\" hostname)\n        containers_needing_reboot+=(\"$container ($container_hostname)\")\n      fi\n      # check if patchmon agent is present in container and run a report if found\n      if pct exec \"$container\" -- [ -e \"/usr/local/bin/patchmon-agent\" ]; then\n        echo -e \"${BL}[Info]${GN} patchmon-agent found in ${BL} $container ${CL}, triggering report. \\n\"\n        pct exec \"$container\" -- \"/usr/local/bin/patchmon-agent\" \"report\"\n      fi\n    fi\n  fi\ndone\nwait\nheader_info\necho -e \"${GN}The process is complete, and the containers have been successfully updated.${CL}\\n\"\nif [ \"${#containers_needing_reboot[@]}\" -gt 0 ]; then\n  echo -e \"${RD}The following containers require a reboot:${CL}\"\n  for container_name in \"${containers_needing_reboot[@]}\"; do\n    echo \"$container_name\"\n  done\nfi\necho \"\"\n"
  },
  {
    "path": "tools/pve/update-repo.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   __  __          __      __          ____                 \n  / / / /___  ____/ /___ _/ /____     / __ \\___  ____  ____ \n / / / / __ \\/ __  / __ `/ __/ _ \\   / /_/ / _ \\/ __ \\/ __ \\\n/ /_/ / /_/ / /_/ / /_/ / /_/  __/  / _, _/  __/ /_/ / /_/ /\n\\____/ .___/\\__,_/\\__,_/\\__/\\___/  /_/ |_|\\___/ .___/\\____/ \n    /_/                                      /_/            \nEOF\n}\n\nset -eEuo pipefail\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nGN=$(echo \"\\033[1;92m\")\nCL=$(echo \"\\033[m\")\n\n# Telemetry\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true\ndeclare -f init_tool_telemetry &>/dev/null && init_tool_telemetry \"update-repo\" \"pve\"\n\nheader_info\necho \"Loading...\"\nNODE=$(hostname)\n\nfunction update_container() {\n  container=$1\n  os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\n\n  if [[ \"$os\" == \"ubuntu\" || \"$os\" == \"debian\" ]]; then\n    echo -e \"${BL}[Info]${GN} Checking /usr/bin/update in ${BL}$container${CL} (OS: ${GN}$os${CL})\"\n\n    if pct exec \"$container\" -- [ -e /usr/bin/update ]; then\n      if pct exec \"$container\" -- grep -q \"community-scripts/ProxmoxVE\" /usr/bin/update; then\n        echo -e \"${RD}[No Change]${CL} /usr/bin/update is already up to date in ${BL}$container${CL}.\\n\"\n      elif pct exec \"$container\" -- grep -q -v \"tteck\" /usr/bin/update; then\n        echo -e \"${RD}[Warning]${CL} /usr/bin/update in ${BL}$container${CL} contains a different entry (${RD}tteck${CL}). No changes made.\\n\"\n      else\n        pct exec \"$container\" -- bash -c \"sed -i 's/tteck\\\\/Proxmox/community-scripts\\\\/ProxmoxVE/g' /usr/bin/update\"\n\n        if pct exec \"$container\" -- grep -q \"community-scripts/ProxmoxVE\" /usr/bin/update; then\n          echo -e \"${GN}[Success]${CL} /usr/bin/update updated in ${BL}$container${CL}.\\n\"\n        else\n          echo -e \"${RD}[Error]${CL} /usr/bin/update in ${BL}$container${CL} could not be updated properly.\\n\"\n        fi\n      fi\n    else\n      echo -e \"${RD}[Error]${CL} /usr/bin/update not found in container ${BL}$container${CL}.\\n\"\n    fi\n  else\n    echo -e \"${BL}[Info]${GN} Skipping ${BL}$container${CL} (not Debian/Ubuntu)\\n\"\n  fi\n}\n\nheader_info\nfor container in $(pct list | awk '{if(NR>1) print $1}'); do\n  update_container \"$container\"\ndone\n\nheader_info\necho -e \"${GN}The process is complete. The repositories have been switched to community-scripts/ProxmoxVE.${CL}\\n\"\n"
  },
  {
    "path": "tools/pve/usb-passthrough.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\necho -e \"\\e[1;33m This script will allow USB passthrough to a PRIVILEGED LXC Container ONLY\\e[0m\"\nwhile true; do\n  read -p \"Did you replace 106 with your LXC ID? Proceed(y/n)?\" yn\n  case $yn in\n  [Yy]*) break ;;\n  [Nn]*) exit ;;\n  *) echo \"Please answer yes or no.\" ;;\n  esac\ndone\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nCHAR_DEVS+=(\"166:.*\")\nCHAR_DEVS+=(\"188:.*\")\nCHAR_DEVS+=(\"189:.*\")\n\nfor char_dev in ${CHAR_DEVS[@]}; do\n  [ ! -z \"${CHAR_DEV_STRING-}\" ] && CHAR_DEV_STRING+=\" -o\"\n  CHAR_DEV_STRING+=\" -regex \\\".*/${char_dev}\\\"\"\ndone\n\nread -r -d '' HOOK_SCRIPT <<-EOF || true\nfor char_dev in \\$(find /sys/dev/char -regextype sed $CHAR_DEV_STRING); do\n  dev=\"/dev/\\$(sed -n \"/DEVNAME/ s/^.*=\\(.*\\)$/\\1/p\" \\${char_dev}/uevent)\";\n  mkdir -p \\$(dirname \\${LXC_ROOTFS_MOUNT}\\${dev});\n  for link in \\$(udevadm info --query=property \\$dev | sed -n \"s/DEVLINKS=//p\"); do\n    mkdir -p \\${LXC_ROOTFS_MOUNT}\\$(dirname \\$link);\n    cp -dpR \\$link \\${LXC_ROOTFS_MOUNT}\\${link};\n  done;\n  cp -dpR \\$dev \\${LXC_ROOTFS_MOUNT}\\${dev};\ndone;\nEOF\nHOOK_SCRIPT=${HOOK_SCRIPT//$'\\n'/}\n\nCTID=$1\nCTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf\nsed '/autodev/d' $CTID_CONFIG_PATH >CTID.conf\ncat CTID.conf >$CTID_CONFIG_PATH\n\ncat <<EOF >>$CTID_CONFIG_PATH\nlxc.autodev: 1\nlxc.hook.autodev: bash -c '$HOOK_SCRIPT'\nEOF\necho -e \"\\e[1;33m Finished....Reboot ${CTID} LXC to apply the changes \\e[0m\"\n"
  },
  {
    "path": "turnkey/turnkey.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\n# Source shared libraries\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)\nload_functions\ncatch_errors\n\nAPP=\"TurnKey LXC\"\nNSAPP=\"turnkey\"\nDIAGNOSTICS=\"no\"\nMETHOD=\"default\"\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nEXECUTION_ID=\"${RANDOM_UUID}\"\n\nheader_info() {\n  clear\n  cat <<\"EOF\"\n ______              __ __           __   _  _______\n/_  __/_ _________  / //_/__ __ __  / /  | |/_/ ___/\n / / / // / __/ _ \\/ ,< / -_) // / / /___>  </ /__\n/_/  \\_,_/_/ /_//_/_/|_|\\__/\\_, / /____/_/|_|\\___/\n                           /___/\nEOF\n}\n\n# Validate if a container ID is available (cluster-aware)\nvalidate_container_id() {\n  local ctid=\"$1\"\n  [[ \"$ctid\" =~ ^[0-9]+$ ]] || return 1\n\n  # Cluster-wide check via pvesh\n  if command -v pvesh &>/dev/null; then\n    local cluster_ids\n    cluster_ids=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null |\n      grep -oP '\"vmid\":\\s*\\K[0-9]+' 2>/dev/null || true)\n    if [[ -n \"$cluster_ids\" ]] && echo \"$cluster_ids\" | grep -qw \"$ctid\"; then\n      return 1\n    fi\n  fi\n\n  # Local fallback\n  if [[ -f \"/etc/pve/qemu-server/${ctid}.conf\" ]] || [[ -f \"/etc/pve/lxc/${ctid}.conf\" ]]; then\n    return 1\n  fi\n\n  # Check all cluster nodes\n  if [[ -d \"/etc/pve/nodes\" ]]; then\n    for node_dir in /etc/pve/nodes/*/; do\n      if [[ -f \"${node_dir}qemu-server/${ctid}.conf\" ]] || [[ -f \"${node_dir}lxc/${ctid}.conf\" ]]; then\n        return 1\n      fi\n    done\n  fi\n\n  # Check LVM volumes\n  if lvs --noheadings -o lv_name 2>/dev/null | grep -qE \"(^|[-_])${ctid}($|[-_])\"; then\n    return 1\n  fi\n  return 0\n}\n\nget_valid_container_id() {\n  local suggested_id=\"${1:-$(pvesh get /cluster/nextid 2>/dev/null || echo 100)}\"\n  while ! validate_container_id \"$suggested_id\"; do\n    suggested_id=$((suggested_id + 1))\n  done\n  echo \"$suggested_id\"\n}\n\ncleanup_ctid() {\n  if pct status \"$CTID\" &>/dev/null; then\n    if [[ \"$(pct status \"$CTID\" | awk '{print $2}')\" == \"running\" ]]; then\n      pct stop \"$CTID\"\n    fi\n    pct destroy \"$CTID\"\n  fi\n}\n\nselect_storage() {\n  local class=\"$1\" content content_label\n  case \"$class\" in\n  container)\n    content='rootdir'\n    content_label='Container'\n    ;;\n  template)\n    content='vztmpl'\n    content_label='Container template'\n    ;;\n  *)\n    msg_error \"Invalid storage class '$class'\"\n    return 1\n    ;;\n  esac\n\n  local -a MENU=()\n  local MSG_MAX_LENGTH=0\n\n  while read -r line; do\n    local TAG TYPE FREE ITEM OFFSET=2\n    TAG=$(echo \"$line\" | awk '{print $1}')\n    TYPE=$(echo \"$line\" | awk '{printf \"%-10s\", $2}')\n    FREE=$(echo \"$line\" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n    ITEM=\"  Type: $TYPE Free: $FREE \"\n    ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))\n    MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\n  done < <(pvesm status -content \"$content\" | awk 'NR>1')\n\n  if [[ $((${#MENU[@]} / 3)) -eq 0 ]]; then\n    msg_error \"'$content_label' needs to be selected for at least one storage location.\"\n    return 1\n  elif [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then\n    printf '%s' \"${MENU[0]}\"\n  else\n    local STORAGE\n    while [[ -z \"${STORAGE:+x}\" ]]; do\n      STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n        \"Which storage pool for the ${content_label,,}?\\n\\n\" \\\n        16 $((MSG_MAX_LENGTH + 23)) 6 \\\n        \"${MENU[@]}\" 3>&1 1>&2 2>&3) || exit_script\n    done\n    printf '%s' \"$STORAGE\"\n  fi\n}\n\n# ==============================================================================\n# MAIN\n# ==============================================================================\n\n# Cleanup on error: destroy container, report telemetry, and restart monitor\nturnkey_cleanup() {\n  local exit_code=$?\n  if [[ $exit_code -ne 0 ]]; then\n    # Report failure to telemetry\n    if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n      post_update_to_api \"failed\" \"$exit_code\" 2>/dev/null || true\n    fi\n    # Destroy failed container\n    if [[ -n \"${CTID:-}\" ]]; then\n      cleanup_ctid 2>/dev/null || true\n    fi\n  fi\n  if [[ -f /etc/systemd/system/ping-instances.service ]]; then\n    systemctl start ping-instances.service 2>/dev/null || true\n  fi\n}\ntrap turnkey_cleanup EXIT\n\n# Stop Proxmox VE Monitor-All if running\nif systemctl is-active -q ping-instances.service; then\n  systemctl stop ping-instances.service\nfi\n\npve_check\nshell_check\nroot_check\n\n# Read diagnostics preference (same logic as build.func diagnostics_check)\nDIAG_CONFIG=\"/usr/local/community-scripts/diagnostics\"\nif [[ -f \"$DIAG_CONFIG\" ]]; then\n  DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' \"$DIAG_CONFIG\") || true\n  DIAGNOSTICS=\"${DIAGNOSTICS:-no}\"\nfi\n\nheader_info\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"TurnKey LXCs\" --yesno \\\n  \"This will allow for the creation of one of the many TurnKey LXC Containers. Proceed?\" 10 68 || exit_script\n\n# Update template catalog early so the menu reflects the latest available templates\nmsg_info \"Updating LXC template list\"\npveam update >/dev/null\nmsg_ok \"Updated LXC template list\"\n\n# Build TurnKey selection menu dynamically from available templates\n# Requires gawk for regex capture groups in match()\ncommand -v gawk &>/dev/null || apt-get install -y gawk &>/dev/null\ndeclare -A TURNKEY_TEMPLATES\nTURNKEY_MENU=()\nMSG_MAX_LENGTH=0\nwhile IFS=$'\\t' read -r TEMPLATE_FILE TAG ITEM; do\n  TURNKEY_TEMPLATES[\"$TAG\"]=\"$TEMPLATE_FILE\"\n  OFFSET=2\n  ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))\n  TURNKEY_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(pveam available -section turnkeylinux | gawk '{\n  tpl = $2\n  if (match(tpl, /debian-([0-9]+)-turnkey-([^_]+)_([^_]+)_/, m)) {\n    app = m[2]; deb = m[1]; ver = m[3]\n    display = app\n    gsub(/-/, \" \", display)\n    n = split(display, words, \" \")\n    display = \"\"\n    for (i = 1; i <= n; i++) {\n      words[i] = toupper(substr(words[i], 1, 1)) substr(words[i], 2)\n      display = display (i > 1 ? \" \" : \"\") words[i]\n    }\n    tag = app \"-\" deb\n    printf \"%s\\t%s\\t%s | Debian %s | %s\\n\", tpl, tag, display, deb, ver\n  }\n}' | sort -t$'\\t' -k2,2)\n\nif [[ ${#TURNKEY_MENU[@]} -eq 0 ]]; then\n  msg_error \"No TurnKey templates found. Check your internet connection or template repository.\"\n  exit 1\nfi\n\nselected=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"TurnKey LXCs\" --radiolist \\\n  \"\\nSelect a TurnKey LXC to create:\\n\" 20 $((MSG_MAX_LENGTH + 58)) 12 \\\n  \"${TURNKEY_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"') || exit_script\n\nif [[ -z \"$selected\" ]]; then\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No TurnKey LXC Selected\" \\\n    --msgbox \"It appears that no TurnKey LXC container was selected\" 10 68\n  exit_script\nfi\n\n# Extract template filename and app name from selection\nTEMPLATE=\"${TURNKEY_TEMPLATES[$selected]}\"\nturnkey=\"${selected%-*}\"\n\n# Generate random password\nPASS=\"$(openssl rand -base64 8)\"\n\n# Prompt for Container ID\nNEXT_ID=$(pvesh get /cluster/nextid 2>/dev/null || echo 100)\nwhile true; do\n  CTID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Container ID\" \\\n    --inputbox \"Enter the container ID...\" 8 40 \"$NEXT_ID\" 3>&1 1>&2 2>&3) || exit_script\n\n  if [[ -z \"$CTID\" ]]; then\n    msg_error \"No Container ID selected\"\n    exit_script\n  fi\n\n  if ! validate_container_id \"$CTID\"; then\n    SUGGESTED_ID=$(get_valid_container_id \"$CTID\")\n    if whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ID Already In Use\" --yesno \\\n      \"Container/VM ID $CTID is already in use.\\n\\nWould you like to use the next available ID ($SUGGESTED_ID)?\" 10 58; then\n      CTID=\"$SUGGESTED_ID\"\n      break\n    fi\n  else\n    break\n  fi\ndone\n\n# Prompt for Hostname\nHOST_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Hostname\" \\\n  --inputbox \"Enter the container hostname...\" 8 40 \"turnkey-${turnkey}\" 3>&1 1>&2 2>&3) || exit_script\n\n# Container options\nPCT_OPTIONS=(\n  -features keyctl=1,nesting=1\n  -hostname \"$HOST_NAME\"\n  -tags community-script\n  -onboot 1\n  -cores 2\n  -memory 2048\n  -password \"$PASS\"\n  -net0 name=eth0,bridge=vmbr0,ip=dhcp\n  -unprivileged 1\n  -arch \"$(dpkg --print-architecture)\"\n)\n\n# Storage selection\nTEMPLATE_STORAGE=$(select_storage template) || {\n  msg_error \"Failed to select template storage\"\n  exit 1\n}\nmsg_ok \"Using '${BL}${TEMPLATE_STORAGE}${CL}' for template storage\"\n\nCONTAINER_STORAGE=$(select_storage container) || {\n  msg_error \"Failed to select container storage\"\n  exit 1\n}\nmsg_ok \"Using '${BL}${CONTAINER_STORAGE}${CL}' for container storage\"\n\n# Download template if not already cached\nif ! pveam list \"$TEMPLATE_STORAGE\" | grep -q \"$TEMPLATE\"; then\n  msg_info \"Downloading LXC template\"\n  pveam download \"$TEMPLATE_STORAGE\" \"$TEMPLATE\" >/dev/null || {\n    msg_error \"Failed to download LXC template '${TEMPLATE}'\"\n    exit 1\n  }\n  msg_ok \"Downloaded LXC template\"\nfi\n\n# Add rootfs if not specified\n[[ \" ${PCT_OPTIONS[*]} \" =~ \" -rootfs \" ]] || PCT_OPTIONS+=(-rootfs \"${CONTAINER_STORAGE}:${PCT_DISK_SIZE:-8}\")\n\n# Set telemetry variables for the selected turnkey\nTELEMETRY_TYPE=\"turnkey\"\nNSAPP=\"turnkey-${turnkey}\"\nCT_TYPE=1\nDISK_SIZE=\"${PCT_DISK_SIZE:-8}\"\nCORE_COUNT=2\nRAM_SIZE=2048\nvar_os=\"turnkey\"\nvar_version=\"${turnkey}\"\n\n# Report installation start to telemetry\npost_to_api\n\n# Create LXC container\nmsg_info \"Creating LXC container\"\npct create \"$CTID\" \"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}\" \"${PCT_OPTIONS[@]}\" >/dev/null || {\n  msg_error \"Failed to create container\"\n  exit 1\n}\nmsg_ok \"Created LXC container (ID: ${BL}${CTID}${CL})\"\n\n# Save credentials securely\nCREDS_FILE=~/turnkey-${turnkey}.creds\necho \"TurnKey ${turnkey} password: ${PASS}\" >>\"$CREDS_FILE\"\nchmod 600 \"$CREDS_FILE\"\n\n# Configure TUN device access for VPN-based turnkeys\nTUN_DEVICE_REQUIRED=(\"openvpn\")\nif printf '%s\\n' \"${TUN_DEVICE_REQUIRED[@]}\" | grep -qw \"${turnkey}\"; then\n  msg_info \"Configuring TUN device access for ${turnkey}\"\n  {\n    echo \"lxc.cgroup2.devices.allow: c 10:200 rwm\"\n    echo \"lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file 0 0\"\n  } >>\"/etc/pve/lxc/${CTID}.conf\"\n  msg_ok \"TUN device access configured\"\n  sleep 5\nfi\n\n# Start container\nmsg_info \"Starting LXC container\"\npct start \"$CTID\"\nmsg_ok \"Started LXC container\"\nsleep 10\n\n# Detect container IP\nmsg_info \"Detecting IP address\"\nIP=\"\"\nfor attempt in $(seq 1 5); do\n  IP=$(pct exec \"$CTID\" -- ip -4 a show dev eth0 2>/dev/null | grep -oP 'inet \\K[^/]+' || true)\n  if [[ -n \"$IP\" ]]; then\n    break\n  fi\n  [[ $attempt -lt 5 ]] && sleep 5\ndone\n\nif [[ -z \"$IP\" ]]; then\n  msg_warn \"IP address not found after 5 attempts\"\n  IP=\"NOT FOUND\"\nelse\n  msg_ok \"IP address: ${BL}${IP}${CL}\"\nfi\n\n# Report success to telemetry\npost_update_to_api \"done\" \"none\"\n\n# Success summary\nheader_info\necho\nmsg_ok \"TurnKey ${BL}${turnkey}${CL} LXC container '${BL}${CTID}${CL}' was successfully created.\"\necho\necho -e \"  ${TAB}${YW}IP Address:${CL}  ${BL}${IP}${CL}\"\necho -e \"  ${TAB}${YW}Login:${CL}       ${GN}root${CL}\"\necho -e \"  ${TAB}${YW}Password:${CL}    ${GN}${PASS}${CL}\"\necho\necho -e \"  ${TAB}Proceed to the LXC console to complete the TurnKey setup.\"\necho -e \"  ${TAB}Credentials stored in: ${BL}~/turnkey-${turnkey}.creds${CL}\"\necho\n"
  },
  {
    "path": "vm/archlinux-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ___              __       __    _                     _    ____  ___\n   /   |  __________/ /_     / /   (_)___  __  ___  __   | |  / /  |/  /\n  / /| | / ___/ ___/ __ \\   / /   / / __ \\/ / / / |/_/   | | / / /|_/ / \n / ___ |/ /  / /__/ / / /  / /___/ / / / / /_/ />  <     | |/ / /  / /  \n/_/  |_/_/   \\___/_/ /_/  /_____/_/_/ /_/\\__,_/_/|_|     |___/_/  /_/   \n                                                                        \nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"archlinux-vm\"\nvar_os=\"arch-linux\"\nvar_version=\"n.d.\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Arch Linux VM\" --yesno \"This will create a New Arch Linux VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  MACHINE=\"\"\n  DISK_SIZE=\"4G\"\n  DISK_CACHE=\"\"\n  HN=\"arch-linux\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"1\"\n  RAM_SIZE=\"1024\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Arch Linux VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 \"$VMID\" --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$MACH\" = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$DISK_CACHE\" = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 arch-linux --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$VM_NAME\" ]; then\n      HN=\"arch-linux\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$CPU_TYPE1\" = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$BRG\" ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 \"$GEN_MAC\" --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Arch Linux VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Arch Linux VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  TYPE=$(echo \"$line\" | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo \"$line\" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the Arch Linux .iso File\"\nURL=https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage \"$STORAGE\" | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir | cifs)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"--format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"--format raw\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"--format raw\"\n  ;;\nesac\n\nmsg_info \"Creating a Arch Linux VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\n\nif qm disk import --help >/dev/null 2>&1; then\n  IMPORT_CMD=(qm disk import)\nelse\n  IMPORT_CMD=(qm importdisk)\nfi\n\nIMPORT_OUT=\"$(\"${IMPORT_CMD[@]}\" \"$VMID\" \"${FILE}\" \"$STORAGE\" ${DISK_IMPORT:-} 2>&1 || true)\"\nDISK_REF_IMPORTED=\"$(printf '%s\\n' \"$IMPORT_OUT\" | sed -n \"s/.*successfully imported disk '\\([^']\\+\\)'.*/\\1/p\" | tr -d \"\\r\\\"'\")\"\n[[ -z \"$DISK_REF_IMPORTED\" ]] && DISK_REF_IMPORTED=\"$(pvesm list \"$STORAGE\" | awk -v id=\"$VMID\" '$5 ~ (\"vm-\"id\"-disk-\") {print $1\":\"$5}' | sort | tail -n1)\"\n[[ -z \"$DISK_REF_IMPORTED\" ]] && {\n  msg_error \"Unable to determine imported disk reference.\"\n  echo \"$IMPORT_OUT\"\n  exit 226\n}\nmsg_ok \"Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})\"\n\nqm set $VMID \\\n  -efidisk0 ${STORAGE}:0,efitype=4m \\\n  -scsi0 ${DISK_REF_IMPORTED},${DISK_CACHE}${THIN%,} \\\n  -ide2 ${STORAGE}:cloudinit \\\n  -boot order=scsi0 \\\n  -serial0 socket >/dev/null\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Arch Linux VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\nif [ -n \"$DISK_SIZE\" ]; then\n  msg_info \"Resizing disk to $DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\nelse\n  msg_info \"Using default disk size of $DEFAULT_DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null\nfi\n\nmsg_ok \"Created a Arch Linux VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Arch Linux VM\"\n  qm start $VMID\n  msg_ok \"Started Arch Linux VM\"\nfi\npost_update_to_api \"done\" \"none\"\n\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/debian-13-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____       __    _                ________\n   / __ \\___  / /_  (_)___ _____     <  /__  /\n  / / / / _ \\/ __ \\/ / __ `/ __ \\    / / /_ <\n / /_/ /  __/ /_/ / / /_/ / / / /   / /___/ /\n/_____/\\___/_.___/_/\\__,_/_/ /_/   /_//____/\n                                              (Trixie)\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"debian-13-vm\"\nvar_os=\"debian\"\nvar_version=\"13\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Debian 13 VM\" --yesno \"This will create a New Debian 13 VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction select_cloud_init() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CLOUD-INIT\" \\\n    --yesno \"Enable Cloud-Init for VM configuration?\\n\\nCloud-Init allows automatic configuration of:\\n- User accounts and passwords\\n- SSH keys\\n- Network settings (DHCP/Static)\\n- DNS configuration\\n\\nYou can also configure these settings later in Proxmox UI.\\n\\nNote: Without Cloud-Init, the nocloud image will be used with console auto-login.\" --defaultno 18 68); then\n    CLOUD_INIT=\"yes\"\n    echo -e \"${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}yes${CL}\"\n  else\n    CLOUD_INIT=\"no\"\n    echo -e \"${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}no${CL}\"\n  fi\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"8G\"\n  DISK_CACHE=\"\"\n  HN=\"debian\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  select_cloud_init\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 \"$VMID\" --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$MACH\" = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$DISK_CACHE\" = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 debian --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$VM_NAME\" ]; then\n      HN=\"debian\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$CPU_TYPE1\" = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$BRG\" ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 \"$GEN_MAC\" --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  select_cloud_init\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Debian 13 VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\n\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo \"$line\" | awk '{print $1}')\n  TYPE=$(echo \"$line\" | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo \"$line\" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\n\n# ==============================================================================\n# PREREQUISITES\n# ==============================================================================\nif ! command -v virt-customize &>/dev/null; then\n  msg_info \"Installing libguestfs-tools\"\n  apt-get update >/dev/null 2>&1\n  apt-get install -y libguestfs-tools >/dev/null 2>&1\n  msg_ok \"Installed libguestfs-tools\"\nfi\n\nmsg_info \"Retrieving the URL for the Debian 13 Qcow2 Disk Image\"\nif [ \"$CLOUD_INIT\" == \"yes\" ]; then\n  URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2\nelse\n  URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2\nfi\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\n# ==============================================================================\n# IMAGE CUSTOMIZATION\n# ==============================================================================\nmsg_info \"Customizing ${FILE} image\"\n\nWORK_FILE=$(mktemp --suffix=.qcow2)\ncp \"$FILE\" \"$WORK_FILE\"\n\n# Set hostname\nvirt-customize -q -a \"$WORK_FILE\" --hostname \"${HN}\" >/dev/null 2>&1\n\n# Prepare for unique machine-id on first boot\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"truncate -s 0 /etc/machine-id\" >/dev/null 2>&1\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"rm -f /var/lib/dbus/machine-id\" >/dev/null 2>&1\n\n# Disable systemd-firstboot to prevent interactive prompts blocking the console\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"systemctl disable systemd-firstboot.service 2>/dev/null; rm -f /etc/systemd/system/sysinit.target.wants/systemd-firstboot.service; ln -sf /dev/null /etc/systemd/system/systemd-firstboot.service\" >/dev/null 2>&1 || true\n\n# Pre-seed firstboot settings so it won't prompt even if triggered\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"echo 'Etc/UTC' > /etc/timezone && ln -sf /usr/share/zoneinfo/Etc/UTC /etc/localtime\" >/dev/null 2>&1 || true\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"touch /etc/locale.conf\" >/dev/null 2>&1 || true\n\nif [ \"$CLOUD_INIT\" == \"yes\" ]; then\n  # Cloud-Init handles SSH and login\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config\" >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config\" >/dev/null 2>&1 || true\nelse\n  # Configure auto-login on serial console (ttyS0) and virtual console (tty1)\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d\" >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf << EOF\n[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %I \\$TERM\nEOF' >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"mkdir -p /etc/systemd/system/getty@tty1.service.d\" >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF\n[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %I \\$TERM\nEOF' >/dev/null 2>&1 || true\nfi\n\nmsg_ok \"Customized image\"\n\nSTORAGE_TYPE=$(pvesm status -storage \"$STORAGE\" | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK\"${i}\"=vm-\"${VMID}\"-disk-\"${i}\"${DISK_EXT:-}\n  eval DISK\"${i}\"_REF=\"${STORAGE}\":\"${DISK_REF:-}\"${!disk}\ndone\n\nmsg_info \"Creating a Debian 13 VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null\nqm importdisk $VMID ${WORK_FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nif [ \"$CLOUD_INIT\" == \"yes\" ]; then\n  qm set $VMID \\\n    -efidisk0 ${DISK0_REF}${FORMAT} \\\n    -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n    -scsi1 ${STORAGE}:cloudinit \\\n    -boot order=scsi0 \\\n    -serial0 socket >/dev/null\nelse\n  qm set $VMID \\\n    -efidisk0 ${DISK0_REF}${FORMAT} \\\n    -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n    -boot order=scsi0 \\\n    -serial0 socket >/dev/null\nfi\n\n# Clean up work file\nrm -f \"$WORK_FILE\"\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Debian VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\nif [ -n \"$DISK_SIZE\" ]; then\n  msg_info \"Resizing disk to $DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\nelse\n  msg_info \"Using default disk size of $DEFAULT_DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null\nfi\n\nmsg_ok \"Created a Debian 13 VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Debian 13 VM\"\n  qm start $VMID\n  msg_ok \"Started Debian 13 VM\"\nfi\n\nmsg_ok \"Completed successfully!\\n\"\necho \"More Info at https://github.com/community-scripts/ProxmoxVE/discussions/836\"\n"
  },
  {
    "path": "vm/debian-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2025 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<< $(wget -qLO - https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    ____       __    _                ______\n   / __ \\___  / /_  (_)___ _____     <  /__ \\\n  / / / / _ \\/ __ \\/ / __ `/ __ \\    / /__/ /\n / /_/ /  __/ /_/ / / /_/ / / / /   / // __/\n/_____/\\___/_.___/_/\\__,_/_/ /_/   /_//____/\n\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"debian-vm\"\nvar_os=\"debian\"\nvar_version=\"12\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Debian 12 VM\" --yesno \"This will create a New Debian 12 VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script only works on PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/community-scripts/ProxmoxVE for AMD64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"8G\"\n  DISK_CACHE=\"\"\n  HN=\"debian\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  CLOUD_INIT=\"no\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Debian 12 VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 debian --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"debian\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CLOUD-INIT\" --yesno \"Configure the VM with Cloud-init?\" --defaultno 10 58); then\n    echo -e \"${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}yes${CL}\"\n    CLOUD_INIT=\"yes\"\n  else\n    echo -e \"${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}\"\n    CLOUD_INIT=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Debian 12 VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Debian 12 VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\n\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the Debian 12 Qcow2 Disk Image\"\nif [ \"$CLOUD_INIT\" == \"yes\" ]; then\n  URL=https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-arm64.qcow2\nelse\n  URL=https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-arm64.qcow2\nfi\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a Debian 12 VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 64M 1>&/dev/null\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nif [ \"$CLOUD_INIT\" == \"yes\" ]; then\n  qm set $VMID \\\n    -efidisk0 ${DISK0_REF}${FORMAT} \\\n    -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n    -scsi1 ${STORAGE}:cloudinit \\\n    -boot order=scsi0 \\\n    -serial0 socket >/dev/null\nelse\n  qm set $VMID \\\n    -efidisk0 ${DISK0_REF}${FORMAT} \\\n    -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n    -boot order=scsi0 \\\n    -serial0 socket >/dev/null\nfi\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Debian VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set \"$VMID\" -description \"$DESCRIPTION\" >/dev/null\t\t\t\t\n\nmsg_ok \"Created a Debian 12 VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Debian 12 VM\"\n  qm start $VMID\n  msg_ok \"Started Debian 12 VM\"\nfi\n\nmsg_ok \"Completed successfully!\\n\"\necho \"More Info at https://github.com/community-scripts/ProxmoxVE/discussions/836\"\n"
  },
  {
    "path": "vm/docker-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96) | michelroegl-brunner | MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE\n\n# ==============================================================================\n# Docker VM - Creates a Docker-ready Virtual Machine\n# ==============================================================================\n\nsource <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/api.func) 2>/dev/null\nsource <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/vm-core.func) 2>/dev/null\nsource <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/cloud-init.func) 2>/dev/null || true\nload_functions\n\n# ==============================================================================\n# SCRIPT VARIABLES\n# ==============================================================================\nAPP=\"Docker\"\nAPP_TYPE=\"vm\"\nNSAPP=\"docker-vm\"\nvar_os=\"debian\"\nvar_version=\"13\"\n\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nDISK_SIZE=\"10G\"\nUSE_CLOUD_INIT=\"no\"\nOS_TYPE=\"\"\nOS_VERSION=\"\"\nTHIN=\"discard=on,ssd=1,\"\n\n# ==============================================================================\n# ERROR HANDLING & CLEANUP\n# ==============================================================================\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\n\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\n# ==============================================================================\n# OS SELECTION FUNCTIONS\n# ==============================================================================\nfunction select_os() {\n  if OS_CHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SELECT OS\" --radiolist \\\n    \"Choose Operating System for Docker VM\" 14 68 4 \\\n    \"debian13\" \"Debian 13 (Trixie) - Latest\" ON \\\n    \"debian12\" \"Debian 12 (Bookworm) - Stable\" OFF \\\n    \"ubuntu2404\" \"Ubuntu 24.04 LTS (Noble)\" OFF \\\n    \"ubuntu2204\" \"Ubuntu 22.04 LTS (Jammy)\" OFF \\\n    3>&1 1>&2 2>&3); then\n    case $OS_CHOICE in\n    debian13)\n      OS_TYPE=\"debian\"\n      OS_VERSION=\"13\"\n      OS_CODENAME=\"trixie\"\n      OS_DISPLAY=\"Debian 13 (Trixie)\"\n      ;;\n    debian12)\n      OS_TYPE=\"debian\"\n      OS_VERSION=\"12\"\n      OS_CODENAME=\"bookworm\"\n      OS_DISPLAY=\"Debian 12 (Bookworm)\"\n      ;;\n    ubuntu2404)\n      OS_TYPE=\"ubuntu\"\n      OS_VERSION=\"24.04\"\n      OS_CODENAME=\"noble\"\n      OS_DISPLAY=\"Ubuntu 24.04 LTS\"\n      ;;\n    ubuntu2204)\n      OS_TYPE=\"ubuntu\"\n      OS_VERSION=\"22.04\"\n      OS_CODENAME=\"jammy\"\n      OS_DISPLAY=\"Ubuntu 22.04 LTS\"\n      ;;\n    esac\n    echo -e \"${OS}${BOLD}${DGN}Operating System: ${BGN}${OS_DISPLAY}${CL}\"\n  else\n    exit_script\n  fi\n}\n\nfunction select_cloud_init() {\n  if [ \"$OS_TYPE\" = \"ubuntu\" ]; then\n    USE_CLOUD_INIT=\"yes\"\n    echo -e \"${CLOUD:-${TAB}☁️${TAB}${CL}}${BOLD}${DGN}Cloud-Init: ${BGN}yes (Ubuntu requires Cloud-Init)${CL}\"\n    return\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CLOUD-INIT\" \\\n    --yesno \"Enable Cloud-Init for VM configuration?\\n\\nCloud-Init allows automatic configuration of:\\n- User accounts and passwords\\n- SSH keys\\n- Network settings (DHCP/Static)\\n- DNS configuration\\n\\nYou can also configure these settings later in Proxmox UI.\\n\\nNote: Debian without Cloud-Init will use nocloud image with console auto-login.\" 18 68); then\n    USE_CLOUD_INIT=\"yes\"\n    echo -e \"${CLOUD:-${TAB}☁️${TAB}${CL}}${BOLD}${DGN}Cloud-Init: ${BGN}yes${CL}\"\n  else\n    USE_CLOUD_INIT=\"no\"\n    echo -e \"${CLOUD:-${TAB}☁️${TAB}${CL}}${BOLD}${DGN}Cloud-Init: ${BGN}no${CL}\"\n  fi\n}\n\nfunction get_image_url() {\n  local arch=$(dpkg --print-architecture)\n  case $OS_TYPE in\n  debian)\n    if [ \"$USE_CLOUD_INIT\" = \"yes\" ]; then\n      echo \"https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-generic-${arch}.qcow2\"\n    else\n      echo \"https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-nocloud-${arch}.qcow2\"\n    fi\n    ;;\n  ubuntu)\n    echo \"https://cloud-images.ubuntu.com/${OS_CODENAME}/current/${OS_CODENAME}-server-cloudimg-${arch}.img\"\n    ;;\n  esac\n}\n\n# ==============================================================================\n# SETTINGS FUNCTIONS\n# ==============================================================================\nfunction default_settings() {\n  select_os\n  select_cloud_init\n\n  VMID=$(get_valid_nextid)\n  FORMAT=\"\"\n  MACHINE=\" -machine q35\"\n  DISK_CACHE=\"\"\n  DISK_SIZE=\"10G\"\n  HN=\"docker\"\n  CPU_TYPE=\" -cpu host\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"4096\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}Q35 (Modern)${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Docker VM using the above settings${CL}\"\n}\n\nfunction advanced_settings() {\n  select_os\n  select_cloud_init\n\n  # SSH Key selection for Cloud-Init VMs\n  if [ \"$USE_CLOUD_INIT\" = \"yes\" ]; then\n    configure_cloudinit_ssh_keys || true\n  fi\n\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n\n  # VM ID\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit_script\n    fi\n  done\n\n  # Machine Type\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"q35\" \"Q35 (Modern, PCIe)\" ON \\\n    \"i440fx\" \"i440fx (Legacy, PCI)\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}Q35 (Modern)${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx (Legacy)${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit_script\n  fi\n\n  # Disk Size\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit_script\n    fi\n  else\n    exit_script\n  fi\n\n  # Disk Cache\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit_script\n  fi\n\n  # Hostname\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 docker --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"docker\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n    fi\n    echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n  else\n    exit_script\n  fi\n\n  # CPU Model\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"1\" \"Host (Recommended)\" ON \\\n    \"0\" \"KVM64\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit_script\n  fi\n\n  # CPU Cores\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit_script\n    fi\n  done\n\n  # RAM Size\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 4096 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"4096\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 4096).\" 8 58\n    else\n      exit_script\n    fi\n  done\n\n  # Bridge\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n    fi\n    echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n  else\n    exit_script\n  fi\n\n  # MAC Address\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit_script\n    fi\n  done\n\n  # VLAN\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan (leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit_script\n    fi\n  done\n\n  # MTU\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit_script\n    fi\n  done\n\n  # Start VM\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  # Confirm\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Docker VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Docker VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\n# ==============================================================================\n# MAIN EXECUTION\n# ==============================================================================\nheader_info\n\ncheck_root\npve_check\n\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Docker VM\" --yesno \"This will create a New Docker VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nstart_script\npost_to_api_vm\n\n# ==============================================================================\n# STORAGE SELECTION\n# ==============================================================================\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\n\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  if [ -n \"$SPINNER_PID\" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi\n  printf \"\\e[?25h\"\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\n\n# ==============================================================================\n# PREREQUISITES\n# ==============================================================================\nif ! command -v virt-customize &>/dev/null; then\n  msg_info \"Installing libguestfs-tools\"\n  apt-get -qq update >/dev/null\n  apt-get -qq install libguestfs-tools lsb-release -y >/dev/null\n  apt-get -qq install dhcpcd-base -y >/dev/null 2>&1 || true\n  msg_ok \"Installed libguestfs-tools\"\nfi\n\n# ==============================================================================\n# IMAGE DOWNLOAD\n# ==============================================================================\nmsg_info \"Retrieving the URL for the ${OS_DISPLAY} Qcow2 Disk Image\"\nURL=$(get_image_url)\nCACHE_DIR=\"/var/lib/vz/template/cache\"\nCACHE_FILE=\"$CACHE_DIR/$(basename \"$URL\")\"\nmkdir -p \"$CACHE_DIR\"\nmsg_ok \"${CL}${BL}${URL}${CL}\"\n\nif [[ ! -s \"$CACHE_FILE\" ]]; then\n  curl -f#SL -o \"$CACHE_FILE\" \"$URL\"\n  echo -en \"\\e[1A\\e[0K\"\n  msg_ok \"Downloaded ${CL}${BL}$(basename \"$CACHE_FILE\")${CL}\"\nelse\n  msg_ok \"Using cached image ${CL}${BL}$(basename \"$CACHE_FILE\")${CL}\"\nfi\n\n# ==============================================================================\n# STORAGE TYPE DETECTION\n# ==============================================================================\nSTORAGE_TYPE=$(pvesm status -storage \"$STORAGE\" | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"--format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"--format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"--format raw\"\n  ;;\nesac\n\n# ==============================================================================\n# IMAGE CUSTOMIZATION WITH DOCKER\n# ==============================================================================\nmsg_info \"Preparing ${OS_DISPLAY} image with Docker\"\n\nWORK_FILE=$(mktemp --suffix=.qcow2)\ncp \"$CACHE_FILE\" \"$WORK_FILE\"\n\nexport LIBGUESTFS_BACKEND_SETTINGS=dns=8.8.8.8,1.1.1.1\n\nDOCKER_PREINSTALLED=\"no\"\n\n# Install qemu-guest-agent and Docker during image customization\nmsg_info \"Installing base packages in image\"\nif virt-customize -a \"$WORK_FILE\" --install qemu-guest-agent,curl,ca-certificates >/dev/null 2>&1; then\n  msg_ok \"Installed base packages\"\n\n  msg_info \"Installing Docker (this may take 2-5 minutes)\"\n  if virt-customize -q -a \"$WORK_FILE\" --run-command \"curl -fsSL https://get.docker.com | sh\" >/dev/null 2>&1 &&\n    virt-customize -q -a \"$WORK_FILE\" --run-command \"systemctl enable docker\" >/dev/null 2>&1; then\n    msg_ok \"Installed Docker\"\n\n    msg_info \"Configuring Docker daemon\"\n    # Optimize Docker daemon configuration\n    virt-customize -q -a \"$WORK_FILE\" --run-command \"mkdir -p /etc/docker\" >/dev/null 2>&1\n    virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /etc/docker/daemon.json << EOF\n{\n  \"storage-driver\": \"overlay2\",\n  \"log-driver\": \"json-file\",\n  \"log-opts\": {\n    \"max-size\": \"10m\",\n    \"max-file\": \"3\"\n  }\n}\nEOF' >/dev/null 2>&1\n    DOCKER_PREINSTALLED=\"yes\"\n    msg_ok \"Configured Docker daemon\"\n  else\n    msg_ok \"Docker will be installed on first boot\"\n  fi\nelse\n  msg_ok \"Packages will be installed on first boot\"\nfi\n\nmsg_info \"Finalizing image (hostname, SSH config)\"\n# Set hostname and prepare for unique machine-id\nvirt-customize -q -a \"$WORK_FILE\" --hostname \"${HN}\" >/dev/null 2>&1 || true\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"truncate -s 0 /etc/machine-id\" >/dev/null 2>&1 || true\nvirt-customize -q -a \"$WORK_FILE\" --run-command \"rm -f /var/lib/dbus/machine-id\" >/dev/null 2>&1 || true\n\n# Configure SSH for Cloud-Init\nif [ \"$USE_CLOUD_INIT\" = \"yes\" ]; then\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config\" >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config\" >/dev/null 2>&1 || true\nelse\n  # Configure auto-login for nocloud images (no Cloud-Init)\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d\" >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf << EOF\n[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %I \\$TERM\nEOF' >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command \"mkdir -p /etc/systemd/system/getty@tty1.service.d\" >/dev/null 2>&1 || true\n  virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF\n[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %I \\$TERM\nEOF' >/dev/null 2>&1 || true\nfi\nmsg_ok \"Finalized image\"\n\n# Create first-boot Docker install script (fallback if virt-customize failed)\nif [ \"$DOCKER_PREINSTALLED\" = \"no\" ]; then\n  if virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /root/install-docker.sh << \"DOCKERSCRIPT\"\n#!/bin/bash\nexec > /var/log/install-docker.log 2>&1\necho \"[$(date)] Starting Docker installation\"\n\nfor i in {1..30}; do\n  ping -c 1 8.8.8.8 >/dev/null 2>&1 && break\n  sleep 2\ndone\n\napt-get update\napt-get install -y qemu-guest-agent curl ca-certificates\ncurl -fsSL https://get.docker.com | sh\nsystemctl enable docker\nsystemctl start docker\n\nmkdir -p /etc/docker\ncat > /etc/docker/daemon.json << DAEMON\n{\n  \"storage-driver\": \"overlay2\",\n  \"log-driver\": \"json-file\",\n  \"log-opts\": { \"max-size\": \"10m\", \"max-file\": \"3\" }\n}\nDAEMON\nsystemctl restart docker\n\ntouch /root/.docker-installed\necho \"[$(date)] Docker installation completed\"\nDOCKERSCRIPT\nchmod +x /root/install-docker.sh' >/dev/null 2>&1; then\n\n    virt-customize -q -a \"$WORK_FILE\" --run-command 'cat > /etc/systemd/system/install-docker.service << \"DOCKERSERVICE\"\n[Unit]\nDescription=Install Docker on First Boot\nAfter=network-online.target\nWants=network-online.target\nConditionPathExists=!/root/.docker-installed\n\n[Service]\nType=oneshot\nExecStart=/root/install-docker.sh\nRemainAfterExit=yes\n\n[Install]\nWantedBy=multi-user.target\nDOCKERSERVICE\nsystemctl enable install-docker.service' >/dev/null 2>&1 || true\n  else\n    msg_warn \"virt-customize failed for this image. Docker must be installed manually after first boot:\"\n    msg_warn \"  curl -fsSL https://get.docker.com | sh\"\n  fi\nfi\n\n# Resize disk to target size\nmsg_info \"Resizing disk image to ${DISK_SIZE}\"\nqemu-img resize \"$WORK_FILE\" \"${DISK_SIZE}\" >/dev/null 2>&1\nmsg_ok \"Resized disk image\"\n\n# ==============================================================================\n# VM CREATION\n# ==============================================================================\nmsg_info \"Creating Docker VM shell\"\n\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null\n\nmsg_ok \"Created VM shell\"\n\n# ==============================================================================\n# DISK IMPORT\n# ==============================================================================\nmsg_info \"Importing disk into storage ($STORAGE)\"\n\nif qm disk import --help >/dev/null 2>&1; then\n  IMPORT_CMD=(qm disk import)\nelse\n  IMPORT_CMD=(qm importdisk)\nfi\n\nIMPORT_OUT=\"$(\"${IMPORT_CMD[@]}\" \"$VMID\" \"$WORK_FILE\" \"$STORAGE\" ${DISK_IMPORT:-} 2>&1 || true)\"\nDISK_REF_IMPORTED=\"$(printf '%s\\n' \"$IMPORT_OUT\" | sed -n \"s/.*successfully imported disk '\\([^']\\+\\)'.*/\\1/p\" | tr -d \"\\r\\\"'\")\"\n[[ -z \"$DISK_REF_IMPORTED\" ]] && DISK_REF_IMPORTED=\"$(pvesm list \"$STORAGE\" | awk -v id=\"$VMID\" '$5 ~ (\"vm-\"id\"-disk-\") {print $1\":\"$5}' | sort | tail -n1)\"\n[[ -z \"$DISK_REF_IMPORTED\" ]] && {\n  msg_error \"Unable to determine imported disk reference.\"\n  echo \"$IMPORT_OUT\"\n  exit 226\n}\n\nmsg_ok \"Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})\"\n\n# Clean up work file\nrm -f \"$WORK_FILE\"\n\n# ==============================================================================\n# VM CONFIGURATION\n# ==============================================================================\nmsg_info \"Attaching EFI and root disk\"\n\nqm set \"$VMID\" \\\n  --efidisk0 \"${STORAGE}:0,efitype=4m\" \\\n  --scsi0 \"${DISK_REF_IMPORTED},${DISK_CACHE}${THIN%,}\" \\\n  --boot order=scsi0 \\\n  --serial0 socket >/dev/null\n\nqm set $VMID --agent enabled=1 >/dev/null\n\nmsg_ok \"Attached EFI and root disk\"\n\n# Set VM description\nset_description\n\n# Cloud-Init configuration\nif [ \"$USE_CLOUD_INIT\" = \"yes\" ]; then\n  msg_info \"Configuring Cloud-Init\"\n  setup_cloud_init \"$VMID\" \"$STORAGE\" \"$HN\" \"yes\"\n  msg_ok \"Cloud-Init configured\"\nfi\n\n# Start VM\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Docker VM\"\n  qm start $VMID >/dev/null 2>&1\n  msg_ok \"Started Docker VM\"\nfi\n\n# ==============================================================================\n# FINAL OUTPUT\n# ==============================================================================\nVM_IP=\"\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  set +e\n  for i in {1..10}; do\n    VM_IP=$(qm guest cmd \"$VMID\" network-get-interfaces 2>/dev/null |\n      jq -r '.[] | select(.name != \"lo\") | .\"ip-addresses\"[]? | select(.\"ip-address-type\" == \"ipv4\") | .\"ip-address\"' 2>/dev/null |\n      grep -v \"^127\\.\" | head -1) || true\n    [ -n \"$VM_IP\" ] && break\n    sleep 3\n  done\n  set -e\nfi\n\necho -e \"\\n${INFO}${BOLD}${GN}Docker VM Configuration Summary:${CL}\"\necho -e \"${TAB}${DGN}VM ID: ${BGN}${VMID}${CL}\"\necho -e \"${TAB}${DGN}Hostname: ${BGN}${HN}${CL}\"\necho -e \"${TAB}${DGN}OS: ${BGN}${OS_DISPLAY}${CL}\"\n[ -n \"$VM_IP\" ] && echo -e \"${TAB}${DGN}IP Address: ${BGN}${VM_IP}${CL}\"\n\nif [ \"$DOCKER_PREINSTALLED\" = \"yes\" ]; then\n  echo -e \"${TAB}${DGN}Docker: ${BGN}Pre-installed (via get.docker.com)${CL}\"\nelse\n  echo -e \"${TAB}${DGN}Docker: ${BGN}Installing on first boot${CL}\"\n  echo -e \"${TAB}${YW}⚠️  Wait 2-3 minutes for installation to complete${CL}\"\n  echo -e \"${TAB}${YW}⚠️  Check progress: ${BL}cat /var/log/install-docker.log${CL}\"\nfi\n\nif [ \"$USE_CLOUD_INIT\" = \"yes\" ]; then\n  display_cloud_init_info \"$VMID\" \"$HN\" 2>/dev/null || true\nfi\n\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/haos-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    __  __                        ___              _      __              __     ____  _____\n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_   / __ \\/ ___/\n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/  / / / /\\__ \\\n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_   / /_/ /___/ /\n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/   \\____//____/\n\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nVERSIONS=(stable beta dev)\nMETHOD=\"\"\nNSAPP=\"haos-vm\"\nvar_os=\"homeassistant\"\nDISK_SIZE=\"32G\"\nCPU_TYPE=\"-cpu host\" # arm64 guests require host CPU model\n\n# Get HAOS versions for generic-aarch64 (arm64) from the channel JSONs\nfor version in \"${VERSIONS[@]}\"; do\n  eval \"$version=$(curl -fsSL https://raw.githubusercontent.com/home-assistant/version/master/${version}.json \\\n      | sed -n 's/.*\"generic-aarch64\": *\"\\([^\"]*\\)\".*/\\1/p')\"\ndone\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nHA=$(echo \"\\033[1;34m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  # Only send telemetry if post_to_api_vm was called (installing status was sent)\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Homeassistant OS VM\" --yesno \"This will create a New Homeassistant OS VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script is for arm64! \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\n# Ensure pv is installed or abort with instructions\nfunction ensure_pv() {\n  if ! command -v pv &>/dev/null; then\n    msg_info \"Installing required package: pv\"\n    if ! apt-get update -qq &>/dev/null || ! apt-get install -y pv &>/dev/null; then\n      msg_error \"Failed to install pv automatically.\"\n      echo -e \"\\nPlease run manually on the Proxmox host:\\n  apt install pv\\n\"\n      exit 237\n    fi\n    msg_ok \"Installed pv\"\n  fi\n}\n\n# Download an .xz file and validate it\n# Args: $1=url $2=cache_file\nfunction download_and_validate_xz() {\n  local url=\"$1\"\n  local file=\"$2\"\n\n  # If file exists, check validity\n  if [[ -s \"$file\" ]]; then\n    if xz -t \"$file\" &>/dev/null; then\n      msg_ok \"Using cached image $(basename \"$file\")\"\n      return 0\n    else\n      msg_error \"Cached file $(basename \"$file\") is corrupted. Deleting and retrying download...\"\n      rm -f \"$file\"\n    fi\n  fi\n\n  # Download fresh file\n  msg_info \"Downloading image: $(basename \"$file\")\"\n  if ! curl -fSL -o \"$file\" \"$url\"; then\n    msg_error \"Download failed: $url\"\n    rm -f \"$file\"\n    exit 115\n  fi\n\n  # Validate again\n  if ! xz -t \"$file\" &>/dev/null; then\n    msg_error \"Downloaded file $(basename \"$file\") is corrupted. Please try again later.\"\n    rm -f \"$file\"\n    exit 115\n  fi\n  msg_ok \"Downloaded and validated $(basename \"$file\")\"\n}\n\n# Extract .xz with pv\n# Args: $1=cache_file $2=target_img\nfunction extract_xz_with_pv() {\n  set -o pipefail\n  local file=\"$1\"\n  local target=\"$2\"\n\n  msg_info \"Decompressing $(basename \"$file\") to $target\"\n  if ! xz -dc \"$file\" | pv -N \"Extracting\" >\"$target\"; then\n    msg_error \"Failed to extract $file\"\n    rm -f \"$target\"\n    exit 115\n  fi\n  msg_ok \"Decompressed to $target\"\n}\n\nfunction default_settings() {\n  BRANCH=\"$stable\"\n  VMID=$(get_valid_nextid)\n  FORMAT=\"\"\n  DISK_SIZE=\"32G\"\n  HN=\"haos-${BRANCH}\"\n  DISK_CACHE=\"cache=writethrough,\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"4096\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Homeassistant OS VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  if BRANCH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Homeassistant OS Version\" --radiolist \"Choose Version\" --cancel-button Exit-Script 10 58 3 \\\n    \"$stable\" \"Stable  \" ON \\\n    \"$beta\" \"Beta  \" OFF \\\n    \"$dev\" \"Dev  \" OFF \\\n    3>&1 1>&2 2>&3); then\n    var_version=\"${BRANCH}\"\n    echo -e \"${DGN}Using HAOS Version: ${BGN}$BRANCH${CL}\"\n  else\n    exit-script\n  fi\n\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None\" OFF \\\n    \"1\" \"Write Through (Default)\" ON \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 haos${BRANCH} --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"haos${BRANCH}\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"4096\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 4096).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create Homeassistant OS ${BRANCH} VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${RD}Creating a Homeassistant OS VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nensure_pv\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    if [ -n \"$SPINNER_PID\" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi\n    printf \"\\e[?25h\"\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\n\nvar_version=\"${BRANCH}\"\nmsg_info \"Retrieving the URL for Home Assistant ${BRANCH} Disk Image (generic-aarch64)\"\n\n# Use generic-aarch64 (arm64), instead of ova (x86_64)\nif [ \"$BRANCH\" == \"$dev\" ]; then\n  URL=\"https://os-artifacts.home-assistant.io/${BRANCH}/haos_generic-aarch64-${BRANCH}.qcow2.xz\"\nelse\n  URL=\"https://github.com/home-assistant/operating-system/releases/download/${BRANCH}/haos_generic-aarch64-${BRANCH}.qcow2.xz\"\nfi\n\nCACHE_DIR=\"/var/lib/vz/template/cache\"\nCACHE_FILE=\"$CACHE_DIR/$(basename \"$URL\")\"\nFILE_IMG=\"/var/lib/vz/template/tmp/$(basename \"${CACHE_FILE%.xz}\")\"\n\nmkdir -p \"$CACHE_DIR\" \"$(dirname \"$FILE_IMG\")\"\nmsg_ok \"${CL}${BL}${URL}${CL}\"\n\ndownload_and_validate_xz \"$URL\" \"$CACHE_FILE\"\n\nmsg_info \"Creating Home Assistant OS VM shell\"\nqm create \"$VMID\" -bios ovmf -agent 1 -tablet 0 -localtime 1 ${CPU_TYPE} \\\n  -cores \"$CORE_COUNT\" -memory \"$RAM_SIZE\" -name \"$HN\" -tags community-script \\\n  -net0 \"virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU\" -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null\nmsg_ok \"Created VM shell\"\n\nextract_xz_with_pv \"$CACHE_FILE\" \"$FILE_IMG\"\n\nmsg_info \"Importing disk into storage ($STORAGE)\"\nif qm disk import --help >/dev/null 2>&1; then\n  IMPORT_CMD=(qm disk import)\nelse\n  IMPORT_CMD=(qm importdisk)\nfi\n\nmsg_info \"Creating EFI disk first (must be disk-0)\"\nqm set \"$VMID\" --efidisk0 \"${STORAGE}:0,efitype=4m,size=64M\" >/dev/null\n\nmsg_info \"Importing HAOS disk (will become disk-1)\"\nIMPORT_OUT=\"$(\"${IMPORT_CMD[@]}\" \"$VMID\" \"$FILE_IMG\" \"$STORAGE\" --format raw 2>&1 || true)\"\nDISK_REF=\"$(printf '%s\\n' \"$IMPORT_OUT\" | sed -n \"s/.*successfully imported disk '\\([^']\\+\\)'.*/\\1/p\")\"\n\n[[ -z \"$DISK_REF\" ]] && DISK_REF=\"$(pvesm list \"$STORAGE\" | awk -v id=\"$VMID\" '$5 ~ (\"vm-\"id\"-disk-\") {print $1\":\"$5}' | sort | tail -n1)\"\n[[ -z \"$DISK_REF\" ]] && {\n  msg_error \"Unable to determine imported disk reference.\"\n  echo \"$IMPORT_OUT\"\n  exit 226\n}\n\nmsg_ok \"Imported disk (${CL}${BL}${DISK_REF}${CL})\"\n\nmsg_info \"Attaching HAOS disk as scsi0\"\nqm set \"$VMID\" --scsi0 \"${DISK_REF},${DISK_CACHE}ssd=1,discard=on\" >/dev/null\n\nmsg_info \"Setting boot order\"\nqm set \"$VMID\" --boot order=scsi0 --serial0 socket >/dev/null\n\nrm -f \"$FILE_IMG\"\n\nqm set \"$VMID\" --agent enabled=1 >/dev/null\nmsg_ok \"Attached EFI and root disk\"\n\nmsg_info \"Resizing disk to $DISK_SIZE\"\nqm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\nmsg_ok \"Resized disk\"\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Homeassistant OS VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\nmsg_ok \"Created Homeassistant OS VM ${CL}${BL}(${HN})\"\n\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Image Cache\" \\\n  --yesno \"Keep downloaded Home Assistant OS image for future VMs?\\n\\nFile: $CACHE_FILE\" 10 70; then\n  msg_ok \"Keeping cached image\"\nelse\n  rm -f \"$CACHE_FILE\"\n  msg_ok \"Deleted cached image\"\nfi\n\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Home Assistant OS VM\"\n  qm start $VMID\n  msg_ok \"Started Home Assistant OS VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/headers/docker-vm",
    "content": "    ____             __            \n   / __ \\____  _____/ /_____  _____\n  / / / / __ \\/ ___/ //_/ _ \\/ ___/\n / /_/ / /_/ / /__/ ,< /  __/ /    \n/_____/\\____/\\___/_/|_|\\___/_/     \n                                   \n"
  },
  {
    "path": "vm/headers/owncloud-vm",
    "content": "  ______                 __ __                                  ________                __   _    ____  ___\n /_  __/_  ___________  / //_/__  __  __   ____ _      ______  / ____/ /___  __  ______/ /  | |  / /  |/  /\n  / / / / / / ___/ __ \\/ ,< / _ \\/ / / /  / __ \\ | /| / / __ \\/ /   / / __ \\/ / / / __  /   | | / / /|_/ / \n / / / /_/ / /  / / / / /| /  __/ /_/ /  / /_/ / |/ |/ / / / / /___/ / /_/ / /_/ / /_/ /    | |/ / /  / /  \n/_/  \\__,_/_/  /_/ /_/_/ |_\\___/\\__, /   \\____/|__/|__/_/ /_/\\____/_/\\____/\\__,_/\\__,_/     |___/_/  /_/   \n                               /____/                                                                      \n"
  },
  {
    "path": "vm/mikrotik-routeros.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n    __  ____ __              __  _ __      ____              __            ____  _____    ________  ______\n   /  |/  (_) /___________  / /_(_) /__   / __ \\____  __  __/ /____  _____/ __ \\/ ___/   / ____/ / / / __ \\\n  / /|_/ / / //_/ ___/ __ \\/ __/ / //_/  / /_/ / __ \\/ / / / __/ _ \\/ ___/ / / /\\__ \\   / /   / /_/ / /_/ /\n / /  / / / ,< / /  / /_/ / /_/ / ,<    / _, _/ /_/ / /_/ / /_/  __/ /  / /_/ /___/ /  / /___/ __  / _, _/\n/_/  /_/_/_/|_/_/   \\____/\\__/_/_/|_|  /_/ |_|\\____/\\__,_/\\__/\\___/_/   \\____//____/   \\____/_/ /_/_/ |_|\n\nEOF\n}\nheader_info\necho -e \"Loading...\"\nGEN_MAC=$(echo '00 60 2f'$(od -An -N3 -t xC /dev/urandom) | sed -e 's/ /:/g' | tr '[:lower:]' '[:upper:]')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"mikrotik-routeros\"\nvar_os=\"mikrotik\"\nvar_version=\" \"\nDISK_SIZE=\"1G\"\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Mikrotik RouterOS CHR VM\" --yesno \"This will create a Mikrotik RouterOS CHR VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"8G\"\n  DISK_CACHE=\"\"\n  HN=\"mikrotik-routeros-chr\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"512\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  CLOUD_INIT=\"no\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Mikrotik RouterOS VM using the above default settings${CL}\"\n}\n\nfunction get_mikrotik_version() {\n  local mode=\"$1\"\n  local rss_url\n  local tree_name\n\n  case \"$mode\" in\n  s) rss_url=\"https://cdn.mikrotik.com/routeros/latest-stable.rss\" ;;\n  d) rss_url=\"https://cdn.mikrotik.com/routeros/latest-development.rss\" ;;\n  l) rss_url=\"https://cdn.mikrotik.com/routeros/latest-long-term.rss\" ;;\n  t) rss_url=\"https://cdn.mikrotik.com/routeros/latest-testing.rss\" ;;\n  *) return 0 ;;\n  esac\n\n  local rss_content\n  rss_content=$(curl -fsSL $rss_url 2>/dev/null)\n  if [ -n \"$rss_content\" ]; then\n    local version\n    version=$(echo \"$rss_content\" | grep -oP '<title>RouterOS \\K[0-9.]+(?= \\[)' 2>/dev/null || echo \"$rss_content\" | sed -n 's/.*<title>RouterOS \\([0-9.]\\+\\) \\[.*/\\1/p' 2>/dev/null)\n    if [[ \"$version\" =~ ^[0-9]+\\.[0-9]+ ]]; then\n      echo \"$version\"\n      return 0\n    fi\n  fi\n\n  case \"$mode\" in\n  s) tree_name=\"Stable release tree\" ;;\n  d) tree_name=\"Development release tree\" ;;\n  l) tree_name=\"Long-term release tree\" ;;\n  t) tree_name=\"Testing release tree\" ;;\n  esac\n\n  local html\n  html=$(curl -fsSL \"https://mikrotik.com/download/changelogs\" 2>/dev/null)\n  if [ -n \"$html\" ]; then\n    local start_line\n    start_line=$(echo \"$html\" | grep -n \"$tree_name\" | cut -d: -f1 | head -n1)\n    if [[ \"$start_line\" =~ ^[0-9]+$ ]]; then\n      local line\n      line=$(echo \"$html\" | tail -n +\"$start_line\" | grep -m 1 -E \"c-(stable|longTerm|testing|development)-v|RouterOS [0-9]+\\.[0-9]+\" 2>/dev/null)\n\n      local version\n      version=$(echo \"$line\" | sed -n 's/.*c-[^\"]*-v\\([0-9_.a-zA-Z-]\\+\\).*/\\1/p' | tr '_' '.' 2>/dev/null)\n      [ -z \"$version\" ] && version=$(echo \"$line\" | grep -oP 'RouterOS \\K[0-9]+\\.[0-9]+(\\.[0-9]+)?' 2>/dev/null)\n\n      if [[ \"$version\" =~ ^[0-9]+\\.[0-9]+ ]]; then\n        echo \"$version\"\n        return 0\n      fi\n    fi\n  fi\n\n  for minor in $(seq 50 -1 15); do\n    local test_version=\"7.${minor}\"\n    if curl -fsSL -I \"https://download.mikrotik.com/routeros/${test_version}/chr-${test_version}.img.zip\" 2>/dev/null | grep -q \"200 OK\"; then\n      echo \"$test_version\"\n      return 0\n    fi\n  done\n\n  return 0\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 mikrotik-routeros-chr --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"mikrotik-routeros-chr\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Mikrotik RouterOS CHR VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Mikrotik RouterOS CHR VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\n\npost_to_api_vm\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  echo -e \"\\n${RD}⚠ Unable to detect a valid storage location.${CL}\"\n  echo -e \"Exiting...\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for the Mikrotik RouterOS CHR VM?\\n\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Getting URL for Latest Mikrotik RouterOS CHR Disk Image\"\n\nMIK_VER=$(get_mikrotik_version s)\n\nif [ -n \"$MIK_VER\" ]; then\n  msg_ok \"Latest stable version: ${CL}${BL}$MIK_VER${CL}.\"\nelse\n  msg_error \"Could not get latest version\"\n  msg_ok \"Defaulting to version 7.20\"\n  MIK_VER=\"7.20\"\nfi\n\nURL=https://download.mikrotik.com/routeros/$MIK_VER/chr-$MIK_VER.img.zip\n\nsleep 2\nmsg_ok \"Downloading from URL: ${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}$FILE${CL}\"\nmsg_info \"Extracting Mikrotik RouterOS CHR Disk Image\"\ngunzip -f -S .zip $FILE\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nzfspool)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\n\nDISK_VAR=\"vm-${VMID}-disk-0${DISK_EXT:-}\"\nDISK_REF=\"${STORAGE}:${DISK_REF:-}${DISK_VAR:-}\"\n\nmsg_ok \"Extracted Mikrotik RouterOS CHR Disk Image\"\nmsg_info \"Creating Mikrotik RouterOS CHR VM\"\nqm create $VMID -tablet 0 -localtime 1 -cores $CORE_COUNT -memory $RAM_SIZE -name $HN \\\n  -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU \\\n  -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\nqm importdisk $VMID ${FILE%.*} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -scsi0 \"$DISK_REF\" \\\n  -boot order=scsi0 >/dev/null\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Mikrotik RouterOS CHR</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\nif [ -n \"$DISK_SIZE\" ]; then\n  msg_info \"Resizing disk to $DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\nelse\n  msg_info \"Using default disk size of $DEFAULT_DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null\nfi\n\nmsg_ok \"Mikrotik RouterOS CHR VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Mikrotik RouterOS CHR VM\"\n  qm start $VMID\n  msg_ok \"Started Mikrotik RouterOS CHR VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/nextcloud-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n ______              __ __           _  __        __      __             __  _   ____  ___\n/_  __/_ _________  / //_/__ __ __  / |/ /____ __/ /_____/ /__  __ _____/ / | | / /  |/  /\n / / / // / __/ _ \\/ ,< / -_) // / /    / -_) \\ / __/ __/ / _ \\/ // / _  /  | |/ / /|_/ / \n/_/  \\_,_/_/ /_//_/_/|_|\\__/\\_, / /_/|_/\\__/_\\_\\\\__/\\__/_/\\___/\\_,_/\\_,_/   |___/_/  /_/  \n                           /___/\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"nextcloud-vm\"\nvar_os=\"turnkey-nextcloud\"\nvar_version=\"n.d.\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Nextcloud VM\" --yesno \"This will create a New Nextcloud VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"10G\"\n  DISK_CACHE=\"\"\n  HN=\"nextcloud-vm\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Nextcloud VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 nextcloud-vm --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"nextcloud-vm\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Nextcloud VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Nextcloud VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\n\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the $NAME Disk Image\"\nURL=http://mirror.turnkeylinux.org/turnkeylinux/images/iso/turnkey-nextcloud-18.1-bookworm-amd64.iso\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1,2}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a $NAME\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios seabios${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null\npvesm alloc $STORAGE $VMID $DISK1 12G 1>&/dev/null\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF}${FORMAT} \\\n  -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN} \\\n  -scsi1 ${DISK2_REF},${DISK_CACHE}${THIN} \\\n  -boot order='scsi1;scsi0' >/dev/null\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Nextcloud VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\nif [ -n \"$DISK_SIZE\" ]; then\n  msg_info \"Resizing disk to $DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\nelse\n  msg_info \"Using default disk size of $DEFAULT_DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null\nfi\n\nmsg_ok \"Created a $NAME ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting $NAME\"\n  qm start $VMID\n  msg_ok \"Started $NAME\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/openwrt-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n#         Jon Spriggs (jontheniceguy)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Based on work from https://i12bretro.github.io/tutorials/0405.html\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ____                 _       __     __\n  / __ \\____  ___  ____| |     / /____/ /_\n / / / / __ \\/ _ \\/ __ \\ | /| / / ___/ __/\n/ /_/ / /_/ /  __/ / / / |/ |/ / /  / /_\n\\____/ .___/\\___/_/ /_/|__/|__/_/   \\__/\n    /_/ W I R E L E S S   F R E E D O M\n\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"openwrt-vm\"\nvar_os=\"openwrt\"\nvar_version=\" \"\nDISK_SIZE=\"1G\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nGEN_MAC_LAN=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nHA=$(echo \"\\033[1;34m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nset -Eeo pipefail\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  post_update_to_api \"failed\" \"$exit_code\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nfunction send_line_to_vm() {\n  echo -e \"${DGN}Sending line: ${YW}$1${CL}\"\n  for ((i = 0; i < ${#1}; i++)); do\n    character=${1:i:1}\n    case $character in\n    \" \") character=\"spc\" ;;\n    \"-\") character=\"minus\" ;;\n    \"=\") character=\"equal\" ;;\n    \",\") character=\"comma\" ;;\n    \".\") character=\"dot\" ;;\n    \"/\") character=\"slash\" ;;\n    \"'\") character=\"apostrophe\" ;;\n    \";\") character=\"semicolon\" ;;\n    '\\') character=\"backslash\" ;;\n    '`') character=\"grave_accent\" ;;\n    \"[\") character=\"bracket_left\" ;;\n    \"]\") character=\"bracket_right\" ;;\n    \"_\") character=\"shift-minus\" ;;\n    \"+\") character=\"shift-equal\" ;;\n    \"?\") character=\"shift-slash\" ;;\n    \"<\") character=\"shift-comma\" ;;\n    \">\") character=\"shift-dot\" ;;\n    '\"') character=\"shift-apostrophe\" ;;\n    \":\") character=\"shift-semicolon\" ;;\n    \"|\") character=\"shift-backslash\" ;;\n    \"~\") character=\"shift-grave_accent\" ;;\n    \"{\") character=\"shift-bracket_left\" ;;\n    \"}\") character=\"shift-bracket_right\" ;;\n    \"A\") character=\"shift-a\" ;;\n    \"B\") character=\"shift-b\" ;;\n    \"C\") character=\"shift-c\" ;;\n    \"D\") character=\"shift-d\" ;;\n    \"E\") character=\"shift-e\" ;;\n    \"F\") character=\"shift-f\" ;;\n    \"G\") character=\"shift-g\" ;;\n    \"H\") character=\"shift-h\" ;;\n    \"I\") character=\"shift-i\" ;;\n    \"J\") character=\"shift-j\" ;;\n    \"K\") character=\"shift-k\" ;;\n    \"L\") character=\"shift-l\" ;;\n    \"M\") character=\"shift-m\" ;;\n    \"N\") character=\"shift-n\" ;;\n    \"O\") character=\"shift-o\" ;;\n    \"P\") character=\"shift-p\" ;;\n    \"Q\") character=\"shift-q\" ;;\n    \"R\") character=\"shift-r\" ;;\n    \"S\") character=\"shift-s\" ;;\n    \"T\") character=\"shift-t\" ;;\n    \"U\") character=\"shift-u\" ;;\n    \"V\") character=\"shift-v\" ;;\n    \"W\") character=\"shift-w\" ;;\n    \"X\") character=\"shift=x\" ;;\n    \"Y\") character=\"shift-y\" ;;\n    \"Z\") character=\"shift-z\" ;;\n    \"!\") character=\"shift-1\" ;;\n    \"@\") character=\"shift-2\" ;;\n    \"#\") character=\"shift-3\" ;;\n    '$') character=\"shift-4\" ;;\n    \"%\") character=\"shift-5\" ;;\n    \"^\") character=\"shift-6\" ;;\n    \"&\") character=\"shift-7\" ;;\n    \"*\") character=\"shift-8\" ;;\n    \"(\") character=\"shift-9\" ;;\n    \")\") character=\"shift-0\" ;;\n    esac\n    qm sendkey $VMID \"$character\"\n  done\n  qm sendkey $VMID ret\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\n\nif (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"OpenWrt VM\" --yesno \"This will create a New OpenWrt VM. Proceed?\" 10 58); then\n  :\nelse\n  header_info && echo -e \"⚠ User exited script \\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${CROSS} This script will not work with PiMox! \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"⚠  User exited script \\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  HN=\"openwrt\"\n  CORE_COUNT=\"1\"\n  RAM_SIZE=\"256\"\n  BRG=\"vmbr0\"\n  LAN_BRG=\"vmbr0\"\n  MAC=$GEN_MAC\n  LAN_MAC=$GEN_MAC_LAN\n  VLAN=\"\"\n  LAN_VLAN=\"\"\n  LAN_IP_ADDR=\"192.168.1.1\"\n  LAN_NETMASK=\"255.255.255.0\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  DISK_SIZE=\"1G\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}VMID: ${BGN}${VMID}${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}WAN Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}LAN Bridge: ${BGN}${LAN_BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}WAN MAC: ${BGN}${MAC}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}LAN MAC: ${BGN}${LAN_MAC}${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 openwrt --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"openwrt\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n    fi\n    echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 1 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"1\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 1).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 256 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"256\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 256).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" \\\n    --inputbox \"Set Disk Size in GiB (e.g., 1, 2, 4)\" 8 58 \"1\" \\\n    --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n    fi\n    echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  else\n    exit-script\n  fi\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN Bridge\" 8 58 vmbr0 --title \"WAN BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n    fi\n    echo -e \"${DGN}Using WAN Bridge: ${BGN}$BRG${CL}\"\n  else\n    exit-script\n  fi\n\n  if LAN_BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN Bridge\" 8 58 vmbr0 --title \"LAN BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $LAN_BRG ]; then\n      LAN_BRG=\"vmbr0\"\n    fi\n    echo -e \"${DGN}Using LAN Bridge: ${BGN}$LAN_BRG${CL}\"\n  else\n    exit-script\n  fi\n\n  if LAN_IP_ADDR=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a router IP\" 8 58 $LAN_IP_ADDR --title \"LAN IP ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $LAN_IP_ADDR ]; then\n      LAN_IP_ADDR=\"192.168.1.1\"\n    fi\n    echo -e \"${DGN}Using LAN IP ADDRESS: ${BGN}$LAN_IP_ADDR${CL}\"\n  else\n    exit-script\n  fi\n\n  if LAN_NETMASK=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a router netmask\" 8 58 $LAN_NETMASK --title \"LAN NETMASK\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $LAN_NETMASK ]; then\n      LAN_NETMASK=\"255.255.255.0\"\n    fi\n    echo -e \"${DGN}Using LAN NETMASK: ${BGN}$LAN_NETMASK${CL}\"\n  else\n    exit-script\n  fi\n\n  if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN MAC Address\" 8 58 $GEN_MAC --title \"WAN MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $MAC1 ]; then\n      MAC=\"$GEN_MAC\"\n    else\n      MAC=\"$MAC1\"\n    fi\n    echo -e \"${DGN}Using WAN MAC Address: ${BGN}$MAC${CL}\"\n  else\n    exit-script\n  fi\n\n  if MAC2=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN MAC Address\" 8 58 $GEN_MAC_LAN --title \"LAN MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $MAC2 ]; then\n      LAN_MAC=\"$GEN_MAC_LAN\"\n    else\n      LAN_MAC=\"$MAC2\"\n    fi\n    echo -e \"${DGN}Using LAN MAC Address: ${BGN}$LAN_MAC${CL}\"\n  else\n    exit-script\n  fi\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN Vlan (leave blank for default)\" 8 58 --title \"WAN VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${DGN}Using WAN Vlan: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${DGN}Using WAN Vlan: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN2=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN Vlan\" 8 58 999 --title \"LAN VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN2\" ]; then\n        VLAN2=\"Default\"\n        LAN_VLAN=\"\"\n        echo -e \"${DGN}Using LAN Vlan: ${BGN}$VLAN2${CL}\"\n        break\n      fi\n      if [[ \"$VLAN2\" =~ ^[0-9]+$ ]] && [ \"$VLAN2\" -ge 1 ] && [ \"$VLAN2\" -le 4094 ]; then\n        LAN_VLAN=\",tag=$VLAN2\"\n        echo -e \"${DGN}Using LAN Vlan: ${BGN}$VLAN2${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    START_VM=\"yes\"\n  else\n    START_VM=\"no\"\n  fi\n  echo -e \"${DGN}Start VM when completed: ${BGN}$START_VM${CL}\"\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create OpenWrt VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${RD}Creating a OpenWrt VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\narch_check\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  echo -e \"\\n${RD}⚠ Unable to detect a valid storage location.${CL}\"\n  echo -e \"Exiting...\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for the OpenWrt VM?\\n\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Getting URL for OpenWrt Disk Image\"\n\nresponse=$(curl -fsSL https://openwrt.org)\nstableversion=$(echo \"$response\" | sed -n 's/.*Current stable release - OpenWrt \\([0-9.]\\+\\).*/\\1/p' | head -n 1)\nURL=\"https://downloads.openwrt.org/releases/$stableversion/targets/x86/64/openwrt-$stableversion-x86-64-generic-ext4-combined.img.gz\"\n\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\nFILE=$(basename \"$URL\")\nmsg_ok \"Downloaded ${CL}${BL}$FILE${CL}\"\n\ngunzip -f \"$FILE\" >/dev/null 2>&1 || true\nFILE=\"${FILE%.*}\"\nmsg_ok \"Extracted OpenWrt Disk Image ${CL}${BL}$FILE${CL}\"\n\nmsg_info \"Creating OpenWrt VM\"\nqm create $VMID -cores $CORE_COUNT -memory $RAM_SIZE -name $HN \\\n  -onboot 1 -ostype l26 -scsihw virtio-scsi-pci --tablet 0\nif [[ \"$(pvesm status | awk -v s=$STORAGE '$1==s {print $2}')\" == \"dir\" ]]; then\n  qm set $VMID -efidisk0 ${STORAGE}:0,efitype=4m,size=4M\nelse\n  pvesm alloc $STORAGE $VMID vm-$VMID-disk-0 4M >/dev/null\n  qm set $VMID -efidisk0 ${STORAGE}:vm-$VMID-disk-0,efitype=4m,size=4M\nfi\n\nIMPORT_OUT=\"$(qm importdisk $VMID $FILE $STORAGE --format raw 2>&1 || true)\"\nDISK_REF=\"$(printf '%s\\n' \"$IMPORT_OUT\" | sed -n \"s/.*successfully imported disk '\\([^']\\+\\)'.*/\\1/p\")\"\n\nif [[ -z \"$DISK_REF\" ]]; then\n  DISK_REF=\"$(pvesm list \"$STORAGE\" | awk -v id=\"$VMID\" '$1 ~ (\"vm-\"id\"-disk-\") {print $1}' | sort | tail -n1)\"\nfi\n\nif [[ -z \"$DISK_REF\" ]]; then\n  msg_error \"Unable to determine imported disk reference.\"\n  echo \"$IMPORT_OUT\"\n  exit 226\nfi\n\nqm set $VMID \\\n  -efidisk0 ${STORAGE}:0,efitype=4m,size=4M \\\n  -scsi0 ${DISK_REF} \\\n  -boot order=scsi0 \\\n  -tags community-script >/dev/null\nmsg_ok \"Attached disk\"\n\nmsg_info \"Resizing disk to ${DISK_SIZE}\"\nqm disk resize \"$VMID\" scsi0 \"${DISK_SIZE}\" >/dev/null\nmsg_ok \"Resized disk to ${DISK_SIZE}\"\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>OpenWrt VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\n\nmsg_ok \"Created OpenWrt VM ${CL}${BL}(${HN})\"\nmsg_info \"OpenWrt is being started in order to configure the network interfaces.\"\nqm start $VMID\nsleep 15\nmsg_info \"Waiting for OpenWrt to boot...\"\nfor i in {1..30}; do\n  if qm status \"$VMID\" | grep -q \"running\"; then\n    sleep 5\n    msg_ok \"OpenWrt is running\"\n    break\n  fi\n  sleep 1\ndone\n\nmsg_ok \"Network interfaces are being configured as OpenWrt initiates.\"\n\nif qm status \"$VMID\" | grep -q \"running\"; then\n  send_line_to_vm \"\"\n  send_line_to_vm \"uci delete network.@device[0]\"\n  send_line_to_vm \"uci set network.wan=interface\"\n  send_line_to_vm \"uci set network.wan.device=eth1\"\n  send_line_to_vm \"uci set network.wan.proto=dhcp\"\n  send_line_to_vm \"uci delete network.lan\"\n  send_line_to_vm \"uci set network.lan=interface\"\n  send_line_to_vm \"uci set network.lan.device=eth0\"\n  send_line_to_vm \"uci set network.lan.proto=static\"\n  send_line_to_vm \"uci set network.lan.ipaddr=${LAN_IP_ADDR}\"\n  send_line_to_vm \"uci set network.lan.netmask=${LAN_NETMASK}\"\n  send_line_to_vm \"uci commit\"\n  send_line_to_vm \"poweroff\"\n  msg_ok \"Network interfaces configured in OpenWrt\"\nelse\n  msg_error \"VM is not running\"\n  exit 226\nfi\n\nmsg_info \"Waiting for OpenWrt to shut down...\"\nuntil qm status \"$VMID\" | grep -q \"stopped\"; do\n  sleep 2\ndone\nmsg_ok \"OpenWrt has shut down\"\n\nmsg_info \"Adding bridge interfaces on Proxmox side\"\nqm set $VMID \\\n  -net0 virtio,bridge=${LAN_BRG},macaddr=${LAN_MAC}${LAN_VLAN}${MTU} \\\n  -net1 virtio,bridge=${BRG},macaddr=${MAC}${VLAN}${MTU} >/dev/null\nmsg_ok \"Bridge interfaces added\"\n\nif [ \"$START_VM\" = \"yes\" ]; then\n  msg_info \"Starting OpenWrt VM\"\n  qm start $VMID\n  msg_ok \"Started OpenWrt VM\"\nfi\n\nVLAN_FINISH=\"\"\nif [ -z \"$VLAN\" ] && [ \"$VLAN2\" != \"999\" ]; then\n  VLAN_FINISH=\" Please remember to adjust the VLAN tags to suit your network.\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed Successfully!${VLAN_FINISH:+\\n$VLAN_FINISH}\"\n"
  },
  {
    "path": "vm/opnsense-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   ____  ____  _   __\n  / __ \\/ __ \\/ | / /_______  ____  ________\n / / / / /_/ /  |/ / ___/ _ \\/ __ \\/ ___/ _ \\\n/ /_/ / ____/ /|  (__  )  __/ / / (__  )  __/\n\\____/_/   /_/ |_/____/\\___/_/ /_/____/\\___/\n\nEOF\n}\nheader_info\necho -e \"Loading...\"\n#API VARIABLES\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"opnsense-vm\"\nvar_os=\"opnsense\"\nvar_version=\"26.1\"\n#\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nGEN_MAC_LAN=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nHA=$(echo \"\\033[1;34m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nCROSS=\"${RD}✗${CL}\"\nset -Eeo pipefail\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  post_update_to_api \"failed\" \"$exit_code\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nfunction check_disk_space() {\n  local path=\"$1\"\n  local required_gb=\"$2\"\n  local available_kb=$(df -k \"$path\" | awk 'NR==2 {print $4}')\n  local available_gb=$((available_kb / 1024 / 1024))\n  if [ $available_gb -lt $required_gb ]; then\n    return 1\n  fi\n  return 0\n}\n\n# Use disk-backed temp directory to avoid tmpfs/RAM size limits in /tmp\nif [ -d \"/var/tmp\" ] && check_disk_space \"/var/tmp\" 20; then\n  TEMP_DIR=$(mktemp -d /var/tmp/opnsense-vm.XXXXXX)\nelif [ -d \"/tmp\" ] && check_disk_space \"/tmp\" 20; then\n  TEMP_DIR=$(mktemp -d)\nelse\n  # Fallback: try /var/tmp anyway, disk space check will catch it later\n  TEMP_DIR=$(mktemp -d /var/tmp/opnsense-vm.XXXXXX)\nfi\npushd $TEMP_DIR >/dev/null\nfunction send_line_to_vm() {\n  echo -e \"${DGN}Sending line: ${YW}$1${CL}\"\n  for ((i = 0; i < ${#1}; i++)); do\n    character=${1:i:1}\n    case $character in\n    \" \") character=\"spc\" ;;\n    \"-\") character=\"minus\" ;;\n    \"=\") character=\"equal\" ;;\n    \",\") character=\"comma\" ;;\n    \".\") character=\"dot\" ;;\n    \"/\") character=\"slash\" ;;\n    \"'\") character=\"apostrophe\" ;;\n    \";\") character=\"semicolon\" ;;\n    '\\') character=\"backslash\" ;;\n    '`') character=\"grave_accent\" ;;\n    \"[\") character=\"bracket_left\" ;;\n    \"]\") character=\"bracket_right\" ;;\n    \"_\") character=\"shift-minus\" ;;\n    \"+\") character=\"shift-equal\" ;;\n    \"?\") character=\"shift-slash\" ;;\n    \"<\") character=\"shift-comma\" ;;\n    \">\") character=\"shift-dot\" ;;\n    '\"') character=\"shift-apostrophe\" ;;\n    \":\") character=\"shift-semicolon\" ;;\n    \"|\") character=\"shift-backslash\" ;;\n    \"~\") character=\"shift-grave_accent\" ;;\n    \"{\") character=\"shift-bracket_left\" ;;\n    \"}\") character=\"shift-bracket_right\" ;;\n    \"A\") character=\"shift-a\" ;;\n    \"B\") character=\"shift-b\" ;;\n    \"C\") character=\"shift-c\" ;;\n    \"D\") character=\"shift-d\" ;;\n    \"E\") character=\"shift-e\" ;;\n    \"F\") character=\"shift-f\" ;;\n    \"G\") character=\"shift-g\" ;;\n    \"H\") character=\"shift-h\" ;;\n    \"I\") character=\"shift-i\" ;;\n    \"J\") character=\"shift-j\" ;;\n    \"K\") character=\"shift-k\" ;;\n    \"L\") character=\"shift-l\" ;;\n    \"M\") character=\"shift-m\" ;;\n    \"N\") character=\"shift-n\" ;;\n    \"O\") character=\"shift-o\" ;;\n    \"P\") character=\"shift-p\" ;;\n    \"Q\") character=\"shift-q\" ;;\n    \"R\") character=\"shift-r\" ;;\n    \"S\") character=\"shift-s\" ;;\n    \"T\") character=\"shift-t\" ;;\n    \"U\") character=\"shift-u\" ;;\n    \"V\") character=\"shift-v\" ;;\n    \"W\") character=\"shift-w\" ;;\n    \"X\") character=\"shift-x\" ;;\n    \"Y\") character=\"shift-y\" ;;\n    \"Z\") character=\"shift-z\" ;;\n    \"!\") character=\"shift-1\" ;;\n    \"@\") character=\"shift-2\" ;;\n    \"#\") character=\"shift-3\" ;;\n    '$') character=\"shift-4\" ;;\n    \"%\") character=\"shift-5\" ;;\n    \"^\") character=\"shift-6\" ;;\n    \"&\") character=\"shift-7\" ;;\n    \"*\") character=\"shift-8\" ;;\n    \"(\") character=\"shift-9\" ;;\n    \")\") character=\"shift-0\" ;;\n    esac\n    qm sendkey $VMID \"$character\"\n  done\n  qm sendkey $VMID ret\n}\n\nif (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"OPNsense VM\" --yesno \"This will create a New OPNsense VM. Proceed?\" 10 58); then\n  :\nelse\n  header_info && echo -e \"⚠ User exited script \\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${CROSS} This script will not work with PiMox! \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"⚠  User exited script \\n\"\n  exit\n}\n\nfunction get_available_bridges() {\n  ip -o link show type bridge 2>/dev/null | awk -F': ' '{print $2}' | sort\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_CACHE=\"\"\n  HN=\"opnsense\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"4\"\n  RAM_SIZE=\"8192\"\n  BRG=\"vmbr0\"\n  IP_ADDR=\"\"\n  WAN_IP_ADDR=\"\"\n  LAN_GW=\"\"\n  WAN_GW=\"\"\n  NETMASK=\"\"\n  WAN_NETMASK=\"\"\n  VLAN=\"\"\n  MAC=$GEN_MAC\n  WAN_MAC=$GEN_MAC_LAN\n  WAN_BRG=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n\n  # Detect available bridges\n  local AVAILABLE_BRIDGES\n  AVAILABLE_BRIDGES=$(get_available_bridges)\n  local BRIDGE_COUNT\n  BRIDGE_COUNT=$(echo \"$AVAILABLE_BRIDGES\" | wc -l)\n\n  echo -e \"${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${DGN}Using Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}\"\n  if ! ip link show \"${BRG}\" &>/dev/null; then\n    msg_error \"Bridge '${BRG}' does not exist\"\n    exit\n  else\n    echo -e \"${DGN}Using LAN Bridge: ${BGN}${BRG}${CL}\"\n  fi\n  echo -e \"${DGN}Using LAN VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DGN}Using LAN MAC Address: ${BGN}${MAC}${CL}\"\n\n  # Determine available network modes based on bridge count\n  local DEFAULT_WAN_BRG\n  DEFAULT_WAN_BRG=$(echo \"$AVAILABLE_BRIDGES\" | grep -v \"^${BRG}$\" | head -n1 || true)\n\n  if [ \"$BRIDGE_COUNT\" -ge 2 ]; then\n    # Multiple bridges available - offer dual or single mode\n    if NETWORK_MODE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"NETWORK CONFIGURATION\" --radiolist --cancel-button Exit-Script \\\n      \"Choose network setup mode for OPNsense:\\n\" 14 70 2 \\\n      \"dual\" \"Dual Interface (Firewall/Router) - uses ${DEFAULT_WAN_BRG}\" ON \\\n      \"single\" \"Single Interface (Proxy/VPN/IDS Server)\" OFF \\\n      3>&1 1>&2 2>&3); then\n      if [ \"$NETWORK_MODE\" = \"dual\" ]; then\n        WAN_BRG=\"$DEFAULT_WAN_BRG\"\n        echo -e \"${DGN}Network Mode: ${BGN}Dual Interface (Firewall)${CL}\"\n        echo -e \"${DGN}Using WAN Bridge: ${BGN}${WAN_BRG}${CL}\"\n        echo -e \"${DGN}Using WAN MAC Address: ${BGN}${WAN_MAC}${CL}\"\n      else\n        echo -e \"${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}\"\n        WAN_BRG=\"\"\n      fi\n    else\n      exit-script\n    fi\n  else\n    # Only one bridge available - single interface mode only\n    echo -e \"${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}\"\n    echo -e \"${YW}  (Only one bridge detected, dual interface requires a second bridge)${CL}\"\n    WAN_BRG=\"\"\n  fi\n  echo -e \"${DGN}Using Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${BL}Creating a OPNsense VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  local ip_regex='^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$'\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${DGN}Using Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${DGN}Using Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${DGN}Using CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${DGN}Using CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DGN}Using Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DGN}Using Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 OPNsense --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$VM_NAME\" ]; then\n      HN=\"OPNsense\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n    fi\n    echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 4 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"4\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 4).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 8192 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"8192\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 8192).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN Bridge\" 8 58 vmbr0 --title \"LAN BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n    fi\n    if ! ip link show \"${BRG}\" &>/dev/null; then\n      msg_error \"Bridge '${BRG}' does not exist\"\n      exit\n    fi\n    echo -e \"${DGN}Using LAN Bridge: ${BGN}$BRG${CL}\"\n  else\n    exit-script\n  fi\n\n  if IP_ADDR=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN IP\" 8 58 $IP_ADDR --title \"LAN IP ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $IP_ADDR ]; then\n      echo -e \"${DGN}Using DHCP AS LAN IP ADDRESS${CL}\"\n    else\n      if [[ -n \"$IP_ADDR\" && ! \"$IP_ADDR\" =~ $ip_regex ]]; then\n        msg_error \"Invalid IP Address format for LAN IP. Needs to be 0.0.0.0, was $IP_ADDR\"\n        exit\n      fi\n      echo -e \"${DGN}Using LAN IP ADDRESS: ${BGN}$IP_ADDR${CL}\"\n      if LAN_GW=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN GATEWAY IP\" 8 58 $LAN_GW --title \"LAN GATEWAY IP ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n        if [ -z $LAN_GW ]; then\n          echo -e \"${DGN}Gateway needs to be set if ip is not dhcp${CL}\"\n          exit-script\n        fi\n        if [[ -n \"$LAN_GW\" && ! \"$LAN_GW\" =~ $ip_regex ]]; then\n          msg_error \"Invalid IP Address format for Gateway. Needs to be 0.0.0.0, was $LAN_GW\"\n          exit\n        fi\n        echo -e \"${DGN}Using LAN GATEWAY ADDRESS: ${BGN}$LAN_GW${CL}\"\n      fi\n      if NETMASK=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN netmmask (24 for example)\" 8 58 $NETMASK --title \"LAN NETMASK\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n        if [ -z $NETMASK ]; then\n          echo -e \"${DGN}Netmask needs to be set if ip is not dhcp${CL}\"\n        fi\n        if [[ -n \"$NETMASK\" && ! (\"$NETMASK\" =~ ^[0-9]+$ && \"$NETMASK\" -ge 1 && \"$NETMASK\" -le 32) ]]; then\n          msg_error \"Invalid LAN NETMASK format. Needs to be 1-32, was $NETMASK\"\n          exit\n        fi\n        echo -e \"${DGN}Using LAN NETMASK: ${BGN}$NETMASK${CL}\"\n      else\n        exit-script\n      fi\n    fi\n  else\n    exit-script\n  fi\n\n  # Build WAN bridge selection from available bridges (excluding LAN bridge)\n  local WAN_BRIDGES\n  WAN_BRIDGES=$(get_available_bridges | grep -v \"^${BRG}$\" || true)\n  if [ -z \"$WAN_BRIDGES\" ]; then\n    msg_error \"No additional bridge available for WAN. Only '${BRG}' exists.\"\n    msg_error \"Create a second bridge (e.g. vmbr1) in Proxmox network config first.\"\n    exit\n  fi\n  local WAN_MENU=()\n  local first=true\n  while IFS= read -r brg; do\n    if $first; then\n      WAN_MENU+=(\"$brg\" \"\" \"ON\")\n      first=false\n    else\n      WAN_MENU+=(\"$brg\" \"\" \"OFF\")\n    fi\n  done <<<\"$WAN_BRIDGES\"\n\n  if WAN_BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"WAN BRIDGE\" --radiolist \"Select WAN Bridge\" 14 58 6 \\\n    \"${WAN_MENU[@]}\" 3>&1 1>&2 2>&3); then\n    if [ -z \"$WAN_BRG\" ]; then\n      WAN_BRG=$(echo \"$WAN_BRIDGES\" | head -n1)\n    fi\n    echo -e \"${DGN}Using WAN Bridge: ${BGN}$WAN_BRG${CL}\"\n  else\n    exit-script\n  fi\n\n  if WAN_IP_ADDR=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN IP\" 8 58 $WAN_IP_ADDR --title \"WAN IP ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $WAN_IP_ADDR ]; then\n      echo -e \"${DGN}Using DHCP AS WAN IP ADDRESS${CL}\"\n    else\n      if [[ -n \"$WAN_IP_ADDR\" && ! \"$WAN_IP_ADDR\" =~ $ip_regex ]]; then\n        msg_error \"Invalid IP Address format for WAN IP. Needs to be 0.0.0.0, was $WAN_IP_ADDR\"\n        exit\n      fi\n      echo -e \"${DGN}Using WAN IP ADDRESS: ${BGN}$WAN_IP_ADDR${CL}\"\n      if WAN_GW=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN GATEWAY IP\" 8 58 $WAN_GW --title \"WAN GATEWAY IP ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n        if [ -z $WAN_GW ]; then\n          echo -e \"${DGN}Gateway needs to be set if ip is not dhcp${CL}\"\n          exit-script\n        fi\n        if [[ -n \"$WAN_GW\" && ! \"$WAN_GW\" =~ $ip_regex ]]; then\n          msg_error \"Invalid IP Address format for WAN Gateway. Needs to be 0.0.0.0, was $WAN_GW\"\n          exit\n        fi\n        echo -e \"${DGN}Using WAN GATEWAY ADDRESS: ${BGN}$WAN_GW${CL}\"\n      else\n        exit-script\n      fi\n      if WAN_NETMASK=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN netmmask (24 for example)\" 8 58 $WAN_NETMASK --title \"WAN NETMASK\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n        if [ -z $WAN_NETMASK ]; then\n          echo -e \"${DGN}WAN Netmask needs to be set if ip is not dhcp${CL}\"\n        fi\n        if [[ -n \"$WAN_NETMASK\" && ! (\"$WAN_NETMASK\" =~ ^[0-9]+$ && \"$WAN_NETMASK\" -ge 1 && \"$WAN_NETMASK\" -le 32) ]]; then\n          msg_error \"Invalid WAN NETMASK format. Needs to be 1-32, was $WAN_NETMASK\"\n          exit\n        fi\n        echo -e \"${DGN}Using WAN NETMASK: ${BGN}$WAN_NETMASK${CL}\"\n      else\n        exit-script\n      fi\n    fi\n  else\n    exit-script\n  fi\n  if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a WAN MAC Address\" 8 58 $GEN_MAC --title \"WAN MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $MAC1 ]; then\n      MAC=\"$GEN_MAC\"\n    else\n      MAC=\"$MAC1\"\n    fi\n    echo -e \"${DGN}Using LAN MAC Address: ${BGN}$MAC${CL}\"\n  else\n    exit-script\n  fi\n\n  if MAC2=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a LAN MAC Address\" 8 58 $GEN_MAC_LAN --title \"LAN MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $MAC2 ]; then\n      WAN_MAC=\"$GEN_MAC_LAN\"\n    else\n      WAN_MAC=\"$MAC2\"\n    fi\n    echo -e \"${DGN}Using WAN MAC Address: ${BGN}$WAN_MAC${CL}\"\n  else\n    exit-script\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create OPNsense VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${RD}Creating a OPNsense VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\narch_check\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the OPNsense Qcow2 Disk Image\"\n# Use latest stable FreeBSD amd64 qcow2 VM image (generic, not UFS/ZFS)\nRELEASE_LIST=\"$(curl -s https://download.freebsd.org/releases/VM-IMAGES/ |\n  grep -Eo '[0-9]+\\.[0-9]+-RELEASE' |\n  sort -Vr |\n  uniq)\"\nURL=\"\"\nFREEBSD_VER=\"\"\nfor ver in $RELEASE_LIST; do\n  candidate=\"https://download.freebsd.org/releases/VM-IMAGES/${ver}/amd64/Latest/FreeBSD-${ver}-amd64.qcow2.xz\"\n  if curl -fsI \"$candidate\" >/dev/null 2>&1; then\n    FREEBSD_VER=\"$ver\"\n    URL=\"$candidate\"\n    break\n  fi\ndone\nif [ -z \"$URL\" ]; then\n  msg_error \"Could not find generic FreeBSD amd64 qcow2 image (non-UFS/ZFS).\"\n  exit 115\nfi\nmsg_ok \"Download URL: ${CL}${BL}${URL}${CL}\"\n\n# Check available disk space (require at least 20GB for safety)\nif ! check_disk_space \"$TEMP_DIR\" 20; then\n  AVAILABLE_GB=$(df -h \"$TEMP_DIR\" | awk 'NR==2 {print $4}')\n  msg_error \"Insufficient disk space in temporary directory ($TEMP_DIR).\"\n  msg_error \"Available: ${AVAILABLE_GB}, Required: ~20GB for FreeBSD image decompression.\"\n  msg_error \"Please free up space or ensure /tmp has sufficient storage.\"\n  exit 214\nfi\n\nmsg_info \"Downloading FreeBSD Image\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nmsg_ok \"Downloaded ${CL}${BL}$(basename \"$URL\")${CL}\"\n\n# Check disk space again before decompression\nif ! check_disk_space \"$TEMP_DIR\" 15; then\n  AVAILABLE_GB=$(df -h \"$TEMP_DIR\" | awk 'NR==2 {print $4}')\n  msg_error \"Insufficient disk space for decompression.\"\n  msg_error \"Available: ${AVAILABLE_GB}, Required: ~15GB for decompressed image.\"\n  exit 214\nfi\n\nmsg_info \"Decompressing FreeBSD Image (this may take a few minutes)\"\nFILE=FreeBSD.qcow2\nif ! unxz -cv $(basename $URL) >${FILE}; then\n  msg_error \"Failed to decompress FreeBSD image.\"\n  msg_error \"This is usually caused by insufficient disk space.\"\n  df -h \"$TEMP_DIR\"\n  exit 115\nfi\n\n# Remove the compressed file to save space\nrm -f \"$(basename \"$URL\")\"\nmsg_ok \"Decompressed ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a OPNsense VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\n\n# Retry pvesm alloc on transient zfs_request \"got timeout\" errors (#14127)\nalloc_attempt=1\nalloc_max=4\nalloc_delay=5\nwhile :; do\n  alloc_err=$(pvesm alloc $STORAGE $VMID $DISK0 4M 2>&1 >/dev/null) && break\n  if [[ \"$alloc_err\" == *\"got timeout\"* && $alloc_attempt -lt $alloc_max ]]; then\n    msg_warn \"pvesm alloc hit zfs timeout (attempt $alloc_attempt/$alloc_max), retrying in ${alloc_delay}s...\"\n    pvesm free \"${DISK0_REF}\" &>/dev/null || true\n    sleep \"$alloc_delay\"\n    alloc_attempt=$((alloc_attempt + 1))\n    alloc_delay=$((alloc_delay * 2))\n    continue\n  fi\n  echo -e \"$alloc_err\" >&2\n  exit 220\ndone\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} &>/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF}${FORMAT} \\\n  -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=2G \\\n  -boot order=scsi0 \\\n  -serial0 socket \\\n  -tags community-script >/dev/null\nqm resize $VMID scsi0 20G >/dev/null\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/michelroegl-brunner/ProxmoxVE/refs/heads/develop/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>OPNsense VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\n\nmsg_info \"Bridge interfaces are being added.\"\nqm set $VMID \\\n  -net0 virtio,bridge=${BRG},macaddr=${MAC}${VLAN}${MTU} 2>/dev/null\nmsg_ok \"Bridge interfaces have been successfully added.\"\n\nmsg_ok \"Created a OPNsense VM ${CL}${BL}(${HN})\"\nmsg_ok \"Starting OPNsense VM (Patience this takes 20-30 minutes)\"\nqm start $VMID\nsleep 90\nsend_line_to_vm \"root\"\nsend_line_to_vm \"fetch https://raw.githubusercontent.com/opnsense/update/master/src/bootstrap/opnsense-bootstrap.sh.in\"\nif [ -n \"$WAN_BRG\" ]; then\n  msg_info \"Adding WAN interface\"\n  qm set $VMID \\\n    -net1 virtio,bridge=${WAN_BRG},macaddr=${WAN_MAC} &>/dev/null\n  msg_ok \"WAN interface added\"\n  sleep 5 # Brief pause after adding network interface\nfi\nsend_line_to_vm \"sh ./opnsense-bootstrap.sh.in -y -f -r 26.1\"\nmsg_ok \"OPNsense VM is being installed, do not close the terminal, or the installation will fail.\"\n#We need to wait for the OPNsense build proccess to finish, this takes a few minutes\nsleep 1000\nsend_line_to_vm \"root\"\nsend_line_to_vm \"opnsense\"\nsend_line_to_vm \"2\"\n\nif [ \"$IP_ADDR\" != \"\" ]; then\n  send_line_to_vm \"1\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"${IP_ADDR}\"\n  send_line_to_vm \"${NETMASK}\"\n  send_line_to_vm \"${LAN_GW}\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \" \"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \" \"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\nelse\n  send_line_to_vm \"1\"\n  send_line_to_vm \"y\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \" \"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\nfi\n#Wait for config changes to be saved\nsleep 20\nif [ -n \"$WAN_BRG\" ] && [ \"$WAN_IP_ADDR\" != \"\" ]; then\n  send_line_to_vm \"2\"\n  send_line_to_vm \"2\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"${WAN_IP_ADDR}\"\n  send_line_to_vm \"${NETMASK}\"\n  send_line_to_vm \"${LAN_GW}\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \" \"\n  send_line_to_vm \"n\"\n  send_line_to_vm \" \"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\n  send_line_to_vm \"n\"\nfi\nsleep 10\nsend_line_to_vm \"0\"\nmsg_ok \"Started OPNsense VM\"\n\nmsg_ok \"Completed successfully!\\n\"\nif [ \"$IP_ADDR\" != \"\" ]; then\n  echo -e \"${INFO}${YW} Access it using the following URL:${CL}\"\n  echo -e \"${TAB}${GATEWAY}${BGN}http://${IP_ADDR}${CL}\"\nelse\n  echo -e \"${INFO}${YW} LAN IP was DHCP.${CL}\"\n  echo -e \"${INFO}${BGN}To find the IP login to the VM shell${CL}\"\nfi\n"
  },
  {
    "path": "vm/owncloud-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n ______              __ __                           _______             __  _   ____  ___\n/_  __/_ _________  / //_/__ __ __  ___ _    _____  / ___/ /__  __ _____/ / | | / /  |/  /\n / / / // / __/ _ \\/ ,< / -_) // / / _ \\ |/|/ / _ \\/ /__/ / _ \\/ // / _  /  | |/ / /|_/ /\n/_/  \\_,_/_/ /_//_/_/|_|\\__/\\_, /  \\___/__,__/_//_/\\___/_/\\___/\\_,_/\\_,_/   |___/_/  /_/\n                           /___/\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"owncloud-vm\"\nvar_os=\"owncloud\"\nvar_version=\"18.0\"\nAPP=\"TurnKey ownCloud VM\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Owncloud VM\" --yesno \"This will create a New Owncloud VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"10G\"\n  DISK_CACHE=\"\"\n  HN=\"owncloud-vm\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Owncloud VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 owncloud-vm --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"owncloud-vm\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Owncloud VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Owncloud VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\n\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the $APP Disk Image\"\nURL=http://mirror.turnkeylinux.org/turnkeylinux/images/iso/turnkey-owncloud-18.0-bookworm-amd64.iso\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1,2}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a $APP\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios seabios${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null\npvesm alloc $STORAGE $VMID $DISK1 12G 1>&/dev/null\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF}${FORMAT} \\\n  -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN} \\\n  -scsi1 ${DISK2_REF},${DISK_CACHE}${THIN} \\\n  -boot order='scsi1;scsi0' >/dev/null\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Owncloud VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\nif [ -n \"$DISK_SIZE\" ]; then\n  msg_info \"Resizing disk to $DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\nelse\n  msg_info \"Using default disk size of $DEFAULT_DISK_SIZE GB\"\n  qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null\nfi\n\nmsg_ok \"Created a $APP ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting $APP\"\n  qm start $VMID\n  msg_ok \"Started $APP\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/pimox-haos-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  cat <<\"EOF\"\n                                      ____  _ __  ___                                        \n                                    / __ \\(_)  |/  /___  _  __                              \n                                   / /_/ / / /|_/ / __ \\| |/_/                              \n                                  / ____/ / /  / / /_/ />  <                                \n    __  __                       /_/_  /_/_/  /_/\\____/_/|_|              __     ____  _____\n   / / / /___  ____ ___  ___     /   |  __________(_)____/ /_____ _____  / /_   / __ \\/ ___/\n  / /_/ / __ \\/ __ `__ \\/ _ \\   / /| | / ___/ ___/ / ___/ __/ __ `/ __ \\/ __/  / / / /\\__ \\ \n / __  / /_/ / / / / / /  __/  / ___ |(__  |__  ) (__  ) /_/ /_/ / / / / /_   / /_/ /___/ / \n/_/ /_/\\____/_/ /_/ /_/\\___/  /_/  |_/____/____/_/____/\\__/\\__,_/_/ /_/\\__/   \\____//____/  \n                                                                                            \nEOF\n}\nclear\nheader_info\necho -e \"Loading...\"\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"pimox-haos-vm\"\nvar_os=\"pimox-haos\"\nvar_version=\" \"\nDISK_SIZE=\"32G\"\nGEN_MAC=$(echo '00 60 2f'$(od -An -N3 -t xC /dev/urandom) | sed -e 's/ /:/g' | tr '[:lower:]' '[:upper:]')\nUSEDID=$(pvesh get /cluster/resources --type vm --output-format yaml | egrep -i 'vmid' | awk '{print substr($2, 1, length($2)-0) }')\nSTABLE=$(curl -fsSL https://raw.githubusercontent.com/home-assistant/version/master/stable.json | grep \"ova\" | awk '{print substr($2, 2, length($2)-3) }')\nBETA=$(curl -fsSL https://raw.githubusercontent.com/home-assistant/version/master/beta.json | grep \"ova\" | awk '{print substr($2, 2, length($2)-3) }')\nDEV=$(curl -fsSL https://raw.githubusercontent.com/home-assistant/version/master/dev.json | grep \"ova\" | awk '{print substr($2, 2, length($2)-3) }')\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nHA=$(echo \"\\033[1;34m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\"-\"\nCM=\"${GN}✓${CL}\"\nINFO=\"\"\nYWB=\"\\033[1;33m\"\nCROSS=\"${RD}✗${CL}\"\nset -o errexit\nset -o errtrace\nset -o nounset\nset -o pipefail\nARCH_CHECK=true\nDIAGNOSTICS=true\nshopt -s expand_aliases\nalias die='EXIT=$? LINE=$LINENO error_exit'\ntrap die ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if $(qm status $VMID &>/dev/null); then\n    if [ \"$(qm status $VMID | awk '{print $2}')\" == \"running\" ]; then\n      qm stop $VMID\n    fi\n    qm destroy $VMID\n  fi\n}\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif ! command -v whiptail &>/dev/null; then\n  echo \"Installing whiptail...\"\n  apt-get update &>/dev/null\n  apt-get install -y whiptail &>/dev/null\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  local ARCH\n  ARCH=$(dpkg --print-architecture)\n  if [ \"$ARCH\" != \"arm64\" ]; then\n    echo -e \"\\n This script will not work on AMD64! \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit 1\n  fi\n}\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \" ${HOLD} ${YW}${msg}...\"\n}\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CM} ${GN}${msg}${CL}\"\n}\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR} ${CROSS} ${RD}${msg}${CL}\"\n}\n\nfunction default_settings() {\n  METHOD=\"default\"\n  echo -e \"${DGN}Using HAOS Version: ${BGN}${STABLE}${CL}\"\n  BRANCH=${STABLE}\n  VMID=$(get_valid_nextid)\n  echo -e \"${DGN}Using Virtual Machine ID: ${BGN}$VMID${CL}\"\n  echo -e \"${DGN}Using Hostname: ${BGN}haos${STABLE}${CL}\"\n  HN=haos${STABLE}\n  echo -e \"${DGN}Allocated Cores: ${BGN}2${CL}\"\n  CORE_COUNT=\"2\"\n  echo -e \"${DGN}Allocated RAM: ${BGN}4096${CL}\"\n  RAM_SIZE=\"4096\"\n  echo -e \"${DGN}Using Bridge: ${BGN}vmbr0${CL}\"\n  BRG=\"vmbr0\"\n  echo -e \"${DGN}Using MAC Address: ${BGN}$GEN_MAC${CL}\"\n  MAC=$GEN_MAC\n  echo -e \"${DGN}Using VLAN: ${BGN}Default${CL}\"\n  VLAN=\"\"\n  echo -e \"${DGN}Using Interface MTU Size: ${BGN}Default${CL}\"\n  MTU=\"\"\n  echo -e \"${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  START_VM=\"yes\"\n  echo -e \"${BL}Creating a HAOS VM using the above default settings${CL}\"\n}\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  BRANCH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HAOS VERSION\" --radiolist \"Choose Version\" --cancel-button Exit-Script 10 58 3 \\\n    \"$STABLE\" \"Stable\" ON \\\n    \"$BETA\" \"Beta\" OFF \\\n    \"$DEV\" \"Dev\" OFF \\\n    3>&1 1>&2 2>&3)\n  exitstatus=$?\n  if [ $exitstatus = 0 ]; then echo -e \"${DGN}Using HAOS Version: ${BGN}$BRANCH${CL}\"; fi\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n  exitstatus=$?\n  if [ -z $VMID ]; then\n    VMID=\"$VMID\"\n    echo -e \"${DGN}Virtual Machine: ${BGN}$VMID${CL}\"\n  else\n    if echo \"$USEDID\" | egrep -q \"$VMID\"; then\n      echo -e \"\\n🚨  ${RD}ID $VMID is already in use${CL} \\n\"\n      echo -e \"Exiting Script \\n\"\n      sleep 2\n      exit\n    else\n      if [ $exitstatus = 0 ]; then echo -e \"${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"; fi\n    fi\n  fi\n  VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 haos${BRANCH} --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n  exitstatus=$?\n  if [ -z $VM_NAME ]; then\n    HN=\"haos${BRANCH}\"\n    echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n  else\n    if [ $exitstatus = 0 ]; then\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n    fi\n  fi\n  while true; do\n    CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n    exitstatus=$?\n    if [ $exitstatus -ne 0 ]; then exit-script; fi\n    if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n    if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n      echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"\n      break\n    fi\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n  done\n  while true; do\n    RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 4096 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n    exitstatus=$?\n    if [ $exitstatus -ne 0 ]; then exit-script; fi\n    if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"4096\"; fi\n    if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n      echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"\n      break\n    fi\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 4096).\" 8 58\n  done\n  BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n  exitstatus=$?\n  if [ -z $BRG ]; then\n    BRG=\"vmbr0\"\n    echo -e \"${DGN}Using Bridge: ${BGN}$BRG${CL}\"\n  else\n    if [ $exitstatus = 0 ]; then echo -e \"${DGN}Using Bridge: ${BGN}$BRG${CL}\"; fi\n  fi\n  while true; do\n    MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n    exitstatus=$?\n    if [ $exitstatus -ne 0 ]; then exit-script; fi\n    if [ -z \"$MAC1\" ]; then\n      MAC=\"$GEN_MAC\"\n      echo -e \"${DGN}Using MAC Address: ${BGN}$MAC${CL}\"\n      break\n    fi\n    if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n      MAC=\"$MAC1\"\n      echo -e \"${DGN}Using MAC Address: ${BGN}$MAC1${CL}\"\n      break\n    fi\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n  done\n  while true; do\n    VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n    exitstatus=$?\n    if [ $exitstatus -ne 0 ]; then exit-script; fi\n    if [ -z \"$VLAN1\" ]; then\n      VLAN1=\"Default\"\n      VLAN=\"\"\n      echo -e \"${DGN}Using Vlan: ${BGN}$VLAN1${CL}\"\n      break\n    fi\n    if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${DGN}Using Vlan: ${BGN}$VLAN1${CL}\"\n      break\n    fi\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n  done\n  while true; do\n    MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3)\n    exitstatus=$?\n    if [ $exitstatus -ne 0 ]; then exit-script; fi\n    if [ -z \"$MTU1\" ]; then\n      MTU1=\"Default\"\n      MTU=\"\"\n      echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n      break\n    fi\n    if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n      break\n    fi\n    whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n  done\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create HAOS ${BRANCH} VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${RD}Creating a HAOS VM using the above advanced settings${CL}\"\n  else\n    clear\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\nfunction START_SCRIPT() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    clear\n    header_info\n    echo -e \"${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    clear\n    header_info\n    echo -e \"${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\narch_check\nSTART_SCRIPT\npost_to_api_vm\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nif [ $((${#STORAGE_MENU[@]} / 3)) -eq 0 ]; then\n  echo -e \"'Disk image' needs to be selected for at least one storage location.\"\n  die \"Unable to detect valid storage location.\"\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for the HAOS VM?\\n\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Getting URL for Home Assistant ${BRANCH} Disk Image\"\nURL=https://github.com/home-assistant/operating-system/releases/download/${BRANCH}/haos_generic-aarch64-${BRANCH}.qcow2.xz\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}haos_generic-aarch64-${BRANCH}.qcow2.xz${CL}\"\nmsg_info \"Extracting Disk Image\"\nunxz $FILE\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\nmsg_ok \"Extracted Disk Image\"\nmsg_info \"Creating HAOS VM\"\nqm create $VMID -bios ovmf -cores $CORE_COUNT -memory $RAM_SIZE -name $HN \\\n  -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 64M 1>&/dev/null\nqm importdisk $VMID ${FILE%.*} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF},efitype=4m,size=64M \\\n  -scsi0 ${DISK1_REF},size=32G >/dev/null\nqm set $VMID \\\n  -boot order=scsi0 \\\n  -description \"<div align='center'><a href='https://Helper-Scripts.com'><img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png'/></a>\n\n  # Home Assistant OS\n\n  <a href='https://ko-fi.com/D1D7EP4GF'><img src='https://img.shields.io/badge/&#x2615;-Buy me a coffee-blue' /></a>\n  </div>\" >/dev/null\nmsg_ok \"Created HAOS VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Home Assistant OS VM\"\n  qm start $VMID\n  msg_ok \"Started Home Assistant OS VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  },
  {
    "path": "vm/truenas-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: juronja\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n# Source: https://www.truenas.com/truenas-community-edition/\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info() {\n  clear\n  cat <<\"EOF\"\n  ______                _   _____   _____\n /_  __/______  _____  / | / /   | / ___/\n  / / / ___/ / / / _ \\/  |/ / /| | \\__ \\\n / / / /  / /_/ /  __/ /|  / ___ |___/ /\n/_/ /_/   \\__,_/\\___/_/ |_/_/  |_/____/\n                                           (Community Edition)\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"truenas-vm\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nISO=\"${TAB}📀${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDISK=\"${TAB}💽${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\nCLOUD=\"${TAB}☁️${TAB}${CL}\"\n\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${command}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction truenas_iso_lookup() {\n  local BASE_URL=\"https://download.truenas.com\"\n  local current_year=$(date +%y)\n  local last_year=$(date -d \"1 year ago\" +%y)\n  local year_pattern=\"${current_year}\\.|${last_year}\\.\"\n\n  declare -A latest_stables\n  local pre_releases=()\n\n  local all_paths=$(\n    curl -sL \"$BASE_URL\" |\n      grep -oE 'href=\"[^\"]+\\.iso\"' |\n      sed 's/href=\"//; s/\"$//' |\n      grep -vE '(MASTER|ALPHA)' |\n      grep -E \"$year_pattern\"\n  )\n\n  while read -r path; do\n    local filename=$(basename \"$path\")\n    local version=$(echo \"$filename\" | sed -E 's/.*TrueNAS-SCALE-([0-9]{2}\\.[0-9]{2}(\\.[0-9]+)*(-RC[0-9]|-BETA[0-9])?)\\.iso.*/\\1/')\n    if [[ \"$version\" =~ (RC|BETA) ]]; then\n      pre_releases+=(\"$path\")\n    else\n      local major_version=$(echo \"$version\" | cut -d'.' -f1,2)\n      local current_stored_path=${latest_stables[\"$major_version\"]}\n      if [[ -z \"$current_stored_path\" ]]; then\n        latest_stables[\"$major_version\"]=\"$path\"\n      else\n        local stored_version=$(basename \"$current_stored_path\" | sed -E 's/.*TrueNAS-SCALE-([0-9]{2}\\.[0-9]{2}(\\.[0-9]+)*)\\.iso.*/\\1/')\n        if printf '%s\\n' \"$version\" \"$stored_version\" | sort -V | tail -n 1 | grep -q \"$version\"; then\n          latest_stables[\"$major_version\"]=\"$path\"\n        fi\n      fi\n    fi\n  done <<<\"$all_paths\"\n\n  for key in \"${!latest_stables[@]}\"; do\n    echo \"${latest_stables[$key]#/}\"\n  done\n\n  for pre in \"${pre_releases[@]}\"; do\n    echo \"${pre#/}\"\n  done | sort -V\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  popd >/dev/null\n  post_update_to_api \"done\" \"none\"\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"TrueNAS VM\" --yesno \"This will create a New TrueNAS VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction pve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not yet supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  ISO_DEFAULT=\"latest stable\"\n  FORMAT=\"\"\n  MACHINE=\"q35\"\n  DISK_SIZE=\"16\"\n  HN=\"truenas\"\n  CPU_TYPE=\"host\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"8192\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${ISO}${BOLD}${DGN}ISO Chosen: ${BGN}${ISO_DEFAULT}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}${MACHINE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}${CPU_TYPE}${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a TrueNAS VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  DISK_SIZE=\"16\"\n  HN=\"truenas\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"8192\"\n  BRG=\"vmbr0\"\n\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  ISOARRAY=()\n  mapfile -t ALL_ISOS < <(truenas_iso_lookup | sort -V)\n  ISO_COUNT=${#ALL_ISOS[@]}\n\n  if [ $ISO_COUNT -eq 0 ]; then\n    echo \"No ISOs found.\"\n    exit 115\n  fi\n\n  # Identify the index of the last stable release\n  LAST_STABLE_INDEX=-1\n  for i in \"${!ALL_ISOS[@]}\"; do\n    if [[ ! \"${ALL_ISOS[$i]}\" =~ (BETA|RC) ]]; then\n      LAST_STABLE_INDEX=$i\n    fi\n  done\n\n  # Build the whiptail array\n  for i in \"${!ALL_ISOS[@]}\"; do\n    ISOPATH=\"${ALL_ISOS[$i]}\"\n    FILENAME=$(basename \"$ISOPATH\")\n\n    # Select ON if it's the last stable found, OR fallback to last item if no stable exists\n    if [[ \"$i\" -eq \"$LAST_STABLE_INDEX\" ]]; then\n      ISOARRAY+=(\"$ISOPATH\" \"$FILENAME\" \"ON\")\n    elif [[ \"$LAST_STABLE_INDEX\" -eq -1 && \"$i\" -eq \"$((ISO_COUNT - 1))\" ]]; then\n      # Fallback: if somehow no stable is found, select the very last item\n      ISOARRAY+=(\"$ISOPATH\" \"$FILENAME\" \"ON\")\n    else\n      ISOARRAY+=(\"$ISOPATH\" \"$FILENAME\" \"OFF\")\n    fi\n  done\n\n  if SELECTED_ISO=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SELECT ISO TO INSTALL\" --notags --radiolist \"\\nSelect version (BETA/RC/Latest stable):\" 20 58 12 \"${ISOARRAY[@]}\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    echo -e \"${ISO}${BOLD}${DGN}ISO Chosen: ${BGN}$(basename \"$SELECTED_ISO\")${CL}\"\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 \"$HN\" --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose CPU Model\" --cancel-button Exit-Script 10 58 2 \\\n    \"KVM64\" \"Default – safe for migration/compatibility\" OFF \\\n    \"Host\" \"Use host CPU features (faster, no migration)\" ON \\\n    3>&1 1>&2 2>&3); then\n    case \"$CPU_TYPE1\" in\n    Host)\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\"host\"\n      ;;\n    *)\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n      ;;\n    esac\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 \"$CORE_COUNT\" --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 \"$RAM_SIZE\" --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"8192\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 8192).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 \"$BRG\" --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"IMPORT ONBOARD DISKS\" --yesno \"Would you like to import onboard disks?\" 10 58); then\n    echo -e \"${DISK}${BOLD}${DGN}Import onboard disks: ${BGN}yes${CL}\"\n    IMPORT_DISKS=\"yes\"\n  else\n    echo -e \"${DISK}${BOLD}${DGN}Import onboard disks: ${BGN}no${CL}\"\n    IMPORT_DISKS=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a TrueNAS VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a TrueNAS VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    if [ -n \"$SPINNER_PID\" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi\n    printf \"\\e[?25h\"\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\n\nif [ -z \"${SELECTED_ISO:-}\" ]; then\n  SELECTED_ISO=$(truenas_iso_lookup | grep -vE 'RC|BETA' | sort -V | tail -n 1)\n\n  if [ -z \"$SELECTED_ISO\" ]; then\n    msg_error \"Could not find a stable ISO for fallback.\"\n    exit 115\n  fi\nfi\n\nFULL_URL=\"https://download.truenas.com/${SELECTED_ISO#/}\"\nISO_NAME=$(basename \"$FULL_URL\")\nCACHE_DIR=\"/var/lib/vz/template/iso\"\nCACHE_FILE=\"$CACHE_DIR/$ISO_NAME\"\n\nif [[ ! -s \"$CACHE_FILE\" ]]; then\n  msg_info \"Retrieving the ISO for the TrueNAS Disk Image\"\n  curl -f#SL -o \"$CACHE_FILE\" \"$FULL_URL\"\n  msg_ok \"Downloaded ${CL}${BL}$(basename \"$CACHE_FILE\")${CL}\"\nelse\n  msg_ok \"Using cached image ${CL}${BL}$(basename \"$CACHE_FILE\")${CL}\"\nfi\n\nset -o pipefail\nmsg_info \"Creating TrueNAS VM shell\"\nqm create \"$VMID\" -machine q35 -bios ovmf -agent enabled=1 -tablet 0 -localtime 1 -cpu \"$CPU_TYPE\" \\\n  -cores \"$CORE_COUNT\" -memory \"$RAM_SIZE\" -balloon 0 -name \"$HN\" -tags community-script \\\n  -net0 \"virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU\" -onboot 1 -ostype l26 \\\n  -efidisk0 $STORAGE:1,efitype=4m,pre-enrolled-keys=0 -sata0 $STORAGE:$DISK_SIZE,ssd=1 \\\n  -scsihw virtio-scsi-single -cdrom local:iso/$ISO_NAME -vga virtio >/dev/null\nmsg_ok \"Created VM shell\"\n\nif [ \"$IMPORT_DISKS\" == \"yes\" ]; then\n  msg_info \"Importing onboard disks\"\n  DISKARRAY=()\n  SCSI_NR=0\n\n  while read -r LSOUTPUT; do\n    TRUNCATED=\"${LSOUTPUT:0:45}\"\n    if [ ${#LSOUTPUT} -gt 45 ]; then\n      TRUNCATED=\"${TRUNCATED}...\"\n    fi\n    DISKARRAY+=(\"$LSOUTPUT\" \"$TRUNCATED\" \"OFF\")\n  done < <(ls /dev/disk/by-id | grep -E '^ata-|^nvme-|^usb-' | grep -v 'part')\n\n  SELECTIONS=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SELECT DISKS TO IMPORT\" --checklist \"\\nSelect disk IDs to import. (Use Spacebar to select)\\n\" --notags --cancel-button \"Exit Script\" 20 58 10 \"${DISKARRAY[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"') || exit\n\n  for SELECTION in $SELECTIONS; do\n    ((++SCSI_NR))\n\n    ID_SERIAL=$(udevadm info --query=property --value --property=ID_SERIAL_SHORT \"/dev/disk/by-id/$SELECTION\")\n    ID_SERIAL=${ID_SERIAL:0:20}\n\n    qm set $VMID --scsi$SCSI_NR /dev/disk/by-id/$SELECTION,serial=$ID_SERIAL\n  done\n  msg_ok \"Disks imported successfully\"\nfi\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>TrueNAS Community Edition</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set \"$VMID\" -description \"$DESCRIPTION\" >/dev/null\n\nsleep 3\n\nmsg_ok \"Created a TrueNAS VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting TrueNAS VM\"\n  qm start $VMID\n  msg_ok \"Started TrueNAS VM\"\nfi\n\nmsg_ok \"Completed Successfully!\\n\"\n"
  },
  {
    "path": "vm/ubuntu2204-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<< $(wget -qLO - https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   __  ____                __           ___  ___    ____  __ __     _    ____  ___\n  / / / / /_  __  ______  / /___  __   |__ \\|__ \\  / __ \\/ // /    | |  / /  |/  /\n / / / / __ \\/ / / / __ \\/ __/ / / /   __/ /__/ / / / / / // /_    | | / / /|_/ /\n/ /_/ / /_/ / /_/ / / / / /_/ /_/ /   / __// __/_/ /_/ /__  __/    | |/ / /  / /\n\\____/_.___/\\__,_/_/ /_/\\__/\\__,_/   /____/____(_)____/  /_/       |___/_/  /_/\n\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"ubuntu2204-vm\"\nvar_os=\"ubuntu\"\nvar_version=\"2204\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  post_update_to_api \"failed\" \"$exit_code\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Ubuntu 22.04 VM\" --yesno \"This will create a New Ubuntu 22.04 VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script only works on PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/community-scripts/ProxmoxVE for AMD64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"5G\"\n  DISK_CACHE=\"\"\n  HN=\"ubuntu\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Ubuntu 22.04 VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 ubuntu --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"ubuntu\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Ubuntu 22.04 VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Ubuntu 22.04 VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the Ubuntu 22.04 Disk Image\"\nURL=https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir | cifs)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a Ubuntu 22.04 VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 64M 1>&/dev/null\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF}${FORMAT} \\\n  -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n  -ide2 ${STORAGE}:cloudinit \\\n  -boot order=scsi0 \\\n  -serial0 socket >/dev/null\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>ubuntu VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set \"$VMID\" -description \"$DESCRIPTION\" >/dev/null\n\nmsg_ok \"Created a Ubuntu 22.04 VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Ubuntu 22.04 VM\"\n  qm start $VMID\n  msg_ok \"Started Ubuntu 22.04 VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\necho -e \"Setup Cloud-Init before starting \\n\nMore info at https://github.com/community-scripts/ProxmoxVE/discussions/272 \\n\"\n"
  },
  {
    "path": "vm/ubuntu2404-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource /dev/stdin <<< $(wget -qLO - https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   __  ____                __           ___  __ __   ____  __ __     _    ____  ___\n  / / / / /_  __  ______  / /___  __   |__ \\/ // /  / __ \\/ // /    | |  / /  |/  /\n / / / / __ \\/ / / / __ \\/ __/ / / /   __/ / // /_ / / / / // /_    | | / / /|_/ /\n/ /_/ / /_/ / /_/ / / / / /_/ /_/ /   / __/__  __// /_/ /__  __/    | |/ / /  / /\n\\____/_.___/\\__,_/_/ /_/\\__/\\__,_/   /____/ /_/ (_)____/  /_/       |___/_/  /_/\n\nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"ubuntu2404-vm\"\nvar_os=\"ubuntu\"\nvar_version=\"2404\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  post_update_to_api \"failed\" \"$exit_code\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Ubuntu 24.04 VM\" --yesno \"This will create a New Ubuntu 24.04 VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script only works on PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/community-scripts/ProxmoxVE for AMD64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"7G\"\n  DISK_CACHE=\"\"\n  HN=\"ubuntu\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Ubuntu 24.04 VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 ubuntu --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"ubuntu\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Ubuntu 24.04 VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Ubuntu 24.04 VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the Ubuntu 24.04 Disk Image\"\nURL=https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir | cifs)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a Ubuntu 24.04 VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 64M 1>&/dev/null\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF}${FORMAT} \\\n  -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n  -ide2 ${STORAGE}:cloudinit \\\n  -boot order=scsi0 \\\n  -serial0 socket >/dev/null\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>ubuntu VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set \"$VMID\" -description \"$DESCRIPTION\" >/dev/null\n\nmsg_ok \"Created a Ubuntu 24.04 VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Ubuntu 24.04 VM\"\n  qm start $VMID\n  msg_ok \"Started Ubuntu 24.04 VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\necho -e \"Setup Cloud-Init before starting \\n\nMore info at https://github.com/community-scripts/ProxmoxVE/discussions/272 \\n\"\n"
  },
  {
    "path": "vm/ubuntu2504-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   __  ____                __           ___   ______ ____  __ __     _    ____  ___\n  / / / / /_  __  ______  / /___  __   |__ \\ / ____// __ \\/ // /    | |  / /  |/  /\n / / / / __ \\/ / / / __ \\/ __/ / / /   __/ //___ \\ / / / / // /_    | | / / /|_/ / \n/ /_/ / /_/ / /_/ / / / / /_/ /_/ /   / __/____/ // /_/ /__  __/    | |/ / /  / /  \n\\____/_.___/\\__,_/_/ /_/\\__/\\__,_/   /____/_____(_)____/  /_/       |___/_/  /_/ (Plucky Puffin)\n                                                                                   \nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"ubuntu2504-vm\"\nvar_os=\"ubuntu\"\nvar_version=\"2504\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nCL=$(echo \"\\033[m\")\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  post_update_to_api \"failed\" \"$exit_code\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Ubuntu 25.04 VM\" --yesno \"This will create a New Ubuntu 25.04 VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"arm64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with Proxmox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/community-scripts/ProxmoxVE for AMD64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  FORMAT=\",efitype=4m\"\n  MACHINE=\"\"\n  DISK_SIZE=\"7G\"\n  DISK_CACHE=\"\"\n  HN=\"ubuntu\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"2048\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"no\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Ubuntu 25.04 VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Type\" 10 58 2 \\\n    \"i440fx\" \"Machine i440fx\" ON \\\n    \"q35\" \"Machine q35\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $MACH = q35 ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 ubuntu --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"ubuntu\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"KVM64 (Default)\" ON \\\n    \"1\" \"Host\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $CPU_TYPE1 = \"1\" ]; then\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n    else\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Ubuntu 25.04 VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Ubuntu 25.04 VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\ncheck_root\narch_check\npve_check\nssh_check\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nwhile read -r line; do\n  TAG=$(echo $line | awk '{print $1}')\n  TYPE=$(echo $line | awk '{printf \"%-10s\", $2}')\n  FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( \"%9sB\", $6)}')\n  ITEM=\"  Type: $TYPE Free: $FREE \"\n  OFFSET=2\n  if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then\n    MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))\n  fi\n  STORAGE_MENU+=(\"$TAG\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1')\nVALID=$(pvesm status -content images | awk 'NR>1')\nif [ -z \"$VALID\" ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 $(($MSG_MAX_LENGTH + 23)) 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\nmsg_info \"Retrieving the URL for the Ubuntu 25.04 Disk Image\"\nURL=https://cloud-images.ubuntu.com/plucky/current/plucky-server-cloudimg-arm64.img\nsleep 2\nmsg_ok \"${CL}${BL}${URL}${CL}\"\ncurl -f#SL -o \"$(basename \"$URL\")\" \"$URL\"\necho -en \"\\e[1A\\e[0K\"\nFILE=$(basename $URL)\nmsg_ok \"Downloaded ${CL}${BL}${FILE}${CL}\"\n\nSTORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')\ncase $STORAGE_TYPE in\nnfs | dir | cifs)\n  DISK_EXT=\".qcow2\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format qcow2\"\n  THIN=\"\"\n  ;;\nbtrfs)\n  DISK_EXT=\".raw\"\n  DISK_REF=\"$VMID/\"\n  DISK_IMPORT=\"-format raw\"\n  FORMAT=\",efitype=4m\"\n  THIN=\"\"\n  ;;\n*)\n  DISK_EXT=\"\"\n  DISK_REF=\"\"\n  DISK_IMPORT=\"-format raw\"\n  ;;\nesac\nfor i in {0,1}; do\n  disk=\"DISK$i\"\n  eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}\n  eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}\ndone\n\nmsg_info \"Creating a Ubuntu 25.04 VM\"\nqm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \\\n  -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci\npvesm alloc $STORAGE $VMID $DISK0 64M 1>&/dev/null\nqm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null\nqm set $VMID \\\n  -efidisk0 ${DISK0_REF}${FORMAT} \\\n  -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \\\n  -ide2 ${STORAGE}:cloudinit \\\n  -boot order=scsi0 \\\n  -serial0 socket >/dev/null\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>ubuntu VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n  \n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set \"$VMID\" -description \"$DESCRIPTION\" >/dev/null\n\nmsg_ok \"Created a Ubuntu 25.04 VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Ubuntu 25.04 VM\"\n  qm start $VMID\n  msg_ok \"Started Ubuntu 25.04 VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\necho -e \"Setup Cloud-Init before starting \\n\nMore info at https://github.com/community-scripts/ProxmoxVE/discussions/272 \\n\"\n"
  },
  {
    "path": "vm/umbrel-os-vm.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE\n\nsource /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)\n\nfunction header_info {\n  clear\n  cat <<\"EOF\"\n   __  __          __              __   ____  _____    _    ____  ___\n  / / / /___ ___  / /_  ________  / /  / __ \\/ ___/   | |  / /  |/  /\n / / / / __ `__ \\/ __ \\/ ___/ _ \\/ /  / / / /\\__ \\    | | / / /|_/ / \n/ /_/ / / / / / / /_/ / /  /  __/ /  / /_/ /___/ /    | |/ / /  / /  \n\\____/_/ /_/ /_/_.___/_/   \\___/_/   \\____//____/     |___/_/  /_/   \n                                                                     \nEOF\n}\nheader_info\necho -e \"\\n Loading...\"\nGEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\\(..\\)/\\1:/g; s/.$//')\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"umbrel-os-vm\"\nvar_os=\"umbrel-os\"\nvar_version=\"n.d.\"\n\nYW=$(echo \"\\033[33m\")\nBL=$(echo \"\\033[36m\")\nHA=$(echo \"\\033[1;34m\")\nRD=$(echo \"\\033[01;31m\")\nBGN=$(echo \"\\033[4;92m\")\nGN=$(echo \"\\033[1;92m\")\nDGN=$(echo \"\\033[32m\")\nCL=$(echo \"\\033[m\")\n\nBOLD=$(echo \"\\033[1m\")\nBFR=\"\\\\r\\\\033[K\"\nHOLD=\" \"\nTAB=\"  \"\n\nCM=\"${TAB}✔️${TAB}${CL}\"\nCROSS=\"${TAB}✖️${TAB}${CL}\"\nINFO=\"${TAB}💡${TAB}${CL}\"\nOS=\"${TAB}🖥️${TAB}${CL}\"\nCONTAINERTYPE=\"${TAB}📦${TAB}${CL}\"\nDISKSIZE=\"${TAB}💾${TAB}${CL}\"\nCPUCORE=\"${TAB}🧠${TAB}${CL}\"\nRAMSIZE=\"${TAB}🛠️${TAB}${CL}\"\nCONTAINERID=\"${TAB}🆔${TAB}${CL}\"\nHOSTNAME=\"${TAB}🏠${TAB}${CL}\"\nBRIDGE=\"${TAB}🌉${TAB}${CL}\"\nGATEWAY=\"${TAB}🌐${TAB}${CL}\"\nDEFAULT=\"${TAB}⚙️${TAB}${CL}\"\nMACADDRESS=\"${TAB}🔗${TAB}${CL}\"\nVLANTAG=\"${TAB}🏷️${TAB}${CL}\"\nCREATING=\"${TAB}🚀${TAB}${CL}\"\nADVANCED=\"${TAB}🧩${TAB}${CL}\"\n\nTHIN=\"discard=on,ssd=1,\"\nset -e\ntrap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\ntrap cleanup EXIT\ntrap 'post_update_to_api \"failed\" \"130\"' SIGINT\ntrap 'post_update_to_api \"failed\" \"143\"' SIGTERM\ntrap 'post_update_to_api \"failed\" \"129\"; exit 129' SIGHUP\nfunction error_handler() {\n  local exit_code=\"$?\"\n  local line_number=\"$1\"\n  local command=\"$2\"\n  local error_message=\"${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\"\n  post_update_to_api \"failed\" \"${exit_code}\"\n  echo -e \"\\n$error_message\\n\"\n  cleanup_vmid\n}\n\nfunction get_valid_nextid() {\n  local try_id\n  try_id=$(pvesh get /cluster/nextid)\n  while true; do\n    if [ -f \"/etc/pve/qemu-server/${try_id}.conf\" ] || [ -f \"/etc/pve/lxc/${try_id}.conf\" ]; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    if lvs --noheadings -o lv_name | grep -qE \"(^|[-_])${try_id}($|[-_])\"; then\n      try_id=$((try_id + 1))\n      continue\n    fi\n    break\n  done\n  echo \"$try_id\"\n}\n\nfunction cleanup_vmid() {\n  if qm status $VMID &>/dev/null; then\n    qm stop $VMID &>/dev/null\n    qm destroy $VMID &>/dev/null\n  fi\n}\n\nfunction cleanup() {\n  local exit_code=$?\n  popd >/dev/null\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -eq 0 ]]; then\n      post_update_to_api \"done\" \"none\"\n    else\n      post_update_to_api \"failed\" \"$exit_code\"\n    fi\n  fi\n  rm -rf $TEMP_DIR\n}\n\nTEMP_DIR=$(mktemp -d)\npushd $TEMP_DIR >/dev/null\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Umbrel OS VM\" --yesno \"This will create a New Umbrel OS VM. Proceed?\" 10 58; then\n  :\nelse\n  header_info && echo -e \"${CROSS}${RD}User exited script${CL}\\n\" && exit\nfi\n\nfunction msg_info() {\n  local msg=\"$1\"\n  echo -ne \"${TAB}${YW}${HOLD}${msg}${HOLD}\"\n}\n\nfunction msg_ok() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CM}${GN}${msg}${CL}\"\n}\n\nfunction msg_error() {\n  local msg=\"$1\"\n  echo -e \"${BFR}${CROSS}${RD}${msg}${CL}\"\n}\n\nfunction check_root() {\n  if [[ \"$(id -u)\" -ne 0 || $(ps -o comm= -p $PPID) == \"sudo\" ]]; then\n    clear\n    msg_error \"Please run this script as root.\"\n    echo -e \"\\nExiting...\"\n    sleep 2\n    exit\n  fi\n}\n\n# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.\n# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.1\npve_check() {\n  local PVE_VER\n  PVE_VER=\"$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')\"\n\n  # Check for Proxmox VE 8.x: allow 8.0–8.9\n  if [[ \"$PVE_VER\" =~ ^8\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 9)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 8.0 – 8.9\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # Check for Proxmox VE 9.x: allow 9.0 and 9.1\n  if [[ \"$PVE_VER\" =~ ^9\\.([0-9]+) ]]; then\n    local MINOR=\"${BASH_REMATCH[1]}\"\n    if ((MINOR < 0 || MINOR > 1)); then\n      msg_error \"This version of Proxmox VE is not supported.\"\n      msg_error \"Supported: Proxmox VE version 9.0 – 9.1\"\n      exit 105\n    fi\n    return 0\n  fi\n\n  # All other unsupported versions\n  msg_error \"This version of Proxmox VE is not supported.\"\n  msg_error \"Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1\"\n  exit 105\n}\n\nfunction arch_check() {\n  if [ \"$(dpkg --print-architecture)\" != \"amd64\" ]; then\n    echo -e \"\\n ${INFO}${YWB}This script will not work with PiMox! \\n\"\n    echo -e \"\\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \\n\"\n    echo -e \"Exiting...\"\n    sleep 2\n    exit\n  fi\n}\n\nfunction ssh_check() {\n  if command -v pveversion >/dev/null 2>&1; then\n    if [ -n \"${SSH_CLIENT:+x}\" ]; then\n      if whiptail --backtitle \"Proxmox VE Helper Scripts\" --defaultno --title \"SSH DETECTED\" --yesno \"It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?\" 10 62; then\n        echo \"you've been warned\"\n      else\n        clear\n        exit\n      fi\n    fi\n  fi\n}\n\nfunction exit-script() {\n  clear\n  echo -e \"\\n${CROSS}${RD}User exited script${CL}\\n\"\n  exit\n}\n\n# Ensure pv is installed or abort with instructions\nfunction ensure_pv() {\n  if ! command -v pv &>/dev/null; then\n    msg_info \"Installing required package: pv\"\n    if ! apt-get update -qq &>/dev/null || ! apt-get install -y pv &>/dev/null; then\n      msg_error \"Failed to install pv automatically.\"\n      echo -e \"\\nPlease run manually on the Proxmox host:\\n  apt install pv\\n\"\n      exit 237\n    fi\n    msg_ok \"Installed pv\"\n  fi\n}\n\n# Download an .xz file and validate it\n# Args: $1=url $2=cache_file\nfunction download_and_validate_xz() {\n  local url=\"$1\"\n  local file=\"$2\"\n\n  # If file exists, check validity\n  if [[ -s \"$file\" ]]; then\n    if xz -t \"$file\" &>/dev/null; then\n      msg_ok \"Using cached image $(basename \"$file\")\"\n      return 0\n    else\n      msg_error \"Cached file $(basename \"$file\") is corrupted. Deleting and retrying download...\"\n      rm -f \"$file\"\n    fi\n  fi\n\n  # Download fresh file\n  msg_info \"Downloading image: $(basename \"$file\")\"\n  if ! curl -fSL -o \"$file\" \"$url\"; then\n    msg_error \"Download failed: $url\"\n    rm -f \"$file\"\n    exit 115\n  fi\n\n  # Validate again\n  if ! xz -t \"$file\" &>/dev/null; then\n    msg_error \"Downloaded file $(basename \"$file\") is corrupted. Please try again later.\"\n    rm -f \"$file\"\n    exit 115\n  fi\n  msg_ok \"Downloaded and validated $(basename \"$file\")\"\n}\n\n# Extract .xz with pv\n# Args: $1=cache_file $2=target_img\nfunction extract_xz_with_pv() {\n  set -o pipefail\n  local file=\"$1\"\n  local target=\"$2\"\n\n  msg_info \"Decompressing $(basename \"$file\") to $target\"\n  if ! xz -dc \"$file\" | pv -N \"Extracting\" >\"$target\"; then\n    msg_error \"Failed to extract $file\"\n    rm -f \"$target\"\n    exit 115\n  fi\n  msg_ok \"Decompressed to $target\"\n}\n\nfunction default_settings() {\n  VMID=$(get_valid_nextid)\n  MACHINE=\"q35\"\n  FORMAT=\"\"\n  DISK_SIZE=\"32G\"\n  HN=\"umbrelos\"\n  CPU_TYPE=\"\"\n  CORE_COUNT=\"2\"\n  RAM_SIZE=\"4096\"\n  BRG=\"vmbr0\"\n  MAC=\"$GEN_MAC\"\n  VLAN=\"\"\n  MTU=\"\"\n  START_VM=\"yes\"\n  METHOD=\"default\"\n  echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}\"\n  echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}\"\n  echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}\"\n  echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}\"\n  echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n  echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}\"\n  echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}\"\n  echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}\"\n  echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}\"\n  echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}\"\n  echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}\"\n  echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n  echo -e \"${CREATING}${BOLD}${DGN}Creating a Umbrel OS VM using the above default settings${CL}\"\n}\n\nfunction advanced_settings() {\n  METHOD=\"advanced\"\n  [ -z \"${VMID:-}\" ] && VMID=$(get_valid_nextid)\n  while true; do\n    if VMID=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Virtual Machine ID\" 8 58 $VMID --title \"VIRTUAL MACHINE ID\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VMID\" ]; then\n        VMID=$(get_valid_nextid)\n      fi\n      if pct status \"$VMID\" &>/dev/null || qm status \"$VMID\" &>/dev/null; then\n        echo -e \"${CROSS}${RD} ID $VMID is already in use${CL}\"\n        sleep 2\n        continue\n      fi\n      echo -e \"${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}\"\n      break\n    else\n      exit-script\n    fi\n  done\n\n  if MACH=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"MACHINE TYPE\" --radiolist --cancel-button Exit-Script \"Choose Machine Type\" 10 58 2 \\\n    \"q35\" \"Modern (PCIe, UEFI, default)\" ON \\\n    \"i440fx\" \"Legacy (older compatibility)\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ \"$MACH\" = \"q35\" ]; then\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}\"\n      FORMAT=\"\"\n      MACHINE=\" -machine q35\"\n    else\n      echo -e \"${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}\"\n      FORMAT=\",efitype=4m\"\n      MACHINE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Disk Size in GiB (e.g., 10, 20)\" 8 58 \"$DISK_SIZE\" --title \"DISK SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    DISK_SIZE=$(echo \"$DISK_SIZE\" | tr -d ' ')\n    if [[ \"$DISK_SIZE\" =~ ^[0-9]+$ ]]; then\n      DISK_SIZE=\"${DISK_SIZE}G\"\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    elif [[ \"$DISK_SIZE\" =~ ^[0-9]+G$ ]]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}\"\n      exit-script\n    fi\n  else\n    exit-script\n  fi\n\n  if DISK_CACHE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"DISK CACHE\" --radiolist \"Choose\" --cancel-button Exit-Script 10 58 2 \\\n    \"0\" \"None (Default)\" ON \\\n    \"1\" \"Write Through\" OFF \\\n    3>&1 1>&2 2>&3); then\n    if [ $DISK_CACHE = \"1\" ]; then\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}\"\n      DISK_CACHE=\"cache=writethrough,\"\n    else\n      echo -e \"${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}\"\n      DISK_CACHE=\"\"\n    fi\n  else\n    exit-script\n  fi\n\n  if VM_NAME=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Hostname\" 8 58 umbrelos --title \"HOSTNAME\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $VM_NAME ]; then\n      HN=\"umbrelos\"\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    else\n      HN=$(echo \"${VM_NAME,,}\" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')\n      if [ \"$HN\" != \"${VM_NAME,,}\" ]; then\n        whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"HOSTNAME ADJUSTED\" --msgbox \"Invalid characters detected. Hostname has been adjusted to:\\n\\n  $HN\" 10 58\n      fi\n      echo -e \"${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  if CPU_TYPE1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"CPU MODEL\" --radiolist \"Choose CPU Model\" --cancel-button Exit-Script 10 58 2 \\\n    \"KVM64\" \"Default – safe for migration/compatibility\" ON \\\n    \"Host\" \"Use host CPU features (faster, no migration)\" OFF \\\n    3>&1 1>&2 2>&3); then\n    case \"$CPU_TYPE1\" in\n    \"Host\")\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}\"\n      CPU_TYPE=\" -cpu host\"\n      ;;\n    *)\n      echo -e \"${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}\"\n      CPU_TYPE=\"\"\n      ;;\n    esac\n  else\n    exit-script\n  fi\n\n  while true; do\n    if CORE_COUNT=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate CPU Cores\" 8 58 2 --title \"CORE COUNT\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$CORE_COUNT\" ]; then CORE_COUNT=\"2\"; fi\n      if [[ \"$CORE_COUNT\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"CPU Cores must be a positive integer (e.g., 2).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if RAM_SIZE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Allocate RAM in MiB\" 8 58 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$RAM_SIZE\" ]; then RAM_SIZE=\"2048\"; fi\n      if [[ \"$RAM_SIZE\" =~ ^[1-9][0-9]*$ ]]; then\n        echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"RAM Size must be a positive integer in MiB (e.g., 2048).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if BRG=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Bridge\" 8 58 vmbr0 --title \"BRIDGE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $BRG ]; then\n      BRG=\"vmbr0\"\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    else\n      echo -e \"${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\n  while true; do\n    if MAC1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a MAC Address\" 8 58 $GEN_MAC --title \"MAC ADDRESS\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MAC1\" ]; then\n        MAC=\"$GEN_MAC\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n        break\n      fi\n      if [[ \"$MAC1\" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then\n        MAC=\"$MAC1\"\n        echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF).\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if VLAN1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set a Vlan(leave blank for default)\" 8 58 --title \"VLAN\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$VLAN1\" ]; then\n        VLAN1=\"Default\"\n        VLAN=\"\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      if [[ \"$VLAN1\" =~ ^[0-9]+$ ]] && [ \"$VLAN1\" -ge 1 ] && [ \"$VLAN1\" -le 4094 ]; then\n        VLAN=\",tag=$VLAN1\"\n        echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"VLAN must be a number between 1 and 4094, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  while true; do\n    if MTU1=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --inputbox \"Set Interface MTU Size (leave blank for default)\" 8 58 --title \"MTU SIZE\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n      if [ -z \"$MTU1\" ]; then\n        MTU1=\"Default\"\n        MTU=\"\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      if [[ \"$MTU1\" =~ ^[0-9]+$ ]] && [ \"$MTU1\" -ge 576 ] && [ \"$MTU1\" -le 65520 ]; then\n        MTU=\",mtu=$MTU1\"\n        echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n        break\n      fi\n      whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"INVALID INPUT\" --msgbox \"MTU Size must be a number between 576 and 65520, or leave blank for default.\" 8 58\n    else\n      exit-script\n    fi\n  done\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"START VIRTUAL MACHINE\" --yesno \"Start VM when completed?\" 10 58); then\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}\"\n    START_VM=\"yes\"\n  else\n    echo -e \"${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}\"\n    START_VM=\"no\"\n  fi\n\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"ADVANCED SETTINGS COMPLETE\" --yesno \"Ready to create a Umbrel OS VM?\" --no-button Do-Over 10 58); then\n    echo -e \"${CREATING}${BOLD}${DGN}Creating a Umbrel OS VM using the above advanced settings${CL}\"\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\nfunction start_script() {\n  if (whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"SETTINGS\" --yesno \"Use Default Settings?\" --no-button Advanced 10 58); then\n    header_info\n    echo -e \"${DEFAULT}${BOLD}${BL}Using Default Settings${CL}\"\n    default_settings\n  else\n    header_info\n    echo -e \"${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}\"\n    advanced_settings\n  fi\n}\n\ncheck_root\narch_check\npve_check\nssh_check\nensure_pv\nstart_script\npost_to_api_vm\n\nmsg_info \"Validating Storage\"\nSTORAGE_MENU=()\nwhile read -r tag type free; do\n  ITEM=\"Type: $type Free: $free\"\n  STORAGE_MENU+=(\"$tag\" \"$ITEM\" \"OFF\")\ndone < <(pvesm status -content images | awk 'NR>1 {printf \"%s %s %s\\n\", $1, $2, $6}')\n\nif [ ${#STORAGE_MENU[@]} -eq 0 ]; then\n  msg_error \"Unable to detect a valid storage location.\"\n  exit 119\nelif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then\n  STORAGE=${STORAGE_MENU[0]}\nelse\n  while [ -z \"${STORAGE:+x}\" ]; do\n    STORAGE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Storage Pools\" --radiolist \\\n      \"Which storage pool would you like to use for ${HN}?\\nTo make a selection, use the Spacebar.\\n\" \\\n      16 70 6 \\\n      \"${STORAGE_MENU[@]}\" 3>&1 1>&2 2>&3)\n  done\nfi\nmsg_ok \"Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location.\"\n\nmsg_ok \"Virtual Machine ID is ${CL}${BL}$VMID${CL}.\"\n\nURL=\"https://download.umbrel.com/release/latest/umbrelos-amd64.img.xz\"\nCACHE_DIR=\"/var/lib/vz/template/cache\"\nCACHE_FILE=\"$CACHE_DIR/$(basename \"$URL\")\"\nFILE_IMG=\"/var/lib/vz/template/tmp/${CACHE_FILE##*/%.xz}\"\n\nmkdir -p \"$CACHE_DIR\" \"$(dirname \"$FILE_IMG\")\"\n\ndownload_and_validate_xz \"$URL\" \"$CACHE_FILE\"\n\nqm create $VMID -machine q35 -bios ovmf -agent 1 -tablet 0 -localtime 1 ${CPU_TYPE} \\\n  -cores \"$CORE_COUNT\" -memory \"$RAM_SIZE\" -name \"$HN\" -tags community-script \\\n  -net0 \"virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU\" -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null\n\nextract_xz_with_pv \"$CACHE_FILE\" \"$FILE_IMG\"\n\nif qm disk import --help >/dev/null 2>&1; then\n  IMPORT_CMD=(qm disk import)\nelse\n  IMPORT_CMD=(qm importdisk)\nfi\nIMPORT_OUT=\"$(\"${IMPORT_CMD[@]}\" \"$VMID\" \"$FILE_IMG\" \"$STORAGE\" --format raw 2>&1 || true)\"\nDISK_REF=\"$(printf '%s\\n' \"$IMPORT_OUT\" | sed -n \"s/.*imported disk '\\([^']\\+\\)'.*/\\1/p\" | tr -d \"\\r\\\"'\")\"\n[[ -z \"$DISK_REF\" ]] && DISK_REF=\"$(pvesm list \"$STORAGE\" | awk -v id=\"$VMID\" '$5 ~ (\"vm-\"id\"-disk-\") {print $1\":\"$5}' | sort | tail -n1)\"\n\nqm set $VMID \\\n  --efidisk0 ${STORAGE}:0,efitype=4m \\\n  --scsi0 ${DISK_REF},ssd=1,discard=on \\\n  --boot order=scsi0 \\\n  --serial0 socket >/dev/null\nqm set $VMID --agent enabled=1 >/dev/null\nqm resize $VMID scsi0 ${DISK_SIZE} >/dev/null\n\nDESCRIPTION=$(\n  cat <<EOF\n<div align='center'>\n  <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>\n    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>\n  </a>\n\n  <h2 style='font-size: 24px; margin: 20px 0;'>Umbrel OS VM</h2>\n\n  <p style='margin: 16px 0;'>\n    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>\n      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />\n    </a>\n  </p>\n\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-github fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-comments fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>\n  </span>\n  <span style='margin: 0 10px;'>\n    <i class=\"fa fa-exclamation-circle fa-fw\" style=\"color: #f5f5f5;\"></i>\n    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>\n  </span>\n</div>\nEOF\n)\nqm set $VMID -description \"$DESCRIPTION\" >/dev/null\n\nif whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Image Cache\" \\\n  --yesno \"Keep downloaded Umbrel OS image for future VMs?\\n\\nFile: $CACHE_FILE\" 10 70; then\n  msg_ok \"Keeping cached image\"\nelse\n  rm -f \"$CACHE_FILE\"\n  msg_ok \"Deleted cached image\"\nfi\nrm -f \"$FILE_IMG\"\n\nmsg_ok \"Created a Umbrel OS VM ${CL}${BL}(${HN})\"\nif [ \"$START_VM\" == \"yes\" ]; then\n  msg_info \"Starting Umbrel OS VM\"\n  qm start $VMID\n  msg_ok \"Started Umbrel OS VM\"\nfi\npost_update_to_api \"done\" \"none\"\nmsg_ok \"Completed successfully!\\n\"\n"
  }
]